[PATCH v5 01/26] perf capstone: Symbolize address operands to match objdump on arm64

From: Tengda Wu

Date: Tue Sep 08 2026 - 09:40:10 EST


Capstone currently outputs address-bearing instructions with a raw hex
value prefixed by '#', without symbolic resolution. This differs from
objdump output and leads to parse failures in jump__parse() and
arm64_mov__parse() on arm64.

Example of the mismatch:
Current: b #0xffff8000800114c8
Objdump: b ffff8000800114c8 <el0t_64_sync+0x108>

Current: adrp x18, #0xffff800081f5f000
Objdump: adrp x18, ffff800081f5f000 <this_cpu_vector>

Fix this by extending symbol__disassemble_capstone() to:
- Enable CS_OPT_DETAIL for arm64 to access instruction group info.
- Detect instructions with address operands requiring symbolization
(jump/call/branch/adr/adrp) via opcode or Capstone group ID.
- Resolve target addresses to symbols via resolve_symbol_from_addr().
- Format output to match objdump's style (strip '#' prefix, add
symbol and offset).

Signed-off-by: Tengda Wu <wutengda@xxxxxxxxxxxxxxx>
---
tools/perf/util/capstone.c | 185 +++++++++++++++++++++++++++++--------
tools/perf/util/disasm.c | 5 +
tools/perf/util/disasm.h | 1 +
3 files changed, 153 insertions(+), 38 deletions(-)

diff --git a/tools/perf/util/capstone.c b/tools/perf/util/capstone.c
index 74213daf8786..7f82c33e3464 100644
--- a/tools/perf/util/capstone.c
+++ b/tools/perf/util/capstone.c
@@ -3,6 +3,7 @@

#include <errno.h>
#include <inttypes.h>
+#include <stdlib.h>
#include <string.h>

#include <dlfcn.h>
@@ -31,6 +32,10 @@
#define CS_MODE_RISCVC 4
#endif

