summaryrefslogtreecommitdiffstats
path: root/tools/perf/util/capstone.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/perf/util/capstone.c')
-rw-r--r--tools/perf/util/capstone.c653
1 files changed, 653 insertions, 0 deletions
diff --git a/tools/perf/util/capstone.c b/tools/perf/util/capstone.c
new file mode 100644
index 000000000..74213daf8
--- /dev/null
+++ b/tools/perf/util/capstone.c
@@ -0,0 +1,653 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "capstone.h"
+
+#include <errno.h>
+#include <inttypes.h>
+#include <string.h>
+
+#include <dlfcn.h>
+#include <elf.h>
+#include <fcntl.h>
+#include <linux/ctype.h>
+
+#include <capstone/capstone.h>
+
+#include "addr_location.h"
+#include "annotate.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"
+
+#if CS_VERSION_MAJOR < 5
+#define CS_ARCH_RISCV 15
+#define CS_MODE_RISCV32 1
+#define CS_MODE_RISCV64 2
+#define CS_MODE_RISCVC 4
+#endif
+
+#ifdef LIBCAPSTONE_DLOPEN
+static void *perf_cs_dll_handle(void)
+{
+ static bool dll_handle_init;
+ static void *dll_handle;
+
+ if (!dll_handle_init) {
+ dll_handle_init = true;
+ dll_handle = dlopen("libcapstone.so", RTLD_LAZY);
+ if (!dll_handle)
+ pr_debug("dlopen failed for libcapstone.so\n");
+ }
+ return dll_handle;
+}
+#endif
+
+static enum cs_err perf_cs_open(enum cs_arch arch, enum cs_mode mode, csh *handle)
+{
+#ifndef LIBCAPSTONE_DLOPEN
+ return cs_open(arch, mode, handle);
+#else
+ static bool fn_init;
+ static enum cs_err (*fn)(enum cs_arch arch, enum cs_mode mode, csh *handle);
+
+ if (!fn_init) {
+ fn = dlsym(perf_cs_dll_handle(), "cs_open");
+ if (!fn)
+ pr_debug("dlsym failed for cs_open\n");
+ fn_init = true;
+ }
+ if (!fn)
+ return CS_ERR_HANDLE;
+ return fn(arch, mode, handle);
+#endif
+}
+
+static enum cs_err perf_cs_option(csh handle, enum cs_opt_type type, size_t value)
+{
+#ifndef LIBCAPSTONE_DLOPEN
+ return cs_option(handle, type, value);
+#else
+ static bool fn_init;
+ static enum cs_err (*fn)(csh handle, enum cs_opt_type type, size_t value);
+
+ if (!fn_init) {
+ fn = dlsym(perf_cs_dll_handle(), "cs_option");
+ if (!fn)
+ pr_debug("dlsym failed for cs_option\n");
+ fn_init = true;
+ }
+ if (!fn)
+ return CS_ERR_HANDLE;
+ return fn(handle, type, value);
+#endif
+}
+
+static size_t perf_cs_disasm(csh handle, const uint8_t *code, size_t code_size,
+ uint64_t address, size_t count, struct cs_insn **insn)
+{
+#ifndef LIBCAPSTONE_DLOPEN
+ return cs_disasm(handle, code, code_size, address, count, insn);
+#else
+ static bool fn_init;
+ static enum cs_err (*fn)(csh handle, const uint8_t *code, size_t code_size,
+ uint64_t address, size_t count, struct cs_insn **insn);
+
+ if (!fn_init) {
+ fn = dlsym(perf_cs_dll_handle(), "cs_disasm");
+ if (!fn)
+ pr_debug("dlsym failed for cs_disasm\n");
+ fn_init = true;
+ }
+ if (!fn)
+ return CS_ERR_HANDLE;
+ return fn(handle, code, code_size, address, count, insn);
+#endif
+}
+
+static void perf_cs_free(struct cs_insn *insn, size_t count)
+{
+#ifndef LIBCAPSTONE_DLOPEN
+ cs_free(insn, count);
+#else
+ static bool fn_init;
+ static void (*fn)(struct cs_insn *insn, size_t count);
+
+ if (!fn_init) {
+ fn = dlsym(perf_cs_dll_handle(), "cs_free");
+ if (!fn)
+ pr_debug("dlsym failed for cs_free\n");
+ fn_init = true;
+ }
+ if (!fn)
+ return;
+ fn(insn, count);
+#endif
+}
+
+static enum cs_err perf_cs_close(csh *handle)
+{
+#ifndef LIBCAPSTONE_DLOPEN
+ return cs_close(handle);
+#else
+ static bool fn_init;
+ static enum cs_err (*fn)(csh *handle);
+
+ if (!fn_init) {
+ fn = dlsym(perf_cs_dll_handle(), "cs_close");
+ if (!fn)
+ pr_debug("dlsym failed for cs_close\n");
+ fn_init = true;
+ }
+ if (!fn)
+ return CS_ERR_HANDLE;
+ return fn(handle);
+#endif
+}
+
+static bool e_machine_to_capstone(uint16_t e_machine, bool is64, bool is_big_endian,
+ enum cs_arch *arch, enum cs_mode *mode)
+{
+ *mode = is_big_endian ? CS_MODE_BIG_ENDIAN : CS_MODE_LITTLE_ENDIAN;
+
+ switch (e_machine) {
+ case EM_X86_64:
+ case EM_386:
+ *arch = CS_ARCH_X86;
+ *mode |= is64 ? CS_MODE_64 : CS_MODE_32;
+ return true;
+ case EM_AARCH64:
+ *arch = CS_ARCH_ARM64;
+ *mode |= CS_MODE_ARM;
+ return true;
+ case EM_ARM:
+ *arch = CS_ARCH_ARM;
+ *mode |= CS_MODE_ARM | CS_MODE_V8;
+ return true;
+ case EM_S390:
+ *arch = CS_ARCH_SYSZ;
+ return true;
+ case EM_MIPS:
+ *arch = CS_ARCH_MIPS;
+ *mode |= is64 ? CS_MODE_MIPS64 : CS_MODE_MIPS32;
+ return true;
+ case EM_PPC:
+ *arch = CS_ARCH_PPC;
+ return true;
+ case EM_PPC64:
+ *arch = CS_ARCH_PPC;
+ *mode |= CS_MODE_64;
+ return true;
+ case EM_SPARC:
+ *arch = CS_ARCH_SPARC;
+ return true;
+ case EM_SPARCV9:
+ *arch = CS_ARCH_SPARC;
+ *mode |= CS_MODE_V9;
+ return true;
+ case EM_RISCV:
+ *arch = CS_ARCH_RISCV;
+ *mode |= (is64 ? CS_MODE_RISCV64 : CS_MODE_RISCV32) | CS_MODE_RISCVC;
+ return true;
+ default:
+ return false;
+ }
+}
+
+static int capstone_init(uint16_t e_machine, csh *cs_handle, bool is64, bool is_big_endian,
+ bool disassembler_style)
+{
+ enum cs_arch arch;
+ enum cs_mode mode;
+
+ if (!e_machine_to_capstone(e_machine, is64, is_big_endian, &arch, &mode))
+ return -1;
+
+ if (perf_cs_open(arch, mode, cs_handle) != CS_ERR_OK) {
+ pr_warning_once("cs_open failed\n");
+ return -1;
+ }
+
+ if (arch == CS_ARCH_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)
+ perf_cs_option(*cs_handle, CS_OPT_SYNTAX, CS_OPT_SYNTAX_ATT);
+ /*
+ * Resolving address operands to symbols is implemented
+ * on x86 by investigating instruction details.
+ */
+ perf_cs_option(*cs_handle, CS_OPT_DETAIL, CS_OPT_ON);
+ }
+
+ return 0;
+}
+
+static size_t print_insn_x86(struct thread *thread, u8 cpumode, struct 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) {
+ struct 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;
+}
+
+ssize_t capstone__fprintf_insn_asm(struct machine *machine, struct thread *thread, u8 cpumode,
+ bool is64bit, const uint8_t *code, size_t code_size, uint64_t ip,
+ int *lenp, int print_opts, FILE *fp)
+{
+ size_t printed;
+ struct cs_insn *insn;
+ csh cs_handle;
+ size_t count;
+ bool is_big_endian = false;
+ uint16_t e_machine = thread__e_machine_endian(thread, machine,
+ /*e_flags=*/NULL, &is_big_endian);
+ int ret;
+
+ /* TODO: Try to initiate capstone only once but need a proper place. */
+ ret = capstone_init(e_machine, &cs_handle, is64bit, is_big_endian,
+ /*disassembler_style=*/true);
+ if (ret < 0)
+ return ret;
+
+ count = perf_cs_disasm(cs_handle, code, code_size, ip, 1, &insn);
+ if (count > 0) {
+ if (e_machine == EM_X86_64 || e_machine == EM_386)
+ 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;
+ perf_cs_free(insn, count);
+ } else {
+ printed = -1;
+ }
+
+ perf_cs_close(&cs_handle);
+ return printed;
+}
+
+static void print_capstone_detail(struct 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_x86(args->arch))
+ return;
+
+ if (insn->detail == NULL)
+ return;
+
+ for (i = 0; i < insn->detail->x86.op_count; i++) {
+ struct cs_x86_op *op = &insn->detail->x86.operands[i];
+ u64 orig_addr;
+ struct map *found_map = NULL;
+
+ 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 then search the symbol.
+ */
+ found_map = maps__find(map__kmaps(map), addr);
+ if (found_map == NULL)
+ continue;
+ map = found_map;
+ }
+
+ /* convert it to map-relative address for search */
+ addr = map__map_ip(map, addr);
+
+ sym = map__find_symbol(map, addr);
+ if (sym == NULL) {
+ map__put(found_map);
+ 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);
+ }
+ map__put(found_map);
+ break;
+ }
+}
+
+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;
+}
+
+int symbol__disassemble_capstone(const char *filename, struct symbol *sym,
+ struct annotate_args *args)
+{
+ 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;
+ struct cs_insn *insn = NULL;
+ char disasm_buf[512];
+ struct disasm_line *dl;
+ bool disassembler_style = false;
+ uint16_t e_machine = EM_NONE;
+ bool is_big_endian = 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, &notes->src->source);
+
+ if (!args->options->disassembler_style ||
+ !strcmp(args->options->disassembler_style, "att"))
+ disassembler_style = true;
+
+ if (args->ms->thread) {
+ e_machine = thread__e_machine_endian(args->ms->thread,
+ /*machine=*/NULL,
+ /*e_flags=*/NULL, &is_big_endian);
+ } else if (dso) {
+ struct maps *kmaps = (map && dso__kernel(dso)) ? map__kmaps(map) : NULL;
+ struct machine *kmap_machine = kmaps ? maps__machine(kmaps) : NULL;
+
+ e_machine = dso__e_machine_endian(dso, kmap_machine, /*e_flags=*/NULL,
+ &is_big_endian);
+ }
+ if (!e_machine || e_machine == EM_NONE) {
+ e_machine = thread__e_machine_endian(NULL,
+ /*machine=*/NULL,
+ /*e_flags=*/NULL, &is_big_endian);
+ }
+ if (capstone_init(e_machine, &handle, is_64bit, is_big_endian, disassembler_style) < 0)
+ goto err;
+
+ needs_cs_close = true;
+
+ free_count = count = perf_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, &notes->src->source);
+
+ offset += insn[i].size;
+ }
+
+ /* It failed in the middle: probably due to unknown instructions */
+ if (offset != buf_len) {
+ struct list_head *list = &notes->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) {
+ perf_cs_close(&handle);
+ if (free_count > 0)
+ perf_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, &notes->src->source, al.node) {
+ list_del(&dl->al.node);
+ disasm_line__free(dl);
+ }
+ }
+ count = -1;
+ goto out;
+}
+
+int symbol__disassemble_capstone_powerpc(const char *filename __maybe_unused,
+ struct symbol *sym __maybe_unused,
+ struct annotate_args *args __maybe_unused)
+{
+ 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;
+ uint16_t e_machine = EM_NONE;
+ bool is_big_endian = 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 (args->ms->thread) {
+ e_machine = thread__e_machine_endian(args->ms->thread,
+ /*machine=*/NULL,
+ /*e_flags=*/NULL, &is_big_endian);
+ } else if (dso) {
+ struct maps *kmaps = (map && dso__kernel(dso)) ? map__kmaps(map) : NULL;
+ struct machine *kmap_machine = kmaps ? maps__machine(kmaps) : NULL;
+
+ e_machine = dso__e_machine_endian(dso, kmap_machine, /*e_flags=*/NULL,
+ &is_big_endian);
+ }
+ if (!e_machine || e_machine == EM_NONE) {
+ e_machine = thread__e_machine_endian(NULL,
+ /*machine=*/NULL,
+ /*e_flags=*/NULL, &is_big_endian);
+ }
+ if (capstone_init(e_machine, &handle, is_64bit, is_big_endian, 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, &notes->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, &notes->src->source);
+
+ offset += 4;
+ }
+
+ /* It failed in the middle */
+ if (offset != len) {
+ struct list_head *list = &notes->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)
+ perf_cs_close(&handle);
+ free(buf);
+ return count < 0 ? count : 0;
+
+err:
+ if (fd >= 0)
+ close(fd);
+ count = -1;
+ goto out;
+}