|  | // 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; | 
|  | } |