+#if CS_VERSION_MAJOR < 4
+#define ARM64_GRP_BRANCH_RELATIVE 7 /* = CS_GRP_BRANCH_RELATIVE */
+#endif
+
#ifdef LIBCAPSTONE_DLOPEN
static void *perf_cs_dll_handle(void)
{
@@ -225,6 +230,12 @@ static int capstone_init(uint16_t e_machine, csh *cs_handle, bool is64, bool is_
* on x86 by investigating instruction details.
*/
perf_cs_option(*cs_handle, CS_OPT_DETAIL, CS_OPT_ON);
+ } else if (arch == CS_ARCH_ARM64) {
+ /*
+ * Same as x86: arm64 needs instruction details to resolve
+ * symbolic addresses.
+ */
+ perf_cs_option(*cs_handle, CS_OPT_DETAIL, CS_OPT_ON);
}

return 0;
@@ -292,16 +303,44 @@ ssize_t capstone__fprintf_insn_asm(struct machine *machine, struct thread *threa
return printed;
}

+static int resolve_symbol_from_addr(struct map *map, u64 addr,
+ const char **sym_name, u64 *sym_offset)
+{
+ struct map *found_map = NULL;
+ struct symbol *sym;
+ u64 al_addr;
+ u64 ip = map__objdump_2mem(map, 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), ip);
+ if (found_map == NULL)
+ return -1;
+ map = found_map;
+ }
+
+ /* convert it to map-relative address for search */
+ al_addr = map__map_ip(map, ip);
+
+ sym = map__find_symbol(map, al_addr);
+ map__put(found_map);
+
+ if (sym == NULL)
+ return -1;
+
+ *sym_name = sym->name;
+ *sym_offset = al_addr - sym->start;
+ return 0;
+}
+
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;
@@ -309,7 +348,8 @@ static void print_capstone_detail(struct cs_insn *insn, char *buf, size_t len,
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;
+ const char *sym_name = NULL;
+ u64 sym_offset;

if (op->type != X86_OP_MEM)
continue;
@@ -320,40 +360,114 @@ static void print_capstone_detail(struct cs_insn *insn, char *buf, size_t len,

/* 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);
+ if (resolve_symbol_from_addr(map, orig_addr, &sym_name, &sym_offset))
continue;
- }

- if (addr == sym->start) {
+ if (sym_offset == 0) {
scnprintf(buf, len, "\t# %"PRIx64" <%s>",
- orig_addr, sym->name);
+ orig_addr, sym_name);
} else {
scnprintf(buf, len, "\t# %"PRIx64" <%s+%#"PRIx64">",
- orig_addr, sym->name, addr - sym->start);
+ orig_addr, sym_name, sym_offset);
}
- map__put(found_map);
break;
}
}

+static int print_default_format(struct cs_insn *insn, char *buf, size_t len)
+{
+ return scnprintf(buf, len, " %-7s %s",
+ insn->mnemonic, insn->op_str);
+}
+
+static void format_capstone_insn_x86(struct cs_insn *insn, char *buf,
+ size_t len, struct annotate_args *args,
+ u64 addr)
+{
+ int printed;
+
+ printed = print_default_format(insn, buf, len);
+ buf += printed;
+ len -= printed;
+
+ print_capstone_detail(insn, buf, len, args, addr);
+}
+
+static bool needs_symbolic_address(struct cs_insn *insn)
+{
+ int i;
+
+ if (insn->id == ARM64_INS_ADR || insn->id == ARM64_INS_ADRP)
+ return true;
+
+ if (insn->detail == NULL)
+ return false;
+
+ for (i = 0; i < insn->detail->groups_count; i++) {
+ if (insn->detail->groups[i] == ARM64_GRP_JUMP ||
+ insn->detail->groups[i] == ARM64_GRP_CALL ||
+ insn->detail->groups[i] == ARM64_GRP_BRANCH_RELATIVE)
+ return true;
+ }
+
+ return false;
+}
+
+static void format_capstone_insn_arm64(struct cs_insn *insn, char *buf,
+ size_t len, struct annotate_args *args)
+{
+ struct map *map = args->ms->map;
+ char *last_imm, *endptr;
+ u64 addr;
+
+ print_default_format(insn, buf, len);
+ /*
+ * Adjust instructions to keep the existing behavior with objdump.
+ *
+ * Example conversion:
+ * From: b #0xffff8000800114c8
+ * To: b ffff8000800114c8 <el0t_64_sync+0x108>
+ */
+ if (needs_symbolic_address(insn)) {
+ const char *sym_name = NULL;
+ u64 sym_offset;
+
+ /* Extract last immediate value as address */
+ last_imm = strrchr(buf, '#');
+ if (!last_imm)
+ return;
+
+ addr = strtoull(last_imm + 1, &endptr, 16);
+ if (endptr == last_imm + 1)
+ return;
+
+ if (resolve_symbol_from_addr(map, addr, &sym_name, &sym_offset))
+ return;
+
+ /* Symbolize the resolved address */
+ len = len - (last_imm - buf);
+ if (sym_offset == 0) {
+ scnprintf(last_imm, len, "%"PRIx64" <%s>",
+ addr, sym_name);
+ } else {
+ scnprintf(last_imm, len, "%"PRIx64" <%s+%#"PRIx64">",
+ addr, sym_name, sym_offset);
+ }
+ }
+}
+
+static void format_capstone_insn(struct cs_insn *insn, char *buf, size_t len,
+ struct annotate_args *args, u64 addr)
+{
+ /* TODO: support more architectures */
+ if (arch__is_x86(args->arch))
+ format_capstone_insn_x86(insn, buf, len, args, addr);
+ else if (arch__is_arm64(args->arch))
+ format_capstone_insn_arm64(insn, buf, len, args);
+ else
+ print_default_format(insn, buf, len);
+}
+
struct find_file_offset_data {
u64 ip;
u64 offset;
@@ -446,14 +560,9 @@ int symbol__disassemble_capstone(const char *filename, struct symbol *sym,

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);
+ format_capstone_insn(&insn[i], disasm_buf,
+ sizeof(disasm_buf), args,
+ start + offset);

args->offset = offset;
args->line = disasm_buf;
diff --git a/tools/perf/util/disasm.c b/tools/perf/util/disasm.c
index 6cfdbabbb8c7..0ba38f70fe1a 100644
--- a/tools/perf/util/disasm.c
+++ b/tools/perf/util/disasm.c
@@ -203,6 +203,11 @@ bool arch__is_powerpc(const struct arch *arch)
return arch->id.e_machine == EM_PPC || arch->id.e_machine == EM_PPC64;
}

+bool arch__is_arm64(const struct arch *arch)
+{
+ return arch->id.e_machine == EM_AARCH64;
+}
+
static void ins_ops__delete(struct ins_operands *ops)
{
if (ops == NULL)
diff --git a/tools/perf/util/disasm.h b/tools/perf/util/disasm.h
index 25756e3f47e4..06c664fd4fc2 100644
--- a/tools/perf/util/disasm.h
+++ b/tools/perf/util/disasm.h
@@ -111,6 +111,7 @@ struct annotate_args {
const struct arch *arch__find(uint16_t e_machine, uint32_t e_flags, const char *cpuid);
bool arch__is_x86(const struct arch *arch);
bool arch__is_powerpc(const struct arch *arch);
+bool arch__is_arm64(const struct arch *arch);

extern const struct ins_ops call_ops;
extern const struct ins_ops dec_ops;
--
2.34.1