// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) /* * Based on: * * Minimal BPF JIT image disassembler * * Disassembles BPF JIT compiler emitted opcodes back to asm insn's for * debugging or verification purposes. * * Copyright 2013 Daniel Borkmann <daniel@iogearbox.net> * Licensed under the GNU General Public License, version 2.0 (GPLv2) */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include <stdio.h> #include <stdarg.h> #include <stdint.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/stat.h> #include <limits.h> #include <bpf/libbpf.h> #ifdef HAVE_LLVM_SUPPORT #include <llvm-c/Core.h> #include <llvm-c/Disassembler.h> #include <llvm-c/Target.h> #include <llvm-c/TargetMachine.h> #endif #ifdef HAVE_LIBBFD_SUPPORT #include <bfd.h> #include <dis-asm.h> #include <tools/dis-asm-compat.h> #endif #include "json_writer.h" #include "main.h" static int oper_count; #ifdef HAVE_LLVM_SUPPORT #define DISASM_SPACER typedef LLVMDisasmContextRef disasm_ctx_t; static int printf_json(char *s) { s = strtok(s, " \t"); jsonw_string_field(json_wtr, "operation", s); jsonw_name(json_wtr, "operands"); jsonw_start_array(json_wtr); oper_count = 1; while ((s = strtok(NULL, " \t,()")) != 0) { jsonw_string(json_wtr, s); oper_count++; } return 0; } /* This callback to set the ref_type is necessary to have the LLVM disassembler * print PC-relative addresses instead of byte offsets for branch instruction * targets. */ static const char * symbol_lookup_callback(__maybe_unused void *disasm_info, __maybe_unused uint64_t ref_value, uint64_t *ref_type, __maybe_unused uint64_t ref_PC, __maybe_unused const char **ref_name) { *ref_type = LLVMDisassembler_ReferenceType_InOut_None; return NULL; } static int init_context(disasm_ctx_t *ctx, const char *arch, __maybe_unused const char *disassembler_options, __maybe_unused unsigned char *image, __maybe_unused ssize_t len) { char *triple; if (arch) triple = LLVMNormalizeTargetTriple(arch); else triple = LLVMGetDefaultTargetTriple(); if (!triple) { p_err("Failed to retrieve triple"); return -1; } *ctx = LLVMCreateDisasm(triple, NULL, 0, NULL, symbol_lookup_callback); LLVMDisposeMessage(triple); if (!*ctx) { p_err("Failed to create disassembler"); return -1; } return 0; } static void destroy_context(disasm_ctx_t *ctx) { LLVMDisposeMessage(*ctx); } static int disassemble_insn(disasm_ctx_t *ctx, unsigned char *image, ssize_t len, int pc) { char buf[256]; int count; count = LLVMDisasmInstruction(*ctx, image + pc, len - pc, pc, buf, sizeof(buf)); if (json_output) printf_json(buf); else printf("%s", buf); return count; } int disasm_init(void) { LLVMInitializeAllTargetInfos(); LLVMInitializeAllTargetMCs(); LLVMInitializeAllDisassemblers(); return 0; } #endif /* HAVE_LLVM_SUPPORT */ #ifdef HAVE_LIBBFD_SUPPORT #define DISASM_SPACER "\t" typedef struct { struct disassemble_info *info; disassembler_ftype disassemble; bfd *bfdf; } disasm_ctx_t; static int get_exec_path(char *tpath, size_t size) { const char *path = "/proc/self/exe"; ssize_t len; len = readlink(path, tpath, size - 1); if (len <= 0) return -1; tpath[len] = 0; return 0; } static int printf_json(void *out, const char *fmt, va_list ap) { char *s; int err; err = vasprintf(&s, fmt, ap); if (err < 0) return -1; if (!oper_count) { int i; /* Strip trailing spaces */ i = strlen(s) - 1; while (s[i] == ' ') s[i--] = '\0'; jsonw_string_field(json_wtr, "operation", s); jsonw_name(json_wtr, "operands"); jsonw_start_array(json_wtr); oper_count++; } else if (!strcmp(fmt, ",")) { /* Skip */ } else { jsonw_string(json_wtr, s); oper_count++; } free(s); return 0; } static int fprintf_json(void *out, const char *fmt, ...) { va_list ap; int r; va_start(ap, fmt); r = printf_json(out, fmt, ap); va_end(ap); return r; } static int fprintf_json_styled(void *out, enum disassembler_style style __maybe_unused, const char *fmt, ...) { va_list ap; int r; va_start(ap, fmt); r = printf_json(out, fmt, ap); va_end(ap); return r; } static int init_context(disasm_ctx_t *ctx, const char *arch, const char *disassembler_options, unsigned char *image, ssize_t len) { struct disassemble_info *info; char tpath[PATH_MAX]; bfd *bfdf; memset(tpath, 0, sizeof(tpath)); if (get_exec_path(tpath, sizeof(tpath))) { p_err("failed to create disassembler (get_exec_path)"); return -1; } ctx->bfdf = bfd_openr(tpath, NULL); if (!ctx->bfdf) { p_err("failed to create disassembler (bfd_openr)"); return -1; } if (!bfd_check_format(ctx->bfdf, bfd_object)) { p_err("failed to create disassembler (bfd_check_format)"); goto err_close; } bfdf = ctx->bfdf; ctx->info = malloc(sizeof(struct disassemble_info)); if (!ctx->info) { p_err("mem alloc failed"); goto err_close; } info = ctx->info; if (json_output) init_disassemble_info_compat(info, stdout, (fprintf_ftype) fprintf_json, fprintf_json_styled); else init_disassemble_info_compat(info, stdout, (fprintf_ftype) fprintf, fprintf_styled); /* Update architecture info for offload. */ if (arch) { const bfd_arch_info_type *inf = bfd_scan_arch(arch); if (inf) { bfdf->arch_info = inf; } else { p_err("No libbfd support for %s", arch); goto err_free; } } info->arch = bfd_get_arch(bfdf); info->mach = bfd_get_mach(bfdf); if (disassembler_options) info->disassembler_options = disassembler_options; info->buffer = image; info->buffer_length = len; disassemble_init_for_target(info); #ifdef DISASM_FOUR_ARGS_SIGNATURE ctx->disassemble = disassembler(info->arch, bfd_big_endian(bfdf), info->mach, bfdf); #else ctx->disassemble = disassembler(bfdf); #endif if (!ctx->disassemble) { p_err("failed to create disassembler"); goto err_free; } return 0; err_free: free(info); err_close: bfd_close(ctx->bfdf); return -1; } static void destroy_context(disasm_ctx_t *ctx) { free(ctx->info); bfd_close(ctx->bfdf); } static int disassemble_insn(disasm_ctx_t *ctx, __maybe_unused unsigned char *image, __maybe_unused ssize_t len, int pc) { return ctx->disassemble(pc, ctx->info); } int disasm_init(void) { bfd_init(); return 0; } #endif /* HAVE_LIBBPFD_SUPPORT */ int disasm_print_insn(unsigned char *image, ssize_t len, int opcodes, const char *arch, const char *disassembler_options, const struct btf *btf, const struct bpf_prog_linfo *prog_linfo, __u64 func_ksym, unsigned int func_idx, bool linum) { const struct bpf_line_info *linfo = NULL; unsigned int nr_skip = 0; int count, i, pc = 0; disasm_ctx_t ctx; if (!len) return -1; if (init_context(&ctx, arch, disassembler_options, image, len)) return -1; if (json_output) jsonw_start_array(json_wtr); do { if (prog_linfo) { linfo = bpf_prog_linfo__lfind_addr_func(prog_linfo, func_ksym + pc, func_idx, nr_skip); if (linfo) nr_skip++; } if (json_output) { jsonw_start_object(json_wtr); oper_count = 0; if (linfo) btf_dump_linfo_json(btf, linfo, linum); jsonw_name(json_wtr, "pc"); jsonw_printf(json_wtr, "\"0x%x\"", pc); } else { if (linfo) btf_dump_linfo_plain(btf, linfo, "; ", linum); printf("%4x:" DISASM_SPACER, pc); } count = disassemble_insn(&ctx, image, len, pc); if (json_output) { /* Operand array, was started in fprintf_json. Before * that, make sure we have a _null_ value if no operand * other than operation code was present. */ if (oper_count == 1) jsonw_null(json_wtr); jsonw_end_array(json_wtr); } if (opcodes) { if (json_output) { jsonw_name(json_wtr, "opcodes"); jsonw_start_array(json_wtr); for (i = 0; i < count; ++i) jsonw_printf(json_wtr, "\"0x%02hhx\"", (uint8_t)image[pc + i]); jsonw_end_array(json_wtr); } else { printf("\n\t"); for (i = 0; i < count; ++i) printf("%02x ", (uint8_t)image[pc + i]); } } if (json_output) jsonw_end_object(json_wtr); else printf("\n"); pc += count; } while (count > 0 && pc < len); if (json_output) jsonw_end_array(json_wtr); destroy_context(&ctx); return 0; }