// SPDX-License-Identifier: GPL-2.0 #include "capstone.h" #include "annotate.h" #include "addr_location.h" #include "debug.h" #include "disasm.h" #include "dso.h" #include "machine.h" #include "map.h" #include "namespaces.h" #include "print_insn.h" #include "symbol.h" #include "thread.h" #include #include #include #ifdef HAVE_LIBCAPSTONE_SUPPORT #include #endif #ifdef HAVE_LIBCAPSTONE_SUPPORT static int capstone_init(struct machine *machine, csh *cs_handle, bool is64, bool disassembler_style) { cs_arch arch; cs_mode mode; if (machine__is(machine, "x86_64") && is64) { arch = CS_ARCH_X86; mode = CS_MODE_64; } else if (machine__normalized_is(machine, "x86")) { arch = CS_ARCH_X86; mode = CS_MODE_32; } else if (machine__normalized_is(machine, "arm64")) { arch = CS_ARCH_ARM64; mode = CS_MODE_ARM; } else if (machine__normalized_is(machine, "arm")) { arch = CS_ARCH_ARM; mode = CS_MODE_ARM + CS_MODE_V8; } else if (machine__normalized_is(machine, "s390")) { arch = CS_ARCH_SYSZ; mode = CS_MODE_BIG_ENDIAN; } else { return -1; } if (cs_open(arch, mode, cs_handle) != CS_ERR_OK) { pr_warning_once("cs_open failed\n"); return -1; } if (machine__normalized_is(machine, "x86")) { /* * In case of using capstone_init while symbol__disassemble * setting CS_OPT_SYNTAX_ATT depends if disassembler_style opts * is set via annotation args */ if (disassembler_style) cs_option(*cs_handle, CS_OPT_SYNTAX, CS_OPT_SYNTAX_ATT); /* * Resolving address operands to symbols is implemented * on x86 by investigating instruction details. */ cs_option(*cs_handle, CS_OPT_DETAIL, CS_OPT_ON); } return 0; } #endif #ifdef HAVE_LIBCAPSTONE_SUPPORT static size_t print_insn_x86(struct thread *thread, u8 cpumode, cs_insn *insn, int print_opts, FILE *fp) { struct addr_location al; size_t printed = 0; if (insn->detail && insn->detail->x86.op_count == 1) { cs_x86_op *op = &insn->detail->x86.operands[0]; addr_location__init(&al); if (op->type == X86_OP_IMM && thread__find_symbol(thread, cpumode, op->imm, &al)) { printed += fprintf(fp, "%s ", insn[0].mnemonic); printed += symbol__fprintf_symname_offs(al.sym, &al, fp); if (print_opts & PRINT_INSN_IMM_HEX) printed += fprintf(fp, " [%#" PRIx64 "]", op->imm); addr_location__exit(&al); return printed; } addr_location__exit(&al); } printed += fprintf(fp, "%s %s", insn[0].mnemonic, insn[0].op_str); return printed; } #endif ssize_t capstone__fprintf_insn_asm(struct machine *machine __maybe_unused, struct thread *thread __maybe_unused, u8 cpumode __maybe_unused, bool is64bit __maybe_unused, const uint8_t *code __maybe_unused, size_t code_size __maybe_unused, uint64_t ip __maybe_unused, int *lenp __maybe_unused, int print_opts __maybe_unused, FILE *fp __maybe_unused) { #ifdef HAVE_LIBCAPSTONE_SUPPORT size_t printed; cs_insn *insn; csh cs_handle; size_t count; int ret; /* TODO: Try to initiate capstone only once but need a proper place. */ ret = capstone_init(machine, &cs_handle, is64bit, true); if (ret < 0) return ret; count = cs_disasm(cs_handle, code, code_size, ip, 1, &insn); if (count > 0) { if (machine__normalized_is(machine, "x86")) printed = print_insn_x86(thread, cpumode, &insn[0], print_opts, fp); else printed = fprintf(fp, "%s %s", insn[0].mnemonic, insn[0].op_str); if (lenp) *lenp = insn->size; cs_free(insn, count); } else { printed = -1; } cs_close(&cs_handle); return printed; #else return -1; #endif } #ifdef HAVE_LIBCAPSTONE_SUPPORT static void print_capstone_detail(cs_insn *insn, char *buf, size_t len, struct annotate_args *args, u64 addr) { int i; struct map *map = args->ms.map; struct symbol *sym; /* TODO: support more architectures */ if (!arch__is(args->arch, "x86")) return; if (insn->detail == NULL) return; for (i = 0; i < insn->detail->x86.op_count; i++) { cs_x86_op *op = &insn->detail->x86.operands[i]; u64 orig_addr; if (op->type != X86_OP_MEM) continue; /* only print RIP-based global symbols for now */ if (op->mem.base != X86_REG_RIP) continue; /* get the target address */ orig_addr = addr + insn->size + op->mem.disp; addr = map__objdump_2mem(map, orig_addr); if (dso__kernel(map__dso(map))) { /* * The kernel maps can be split into sections, let's * find the map first and the search the symbol. */ map = maps__find(map__kmaps(map), addr); if (map == NULL) continue; } /* convert it to map-relative address for search */ addr = map__map_ip(map, addr); sym = map__find_symbol(map, addr); if (sym == NULL) continue; if (addr == sym->start) { scnprintf(buf, len, "\t# %"PRIx64" <%s>", orig_addr, sym->name); } else { scnprintf(buf, len, "\t# %"PRIx64" <%s+%#"PRIx64">", orig_addr, sym->name, addr - sym->start); } break; } } #endif #ifdef HAVE_LIBCAPSTONE_SUPPORT struct find_file_offset_data { u64 ip; u64 offset; }; /* This will be called for each PHDR in an ELF binary */ static int find_file_offset(u64 start, u64 len, u64 pgoff, void *arg) { struct find_file_offset_data *data = arg; if (start <= data->ip && data->ip < start + len) { data->offset = pgoff + data->ip - start; return 1; } return 0; } #endif int symbol__disassemble_capstone(const char *filename __maybe_unused, struct symbol *sym __maybe_unused, struct annotate_args *args __maybe_unused) { #ifdef HAVE_LIBCAPSTONE_SUPPORT struct annotation *notes = symbol__annotation(sym); struct map *map = args->ms.map; struct dso *dso = map__dso(map); u64 start = map__rip_2objdump(map, sym->start); u64 offset; int i, count, free_count; bool is_64bit = false; bool needs_cs_close = false; /* Malloc-ed buffer containing instructions read from disk. */ u8 *code_buf = NULL; /* Pointer to code to be disassembled. */ const u8 *buf; u64 buf_len; csh handle; cs_insn *insn = NULL; char disasm_buf[512]; struct disasm_line *dl; bool disassembler_style = false; if (args->options->objdump_path) return -1; buf = dso__read_symbol(dso, filename, map, sym, &code_buf, &buf_len, &is_64bit); if (buf == NULL) return errno; /* add the function address and name */ scnprintf(disasm_buf, sizeof(disasm_buf), "%#"PRIx64" <%s>:", start, sym->name); args->offset = -1; args->line = disasm_buf; args->line_nr = 0; args->fileloc = NULL; args->ms.sym = sym; dl = disasm_line__new(args); if (dl == NULL) goto err; annotation_line__add(&dl->al, ¬es->src->source); if (!args->options->disassembler_style || !strcmp(args->options->disassembler_style, "att")) disassembler_style = true; if (capstone_init(maps__machine(args->ms.maps), &handle, is_64bit, disassembler_style) < 0) goto err; needs_cs_close = true; free_count = count = cs_disasm(handle, buf, buf_len, start, buf_len, &insn); for (i = 0, offset = 0; i < count; i++) { int printed; printed = scnprintf(disasm_buf, sizeof(disasm_buf), " %-7s %s", insn[i].mnemonic, insn[i].op_str); print_capstone_detail(&insn[i], disasm_buf + printed, sizeof(disasm_buf) - printed, args, start + offset); args->offset = offset; args->line = disasm_buf; dl = disasm_line__new(args); if (dl == NULL) goto err; annotation_line__add(&dl->al, ¬es->src->source); offset += insn[i].size; } /* It failed in the middle: probably due to unknown instructions */ if (offset != buf_len) { struct list_head *list = ¬es->src->source; /* Discard all lines and fallback to objdump */ while (!list_empty(list)) { dl = list_first_entry(list, struct disasm_line, al.node); list_del_init(&dl->al.node); disasm_line__free(dl); } count = -1; } out: if (needs_cs_close) { cs_close(&handle); if (free_count > 0) cs_free(insn, free_count); } free(code_buf); return count < 0 ? count : 0; err: if (needs_cs_close) { struct disasm_line *tmp; /* * It probably failed in the middle of the above loop. * Release any resources it might add. */ list_for_each_entry_safe(dl, tmp, ¬es->src->source, al.node) { list_del(&dl->al.node); disasm_line__free(dl); } } count = -1; goto out; #else return -1; #endif } int symbol__disassemble_capstone_powerpc(const char *filename __maybe_unused, struct symbol *sym __maybe_unused, struct annotate_args *args __maybe_unused) { #ifdef HAVE_LIBCAPSTONE_SUPPORT struct annotation *notes = symbol__annotation(sym); struct map *map = args->ms.map; struct dso *dso = map__dso(map); struct nscookie nsc; u64 start = map__rip_2objdump(map, sym->start); u64 end = map__rip_2objdump(map, sym->end); u64 len = end - start; u64 offset; int i, fd, count; bool is_64bit = false; bool needs_cs_close = false; u8 *buf = NULL; struct find_file_offset_data data = { .ip = start, }; csh handle; char disasm_buf[512]; struct disasm_line *dl; u32 *line; bool disassembler_style = false; if (args->options->objdump_path) return -1; nsinfo__mountns_enter(dso__nsinfo(dso), &nsc); fd = open(filename, O_RDONLY); nsinfo__mountns_exit(&nsc); if (fd < 0) return -1; if (file__read_maps(fd, /*exe=*/true, find_file_offset, &data, &is_64bit) == 0) goto err; if (!args->options->disassembler_style || !strcmp(args->options->disassembler_style, "att")) disassembler_style = true; if (capstone_init(maps__machine(args->ms.maps), &handle, is_64bit, disassembler_style) < 0) goto err; needs_cs_close = true; buf = malloc(len); if (buf == NULL) goto err; count = pread(fd, buf, len, data.offset); close(fd); fd = -1; if ((u64)count != len) goto err; line = (u32 *)buf; /* add the function address and name */ scnprintf(disasm_buf, sizeof(disasm_buf), "%#"PRIx64" <%s>:", start, sym->name); args->offset = -1; args->line = disasm_buf; args->line_nr = 0; args->fileloc = NULL; args->ms.sym = sym; dl = disasm_line__new(args); if (dl == NULL) goto err; annotation_line__add(&dl->al, ¬es->src->source); /* * TODO: enable disassm for powerpc * count = cs_disasm(handle, buf, len, start, len, &insn); * * For now, only binary code is saved in disassembled line * to be used in "type" and "typeoff" sort keys. Each raw code * is 32 bit instruction. So use "len/4" to get the number of * entries. */ count = len/4; for (i = 0, offset = 0; i < count; i++) { args->offset = offset; sprintf(args->line, "%x", line[i]); dl = disasm_line__new(args); if (dl == NULL) break; annotation_line__add(&dl->al, ¬es->src->source); offset += 4; } /* It failed in the middle */ if (offset != len) { struct list_head *list = ¬es->src->source; /* Discard all lines and fallback to objdump */ while (!list_empty(list)) { dl = list_first_entry(list, struct disasm_line, al.node); list_del_init(&dl->al.node); disasm_line__free(dl); } count = -1; } out: if (needs_cs_close) cs_close(&handle); free(buf); return count < 0 ? count : 0; err: if (fd >= 0) close(fd); count = -1; goto out; #else return -1; #endif }