Skip to contentSkip to conversation

perf arm64: Support data type profiling

PATCH Applied Tengda Wu 6 participants 70 messages Sep 16 – Sep 21

Subsystems (3)

v6 of 8 Applied Latest is v8 →

lore
Share & tools
lore

Tengda Wu

author
#0 lore raw

v6 cover perf arm64: Support data type profiling

This patch series implements data type profiling support for arm64, enabling 'perf annotate --data-type' to resolve memory locations and variable types on arm64 platforms.

The main changes since v5 include:
v5: https://lore.kernel.org/all/[email protected]/

* __get_dwarf_regnum_arm64 optimization: compute directly based on character judgment, avoiding strtol calls. (Ian Rogers)

* extract_op_location_arm64 issue fix: support parsing instructions such as 'cmp x0, x1, lsl #3'.

* Instruction tracking part, fixing issues pointed out by Sashiko, including:
- adding TSR_KIND_POINTER handling for ldr.
- premature returns in add and ldr causing percpu handling to be skipped.
- adding a reg != -1 check for stack type propagation, etc.

Patch organization
==================

The series is organized as follows:

1. Fix disassembly mismatches (Patches 01-02)
Current perf annotate supports three disassembly backends: llvm, capstone, and objdump. On arm64, inconsistencies between the output of these backends (specifically llvm/capstone vs. objdump) often prevent the tracker from correctly identifying registers and offsets. These patches resolve these mismatches, ensuring consistent instruction parsing across all supported backends.

2. Infrastructure for arm64 operand parsing (Patches 03-09) These patches establish the necessary infrastructure for arm64-specific operand handling. This includes implementing new callbacks and data structures to manage arm64's unique addressing modes and register sets. This foundation is essential for the subsequent type-tracking logic.

3. ARM SPE event handling (Patches 10-11)
Patch 10 automatically deduplicates overlapping ARM SPE events (e.g., l1d-miss, tlb-access) in 'perf annotate' by retaining only the "instructions" event when data type profiling is enabled. Patch 11 defaults the synthesized event period to 1 for ARM SPE to fix zero 'Percent' values in annotate output.

4. Core instruction tracking (Patches 12-26)
These patches implement the core logic for type tracking on arm64, covering several key types of instructions, including:
* Memory Access: ldr/str variants (including stack-based access).
* Arithmetic & Data Processing: mov, add, and adrp.
* Special Access: System register access (mrs) and per-cpu variable tracking.

The implementation draws inspiration from the existing x86 logic while adapting it to the nuances of the AArch64 ISA [2][3]. With these changes, perf annotate can successfully resolve memory locations and register types, providing basic support for data type profiling on arm64 platforms.

Example Result
==============

# perf mem record -a -K -- sleep 1
# perf annotate --data-type --stdio --type-stat
Annotate data type stats:
total 1138, ok 852 (74.9%), bad 286 (25.1%)
-----------------------------------------------------------
         6 : no_sym
        42 : no_var
       230 : no_typeinfo
         8 : bad_offset
       213 : insn_track

Annotate type: 'struct page' in [kernel.kallsyms] (66948 samples):
============================================================================

 Percent     offset       size  field
  100.00          0       0x40  struct page      {
    9.01          0        0x8      long unsigned int   flags;
   57.99        0x8       0x28      union        {
   57.99        0x8       0x28          struct   {
   33.00        0x8       0x10              union        {
   33.00        0x8       0x10                  struct list_head        lru {
   33.00        0x8        0x8                      struct list_head*   next;
    0.00       0x10        0x8                      struct list_head*   prev;
                                                };
   33.00        0x8       0x10                  struct   {
   33.00        0x8        0x8                      void*       __filler;
    0.00       0x10        0x4                      unsigned int        mlock_count;

...

Each patch's type profiling results are as follows:

Patch | Feature | no_sym | no_var | no_typeinfo | bad_offset | insn_track | ok(%)
------+----------------------------+--------+--------+-------------+------------+------------+------

0011  | base (default spe period)  | 6      | 493    | -           | -          | -          | 56.2%
0013  | enable insn tracking       | 6      | 42     | 437         | 2          | 12         | 57.2%
0016  | support 'load' insn        | 6      | 42     | 398         | 1          | 52         | 60.7%
0017  | support 'store' insn       | 6      | 42     | 398         | 1          | 52         | 60.7%
0021  | support stack variable     | 6      | 42     | 391         | 1          | 59         | 61.3%
0022  | support 'mov' insn         | 6      | 42     | 372         | 3          | 76         | 62.8%
0023  | support 'add' insn         | 6      | 42     | 318         | 7          | 126        | 67.2%
0024  | support 'adrp' insn        | 6      | 42     | 238         | 8          | 205        | 74.2%
0025  | support per-cpu variable   | 6      | 42     | 233         | 8          | 210        | 74.6%
0026  | support 'mrs' insn         | 6      | 42     | 230         | 8          | 213        | 74.9%

Limitations
===========

* SIMD/FP & SVE Vector Support:
Data type profiling currently focuses on General-Purpose (GP) register operations. Vector/SIMD registers (v0-v31, d0-d31, q0-q31) and Scalable Vector Extension (SVE/SME) instructions are not tracked yet.

* Regular Instructions Only:
Only regular instructions are currently supported. Instructions following special rules or non-standard patterns are not supported yet. For example, load/store tracking only matches standard variants (ldr/ldur/ldar/ldp and str/stur/stlr/stp) with straightforward semantics; exclusive loads/stores (ldxr, stxr, etc.) and acquire/exclusive combination variants are excluded.

* Compiler Prologue/Epilogue Code:
As shown by the previous test results, approximately 14% of the failed type profiling results originate from compiler-generated prologue or epilogue code (e.g., ldp x19, x20, [sp, #16]). These instructions manage callee-saved registers across function boundaries, propagating type context through these operations requires inter-procedural (cross-function) instruction analysis, which is currently unsupported by the local backward instruction tracker.

Testing
=======

Tested on arm64 (all passed):

  # perf test -v "perf data type profiling tests"
  81: perf data type profiling tests                                                                  : Ok

=== Test Summary ===
Passed main tests : 1

  Passed subtests   : 0
  Skipped tests     : 0
  Failed tests      : 0

Tested on x86. The profiling results show no change before/after applying this patch series:

before : total 880, ok 711 (80.8%), bad 169 (19.2%) after : total 880, ok 711 (80.8%), bad 169 (19.2%)

Changelog
=========
v5 -> v6:
- v5: https://lore.kernel.org/all/[email protected]/
- Fix a potential UAF issue with capstone sym_name.
- Optimize __get_dwarf_regnum_arm64 to compute directly based on character judgment, avoiding strtol calls. (Ian Rogers)
- Fix an issue in extract_op_location_arm64 when parsing instructions such as

    'cmp x0, x1, lsl #3'.

- Fix several minor issues in instruction tracking. (Sashiko)
v4 -> v5:
- v4: https://lore.kernel.org/all/[email protected]/
- Introduce arch_get_reg_offset() to uniformly handle reg offset.
- Add support for parsing extension type and shift amount in arm64 instructions.
- Refine which instructions to track or skip. (Shuai Xue)
- Rename dont_overlap to default_single_event_per_ip and use it in itrace_synth_opts__set_default(). (Adrian Hunter)
- Introduce delete_stack_state() to clean up obsolete stack state. (Shuai Xue)
- Normalize arch__dwarf_regnum() error return values. (Shuai Xue)
- Drop canary support due to unresolved bugs.
- Fix various minor issues, such as name memory leaks and header includes.
v3 -> v4:
- v3: https://lore.kernel.org/all/[email protected]/
- Fix Capstone compilation failure.
- Stop adding new pcrel_adrp_addr in LLVM; reuse pcrel_load_addr instead.
- Fix parsing issue in arm64_mov__parse.
- Add PC-relative load instruction parsing logic to arm64_ldst__parse, and introduce rstrip_space_and_comment to strip comments.
- Remove wzr/xzr register parsing (not planning to handle this yet).
- Add post-index addressing mode parsing for the '[base], reg' format.
- Update built-in implementation of --itrace=i1i to deduplicate early during arm_spe_process_auxtrace_info.
- Restrict the "default period to 1" behavior to ARM SPE, instead of applying it to all architectures.
- Add register type tracking for function call instructions.
- Add dual-register type tracking for load pair and store pair instructions.
- Correct stack variable offset calculations.
- Add type invalidation upon retry failure.
- Reuse imm_value instead of introducing addr for 'adrp' instruction tracking.
- Fix potential stale type resolution errors caused by TSR_KIND_GLOBAL_ADDR and TSR_KIND_CONST during stack passing.
- Fix a strbuf memory leak during 'mrs' instruction tracking.
- Fix stale dieoff issue when debug info changes.
- Simplify add type propagation: only propagate offset/imm updates, leave type parsing to chk.
v2 -> v3:
- v2: https://lore.kernel.org/all/[email protected]/
- Instead of always parsing the left operand as src and the right operand as dst, set them based on the actual instruction definition. (Namhyung Kim)
- Fix refcount leak in print_capstone_detail().
- Remove useless '<' check when parsing 'addr <symbol>' in arm64_mov__parse().
- Add example comments in arm64_ldst__parse().
- Split arch__dwarf_regnum() changes into a separate commit.
- Rename annotated_addr_mode enum: INSN_ADDR_* -> PERF_ADDR_MODE_*.
- Set caller-saved registers in init_type_state().
- For instructions with addressing mode, always goto adjust_reg_index_state() at the end to update the src register state.
- Handle TSR_KIND_CONST registers for 'mov' and 'add' instructions.
- Invalidate dst register for all other unsupported instructions.
- Verify type DIE is task_struct pointer before caching globally.
- Enable --itrace=i1i by default for ARM SPE data type profiling in 'perf annotate' to avoid overlapping event counting for the same instruction. (James Clark)
- Fix global variable type resolving error in check_matching_type(). (James Clark)
- Address review comments from sashiko [1]:
- Fix unconditional call to arch->extract_op_location()
- Handle multi_regs correctly
- Fix invalid register state in error path
- Other misc fixes
v1 -> v2:
- v1: https://lore.kernel.org/all/[email protected]/
- Fix inconsistencies in arm64 instruction output across llvm, capstone, and objdump disassembly backends.
- Support arm64-specific addressing modes and operand formats. (Leo Yan)
- Extend instruction tracking to support mov and add instructions, along with per-cpu and stack variables.
- Include real-world examples in commit messages to demonstrate practical effects. (Namhyung Kim)
- Improve type-tracking success rate (type stat) from 64.2% to 82.1%.

Please let me know if you have any feedback.

Thanks,
Tengda

[1] https://sashiko.dev/#/patchset/20260403094800.1418825-1-wutengda%40huaweicloud.com
[2] https://developer.arm.com/documentation/102374/0103
[3] https://github.com/flynd/asmsheets/releases/tag/v8

Tengda Wu (26):
perf capstone: Symbolize address operands to match objdump on arm64 perf llvm: Fix arm64 adrp instruction disassembly mismatch with
objdump
perf annotate-arm64: Generalize arm64_mov__parse to support more
instructions
perf annotate-arm64: Handle load and store instructions perf annotate: Normalize arch__dwarf_regnum() error return values perf annotate: Introduce extract_op_location callback for
arch-specific parsing
perf dwarf-regs: Adapt get_dwarf_regnum() for arm64
perf annotate: Adapt arch__dwarf_regnum() for arm64
perf annotate-arm64: Implement extract_op_location() callback perf annotate: Default to --itrace=i1i for data type profiling perf arm-spe: Set default synthesized event period to 1 perf annotate-data: Extract invalidate_reg_state() as a common helper perf annotate-arm64: Enable instruction tracking support perf annotate-data: Add arch_get_reg_offset helper
perf annotate-arm64: Track return type after call instructions perf annotate-arm64: Support load instruction tracking perf annotate-arm64: Support store instruction tracking perf annotate-data: Expand type_state_reg imm_value to u64 perf annotate-data: Track imm_value for stack variables perf annotate-x86: Delete stale stack state on store of untracked
register
perf annotate-arm64: Support stack variable tracking
perf annotate-arm64: Support 'mov' instruction tracking perf annotate-arm64: Support 'add' instruction tracking perf annotate-arm64: Support 'adrp' instruction to track global
variables
perf annotate-arm64: Support per-cpu variable access tracking perf annotate-arm64: Support 'mrs' instruction to track 'current'
pointer

 tools/perf/builtin-annotate.c                 |    8 +
 .../perf/util/annotate-arch/annotate-arm64.c  | 1211 ++++++++++++++++-
 .../util/annotate-arch/annotate-powerpc.c     |    9 +
 tools/perf/util/annotate-arch/annotate-x86.c  |  117 +-
 tools/perf/util/annotate-data.c               |  205 ++-
 tools/perf/util/annotate-data.h               |   14 +-
 tools/perf/util/annotate.c                    |  108 +-
 tools/perf/util/annotate.h                    |   63 +
 tools/perf/util/arm-spe.c                     |   16 +-
 tools/perf/util/auxtrace.c                    |   12 +-
 tools/perf/util/auxtrace.h                    |    7 +-
 tools/perf/util/capstone.c                    |  186 ++-
 tools/perf/util/cs-etm.c                      |    2 +-
 tools/perf/util/disasm.c                      |    5 +
 tools/perf/util/disasm.h                      |    5 +
 .../util/dwarf-regs-arch/dwarf-regs-arm64.c   |   25 +
 tools/perf/util/dwarf-regs.c                  |    2 +-
 tools/perf/util/include/dwarf-regs.h          |    1 +
 tools/perf/util/intel-bts.c                   |    2 +-
 tools/perf/util/intel-pt.c                    |    3 +-
 tools/perf/util/llvm.c                        |   52 +-
 21 files changed, 1831 insertions(+), 222 deletions(-)

base-commit: e6e35979777d646fe3c7c94dca7dd32fb25d45f4

Signature · 1 line
2.34.1
author
#1 lore raw

PATCH v6 1/26 perf capstone: Symbolize address operands to match objdump on arm64

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 find_symbol_from_addr().
- Format output to match objdump's style (strip '#' prefix, add symbol and offset).

---
 tools/perf/util/capstone.c | 186 ++++++++++++++++++++++++++++++-------
 tools/perf/util/disasm.c   |   5 +
 tools/perf/util/disasm.h   |   1 +
 3 files changed, 157 insertions(+), 35 deletions(-)
tools/perf/util/capstone.c +151 -35
index 74213daf8786..95881f15324a 100644
@@ -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,6 +303,40 @@ ssize_t capstone__fprintf_insn_asm(struct machine *machine, struct thread *threa
 	return printed;
 }
 
+static struct symbol *find_symbol_from_addr(struct map *map, u64 addr,
+					    struct map **found_map, u64 *sym_offset)
+{
+	struct symbol *sym;
+	u64 al_addr;
+	u64 ip = map__objdump_2mem(map, addr);
+
+	*found_map = NULL;
+	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 NULL;
+		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);
+
+	if (sym == NULL) {
+		map__put(*found_map);
+		*found_map = NULL;
+		return NULL;
+	}
+
+	*sym_offset = al_addr - sym->start;
+	return sym;
+}
+
 static void print_capstone_detail(struct cs_insn *insn, char *buf, size_t len,
 				  struct annotate_args *args, u64 addr)
 {
@@ -299,17 +344,14 @@ static void print_capstone_detail(struct cs_insn *insn, char *buf, size_t len,
 	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;
+		struct map *found_map;
+		u64 sym_offset;
 
 		if (op->type != X86_OP_MEM)
 			continue;
@@ -320,40 +362,119 @@ 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);
+		sym = find_symbol_from_addr(map, orig_addr, &found_map, &sym_offset);
+		if (sym == NULL)
 			continue;
-		}
 
-		if (addr == sym->start) {
+		if (sym_offset == 0) {
 			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);
+				  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)) {
+		struct symbol *sym;
+		struct map *found_map;
+		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;
+
+		sym = find_symbol_from_addr(map, addr, &found_map, &sym_offset);
+		if (sym == NULL)
+			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);
+		}
+		map__put(found_map);
+	}
+}
+
+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 +567,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;
tools/perf/util/disasm.c +5 -0
index 49c206a88eec..b39636ec35c1 100644
@@ -214,6 +214,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)
tools/perf/util/disasm.h +1 -0
index adbdbf8f1f35..3a42166f7f49 100644
@@ -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;
Signature · 1 line
2.34.1
#2 lore raw

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

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 20 lines

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 find_symbol_from_addr().
- Format output to match objdump's style (strip '#' prefix, add symbol and offset).

Quoted 1 line

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
maintainer Reviewed-by on v6 1/26
#3 lore raw

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

In reply to Tengda Wu
Quoted 20 lines · Tengda Wu

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 find_symbol_from_addr().
- Format output to match objdump's style (strip '#' prefix, add symbol and offset).

Quoted 1 line

Thanks,
Namhyung

Quoted 294 lines
---
 tools/perf/util/capstone.c | 186 ++++++++++++++++++++++++++++++-------
 tools/perf/util/disasm.c   |   5 +
 tools/perf/util/disasm.h   |   1 +
 3 files changed, 157 insertions(+), 35 deletions(-)
tools/perf/util/capstone.c +151 -35
index 74213daf8786..95881f15324a 100644
@@ -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,6 +303,40 @@ ssize_t capstone__fprintf_insn_asm(struct machine *machine, struct thread *threa
 	return printed;
 }
 
+static struct symbol *find_symbol_from_addr(struct map *map, u64 addr,
+					    struct map **found_map, u64 *sym_offset)
+{
+	struct symbol *sym;
+	u64 al_addr;
+	u64 ip = map__objdump_2mem(map, addr);
+
+	*found_map = NULL;
+	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 NULL;
+		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);
+
+	if (sym == NULL) {
+		map__put(*found_map);
+		*found_map = NULL;
+		return NULL;
+	}
+
+	*sym_offset = al_addr - sym->start;
+	return sym;
+}
+
 static void print_capstone_detail(struct cs_insn *insn, char *buf, size_t len,
 				  struct annotate_args *args, u64 addr)
 {
@@ -299,17 +344,14 @@ static void print_capstone_detail(struct cs_insn *insn, char *buf, size_t len,
 	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;
+		struct map *found_map;
+		u64 sym_offset;
 
 		if (op->type != X86_OP_MEM)
 			continue;
@@ -320,40 +362,119 @@ 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);
+		sym = find_symbol_from_addr(map, orig_addr, &found_map, &sym_offset);
+		if (sym == NULL)
 			continue;
-		}
 
-		if (addr == sym->start) {
+		if (sym_offset == 0) {
 			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);
+				  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)) {
+		struct symbol *sym;
+		struct map *found_map;
+		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;
+
+		sym = find_symbol_from_addr(map, addr, &found_map, &sym_offset);
+		if (sym == NULL)
+			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);
+		}
+		map__put(found_map);
+	}
+}
+
+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 +567,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;
tools/perf/util/disasm.c +5 -0
index 49c206a88eec..b39636ec35c1 100644
@@ -214,6 +214,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)
tools/perf/util/disasm.h +1 -0
index adbdbf8f1f35..3a42166f7f49 100644
@@ -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;
Signature · 1 line
2.34.1
author
#4 lore raw

PATCH v6 2/26 perf llvm: Fix arm64 adrp instruction disassembly mismatch with objdump

In reply to Tengda Wu

The operands of 'adrp' instructions parsed by libllvm are currently represented as raw immediates rather than the "address <symbol+offset>" format used by objdump. This inconsistency causes arm64_mov__parse() to fail when parsing these instructions during post-processing.

Example of the mismatch:
Current: adrp x18, 8014
Objdump: adrp x18, ffff800081f5f000 <this_cpu_vector>

Fix this by manually extracting the target address from the raw adrp instruction via symbol_lookup_callback(). The address is then converted to a specific symbol during symbol__disassemble_llvm() and formatted to match objdump's output, ensuring compatibility with existing parsers.

---
 tools/perf/util/llvm.c | 52 ++++++++++++++++++++++++++++++++++++++----
 1 file changed, 47 insertions(+), 5 deletions(-)
tools/perf/util/llvm.c +47 -5
index a0deb742a733..a78845689aa1 100644
@@ -9,8 +9,10 @@
 #include "symbol.h"
 #include <errno.h>
 #include <fcntl.h>
+#include <string.h>
 #include <unistd.h>
 #include <linux/zalloc.h>
+#include <linux/bitops.h>
 
 #ifdef HAVE_LIBLLVM_SUPPORT
 #include "llvm-c-helpers.h"
@@ -108,6 +110,16 @@ symbol_lookup_callback(void *disinfo, uint64_t value,
 		storage->branch_addr = value;
 	else if (*ref_type == LLVMDisassembler_ReferenceType_In_PCrel_Load)
 		storage->pcrel_load_addr = value;
+	else if (*ref_type == LLVMDisassembler_ReferenceType_In_ARM64_ADRP) {
+		uint64_t adrp_imm;
+
+		/* immhi (bits 23:5) and immlo (bits 30:29) */
+		adrp_imm = ((value & 0x00ffffe0) >> 3) | ((value >> 29) & 0x3);
+		adrp_imm = sign_extend64(adrp_imm, 20);
+
+		/* Calculate the target page address */
+		storage->pcrel_load_addr = (address & ~0xfffULL) + (adrp_imm << 12);
+	}
 	*ref_type = LLVMDisassembler_ReferenceType_InOut_None;
 	return NULL;
 }
@@ -228,12 +240,42 @@ int symbol__disassemble_llvm(const char *filename, struct symbol *sym,
 			}
 		}
 		if (storage.pcrel_load_addr != 0) {
-			char *name = llvm_name_for_data(dso, filename,
+			char *name;
+
+			if (arch__is_arm64(args->arch)) {
+				/*
+				 * For arm64, replace the immediate operand with
+				 * the resolved address to match objdump's output.
+				 *
+				 * Example conversion:
+				 *   From: adrp  x18, 8014
+				 *   To:   adrp  x18, ffff800081f5f000
+				 */
+				char *s = strchr(disasm_buf, ',');
+
+				if (s == NULL)
+					goto err;
+
+				s++;
+				*s = '\0';
+				disasm_len = strlen(disasm_buf);
+				disasm_len += scnprintf(disasm_buf + disasm_len,
+							sizeof(disasm_buf) - disasm_len,
+							" %"PRIx64,
 							storage.pcrel_load_addr);
-			disasm_len += scnprintf(disasm_buf + disasm_len,
-						sizeof(disasm_buf) - disasm_len,
-						"  # %#"PRIx64,
-						storage.pcrel_load_addr);
+			} else {
+				/*
+				 * For other archs, append the resolved address
+				 * as an inline comment.
+				 */
+				disasm_len += scnprintf(disasm_buf + disasm_len,
+							sizeof(disasm_buf) - disasm_len,
+							"  # %#"PRIx64,
+							storage.pcrel_load_addr);
+			}
+
+			/* Append the resolved symbol name if available */
+			name = llvm_name_for_data(dso, filename, storage.pcrel_load_addr);
 			if (name) {
 				disasm_len += scnprintf(disasm_buf + disasm_len,
 							sizeof(disasm_buf) -
Signature · 1 line
2.34.1
#5 lore raw

Re: [PATCH v6 02/26] perf llvm: Fix arm64 adrp instruction disassembly mismatch with objdump

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] perf: arm64 pcrel load formatting failure aborts disassembly --

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] perf: arm64 pcrel load formatting failure aborts disassembly
--

commit ad5ee451ad40bd108c85e2fea401d911d9304dee
Author: Tengda Wu <[email protected]>

perf llvm: Fix arm64 adrp instruction disassembly mismatch with objdump

This commit fixes an inconsistency in how libllvm parses arm64 adrp instructions by manually extracting the target address via symbol_lookup_callback(). The extracted address is then converted to match objdump's format, ensuring compatibility with existing parsers.

Quoted 4 lines
tools/perf/util/llvm.c +0 -0
index a0deb742a7331..a78845689aa1c 100644

[ ... ]

Quoted 1 line
tools/perf/util/llvm.c +0 -0
@@ -228,12 +240,42 @@ int symbol__disassemble_llvm(const char *filename, struct symbol *sym,

[ ... ]

Quoted 7 lines
tools/perf/util/llvm.c +5 -1
 		if (storage.pcrel_load_addr != 0) {
-			char *name = llvm_name_for_data(dso, filename,
+			char *name;
+
+			if (arch__is_arm64(args->arch)) {
+				/*
+				 * For arm64, replace the immediate operand with

tools/perf/util/llvm.c int symbol__disassemble_llvm(const char *filename, struct symbol *sym, · line 257

Quoted 10 lines
tools/perf/util/llvm.c +10 -0
+				 * the resolved address to match objdump's output.
+				 *
+				 * Example conversion:
+				 *   From: adrp  x18, 8014
+				 *   To:   adrp  x18, ffff800081f5f000
+				 */
+				char *s = strchr(disasm_buf, ',');
+
+				if (s == NULL)
+					goto err;

[Severity: Medium]
Could this error path escalate a minor string formatting mismatch into a complete failure of the disassembly operation?

If an ARM64 instruction triggers a PC-relative load but its disassembled text lacks a comma (e.g., due to an unexpected mnemonic format from LLVM, or data incorrectly decoded as an instruction), strchr() returns NULL and triggers this abort.

Because the error path in symbol__disassemble_llvm() fails to clean up partially populated lists, aborting the disassembly here discards the LLVM disassembly for the function. When the fallback disassembler takes over, this leaves interleaved, corrupted disassembly lines and causes a memory leak.

Quoted 8 lines
tools/perf/util/llvm.c +7 -0
+
+				s++;
+				*s = '\0';
+				disasm_len = strlen(disasm_buf);
+				disasm_len += scnprintf(disasm_buf + disasm_len,
+							sizeof(disasm_buf) - disasm_len,
+							" %"PRIx64,
 							storage.pcrel_load_addr);

[ ... ]

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
author
#6 lore raw

Re: [PATCH v6 02/26] perf llvm: Fix arm64 adrp instruction disassembly mismatch with objdump

Quoted 3 lines · On 2026/9/16 9:43, [email protected] wrote:

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] perf: arm64 pcrel load formatting failure aborts disassembly
--

Interesting. In the v5 version, Sashiko reviewed and approved this patch.

Quoted 40 lines

commit ad5ee451ad40bd108c85e2fea401d911d9304dee
Author: Tengda Wu <[email protected]>

perf llvm: Fix arm64 adrp instruction disassembly mismatch with objdump

This commit fixes an inconsistency in how libllvm parses arm64 adrp instructions by manually extracting the target address via symbol_lookup_callback(). The extracted address is then converted to match objdump's format, ensuring compatibility with existing parsers.

Quoted 4 lines
tools/perf/util/llvm.c +0 -0
index a0deb742a7331..a78845689aa1c 100644

[ ... ]

Quoted 1 line
tools/perf/util/llvm.c +0 -0
@@ -228,12 +240,42 @@ int symbol__disassemble_llvm(const char *filename, struct symbol *sym,

[ ... ]

Quoted 7 lines
tools/perf/util/llvm.c +5 -1
 		if (storage.pcrel_load_addr != 0) {
-			char *name = llvm_name_for_data(dso, filename,
+			char *name;
+
+			if (arch__is_arm64(args->arch)) {
+				/*
+				 * For arm64, replace the immediate operand with

tools/perf/util/llvm.c int symbol__disassemble_llvm(const char *filename, struct symbol *sym, · line 257

Quoted 10 lines
tools/perf/util/llvm.c +10 -0
+				 * the resolved address to match objdump's output.
+				 *
+				 * Example conversion:
+				 *   From: adrp  x18, 8014
+				 *   To:   adrp  x18, ffff800081f5f000
+				 */
+				char *s = strchr(disasm_buf, ',');
+
+				if (s == NULL)
+					goto err;

[Severity: Medium]
Could this error path escalate a minor string formatting mismatch into a complete failure of the disassembly operation?

Quoted 3 lines

If an ARM64 instruction triggers a PC-relative load but its disassembled text lacks a comma (e.g., due to an unexpected mnemonic format from LLVM, or data incorrectly decoded as an instruction)

Is such a possibility actually possible? At least so far I haven't seen it. If it does exist, it should probably be considered an LLVM bug?

, strchr() returns NULL and

Quoted 1 line

triggers this abort.

The abort should be the expected behavior; otherwise, this problem would be silently propagated to the arm64_mov__parse stage, making it hard to detect.

Quoted 7 lines

Because the error path in symbol__disassemble_llvm() fails to clean up partially populated lists, aborting the disassembly here discards the LLVM disassembly for the function. When the fallback disassembler takes over, this leaves interleaved, corrupted disassembly lines and causes a memory leak.

This should be referring to here:

symbol__disassemble_llvm:

		llvm_addr2line(filename, pc, &args->fileloc,
			       (unsigned int *)&args->line_nr, false, NULL);

		dl = disasm_line__new(args);
		if (dl == NULL)
			goto err;  // args->fileloc is not freed

		annotation_line__add(&dl->al, &notes->src->source);

		free(args->fileloc);

This is a pre-existing issue. Currently it does not affect this patch, because when this patch does goto err, llvm_addr2line has not been called yet. So I will *keep this patch unchanged* and submit a separate patch to fix this issue.

Thanks,
Tengda

Quoted 10 lines
Quoted 8 lines
tools/perf/util/llvm.c +7 -0
+
+				s++;
+				*s = '\0';
+				disasm_len = strlen(disasm_buf);
+				disasm_len += scnprintf(disasm_buf + disasm_len,
+							sizeof(disasm_buf) - disasm_len,
+							" %"PRIx64,
 							storage.pcrel_load_addr);

[ ... ]

author
#7 lore raw

PATCH v6 3/26 perf annotate-arm64: Generalize arm64_mov__parse to support more instructions

In reply to Tengda Wu

As the default parser for arm64 instructions, arm64_mov__parse() currently only supports parsing instructions with the 'addr <symbol>' suffix. Other instructions are not supported, causing a lack of source and target information for most regular instructions.

Generalize arm64_mov__parse to extend its parsing capabilities:
1. Parse 'ops->target.raw' and 'ops->source.raw' by default.
2. Utilize arm64__check_multi_regs() to identify if source and target contain multiple registers.
3. Maintain backward compatibility for instructions containing the 'addr <symbol>' suffix.

---
 .../perf/util/annotate-arch/annotate-arm64.c  | 166 ++++++++++++++----
 1 file changed, 134 insertions(+), 32 deletions(-)
tools/perf/util/annotate-arch/annotate-arm64.c +134 -32
index 33080fdca125..151184210691 100644
@@ -3,6 +3,8 @@
 #include <errno.h>
 #include <stdlib.h>
 #include <string.h>
+#include <linux/ctype.h>
+#include <linux/string.h>
 #include <linux/zalloc.h>
 #include <regex.h>
 #include "../annotate.h"
@@ -14,59 +16,158 @@ struct arch_arm64 {
 	regex_t jump_insn;
 };
 
-static int arm64_mov__parse(const struct arch *arch __maybe_unused,
+static bool arm64__is_reg(const char *op)
+{
+	if (!op || !*op)
+		return false;
+
+	/*
+	 * General-purpose registers: x0-x30, w0-w30.
+	 * Check for 'x' or 'w' prefix followed by a numeric index.
+	 */
+	if ((op[0] == 'x' || op[0] == 'w') && isdigit(op[1]))
+		return true;
+
+	/* Special-purpose registers: sp, wzr, xzr. */
+	if (!strncmp(op, "sp", 2) || !strncmp(op, "xzr", 3) ||
+	    !strncmp(op, "wzr", 3))
+		return true;
+
+	/* TODO: Support more registers. */
+	return false;
+}
+
+static bool arm64__check_multi_regs(const struct arch *arch, const char *op)
+{
+	const char *p = op;
+	int reg_count = 0;
+
+	while (p && *p) {
+		p = skip_spaces(p);
+		if (*p == arch->objdump.memory_ref_char)
+			p++;
+
+		if (arm64__is_reg(p))
+			reg_count++;
+
+		if (reg_count >= 2)
+			return true;
+
+		/* Move to next operand after comma */
+		p = strchr(p, ',');
+		if (p)
+			p++;
+	}
+
+	return false;
+}
+
+/*
+ * Duplicate @insn, stripping the comment and trailing whitespace.
+ * Returns a newly allocated string which the caller must free(),
+ * or NULL on allocation failure or if @insn is NULL.
+ */
+static char *rstrip_space_and_comment(const char *insn, char comment_char)
+{
+	const char *end, *comment;
+	size_t len;
+	char *result;
+
+	if (insn == NULL)
+		return NULL;
+
+	comment = strchr(insn, comment_char);
+	if (comment != NULL)
+		end = comment;
+	else
+		end = insn + strlen(insn);
+
+	while (end > insn && isspace(end[-1]))
+		--end;
+
+	len = end - insn;
+	result = malloc(len + 1);
+	if (result == NULL)
+		return NULL;
+
+	memcpy(result, insn, len);
+	result[len] = '\0';
+
+	return result;
+}
+
+static int arm64_mov__parse(const struct arch *arch,
 			    struct ins_operands *ops,
 			    struct map_symbol *ms __maybe_unused,
 			    struct disasm_line *dl __maybe_unused)
 {
-	char *s = strchr(ops->raw, ','), *target, *endptr;
+	char *s = strchr(ops->raw, ','), *source, *endptr;
 
 	if (s == NULL)
 		return -1;
 
+	/* Parse target */
 	*s = '\0';
-	ops->source.raw = strdup(ops->raw);
+	ops->target.raw = strdup(ops->raw);
 	*s = ',';
 
-	if (ops->source.raw == NULL)
-		return -1;
-
-	target = ++s;
-	ops->target.raw = strdup(target);
 	if (ops->target.raw == NULL)
-		goto out_free_source;
+		return -1;
 
-	ops->target.addr = strtoull(target, &endptr, 16);
-	if (endptr == target)
-		goto out_free_target;
+	ops->target.multi_regs = arm64__check_multi_regs(arch, ops->target.raw);
 
-	s = strchr(endptr, '<');
-	if (s == NULL)
-		goto out_free_target;
-	endptr = strchr(s + 1, '>');
-	if (endptr == NULL)
-		goto out_free_target;
-
-	*endptr = '\0';
-	*s = ' ';
-	ops->target.name = strdup(s);
-	*s = '<';
-	*endptr = '>';
-	if (ops->target.name == NULL)
-		goto out_free_target;
+	/* Parse source, stripping comment if present */
+	source = skip_spaces(++s);
+	ops->source.raw = rstrip_space_and_comment(source, arch->objdump.comment_char);
+	if (ops->source.raw == NULL) {
+		zfree(&ops->target.raw);
+		return -1;
+	}
+
+	ops->source.multi_regs = arm64__check_multi_regs(arch, ops->source.raw);
+
+	/*
+	 * Parse 'addr <symbol>' from source (if any).
+	 *
+	 * A raw hex string may be a scalar immediate, not an address.
+	 * Only validate 'source.addr' if accompanied by a '<symbol>' tag,
+	 * otherwise reset it to 0 to avoid false positive address tracking.
+	 */
+	ops->source.addr = strtoull(ops->source.raw, &endptr, 16);
+	if (endptr != ops->source.raw) {
+		s = strchr(endptr, '<');
+		if (s == NULL) {
+			ops->source.addr = 0;
+			return 0;
+		}
+		endptr = strrchr(s + 1, '>');
+		if (endptr == NULL) {
+			ops->source.addr = 0;
+			return 0;
+		}
+
+		*endptr = '\0';
+		ops->source.name = strdup(s + 1);
+		*endptr = '>';
+		if (ops->source.name == NULL) {
+			ops->source.addr = 0;
+			return 0;
+		}
+	}
 
 	return 0;
+}
 
-out_free_target:
-	zfree(&ops->target.raw);
-out_free_source:
-	zfree(&ops->source.raw);
-	return -1;
+static int arm64_mov__scnprintf(const struct ins *ins, char *bf, size_t size,
+				struct ins_operands *ops, int max_ins_name)
+{
+	return scnprintf(bf, size, "%-*s %s, %s", max_ins_name, ins->name,
+			 ops->target.raw, ops->source.name ?: ops->source.raw);
 }
 
 static const struct ins_ops arm64_mov_ops = {
 	.parse	   = arm64_mov__parse,
-	.scnprintf = mov__scnprintf,
+	.scnprintf = arm64_mov__scnprintf,
 };
 
 static const struct ins_ops *arm64__associate_instruction_ops(struct arch *arch, const char *name)
@@ -103,6 +204,7 @@ const struct arch *arch__new_arm64(const struct e_machine_and_e_flags *id,
 	arch->id = *id;
 	arch->objdump.comment_char	  = '/';
 	arch->objdump.skip_functions_char = '+';
+	arch->objdump.memory_ref_char	  = '[';
 	arch->associate_instruction_ops   = arm64__associate_instruction_ops;
 
 	/* bl, blr */
Signature · 1 line
2.34.1
#8 lore raw

Re: [PATCH v6 03/26] perf annotate-arm64: Generalize arm64_mov__parse to support more instructions

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 12 lines

As the default parser for arm64 instructions, arm64_mov__parse() currently only supports parsing instructions with the 'addr <symbol>' suffix. Other instructions are not supported, causing a lack of source and target information for most regular instructions.

Generalize arm64_mov__parse to extend its parsing capabilities:
1. Parse 'ops->target.raw' and 'ops->source.raw' by default.
2. Utilize arm64__check_multi_regs() to identify if source and target contain multiple registers.
3. Maintain backward compatibility for instructions containing the 'addr <symbol>' suffix.

Quoted 2 lines

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
author
#9 lore raw

PATCH v6 4/26 perf annotate-arm64: Handle load and store instructions

In reply to Tengda Wu

Add ldst_ops to handle load and store instructions in order to parse the data types and offsets associated with PMU events for memory access instructions. There are many variants of load and store instructions in arm64, making it difficult to match all of these instruction names completely. Therefore, only the instruction prefixes are matched. The prefix 'ld|st' covers most of the memory access instructions, 'cas|swp' matches atomic instructions, and 'prf' matches memory prefetch instructions.

---
 .../perf/util/annotate-arch/annotate-arm64.c  | 140 ++++++++++++++++++
 1 file changed, 140 insertions(+)
tools/perf/util/annotate-arch/annotate-arm64.c +140 -0
index 151184210691..a7595a954cfa 100644
@@ -14,6 +14,7 @@ struct arch_arm64 {
 	struct arch arch;
 	regex_t call_insn;
 	regex_t jump_insn;
+	regex_t ldst_insn; /* load and store instruction */
 };
 
 static bool arm64__is_reg(const char *op)
@@ -170,6 +171,130 @@ static const struct ins_ops arm64_mov_ops = {
 	.scnprintf = arm64_mov__scnprintf,
 };
 
+static bool arm64__insn_is_target_on_right(const char *ins_name)
+{
+	/*
+	 * Store instructions write to the memory operand on the right,
+	 * unlike standard syntax where the target is the left operand.
+	 */
+	return !strncmp(ins_name, "st", 2);
+}
+
+/*
+ * This function is used to parse arm64 load/store instructions into
+ * instruction operands.
+ *
+ * Typical instructions and their parsing logic:
+ *
+ * 1. Immediate offset:
+ *    ldr   x2, [x0]                -> target="x2", source="[x0]"
+ *    ldr   x2, [x0, #24]           -> target="x2", source="[x0, #24]"
+ *    ldp   x19, x20, [sp, #16]     -> target="x19, x20", source="[sp, #16]"
+ *
+ * 2. Pre-index addressing:
+ *    stp   x29, x30, [sp, #-64]!   -> target="[sp, #-64]!", source="x29, x30"
+ *
+ * 3. Post-index addressing:
+ *    str   x1, [x0], #8            -> target="[x0], #8", source="x1"
+ *    ldr   w1, [x21], #4           -> target="w1", source="[x21], #4"
+ *    ldp   x29, x30, [sp], #32     -> target="x29, x30", source="[sp], #32"
+ *
+ * 4. Register offset / extension:
+ *    ldr   x0, [x1, w0, sxtw #3]   -> target="x0", source="[x1, w0, sxtw #3]"
+ *    ldr   x0, [x1, x0, lsl #3]    -> target="x0", source="[x1, x0, lsl #3]"
+ *
+ * 5. Atomic operations:
+ *    cas   w3, w1, [x0]            -> target="w3, w1", source="[x0]"
+ *    swp   x3, x0, [x2]            -> target="x3, x0", source="[x2]"
+ *
+ * 6. Prefetch memory:
+ *    prfm  pstl1strm, [x4]         -> target="pstl1strm", source="[x4]"
+ *
+ * 7. PC-relative loads (No bracket found):
+ *    ldr   x0, ffff800080f40c68 <__kvm_nvhe_$d>  -> Fallback to default parser
+ *
+ * Parsing strategy:
+ * Use the '[' bracket as the boundary to split the operands into left
+ * and right sides. For non-store instructions, the left side is the
+ * target and the right side is the source. For store instructions, the
+ * roles are reversed.
+ */
+static int arm64_ldst__parse(const struct arch *arch, struct ins_operands *ops,
+			     struct map_symbol *ms, struct disasm_line *dl)
+{
+	char *raw, *s, *left, *right;
+	int ret = -1;
+
+	raw = rstrip_space_and_comment(ops->raw, arch->objdump.comment_char);
+	if (!raw)
+		return -1;
+
+	s = strchr(raw, arch->objdump.memory_ref_char);
+	if (!s) {
+		/* Fallback to default parser for PC-relative loads. */
+		free(raw);
+		return arm64_mov__parse(arch, ops, ms, dl);
+	}
+
+	right = strdup(s);
+	if (!right)
+		goto out_free_raw;
+
+	while (s > raw && *s != ',')
+		--s;
+
+	if (s == raw)
+		goto out_free_right;
+
+	*s = '\0';
+	left = strdup(raw);
+	*s = ',';
+	if (!left)
+		goto out_free_right;
+
+	free(raw);
+
+	if (arm64__insn_is_target_on_right(dl->ins.name)) {
+		ops->source.raw = left;
+		ops->source.mem_ref = false;
+
+		ops->target.raw = right;
+		ops->target.mem_ref = true;
+	} else {
+		ops->source.raw = right;
+		ops->source.mem_ref = true;
+
+		ops->target.raw = left;
+		ops->target.mem_ref = false;
+	}
+
+	ops->source.multi_regs = arm64__check_multi_regs(arch, ops->source.raw);
+	ops->target.multi_regs = arm64__check_multi_regs(arch, ops->target.raw);
+
+	return 0;
+
+out_free_right:
+	free(right);
+out_free_raw:
+	free(raw);
+	return ret;
+}
+
+static int arm64_ldst__scnprintf(const struct ins *ins, char *bf, size_t size,
+				 struct ins_operands *ops, int max_ins_name)
+{
+	if (arm64__insn_is_target_on_right(ins->name))
+		return scnprintf(bf, size, "%-*s %s", max_ins_name, ins->name, ops->raw);
+
+	return scnprintf(bf, size, "%-*s %s, %s", max_ins_name, ins->name,
+			 ops->target.raw, ops->source.name ?: ops->source.raw);
+}
+
+static struct ins_ops arm64_ldst_ops = {
+	.parse	   = arm64_ldst__parse,
+	.scnprintf = arm64_ldst__scnprintf,
+};
+
 static const struct ins_ops *arm64__associate_instruction_ops(struct arch *arch, const char *name)
 {
 	struct arch_arm64 *arm = container_of(arch, struct arch_arm64, arch);
@@ -180,6 +305,8 @@ static const struct ins_ops *arm64__associate_instruction_ops(struct arch *arch,
 		ops = &jump_ops;
 	else if (!regexec(&arm->call_insn, name, 2, match, 0))
 		ops = &call_ops;
+	else if (!regexec(&arm->ldst_insn, name, 2, match, 0))
+		ops = &arm64_ldst_ops;
 	else if (!strcmp(name, "ret"))
 		ops = &ret_ops;
 	else
@@ -205,6 +332,7 @@ const struct arch *arch__new_arm64(const struct e_machine_and_e_flags *id,
 	arch->objdump.comment_char	  = '/';
 	arch->objdump.skip_functions_char = '+';
 	arch->objdump.memory_ref_char	  = '[';
+	arch->objdump.imm_char		  = '#';
 	arch->associate_instruction_ops   = arm64__associate_instruction_ops;
 
 	/* bl, blr */
@@ -218,8 +346,20 @@ const struct arch *arch__new_arm64(const struct e_machine_and_e_flags *id,
 	if (err)
 		goto out_free_call;
 
+	/*
+	 * The ARM64 architecture has many variants of load/store instructions.
+	 * It is quite challenging to match all of them completely. Here, we
+	 * only match the prefixes of these instructions.
+	 */
+	err = regcomp(&arm->ldst_insn, "^(ld|st|cas|prf|swp)",
+		      REG_EXTENDED);
+	if (err)
+		goto out_free_jump;
+
 	return arch;
 
+out_free_jump:
+	regfree(&arm->jump_insn);
 out_free_call:
 	regfree(&arm->call_insn);
 out_free_arm:
Signature · 1 line
2.34.1
#10 lore raw

Re: [PATCH v6 04/26] perf annotate-arm64: Handle load and store instructions

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 9 lines

Add ldst_ops to handle load and store instructions in order to parse the data types and offsets associated with PMU events for memory access instructions. There are many variants of load and store instructions in arm64, making it difficult to match all of these instruction names completely. Therefore, only the instruction prefixes are matched. The prefix 'ld|st' covers most of the memory access instructions, 'cas|swp' matches atomic instructions, and 'prf' matches memory prefetch instructions.

Quoted 3 lines

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=4
author
#11 lore raw

PATCH v6 5/26 perf annotate: Normalize arch__dwarf_regnum() error return values

In reply to Tengda Wu

arch__dwarf_regnum() returns inconsistent error values: early path returns -1 while the main lookup returns -ENOENT/-EINVAL from get_dwarf_regnum(). Callers only check for -1, so genuine failures can slip through.

Normalize all failures to -1 to match the callers' expectation.

---
 tools/perf/util/annotate.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
tools/perf/util/annotate.c +1 -1
index fd789eb53e74..b0f8a8d37333 100644
@@ -2600,7 +2600,7 @@ static int arch__dwarf_regnum(const struct arch *arch, const char *str)
 
 	reg = get_dwarf_regnum(regname, arch->id.e_machine, arch->id.e_flags);
 	free(regname);
-	return reg;
+	return reg < 0 ? -1 : reg;
 }
 
 /*
Signature · 1 line
2.34.1
#12 lore raw

Re: [PATCH v6 05/26] perf annotate: Normalize arch__dwarf_regnum() error return values

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 11 lines

arch__dwarf_regnum() returns inconsistent error values: early path returns -1 while the main lookup returns -ENOENT/-EINVAL from get_dwarf_regnum(). Callers only check for -1, so genuine failures can slip through.

Normalize all failures to -1 to match the callers' expectation.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=5
author
#13 lore raw

PATCH v6 6/26 perf annotate: Introduce extract_op_location callback for arch-specific parsing

In reply to Tengda Wu

Assembly syntax for operands varies significantly across different architectures, which prevents the operand location (op_loc) parsing logic in annotate_get_insn_location() from being directly reused.

To simplify the core logic and improve maintainability, move the operand parsing inside the for_each_insn_op_loc loop into arch-specific extract_op_location callbacks. This refactoring is intended to be a cleanup with no functional changes.

---
 .../util/annotate-arch/annotate-powerpc.c     |  9 ++
 tools/perf/util/annotate-arch/annotate-x86.c  | 79 ++++++++++++++++
 tools/perf/util/annotate.c                    | 94 +------------------
 tools/perf/util/annotate.h                    |  2 +
 tools/perf/util/disasm.h                      |  4 +
 5 files changed, 99 insertions(+), 89 deletions(-)
tools/perf/util/annotate-arch/annotate-powerpc.c +9 -0
index 218207b52581..00b93296fc94 100644
@@ -390,6 +390,14 @@ static void update_insn_state_powerpc(struct type_state *state,
 }
 #endif /* HAVE_LIBDW_SUPPORT */
 
+static void extract_op_location_powerpc(const struct arch *arch __maybe_unused,
+					struct disasm_line *dl,
+					const char *op_str __maybe_unused, int op_idx,
+					struct annotated_op_loc *op_loc)
+{
+	get_powerpc_regs(dl->raw.raw_insn, !op_idx, op_loc);
+}
+
 const struct arch *arch__new_powerpc(const struct e_machine_and_e_flags *id,
 				     const char *cpuid __maybe_unused)
 {
@@ -406,5 +414,6 @@ const struct arch *arch__new_powerpc(const struct e_machine_and_e_flags *id,
 #ifdef HAVE_LIBDW_SUPPORT
 	arch->update_insn_state = update_insn_state_powerpc;
 #endif
+	arch->extract_op_location = extract_op_location_powerpc;
 	return arch;
 }
tools/perf/util/annotate-arch/annotate-x86.c +79 -0

96 lines not on this page · raw message

tools/perf/util/annotate.c +5 -89

113 lines not on this page · raw message

tools/perf/util/annotate.h +2 -0
index 4c49d41e73a4..6cd3ef537564 100644
@@ -524,6 +524,8 @@ int annotate_parse_percent_type(const struct option *opt, const char *_str,
 
 int annotate_check_args(void);
 
+int arch__dwarf_regnum(const struct arch *arch, const char *str);
+
 /**
  * struct annotated_op_loc - Location info of instruction operand
  * @reg1: First register in the operand
tools/perf/util/disasm.h +4 -0
index 3a42166f7f49..7a1c889f2c50 100644
@@ -16,6 +16,7 @@ struct symbol;
 struct data_loc_info;
 struct type_state;
 struct disasm_line;
+struct annotated_op_loc;
 
 struct e_machine_and_e_flags {
 	uint32_t e_flags;
@@ -49,6 +50,9 @@ struct arch {
 				struct data_loc_info *dloc, Dwarf_Die *cu_die,
 				struct disasm_line *dl);
 #endif
+	void		(*extract_op_location)(const struct arch *arch, struct disasm_line *dl,
+					       const char *op_str, int op_idx,
+					       struct annotated_op_loc *op_loc);
 };
 
 struct ins {
Signature · 1 line
2.34.1
#14 lore raw

Re: [PATCH v6 06/26] perf annotate: Introduce extract_op_location callback for arch-specific parsing

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 4 lines

Assembly syntax for operands varies significantly across different architectures, which prevents the operand location (op_loc) parsing logic in annotate_get_insn_location() from being directly reused.

Quoted 8 lines

To simplify the core logic and improve maintainability, move the operand parsing inside the for_each_insn_op_loc loop into arch-specific extract_op_location callbacks. This refactoring is intended to be a cleanup with no functional changes.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=6
author
#15 lore raw

PATCH v6 7/26 perf dwarf-regs: Adapt get_dwarf_regnum() for arm64

In reply to Tengda Wu

The current arm64 DWARF register lookup relies on 'aarch64_regstr_tbl', a static string table. While this works for kprobe-tracer where register names start with '%', it is insufficient for parsing register numbers directly from raw instructions (e.g., extracting '6' from 'x6' or 'w6') during annotation.

Since get_dwarf_regnum() is currently used only by 'perf annotate' and does not affect kprobe-tracer, replace the limited table-based lookup with a programmatic implementation in __get_dwarf_regnum_arm64(). This allows resolving arm64 register names (x0-x30, w0-w30, sp, etc.) directly into their corresponding DWARF register numbers.

---
 .../util/dwarf-regs-arch/dwarf-regs-arm64.c   | 25 +++++++++++++++++++
 tools/perf/util/dwarf-regs.c                  |  2 +-
 tools/perf/util/include/dwarf-regs.h          |  1 +
 3 files changed, 27 insertions(+), 1 deletion(-)
tools/perf/util/dwarf-regs-arch/dwarf-regs-arm64.c +25 -0
index 593ca7d4fccc..425c8aa2e984 100644
@@ -1,5 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0
 #include <errno.h>
+#include <ctype.h>
+#include <string.h>
 #include <dwarf-regs.h>
 #include "../../../arch/arm64/include/uapi/asm/perf_regs.h"
 
@@ -10,3 +12,26 @@ int __get_dwarf_regnum_for_perf_regnum_arm64(int perf_regnum)
 
 	return perf_regnum;
 }
+
+int __get_dwarf_regnum_arm64(const char *name)
+{
+	int reg;
+
+	if (!strcmp(name, "sp"))
+		return 31;
+
+	if (*name != 'x' && *name != 'w')
+		return -ENOENT;
+
+	name++;
+	if (!isdigit(name[0]))
+		return -ENOENT;
+	else if (isdigit(name[1]) && name[2] == '\0')
+		reg = (name[0] - '0') * 10 + name[1] - '0';
+	else if (name[1] == '\0')
+		reg = name[0] - '0';
+	else
+		return -ENOENT;
+
+	return reg >= 0 && reg <= 30 ? reg : -ENOENT;
+}
tools/perf/util/dwarf-regs.c +1 -1
index 67f38342247e..09aca6304002 100644
@@ -114,7 +114,7 @@ int get_dwarf_regnum(const char *name, unsigned int machine, unsigned int flags)
 		reg = _get_dwarf_regnum(arm_regstr_tbl, name);
 		break;
 	case EM_AARCH64:
-		reg = _get_dwarf_regnum(aarch64_regstr_tbl, name);
+		reg = __get_dwarf_regnum_arm64(name);
 		break;
 	case EM_CSKY:
 		reg = __get_csky_regnum(name, flags);
tools/perf/util/include/dwarf-regs.h +1 -0
index 92cf0af93e9e..1da915c2683a 100644
@@ -105,6 +105,7 @@ int __get_dwarf_regnum_x86_64(const char *name);
 int __get_dwarf_regnum_for_perf_regnum_i386(int perf_regnum);
 int __get_dwarf_regnum_for_perf_regnum_x86_64(int perf_regnum, int abi);
 
+int __get_dwarf_regnum_arm64(const char *name);
 int __get_dwarf_regnum_for_perf_regnum_arm(int perf_regnum);
 int __get_dwarf_regnum_for_perf_regnum_arm64(int perf_regnum);
 
Signature · 1 line
2.34.1
#16 lore raw

Re: [PATCH v6 07/26] perf dwarf-regs: Adapt get_dwarf_regnum() for arm64

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] perf: arm64 DWARF register parser misses 'lr' and 'fp' aliases --

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] perf: arm64 DWARF register parser misses 'lr' and 'fp' aliases --

commit 7f62b33196a65173a3a904647c4eb49f9a4b65f8
Author: Tengda Wu <[email protected]>

perf dwarf-regs: Adapt get_dwarf_regnum() for arm64

This commit replaces the static string table lookup for AArch64 DWARF registers with a programmatic implementation in __get_dwarf_regnum_arm64(). This change allows resolving arm64 register names directly from raw instructions during annotation.

Quoted 4 lines
tools/perf/util/dwarf-regs-arch/dwarf-regs-arm64.c +0 -0
index 593ca7d4fccca..425c8aa2e9840 100644

[ ... ]

Quoted 4 lines
tools/perf/util/dwarf-regs-arch/dwarf-regs-arm64.c +0 -0
@@ -10,3 +12,26 @@ int __get_dwarf_regnum_for_perf_regnum_arm64(int perf_regnum)
 
 	return perf_regnum;
 }

tools/perf/util/dwarf-regs-arch/dwarf-regs-arm64.c int __get_dwarf_regnum_for_perf_regnum_arm64(int perf_regnum) · line 24

Quoted 10 lines
tools/perf/util/dwarf-regs-arch/dwarf-regs-arm64.c +10 -0
+
+int __get_dwarf_regnum_arm64(const char *name)
+{
+	int reg;
+
+	if (!strcmp(name, "sp"))
+		return 31;
+
+	if (*name != 'x' && *name != 'w')
+		return -ENOENT;

[Severity: Medium]
Does this prefix check erroneously reject the standard AArch64 aliases 'lr' and 'fp'?

When perf annotate parses instructions where AArch64 objdump outputs 'lr' and 'fp' instead of 'x30' and 'x29', extract_op_location_arm64() passes these names to __get_dwarf_regnum_arm64(). By strictly requiring an 'x' or 'w' prefix (or exactly 'sp'), this rejects 'lr' or 'fp' with -ENOENT.

Can this effectively disable data type tracking for variables accessed via the link register or frame pointer, resulting in incomplete profiling output?

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=7
author
#17 lore raw

Re: [PATCH v6 07/26] perf dwarf-regs: Adapt get_dwarf_regnum() for arm64

Quoted 38 lines · On 2026/9/16 9:43, [email protected] wrote:

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] perf: arm64 DWARF register parser misses 'lr' and 'fp' aliases --

commit 7f62b33196a65173a3a904647c4eb49f9a4b65f8
Author: Tengda Wu <[email protected]>

perf dwarf-regs: Adapt get_dwarf_regnum() for arm64

This commit replaces the static string table lookup for AArch64 DWARF registers with a programmatic implementation in __get_dwarf_regnum_arm64(). This change allows resolving arm64 register names directly from raw instructions during annotation.

Quoted 4 lines
tools/perf/util/dwarf-regs-arch/dwarf-regs-arm64.c +0 -0
index 593ca7d4fccca..425c8aa2e9840 100644

[ ... ]

Quoted 4 lines
tools/perf/util/dwarf-regs-arch/dwarf-regs-arm64.c +0 -0
@@ -10,3 +12,26 @@ int __get_dwarf_regnum_for_perf_regnum_arm64(int perf_regnum)
 
 	return perf_regnum;
 }

tools/perf/util/dwarf-regs-arch/dwarf-regs-arm64.c int __get_dwarf_regnum_for_perf_regnum_arm64(int perf_regnum) · line 24

Quoted 10 lines
tools/perf/util/dwarf-regs-arch/dwarf-regs-arm64.c +10 -0
+
+int __get_dwarf_regnum_arm64(const char *name)
+{
+	int reg;
+
+	if (!strcmp(name, "sp"))
+		return 31;
+
+	if (*name != 'x' && *name != 'w')
+		return -ENOENT;

[Severity: Medium]
Does this prefix check erroneously reject the standard AArch64 aliases 'lr' and 'fp'?

Quoted 8 lines

When perf annotate parses instructions where AArch64 objdump outputs 'lr' and 'fp' instead of 'x30' and 'x29', extract_op_location_arm64() passes these names to __get_dwarf_regnum_arm64(). By strictly requiring an 'x' or 'w' prefix (or exactly 'sp'), this rejects 'lr' or 'fp' with -ENOENT.

Can this effectively disable data type tracking for variables accessed via the link register or frame pointer, resulting in incomplete profiling output?

This was clarified before -- it can be ignored.

Thanks,
Tengda

author
#18 lore raw

PATCH v6 8/26 perf annotate: Adapt arch__dwarf_regnum() for arm64

In reply to Tengda Wu

Currently, arch__dwarf_regnum() assumes that all architectures use a register prefix character (e.g., '%' for x86) defined by arch->objdump.register_char, and uses it to match register names in objdump output. However, this assumption does not hold for arm64, where assembly syntax uses bare register names like 'x0', 'w1' without any prefix.

As a result, arm64 builds may fail to correctly recognize register names from objdump disassembly, leading to incomplete or incorrect annotation output.

To address this:

- Make the register prefix check optional, allowing architectures without a prefix character to be parsed correctly.

- Extend the delimiter set in strpbrk() to include the closing square bracket ']'. In arm64 assembly, memory operands often use bracketed syntax such as '[x1, #16]' or '[x2]'. Adding ']' ensures clean extraction of register names like 'x2' without trailing characters.

---
 tools/perf/util/annotate.c | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)
tools/perf/util/annotate.c +7 -5
index d3482eba9283..86a4beec6528 100644
@@ -2582,19 +2582,21 @@ int annotate_check_args(void)
 
 int arch__dwarf_regnum(const struct arch *arch, const char *str)
 {
-	const char *p;
+	const char *p = str;
 	char *regname, *q;
 	int reg;
 
-	p = strchr(str, arch->objdump.register_char);
-	if (p == NULL)
-		return -1;
+	if (arch->objdump.register_char) {
+		p = strchr(str, arch->objdump.register_char);
+		if (p == NULL)
+			return -1;
+	}
 
 	regname = strdup(p);
 	if (regname == NULL)
 		return -1;
 
-	q = strpbrk(regname, ",) ");
+	q = strpbrk(regname, ",)] ");
 	if (q)
 		*q = '\0';
 
Signature · 1 line
2.34.1
#19 lore raw

Re: [PATCH v6 08/26] perf annotate: Adapt arch__dwarf_regnum() for arm64

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 16 lines

Currently, arch__dwarf_regnum() assumes that all architectures use a register prefix character (e.g., '%' for x86) defined by arch->objdump.register_char, and uses it to match register names in objdump output. However, this assumption does not hold for arm64, where assembly syntax uses bare register names like 'x0', 'w1' without any prefix.

As a result, arm64 builds may fail to correctly recognize register names from objdump disassembly, leading to incomplete or incorrect annotation output.

To address this:

- Make the register prefix check optional, allowing architectures without a prefix character to be parsed correctly.

Quoted 7 lines

- Extend the delimiter set in strpbrk() to include the closing square bracket ']'. In arm64 assembly, memory operands often use bracketed syntax such as '[x1, #16]' or '[x2]'. Adding ']' ensures clean extraction of register names like 'x2' without trailing characters.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=8
author
#20 lore raw

PATCH v6 9/26 perf annotate-arm64: Implement extract_op_location() callback

In reply to Tengda Wu

Implement the extract_op_location() callback for the arm64 architecture to handle its specific assembly syntax and addressing modes.

The extractor handles:
1. Immediate operands (e.g., '#0x10' or '#0xc600, lsl #16').
2. Memory references with diverse addressing modes:
- Signed offset: [base{, #imm|reg}]
- Pre-index: [base, #imm]!
- Post-index: [base], #imm|reg
3. Multi-register operands and primary/secondary register extraction.
4. Register extension specifiers (e.g., uxtw, sxtx) and shift amounts for shifted/extended operands.

With this callback in place, 'perf annotate' can now resolve memory locations and register types on arm64, marking the initial enablement of data type profiling for this architecture.

---
 .../perf/util/annotate-arch/annotate-arm64.c  | 133 ++++++++++++++++++
 tools/perf/util/annotate.h                    |  61 ++++++++
 2 files changed, 194 insertions(+)
tools/perf/util/annotate-arch/annotate-arm64.c +133 -0
index a7595a954cfa..508a95d0ad4b 100644
@@ -316,6 +316,138 @@ static const struct ins_ops *arm64__associate_instruction_ops(struct arch *arch,
 	return ops;
 }
 
+static enum annotated_ext_type arm64__check_ext_type(const char *op)
+{
+	if (!strncmp(op, "uxtb", 4)) return PERF_EXT_UXTB;
+	if (!strncmp(op, "uxth", 4)) return PERF_EXT_UXTH;
+	if (!strncmp(op, "uxtw", 4)) return PERF_EXT_UXTW;
+	if (!strncmp(op, "uxtx", 4)) return PERF_EXT_UXTX;
+	if (!strncmp(op, "sxtb", 4)) return PERF_EXT_SXTB;
+	if (!strncmp(op, "sxth", 4)) return PERF_EXT_SXTH;
+	if (!strncmp(op, "sxtw", 4)) return PERF_EXT_SXTW;
+	if (!strncmp(op, "sxtx", 4)) return PERF_EXT_SXTX;
+
+	return PERF_EXT_NONE;
+}
+
+static enum annotated_shift_type arm64__check_shift_type(const char *op)
+{
+	if (!strncmp(op, "lsl", 3)) return PERF_SHIFT_LSL;
+	if (!strncmp(op, "lsr", 3)) return PERF_SHIFT_LSR;
+	if (!strncmp(op, "asr", 3)) return PERF_SHIFT_ASR;
+	if (!strncmp(op, "ror", 3)) return PERF_SHIFT_ROR;
+
+	return PERF_SHIFT_NONE;
+}
+
+static const char *next_operand_field(const char *s)
+{
+	s = strchr(s, ',');
+	return s ? skip_spaces(s + 1) : NULL;
+}
+
+static void extract_op_location_arm64(const struct arch *arch,
+				      struct disasm_line *dl __maybe_unused,
+				      const char *op_str, int op_idx __maybe_unused,
+				      struct annotated_op_loc *op_loc)
+{
+	const char *s = op_str;
+	char *p = NULL;
+
+	if (op_str == NULL)
+		return;
+
+	/*
+	 * Handle immediate operand.
+	 * e.g., "#0xc600" -> offset = 0xc600, imm = true
+	 *
+	 * After parsing, check for post-modifiers like "lsl #16", which
+	 * typically occur in instructions such as movk, movz, movn, etc.
+	 */
+	if (*s == arch->objdump.imm_char) {
+		op_loc->offset = strtol(s + 1, &p, 0);
+		if (p && p != s + 1) {
+			op_loc->imm = true;
+			s = p;
+		}
+		s = next_operand_field(s);
+		goto check_modifiers;
+	}
+
+	/*
+	 * Handle memory references, identify arm64 specific addressing modes.
+	 * Reference: Arm Architecture Reference Manual
+	 *            (DDI 0487), Chapter C1.3.3: Load/store addressing modes.
+	 */
+	if (*s == arch->objdump.memory_ref_char) {
+		op_loc->mem_ref = true;
+
+		p = strchr(s, ']');
+		if (p == NULL)
+			return;
+
+		/* Pre-index: [base, #imm]! */
+		if (p[1] == '!')
+			op_loc->addr_mode = PERF_AAM_PRE_INDEX;
+		/* Post-index: [base], #imm|reg */
+		else if (p[1] == ',' &&
+			 (strchr(p + 1, arch->objdump.imm_char) ||
+			  arm64__is_reg(skip_spaces(p + 2))))
+			op_loc->addr_mode = PERF_AAM_POST_INDEX;
+		/* Signed offset: [base{, #imm|reg}] */
+		else
+			op_loc->addr_mode = PERF_AAM_SIGNED_OFFSET;
+
+		s++;
+	}
+
+	/* Extract the primary register */
+	op_loc->reg1 = arch__dwarf_regnum(arch, s);
+	if (op_loc->reg1 == -1)
+		return;
+
+	s = next_operand_field(s);
+	if (s == NULL)
+		return;
+
+	/* Extract secondary register or immediate offset */
+	if (op_loc->multi_regs) {
+		op_loc->reg2 = arch__dwarf_regnum(arch, s);
+		s = next_operand_field(s);
+	} else if (*s == arch->objdump.imm_char) {
+		op_loc->offset = strtol(s + 1, NULL, 0);
+		s = next_operand_field(s);
+	}
+
+check_modifiers:
+	/*
+	 * Look for a following shift or extension modifier:
+	 *   "lsl #3"   -> extend_type = PERF_EXT_NONE,
+	 *                 shift_type = PERF_SHIFT_LSL, amount = 3
+	 *   "uxtw #3"  -> extend_type = PERF_EXT_UXTW,
+	 *                 shift_type = PERF_SHIFT_LSL, amount = 3
+	 *   "uxtw"     -> extend_type = PERF_EXT_UXTW,
+	 *                 shift_type = PERF_SHIFT_LSL, amount = 0
+	 */
+	if (s == NULL)
+		return;
+
+	op_loc->extend_type = arm64__check_ext_type(s);
+	op_loc->shift_type = arm64__check_shift_type(s);
+	/* ARM64 extended operands are implicitly shifted by LSL. */
+	if (op_loc->extend_type != PERF_EXT_NONE)
+		op_loc->shift_type = PERF_SHIFT_LSL;
+
+	/* Parse shift amount if present */
+	op_loc->amount = 0;
+	if (op_loc->extend_type != PERF_EXT_NONE ||
+	    op_loc->shift_type != PERF_SHIFT_NONE) {
+		s = strchr(s, arch->objdump.imm_char);
+		if (s)
+			op_loc->amount = (u8)strtol(s + 1, NULL, 0);
+	}
+}
+
 const struct arch *arch__new_arm64(const struct e_machine_and_e_flags *id,
 				   const char *cpuid __maybe_unused)
 {
@@ -334,6 +466,7 @@ const struct arch *arch__new_arm64(const struct e_machine_and_e_flags *id,
 	arch->objdump.memory_ref_char	  = '[';
 	arch->objdump.imm_char		  = '#';
 	arch->associate_instruction_ops   = arm64__associate_instruction_ops;
+	arch->extract_op_location	  = extract_op_location_arm64;
 
 	/* bl, blr */
 	err = regcomp(&arm->call_insn, "^blr?$", REG_EXTENDED);
tools/perf/util/annotate.h +61 -0

79 lines not on this page · raw message

Signature · 1 line
2.34.1
#21 lore raw

Re: [PATCH v6 09/26] perf annotate-arm64: Implement extract_op_location() callback

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 13 lines

Implement the extract_op_location() callback for the arm64 architecture to handle its specific assembly syntax and addressing modes.

The extractor handles:
1. Immediate operands (e.g., '#0x10' or '#0xc600, lsl #16').
2. Memory references with diverse addressing modes:
- Signed offset: [base{, #imm|reg}]
- Pre-index: [base, #imm]!
- Post-index: [base], #imm|reg
3. Multi-register operands and primary/secondary register extraction.
4. Register extension specifiers (e.g., uxtw, sxtx) and shift amounts for shifted/extended operands.

Quoted 5 lines

With this callback in place, 'perf annotate' can now resolve memory locations and register types on arm64, marking the initial enablement of data type profiling for this architecture.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=9
author
#22 lore raw

PATCH v6 10/26 perf annotate: Default to --itrace=i1i for data type profiling

In reply to Tengda Wu

ARM SPE-based sampling can synthesize multiple events on a single instruction, as shown below. This would affect data type profiling statistics:

Available samples
0 arm_spe_0/ts_enable=1,pa_enable=1,load_filter=1,store_filter=1,min_latency=30/ 0 dummy:u
84K l1d-miss
95K l1d-access
77K llc-miss
58K llc-access
9K tlb-miss
108K tlb-access
0 branch
13K remote-access
108K memory
108K instructions

While 'perf report' provides an interactive menu for users to select a specific event to prevent duplicate counting, 'perf annotate' lacks such a mechanism. Consequently, it counts all instructions across these overlapping events, which inflates the profile and distorts the data type statistics.

Although using the '--itrace' option can work around this issue (e.g.:
perf annotate --data-type --stdio --itrace=i1i), it is inconvenient for users to specify this explicitly every time.

To address this, introduce itrace_synth_opts.default_single_event_per_ip. Set this field to true when data type profiling is enabled and the user has not explicitly specified an itrace option. In itrace_synth_opts__set_default(), use this flag to default to synthesizing at most one event per IP (equivalent to --itrace=i1i) to prevent duplicate sample counts.

---
 tools/perf/builtin-annotate.c |  8 ++++++++
 tools/perf/util/arm-spe.c     |  5 ++++-
 tools/perf/util/auxtrace.c    | 12 ++++++++++--
 tools/perf/util/auxtrace.h    |  7 ++++++-
 tools/perf/util/cs-etm.c      |  2 +-
 tools/perf/util/intel-bts.c   |  2 +-
 tools/perf/util/intel-pt.c    |  3 ++-
 7 files changed, 32 insertions(+), 7 deletions(-)
tools/perf/builtin-annotate.c +8 -0
index 850fc72fa75f..8863f3a570f6 100644
@@ -885,6 +885,14 @@ int cmd_annotate(int argc, const char **argv)
 	annotate.session = perf_session__new(&data, &annotate.tool);
 	if (IS_ERR(annotate.session))
 		return PTR_ERR(annotate.session);
+	/*
+	 * Hardware tracing (e.g., ARM SPE) may synthesize multiple events per
+	 * instruction. When data type profiling is enabled, default to synthesizing
+	 * at most one event (equivalent to --itrace=i1i) to prevent skewed
+	 * statistics.
+	 */
+	if (annotate.data_type && !itrace_synth_opts.set)
+		itrace_synth_opts.default_single_event_per_ip = true;
 
 	annotate.session->itrace_synth_opts = &itrace_synth_opts;
 
tools/perf/util/arm-spe.c +4 -1
index 401aab529309..ae7a5e9c3892 100644
@@ -2029,7 +2029,10 @@ int arm_spe_process_auxtrace_info(union perf_event *event,
 	if (session->itrace_synth_opts && session->itrace_synth_opts->set) {
 		spe->synth_opts = *session->itrace_synth_opts;
 	} else {
-		itrace_synth_opts__set_default(&spe->synth_opts, false);
+		struct itrace_synth_opts *opts = session->itrace_synth_opts;
+		bool single_event_per_ip = opts ? opts->default_single_event_per_ip : false;
+
+		itrace_synth_opts__set_default(&spe->synth_opts, false, single_event_per_ip);
 		/* Default nanoseconds period not supported */
 		spe->synth_opts.period_type = PERF_ITRACE_PERIOD_INSTRUCTIONS;
 		spe->synth_opts.period = 1;
tools/perf/util/auxtrace.c +10 -2
index aa749e1c3036..ed2113190785 100644
@@ -1478,8 +1478,15 @@ s64 perf_event__process_auxtrace(const struct perf_tool *tool __maybe_unused,
 #define PERF_ITRACE_MAX_LAST_BRANCH_SZ		1024
 
 void itrace_synth_opts__set_default(struct itrace_synth_opts *synth_opts,
-				    bool no_sample)
+				    bool no_sample, bool single_event_per_ip)
 {
+	if (single_event_per_ip) {
+		synth_opts->instructions = true;
+		synth_opts->period_type = PERF_ITRACE_PERIOD_INSTRUCTIONS;
+		synth_opts->period = 1;
+		return;
+	}
+
 	synth_opts->branches = true;
 	synth_opts->transactions = true;
 	synth_opts->ptwrites = true;
@@ -1582,7 +1589,8 @@ int itrace_do_parse_synth_opts(struct itrace_synth_opts *synth_opts,
 
 	if (!str) {
 		itrace_synth_opts__set_default(synth_opts,
-					       synth_opts->default_no_sample);
+					       synth_opts->default_no_sample,
+					       synth_opts->default_single_event_per_ip);
 		return 0;
 	}
 
tools/perf/util/auxtrace.h +6 -1
index 6947f3f284c0..5012bead1b92 100644
@@ -65,6 +65,10 @@ enum itrace_period_type {
  * struct itrace_synth_opts - AUX area tracing synthesis options.
  * @set: indicates whether or not options have been set
  * @default_no_sample: Default to no sampling.
+ * @default_single_event_per_ip: Default to synthesizing at most 1 event per IP
+ *                               (equivalent to --itrace=i1i). Used by commands
+ *                               like 'perf annotate --data-type' to avoid duplicate
+ *                               sample counts for a single instruction.
  * @inject: indicates the event (not just the sample) must be fully synthesized
  *          because 'perf inject' will write it out
  * @instructions: whether to synthesize 'instructions' events
@@ -117,6 +121,7 @@ enum itrace_period_type {
 struct itrace_synth_opts {
 	bool			set;
 	bool			default_no_sample;
+	bool			default_single_event_per_ip;
 	bool			inject;
 	bool			instructions;
 	bool			cycles;
@@ -622,7 +627,7 @@ int itrace_do_parse_synth_opts(struct itrace_synth_opts *synth_opts,
 int itrace_parse_synth_opts(const struct option *opt, const char *str,
 			    int unset);
 void itrace_synth_opts__set_default(struct itrace_synth_opts *synth_opts,
-				    bool no_sample);
+				    bool no_sample, bool single_event_per_ip);
 
 size_t perf_event__fprintf_auxtrace_error(union perf_event *event, FILE *fp);
 void perf_session__auxtrace_error_inc(struct perf_session *session,
tools/perf/util/cs-etm.c +1 -1
index 114b3cd2da49..2d1ab34f7b6b 100644
@@ -3626,7 +3626,7 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event,
 		etm->synth_opts = *session->itrace_synth_opts;
 	} else {
 		itrace_synth_opts__set_default(&etm->synth_opts,
-				session->itrace_synth_opts->default_no_sample);
+				session->itrace_synth_opts->default_no_sample, false);
 		etm->synth_opts.callchain = false;
 		etm->synth_opts.thread_stack = session->itrace_synth_opts->thread_stack;
 	}
tools/perf/util/intel-bts.c +1 -1
index 02df3e460489..4c9cca3acb6c 100644
@@ -877,7 +877,7 @@ int intel_bts_process_auxtrace_info(union perf_event *event,
 		bts->synth_opts = *session->itrace_synth_opts;
 	} else {
 		itrace_synth_opts__set_default(&bts->synth_opts,
-				session->itrace_synth_opts->default_no_sample);
+				session->itrace_synth_opts->default_no_sample, false);
 		bts->synth_opts.thread_stack =
 				session->itrace_synth_opts->thread_stack;
 	}
tools/perf/util/intel-pt.c +2 -1
index 9d6628169fd9..8c21c9f52d57 100644
@@ -4463,7 +4463,8 @@ int intel_pt_process_auxtrace_info(union perf_event *event,
 	} else {
 		struct itrace_synth_opts *opts = session->itrace_synth_opts;
 
-		itrace_synth_opts__set_default(&pt->synth_opts, opts->default_no_sample);
+		itrace_synth_opts__set_default(&pt->synth_opts,
+					opts->default_no_sample, false);
 		if (!opts->default_no_sample && !opts->inject) {
 			pt->synth_opts.branches = false;
 			pt->synth_opts.callchain = true;
Signature · 1 line
2.34.1
#23 lore raw

Re: [PATCH v6 10/26] perf annotate: Default to --itrace=i1i for data type profiling

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 19 lines

ARM SPE-based sampling can synthesize multiple events on a single instruction, as shown below. This would affect data type profiling statistics:

Available samples
0 arm_spe_0/ts_enable=1,pa_enable=1,load_filter=1,store_filter=1,min_latency=30/ 0 dummy:u
84K l1d-miss
95K l1d-access
77K llc-miss
58K llc-access
9K tlb-miss
108K tlb-access
0 branch
13K remote-access
[ ... ]
use this flag to default to synthesizing at most one event per IP (equivalent to --itrace=i1i) to prevent duplicate sample counts.

Quoted 2 lines

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=10
author
#24 lore raw

PATCH v6 11/26 perf arm-spe: Set default synthesized event period to 1

In reply to Tengda Wu

When using --itrace=M for data type profiling on arm64, the 'Percent' values in perf annotate output are all zero:

Annotate type: 'struct mmu_gather_batch' in [kernel.kallsyms] (15 samples):
============================================================================

 Percent     offset       size  field
    0.00          0       0x10  struct mmu_gather_batch  {
    0.00          0        0x8      struct mmu_gather_batch*    next;
    0.00        0x8        0x4      unsigned int        nr;
    0.00        0xc        0x4      unsigned int        max;
    0.00       0x10          0      struct encoded_page*[]      encoded_pages;
                                };

However, adding the -n option (show-nr-samples) reveals non-zero sample counts:

Annotate type: 'struct mmu_gather_batch' in [kernel.kallsyms] (15 samples):
============================================================================

 Samples     offset       size  field
      15          0       0x10  struct mmu_gather_batch  {
      13          0        0x8      struct mmu_gather_batch*    next;
       2        0x8        0x4      unsigned int        nr;
       0        0xc        0x4      unsigned int        max;
       0       0x10          0      struct encoded_page*[]      encoded_pages;
                                };

The root cause is that when --itrace is used with non-i/y options (e.g., --itrace=M), the period remains zero after parsing. During subsequent propagation, the final percentage calculation results in zero:

   arm_spe_prep_sample()
      sample->period = spe->synth_opts.period;

   __hists__add_entry()
      he.stat.period = sample->period;

   annotated_data_type__update_samples()
      h->period += period;
      h->addr[offset].period += period;

   print_annotated_data_value()
      double percent = h->period ? (100.0 * period / h->period) : 0;

Fix this by defaulting the period to 1 in the ARM SPE auxtrace info processing path if it is not explicitly specified. This ensures that downstream tool components can correctly accumulate periods and compute event percentages for all synthesized SPE events.

With this fix applied, the result is as follows:

Annotate type: 'struct mmu_gather_batch' in [kernel.kallsyms] (15 samples):
============================================================================

 Percent     offset       size  field
  100.00          0       0x10  struct mmu_gather_batch  {
   86.67          0        0x8      struct mmu_gather_batch*    next;
   13.33        0x8        0x4      unsigned int        nr;
    0.00        0xc        0x4      unsigned int        max;
    0.00       0x10          0      struct encoded_page*[]      encoded_pages;
                                };
---
 tools/perf/util/arm-spe.c | 11 +++++++++++
 1 file changed, 11 insertions(+)
tools/perf/util/arm-spe.c +11 -0
index ae7a5e9c3892..7805a1a98a89 100644
@@ -2043,6 +2043,17 @@ int arm_spe_process_auxtrace_info(union perf_event *event,
 		err = -EINVAL;
 		goto err_free_queues;
 	}
+
+	/*
+	 * When --itrace is used with non-i/y options (e.g., --itrace=M),
+	 * the period remains 0 because the parser does not apply a default
+	 * for those paths. However, synthesized SPE events such as memory
+	 * accesses, TLB walks, and cache misses still require a valid
+	 * sample->period to correctly accumulate periods and compute event
+	 * percentages. Set it to 1 to ensure proper accounting.
+	 */
+	spe->synth_opts.period = spe->synth_opts.period ?: 1;
+
 	if (spe->synth_opts.period > 1)
 		ui__warning("Arm SPE has a hardware-based sampling period.\n\n"
 			    "--itrace periods > 1i downsample by an interval of n SPE samples rather than n instructions.\n");
Signature · 1 line
2.34.1
#25 lore raw

Re: [PATCH v6 11/26] perf arm-spe: Set default synthesized event period to 1

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 13 lines

When using --itrace=M for data type profiling on arm64, the 'Percent' values in perf annotate output are all zero:

Annotate type: 'struct mmu_gather_batch' in [kernel.kallsyms] (15 samples):
============================================================================

 Percent     offset       size  field
    0.00          0       0x10  struct mmu_gather_batch  {
    0.00          0        0x8      struct mmu_gather_batch*    next;
    0.00        0x8        0x4      unsigned int        nr;
    0.00        0xc        0x4      unsigned int        max;
    0.00       0x10          0      struct encoded_page*[]      encoded_pages;
                                };
Quoted 8 lines

However, adding the -n option (show-nr-samples) reveals non-zero sample counts:

[ ... ]

    0.00        0xc        0x4      unsigned int        max;
    0.00       0x10          0      struct encoded_page*[]      encoded_pages;
                                };

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=11
author
#26 lore raw

PATCH v6 12/26 perf annotate-data: Extract invalidate_reg_state() as a common helper

In reply to Tengda Wu

The invalidate_reg_state() function is currently static and only used within the x86 data type profiling implementation. To prepare for adding arm64 instruction tracking support, extract it as a common helper that can be shared across architectures.

No functional change intended.

---
 tools/perf/util/annotate-arch/annotate-x86.c | 9 ---------
 tools/perf/util/annotate-data.c              | 9 +++++++++
 tools/perf/util/annotate-data.h              | 1 +
 3 files changed, 10 insertions(+), 9 deletions(-)
tools/perf/util/annotate-arch/annotate-x86.c +0 -9
index 1acf31a2c759..aa381d25a63b 100644
@@ -207,15 +207,6 @@ static int x86__cpuid_parse(struct arch *arch, const char *cpuid)
 }
 
 #ifdef HAVE_LIBDW_SUPPORT
-static void invalidate_reg_state(struct type_state_reg *reg)
-{
-	reg->kind = TSR_KIND_INVALID;
-	reg->ok = false;
-	reg->lifetime_active = false;
-	reg->lifetime_end = 0;
-	reg->copied_from = -1;
-}
-
 static void update_insn_state_x86(struct type_state *state,
 				  struct data_loc_info *dloc, Dwarf_Die *cu_die,
 				  struct disasm_line *dl)
tools/perf/util/annotate-data.c +9 -0
index aff60a630fd0..82003d2c240d 100644
@@ -638,6 +638,15 @@ struct type_state_stack *findnew_stack_state(struct type_state *state,
 	return stack;
 }
 
+void invalidate_reg_state(struct type_state_reg *reg)
+{
+	reg->kind = TSR_KIND_INVALID;
+	reg->ok = false;
+	reg->lifetime_active = false;
+	reg->lifetime_end = 0;
+	reg->copied_from = -1;
+}
+
 /* Maintain a cache for quick global variable lookup */
 struct global_var_entry {
 	struct rb_node node;
tools/perf/util/annotate-data.h +1 -0
index ca2096a9ee62..2af70e3cf73f 100644
@@ -258,6 +258,7 @@ void set_stack_state(struct type_state_stack *stack, int offset, u8 kind,
 				Dwarf_Die *type_die, int ptr_offset);
 struct type_state_stack *find_stack_state(struct type_state *state,
 						int offset);
+void invalidate_reg_state(struct type_state_reg *reg);
 bool get_global_var_type(Dwarf_Die *cu_die, struct data_loc_info *dloc,
 				u64 ip, u64 var_addr, int *var_offset,
 				Dwarf_Die *type_die);
Signature · 1 line
2.34.1
#27 lore raw

Re: [PATCH v6 12/26] perf annotate-data: Extract invalidate_reg_state() as a common helper

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 9 lines

The invalidate_reg_state() function is currently static and only used within the x86 data type profiling implementation. To prepare for adding arm64 instruction tracking support, extract it as a common helper that can be shared across architectures.

No functional change intended.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=12
author
#28 lore raw

PATCH v6 13/26 perf annotate-arm64: Enable instruction tracking support

In reply to Tengda Wu

Enable basic instruction tracking for arm64 by implementing three essential functions in the find_data_type_block() call path:

find_data_type_block

    -> arch_supports_insn_tracking  (1)
    -> find_data_type_insn
         -> init_type_state         (2)
         -> update_var_state
         -> update_insn_state       (3)

Changes:

* arch_supports_insn_tracking(): add arm64 to the list of supported architectures, allowing find_data_type_block() to proceed with data type analysis.

* init_type_state(): correctly identify ret_reg, stack_reg, and caller-saved registers for arm64 during type state initialization.

* update_insn_state(): add the update_insn_state_arm64() callback with conservative register invalidation for call instructions and those with destination register(s), preventing stale type propagation. Full instruction-level analysis support will be added incrementally in later patches.

With these changes, arm64 gains support for basic variable type inference during instruction tracking.

---
 .../perf/util/annotate-arch/annotate-arm64.c  | 78 +++++++++++++++++++
 tools/perf/util/annotate-data.c               | 15 +++-
 2 files changed, 92 insertions(+), 1 deletion(-)
tools/perf/util/annotate-arch/annotate-arm64.c +78 -0
index 508a95d0ad4b..cd369fcd7dc0 100644
@@ -9,6 +9,10 @@
 #include <regex.h>
 #include "../annotate.h"
 #include "../disasm.h"
+#include "../annotate-data.h"
+#include "../debug.h"
+#include "../map.h"
+#include "../symbol.h"
 
 struct arch_arm64 {
 	struct arch arch;
@@ -448,6 +452,77 @@ static void extract_op_location_arm64(const struct arch *arch,
 	}
 }
 
+#ifdef HAVE_LIBDW_SUPPORT
+static bool is_readonly_branch_or_cmp(const char *name)
+{
+	return !strncmp(name, "cb", 2) || !strncmp(name, "tb", 2) ||
+	       !strcmp(name, "tst") || !strcmp(name, "teq") ||
+	       !strcmp(name, "cmp") || !strcmp(name, "cmn") ||
+	       !strcmp(name, "ccmp") || !strcmp(name, "ccmn");
+}
+
+static void update_insn_state_arm64(struct type_state *state,
+				    struct data_loc_info *dloc, Dwarf_Die *cu_die __maybe_unused,
+				    struct disasm_line *dl)
+{
+	struct annotated_insn_loc loc;
+	struct annotated_op_loc *dst = &loc.ops[INSN_OP_TARGET];
+	u32 insn_offset = dl->al.offset;
+
+	/* Skip read-only instructions that do not affect register type state */
+	if (is_readonly_branch_or_cmp(dl->ins.name))
+		return;
+
+	if (annotate_get_insn_location(dloc->arch, dl, &loc) < 0)
+		return;
+
+	/*
+	 * Invalidate caller-saved registers on function calls per ARM64 AAPCS64
+	 * ABI, unless DWARF location info indicates the register remains valid
+	 * beyond the call address.
+	 */
+	if (ins__is_call(&dl->ins)) {
+		struct symbol *func = dl->ops.target.sym;
+		const char *call_name;
+		u64 call_addr;
+
+		call_name = func ? func->name : dl->ops.target.name;
+		pr_debug_dtp("call [%x] %s\n", insn_offset, call_name ?: "<unknown>");
+
+		/* Invalidate caller-saved registers after call */
+		call_addr = map__rip_2objdump(dloc->ms->map,
+					      dloc->ms->sym->start + dl->al.offset);
+		for (unsigned int i = 0; i < ARRAY_SIZE(state->regs); i++) {
+			struct type_state_reg *reg = &state->regs[i];
+
+			if (!reg->caller_saved)
+				continue;
+			/* Keep register valid within DWARF location lifetime */
+			if (reg->lifetime_active && call_addr < reg->lifetime_end)
+				continue;
+			invalidate_reg_state(reg);
+		}
+		return;
+	}
+
+	/*
+	 * Invalidate destination register(s) for unsupported instructions to
+	 * prevent stale type info from propagating to subsequent instructions.
+	 */
+	if (has_reg_type(state, dst->reg1) && !dst->mem_ref) {
+		pr_debug_dtp("%s [%x] invalidate reg%d",
+			     dl->ins.name, insn_offset, dst->reg1);
+		invalidate_reg_state(&state->regs[dst->reg1]);
+		if (dst->multi_regs && has_reg_type(state, dst->reg2)) {
+			pr_debug_dtp(" and reg%d", dst->reg2);
+			invalidate_reg_state(&state->regs[dst->reg2]);
+		}
+		pr_debug_dtp("\n");
+		return;
+	}
+}
+#endif
+
 const struct arch *arch__new_arm64(const struct e_machine_and_e_flags *id,
 				   const char *cpuid __maybe_unused)
 {
@@ -467,6 +542,9 @@ const struct arch *arch__new_arm64(const struct e_machine_and_e_flags *id,
 	arch->objdump.imm_char		  = '#';
 	arch->associate_instruction_ops   = arm64__associate_instruction_ops;
 	arch->extract_op_location	  = extract_op_location_arm64;
+#ifdef HAVE_LIBDW_SUPPORT
+	arch->update_insn_state		  = update_insn_state_arm64;
+#endif
 
 	/* bl, blr */
 	err = regcomp(&arm->call_insn, "^blr?$", REG_EXTENDED);
tools/perf/util/annotate-data.c +14 -1
index 82003d2c240d..9196f54b7297 100644
@@ -28,6 +28,7 @@
 
 /* register number of the stack pointer */
 #define X86_REG_SP 7
+#define ARM64_REG_SP 31
 
 static void delete_var_types(struct die_var_type *var_types);
 
@@ -178,6 +179,17 @@ static void init_type_state(struct type_state *state, const struct arch *arch)
 		state->regs[11].caller_saved = true;
 		state->ret_reg = 0;
 		state->stack_reg = X86_REG_SP;
+	} else if (arch__is_arm64(arch)) {
+		/*
+		 * Mark x0..x18 as caller-saved. x30 (LR) is intentionally
+		 * omitted from the caller_saved set: it is clobbered by
+		 * calls, but holds only the return address and is not used
+		 * to carry data type information.
+		 */
+		for (int i = 0; i < 19; i++)
+			state->regs[i].caller_saved = true;
+		state->ret_reg = 0;
+		state->stack_reg = ARM64_REG_SP;
 	}
 }
 
@@ -1437,7 +1449,8 @@ static enum type_match_result find_data_type_insn(struct data_loc_info *dloc,
 
 static int arch_supports_insn_tracking(struct data_loc_info *dloc)
 {
-	if ((arch__is_x86(dloc->arch)) || (arch__is_powerpc(dloc->arch)))
+	if (arch__is_x86(dloc->arch) || arch__is_powerpc(dloc->arch) ||
+	    arch__is_arm64(dloc->arch))
 		return 1;
 	return 0;
 }
Signature · 1 line
2.34.1
#29 lore raw

Re: [PATCH v6 13/26] perf annotate-arm64: Enable instruction tracking support

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 25 lines

Enable basic instruction tracking for arm64 by implementing three essential functions in the find_data_type_block() call path:

find_data_type_block

    -> arch_supports_insn_tracking  (1)
    -> find_data_type_insn
         -> init_type_state         (2)
         -> update_var_state
         -> update_insn_state       (3)

Changes:

* arch_supports_insn_tracking(): add arm64 to the list of supported architectures, allowing find_data_type_block() to proceed with data type analysis.

* init_type_state(): correctly identify ret_reg, stack_reg, and caller-saved registers for arm64 during type state initialization.

* update_insn_state(): add the update_insn_state_arm64() callback with conservative register invalidation for call instructions and those with destination register(s), preventing stale type propagation. Full instruction-level analysis support will be added incrementally in later patches.

Quoted 5 lines

With these changes, arm64 gains support for basic variable type inference during instruction tracking.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=13
author
#30 lore raw

PATCH v6 14/26 perf annotate-data: Add arch_get_reg_offset helper

In reply to Tengda Wu

Add a helper to compute the effective offset from a base register for instruction operands. Currently handles arm64 addressing modes including post-indexed, single-register, SIB-style indexed with shift/extension, and constant-offset register pairs.

Fallback to instruction's raw offset for other architectures.

---
 tools/perf/util/annotate-data.c | 115 ++++++++++++++++++++++++++------
 tools/perf/util/annotate-data.h |   4 +-
 2 files changed, 98 insertions(+), 21 deletions(-)
tools/perf/util/annotate-data.c +95 -20
index 9196f54b7297..d9f0bfd3f0a7 100644
@@ -1122,6 +1122,74 @@ static void setup_stack_canary(struct data_loc_info *dloc)
 	}
 }
 
+int arch_get_reg_offset(const struct arch *arch, struct annotated_op_loc *op_loc,
+			int breg, struct type_state *state, bool apply_index,
+			int *offset)
+{
+	if (arch__is_arm64(arch)) {
+		int reg2;
+
+		/* Post-indexed addressing: offset is 0 when not applying index */
+		if (op_loc->addr_mode == PERF_AAM_POST_INDEX && !apply_index) {
+			*offset = 0;
+			return 0;
+		}
+
+		/* Single-register addressing: return the stored offset directly */
+		if (!op_loc->multi_regs) {
+			*offset = op_loc->offset;
+			return 0;
+		}
+
+		/*
+		 * SIB (Scale-Index-Base) style indexed addressing, e.g.:
+		 *   Array element load:  ldr  dst, [base, index, lsl #3]
+		 *   Array element load:  ldr  dst, [base, index, uxtw #3]
+		 *   Array address calc:  add  dst, base, index lsl #3
+		 *
+		 * The index reg only computes element offset without modifying
+		 * the underlying data type; the dst type is inherited from base.
+		 * Therefore, the base reg offset can be treated as 0.
+		 */
+		if (op_loc->extend_type || op_loc->shift_type) {
+			/* Memory references use the breg as the address base. */
+			if (op_loc->mem_ref) {
+				*offset = 0;
+				return 0;
+			}
+			/*
+			 * Otherwise, require breg to be a known pointer type to
+			 * avoid treating integer operations as address calculations.
+			 */
+			if (has_reg_type(state, breg) && state->regs[breg].ok &&
+			    (state->regs[breg].kind == TSR_KIND_POINTER ||
+			     (state->regs[breg].kind == TSR_KIND_TYPE &&
+			      dwarf_tag(&state->regs[breg].type) == DW_TAG_pointer_type))) {
+				*offset = 0;
+				return 0;
+			}
+			return -1;
+		}
+
+		/*
+		 * For register-based addressing without shift/extension,
+		 * reg2 may be a constant offset. Use its value as the offset
+		 * from the base register.
+		 */
+		reg2 = op_loc->reg1 == breg ? op_loc->reg2 : op_loc->reg1;
+		if (has_reg_type(state, reg2) && state->regs[reg2].ok &&
+			state->regs[reg2].kind == TSR_KIND_CONST) {
+			*offset = (s64)state->regs[reg2].imm_value;
+			return 0;
+		}
+
+		return -1;
+	}
+
+	*offset = op_loc->offset;
+	return 0;
+}
+
 /*
  * It's at the target address, check if it has a matching type.
  * It returns PERF_TMR_BAIL_OUT when it looks up per-cpu variables which
@@ -1136,18 +1204,25 @@ static enum type_match_result check_matching_type(struct type_state *state,
 	Dwarf_Word size;
 	u32 insn_offset = dl->al.offset;
 	int reg = dloc->op->reg1;
-	int offset = dloc->op->offset;
-	const char *offset_sign = "";
+	int offset;
+	const char *offset_sign;
 	bool retry = true;
 
-	if (offset < 0) {
-		offset = -offset;
-		offset_sign = "-";
+again:
+	if (arch_get_reg_offset(dloc->arch, dloc->op, reg, state, false, &offset)) {
+		/*
+		 * Fall back to the instruction's offset. This prevents the
+		 * register type of the instruction from being completely lost.
+		 * Compared to dropping this type inference entirely, providing
+		 * a rough type hint is likely more useful.
+		 */
+		offset = dloc->op->offset;
 	}
 
-again:
+	offset_sign = offset < 0 ? "-" : "";
+
 	pr_debug_dtp("chk [%x] reg%d offset=%s%#x ok=%d kind=%d ",
-		     insn_offset, reg, offset_sign, offset,
+		     insn_offset, reg, offset_sign, abs(offset),
 		     state->regs[reg].ok, state->regs[reg].kind);
 
 	if (!state->regs[reg].ok)
@@ -1170,7 +1245,7 @@ static enum type_match_result check_matching_type(struct type_state *state,
 		 */
 		ptr_type = die_get_pointer_type(&state->regs[reg].type, &ptr_die);
 		if (!ptr_type) {
-			if (dloc->op->offset < 0 && reg != state->stack_reg)
+			if (offset < 0 && reg != state->stack_reg)
 				goto check_kernel;
 
 			return PERF_TMR_NO_POINTER;
@@ -1180,7 +1255,7 @@ static enum type_match_result check_matching_type(struct type_state *state,
 		if (__die_get_real_type(ptr_type, type_die) == NULL)
 			return PERF_TMR_NO_POINTER;
 
-		dloc->type_offset = dloc->op->offset + state->regs[reg].offset;
+		dloc->type_offset = offset + state->regs[reg].offset;
 
 		if (dwarf_tag(type_die) == DW_TAG_typedef)
 			die_get_real_type(type_die, &sized_type);
@@ -1209,7 +1284,7 @@ static enum type_match_result check_matching_type(struct type_state *state,
 		 */
 		*type_die = state->regs[reg].type;
 
-		dloc->type_offset = dloc->op->offset + state->regs[reg].offset;
+		dloc->type_offset = offset + state->regs[reg].offset;
 
 		/* Get the size of the actual type */
 		if (dwarf_aggregate_size(type_die, &size) < 0 ||
@@ -1228,7 +1303,7 @@ static enum type_match_result check_matching_type(struct type_state *state,
 		 */
 		*type_die = state->regs[reg].type;
 
-		dloc->type_offset = dloc->op->offset;
+		dloc->type_offset = offset;
 
 		/* Get the size of the actual type */
 		if (dwarf_aggregate_size(type_die, &size) < 0 ||
@@ -1252,7 +1327,7 @@ static enum type_match_result check_matching_type(struct type_state *state,
 	}
 
 	if (state->regs[reg].kind == TSR_KIND_PERCPU_BASE) {
-		u64 var_addr = dloc->op->offset;
+		u64 var_addr = (s64) offset;
 		int var_offset;
 
 		pr_debug_dtp("percpu var");
@@ -1279,7 +1354,7 @@ static enum type_match_result check_matching_type(struct type_state *state,
 
 	if (state->regs[reg].kind == TSR_KIND_CONST &&
 	    dso__kernel(map__dso(dloc->ms->map))) {
-		if (dloc->op->offset < 0 && reg != state->stack_reg && reg != dloc->fbreg)
+		if (offset < 0 && reg != state->stack_reg && reg != dloc->fbreg)
 			goto check_kernel;
 	}
 check_non_register:
@@ -1288,7 +1363,7 @@ static enum type_match_result check_matching_type(struct type_state *state,
 
 		pr_debug_dtp("%s", reg == dloc->fbreg ? "fbreg" : "stack");
 
-		stack = find_stack_state(state, dloc->type_offset);
+		stack = find_stack_state(state, offset);
 		if (stack == NULL) {
 			if (retry) {
 				pr_debug_dtp(" : retry\n");
@@ -1311,7 +1386,7 @@ static enum type_match_result check_matching_type(struct type_state *state,
 
 		*type_die = stack->type;
 		/* Update the type offset from the start of slot */
-		dloc->type_offset -= stack->offset;
+		dloc->type_offset = offset - stack->offset;
 
 		return PERF_TMR_OK;
 	}
@@ -1329,7 +1404,7 @@ static enum type_match_result check_matching_type(struct type_state *state,
 		if (reg != fbreg)
 			return PERF_TMR_NO_TYPE;
 
-		stack = find_stack_state(state, dloc->type_offset - fboff);
+		stack = find_stack_state(state, offset - fboff);
 		if (stack == NULL) {
 			if (retry) {
 				pr_debug_dtp(" : retry\n");
@@ -1352,7 +1427,7 @@ static enum type_match_result check_matching_type(struct type_state *state,
 
 		*type_die = stack->type;
 		/* Update the type offset from the start of slot */
-		dloc->type_offset -= fboff + stack->offset;
+		dloc->type_offset = offset - fboff - stack->offset;
 
 		return PERF_TMR_OK;
 	}
@@ -1366,7 +1441,7 @@ static enum type_match_result check_matching_type(struct type_state *state,
 		    arch__is_x86(dloc->arch)) {
 			pr_debug_dtp("this-cpu var");
 
-			addr = dloc->op->offset;
+			addr = (s64) offset;
 
 			if (get_global_var_type(cu_die, dloc, dloc->ip, addr,
 						&offset, type_die)) {
@@ -1377,8 +1452,8 @@ static enum type_match_result check_matching_type(struct type_state *state,
 		}
 
 		/* Access to global variable like "-0x7dcf0500(,%rdx,8)" */
-		if (dloc->op->offset < 0 && reg != state->stack_reg) {
-			addr = (s64) dloc->op->offset;
+		if (offset < 0 && reg != state->stack_reg) {
+			addr = (s64) offset;
 
 			if (get_global_var_type(cu_die, dloc, dloc->ip, addr,
 						&offset, type_die)) {
tools/perf/util/annotate-data.h +3 -1
index 2af70e3cf73f..68e3feda2eda 100644
@@ -265,7 +265,9 @@ bool get_global_var_type(Dwarf_Die *cu_die, struct data_loc_info *dloc,
 bool get_global_var_info(struct data_loc_info *dloc, u64 addr,
 				const char **var_name, int *var_offset);
 void pr_debug_type_name(Dwarf_Die *die, enum type_state_kind kind);
-
+int arch_get_reg_offset(const struct arch *arch, struct annotated_op_loc *op_loc,
+			int breg, struct type_state *state, bool apply_index,
+			int *offset);
 #else /* HAVE_LIBDW_SUPPORT */
 
 static inline struct annotated_data_type *
Signature · 1 line
2.34.1
#31 lore raw

Re: [PATCH v6 14/26] perf annotate-data: Add arch_get_reg_offset helper

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 8 lines

Add a helper to compute the effective offset from a base register for instruction operands. Currently handles arm64 addressing modes including post-indexed, single-register, SIB-style indexed with shift/extension, and constant-offset register pairs.

Fallback to instruction's raw offset for other architectures.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=14
author
#32 lore raw

PATCH v6 15/26 perf annotate-arm64: Track return type after call instructions

In reply to Tengda Wu

Enable data type profiling to track function return values in arm64 annotations by looking up the DWARF return type of called functions.

---
 tools/perf/util/annotate-arch/annotate-arm64.c | 18 +++++++++++++++++-
 1 file changed, 17 insertions(+), 1 deletion(-)
tools/perf/util/annotate-arch/annotate-arm64.c +17 -1
index cd369fcd7dc0..d3d994a22cf9 100644
@@ -462,7 +462,7 @@ static bool is_readonly_branch_or_cmp(const char *name)
 }
 
 static void update_insn_state_arm64(struct type_state *state,
-				    struct data_loc_info *dloc, Dwarf_Die *cu_die __maybe_unused,
+				    struct data_loc_info *dloc, Dwarf_Die *cu_die,
 				    struct disasm_line *dl)
 {
 	struct annotated_insn_loc loc;
@@ -485,6 +485,8 @@ static void update_insn_state_arm64(struct type_state *state,
 		struct symbol *func = dl->ops.target.sym;
 		const char *call_name;
 		u64 call_addr;
+		struct type_state_reg *tsr;
+		Dwarf_Die type_die;
 
 		call_name = func ? func->name : dl->ops.target.name;
 		pr_debug_dtp("call [%x] %s\n", insn_offset, call_name ?: "<unknown>");
@@ -502,6 +504,20 @@ static void update_insn_state_arm64(struct type_state *state,
 				continue;
 			invalidate_reg_state(reg);
 		}
+
+		/* Update register with the return type (if any) */
+		if (call_name && die_find_func_rettype(cu_die, call_name, &type_die)) {
+			tsr = &state->regs[state->ret_reg];
+			invalidate_reg_state(tsr);
+			tsr->type = type_die;
+			tsr->kind = TSR_KIND_TYPE;
+			tsr->offset = 0;
+			tsr->ok = true;
+
+			pr_debug_dtp("call [%x] return -> reg%d",
+				     insn_offset, state->ret_reg);
+			pr_debug_type_name(&type_die, tsr->kind);
+		}
 		return;
 	}
 
Signature · 1 line
2.34.1
#33 lore raw

Re: [PATCH v6 15/26] perf annotate-arm64: Track return type after call instructions

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 4 lines

Enable data type profiling to track function return values in arm64 annotations by looking up the DWARF return type of called functions.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=15
author
#34 lore raw

PATCH v6 16/26 perf annotate-arm64: Support load instruction tracking

In reply to Tengda Wu

Extend update_insn_state_arm64() to handle load instructions, tracking register state changes when data is loaded from memory to registers.

Two main categories are considered: standard load (e.g, ldr variants) and load pair (ldp), for example:

ldr dst, [src, #imm]
ldp dst1, dst2, [src, #imm]

For data type propagation, a load pair can be treated as two standard loads combined, first loading dst1 and then dst2. The difference is that when loading dst2, an additional memory spacing 'mem_spacing' after the first load must be added. This mem_spacing can be derived from the instruction mnemonic and the size of the dst register. Each load operation is handled by introducing propagate_load_reg_state().

When processing a load pair, the case where dst and src registers are the same (e.g., ldp x0, x1, [x0]) also needs to be considered. Therefore, before propagating data types, a snapshot of the src register's state must be saved and then passed to propagate_load_reg_state().

Finally, handle the side effects of pre-index and post-index addressing via adjust_reg_index_state().

A real-world example is shown below:

ffff80008011f5b0 <pick_task_stop>:

  ffff80008011f5b8:  ldr  x0, [x0, #2712] // x0: struct rq* -> task_struct*
* ffff80008011f5c0:  ldr  w1, [x0, #104]

Before this commit, the type of x0 was incorrectly inferred as 'struct rq':

find data type for 0x68(reg0) at pick_task_stop+0x10 var [8] reg0 offset 0 type='struct rq*'
chk [10] reg0 offset=0x68 ok=1 kind=1 (struct rq*) : Good! final result: type='struct rq'

After this commit, the type of x0 is correctly inferred as 'struct task_struct':

find data type for 0x68(reg0) at pick_task_stop+0x10
var [8] reg0 offset 0 type='struct rq*'
ldr [8] 0xa98(reg0) -> reg0 type='struct task_struct*' chk [10] reg0 offset=0x68 ok=1 kind=1 (struct task_struct*) : Good! final result: type='struct task_struct'

---
 .../perf/util/annotate-arch/annotate-arm64.c  | 206 +++++++++++++++++-
 1 file changed, 205 insertions(+), 1 deletion(-)
tools/perf/util/annotate-arch/annotate-arm64.c +205 -1
index d3d994a22cf9..11b2bdceaacb 100644
@@ -461,11 +461,210 @@ static bool is_readonly_branch_or_cmp(const char *name)
 	       !strcmp(name, "ccmp") || !strcmp(name, "ccmn");
 }
 
+static int arm64__reg_size(const char *reg)
+{
+	if (!reg || !*reg || !arm64__is_reg(reg))
+		return -1;
+
+	if (reg[0] == 'w')
+		return 4;
+
+	if (reg[0] == 'x' || !strncmp(reg, "sp", 2))
+		return 8;
+
+	return -1;
+}
+
+/* Apply addressing mode (pre-index, post-index) to register state */
+static void adjust_reg_index_state(struct type_state *state,
+				   struct data_loc_info *dloc,
+				   struct disasm_line *dl,
+				   struct annotated_op_loc *op_loc)
+{
+	struct type_state_reg *tsr;
+	int reg = op_loc->reg1;
+	int offset;
+
+	if (op_loc->addr_mode != PERF_AAM_PRE_INDEX &&
+	    op_loc->addr_mode != PERF_AAM_POST_INDEX)
+		return;
+
+	if (!has_reg_type(state, reg) || !state->regs[reg].ok)
+		return;
+
+	tsr = &state->regs[reg];
+	tsr->copied_from = -1;
+
+	if (arch_get_reg_offset(dloc->arch, op_loc, reg, state, true, &offset)) {
+		invalidate_reg_state(tsr);
+		return;
+	}
+
+	tsr->offset += offset;
+
+	pr_debug_dtp("%s [%x] %s-index %#x(reg%d) -> reg%d", dl->ins.name,
+		     (u32) dl->al.offset, op_loc->addr_mode == PERF_AAM_PRE_INDEX ?
+		     "pre" : "post", offset, reg, reg);
+	pr_debug_type_name(&tsr->type, tsr->kind);
+}
+
+/*
+ * Match standard load variants (ldr, ldur, ldar, ldp) that follow
+ * straightforward load semantics: memory source on the right
+ * and target registers on the left (written). Excludes exclusive loads
+ * (ldxr, ldxp, etc.) and acquire/exclusive combination variants.
+ */
+static bool is_standard_load_insn(const char *name)
+{
+	return !strncmp(name, "ldr", 3)  ||  /* ldr, ldrb, ldrh, ldrsb, ldrsh, ldrsw */
+	       !strncmp(name, "ldur", 4) ||  /* ldur, ldurb, ldurh, ldursb, ldursh, ldursw */
+	       !strncmp(name, "ldar", 4) ||  /* ldar, ldarb, ldarh */
+	       !strncmp(name, "ldp", 3);     /* ldp, ldpsw */
+}
+
+/*
+ * For load insns: propagate type from source reg state @src_states to @dreg.
+ *
+ * @mem_spacing accounts for the additional in-memory spacing between the two
+ * loads when handling multi-reg loads (e.g. the second target reg in 'ldp'),
+ * which is added to the reg offset.
+ */
+static int propagate_load_reg_state(struct type_state *state,
+				    struct data_loc_info *dloc,
+				    struct disasm_line *dl, int dreg,
+				    struct annotated_op_loc *src,
+				    struct type_state_reg **src_states,
+				    int mem_spacing)
+{
+	struct type_state_reg *tsr;
+	struct type_state_reg *src_tsr = src_states[0];
+	Dwarf_Die type_die;
+	u32 insn_offset = dl->al.offset;
+	int sreg = src->reg1;
+	int reg_offset;
+
+	if (!has_reg_type(state, dreg))
+		return -1;
+
+	tsr = &state->regs[dreg];
+	tsr->copied_from = -1;
+
+retry:
+	if (arch_get_reg_offset(dloc->arch, src, sreg, state, false, &reg_offset))
+		goto load_non_regoff;
+
+	reg_offset += mem_spacing;
+
+	if (!src_tsr || !src_tsr->ok)
+		return -1;
+
+	/* Dereference the pointer if it has one */
+	if (src_tsr->kind == TSR_KIND_TYPE &&
+	    die_deref_ptr_type(&src_tsr->type,
+			       src_tsr->offset + reg_offset, &type_die)) {
+		tsr->type = type_die;
+		tsr->kind = TSR_KIND_TYPE;
+		tsr->offset = 0;
+		tsr->ok = true;
+
+		pr_debug_dtp("%s [%x] %#x(reg%d) -> reg%d",
+			     dl->ins.name, insn_offset, reg_offset, sreg, dreg);
+		pr_debug_type_name(&tsr->type, tsr->kind);
+		return 0;
+	}
+
+	/* Handle dereference of TSR_KIND_POINTER registers */
+	if (src_tsr->kind == TSR_KIND_POINTER &&
+	    die_get_member_type(&src_tsr->type,
+				src_tsr->offset + reg_offset, &type_die)) {
+		tsr->type = src_tsr->type;
+		tsr->kind = TSR_KIND_TYPE;
+		tsr->offset = src_tsr->offset + reg_offset;
+		tsr->imm_value = 0;
+		tsr->ok = true;
+
+		pr_debug_dtp("%s [%x] addr %#x(reg%d) -> reg%d",
+			     dl->ins.name, insn_offset, reg_offset, sreg, dreg);
+		pr_debug_type_name(&tsr->type, tsr->kind);
+		return 0;
+	}
+
+load_non_regoff:
+	/* Try another register if any */
+	if (src->multi_regs && src->reg1 != src->reg2 && sreg != src->reg2 &&
+	    !(src->extend_type || src->shift_type)) {
+		sreg = src->reg2;
+		src_tsr = src_states[1];
+		goto retry;
+	}
+
+	return -1;
+}
+
+static void update_load_insn_state(struct type_state *state,
+				   struct data_loc_info *dloc,
+				   struct disasm_line *dl,
+				   struct annotated_op_loc *src,
+				   struct annotated_op_loc *dst)
+{
+	struct type_state_reg snapshots[2];
+	struct type_state_reg *src_states[2] = {};
+	int mem_spacing = 0;
+
+	if (!src->mem_ref ||  /* exclude PC-relative loads */
+	    !has_reg_type(state, src->reg1)) {
+		/* Invalidate tracked destination registers */
+		if (has_reg_type(state, dst->reg1))
+			invalidate_reg_state(&state->regs[dst->reg1]);
+		if (dst->multi_regs && has_reg_type(state, dst->reg2))
+			invalidate_reg_state(&state->regs[dst->reg2]);
+		goto out_adjust;
+	}
+
+	/*
+	 * Snapshot source register states before updating destination registers.
+	 * This preserves the original source state for aliased loads (e.g.,
+	 * ldp x0, x1, [x0]), where updating the first destination may overwrite
+	 * the source register.
+	 */
+	snapshots[0] = state->regs[src->reg1];
+	src_states[0] = &snapshots[0];
+	if (src->multi_regs && has_reg_type(state, src->reg2)) {
+		snapshots[1] = state->regs[src->reg2];
+		src_states[1] = &snapshots[1];
+	}
+
+	/* Handle the first destination register */
+	if (has_reg_type(state, dst->reg1)) {
+		if (propagate_load_reg_state(state, dloc, dl, dst->reg1,
+					     src, src_states, mem_spacing))
+			invalidate_reg_state(&state->regs[dst->reg1]);
+	}
+
+	if (!dst->multi_regs || !has_reg_type(state, dst->reg2))
+		goto out_adjust;
+
+	/* Handle the second destination register */
+	if (!strcmp(dl->ins.name, "ldpsw"))
+		mem_spacing = 4;  /* ldpsw loads two 32-bit signed words (4-byte spacing) */
+	else
+		mem_spacing = arm64__reg_size(dl->ops.target.raw);
+
+	if (mem_spacing < 0 ||
+	    propagate_load_reg_state(state, dloc, dl, dst->reg2,
+				     src, src_states, mem_spacing))
+		invalidate_reg_state(&state->regs[dst->reg2]);
+
+out_adjust:
+	adjust_reg_index_state(state, dloc, dl, src);
+}
+
 static void update_insn_state_arm64(struct type_state *state,
 				    struct data_loc_info *dloc, Dwarf_Die *cu_die,
 				    struct disasm_line *dl)
 {
 	struct annotated_insn_loc loc;
+	struct annotated_op_loc *src = &loc.ops[INSN_OP_SOURCE];
 	struct annotated_op_loc *dst = &loc.ops[INSN_OP_TARGET];
 	u32 insn_offset = dl->al.offset;
 
@@ -525,7 +724,8 @@ static void update_insn_state_arm64(struct type_state *state,
 	 * Invalidate destination register(s) for unsupported instructions to
 	 * prevent stale type info from propagating to subsequent instructions.
 	 */
-	if (has_reg_type(state, dst->reg1) && !dst->mem_ref) {
+	if (has_reg_type(state, dst->reg1) && !dst->mem_ref &&
+	    !is_standard_load_insn(dl->ins.name)) {
 		pr_debug_dtp("%s [%x] invalidate reg%d",
 			     dl->ins.name, insn_offset, dst->reg1);
 		invalidate_reg_state(&state->regs[dst->reg1]);
@@ -536,6 +736,10 @@ static void update_insn_state_arm64(struct type_state *state,
 		pr_debug_dtp("\n");
 		return;
 	}
+
+	/* Memory to register transfers */
+	if (is_standard_load_insn(dl->ins.name))
+		update_load_insn_state(state, dloc, dl, src, dst);
 }
 #endif
 
Signature · 1 line
2.34.1
#35 lore raw

Re: [PATCH v6 16/26] perf annotate-arm64: Support load instruction tracking

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Medium] perf annotate: stale lifetime metadata retained on load - [Medium] perf annotate: incorrect type…

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] perf annotate: stale lifetime metadata retained on load
- [Medium] perf annotate: incorrect type propagation for TSR_KIND_POINTER loads --

commit 9763211f6135d1b6c3e44acf4ac681c34ac60395
Author: Tengda Wu <[email protected]>

perf annotate-arm64: Support load instruction tracking

Extend update_insn_state_arm64() to handle load instructions, tracking register state changes when data is loaded from memory to registers.

Quoted 4 lines
tools/perf/util/annotate-arch/annotate-arm64.c +0 -0
index d3d994a22cf92..11b2bdceaacb1 100644

[ ... ]

Quoted 13 lines
tools/perf/util/annotate-arch/annotate-arm64.c +12 -0
@@ -461,11 +461,210 @@ static bool is_readonly_branch_or_cmp(const char *name)
+static int propagate_load_reg_state(struct type_state *state,
+				    struct data_loc_info *dloc,
+				    struct disasm_line *dl, int dreg,
+				    struct annotated_op_loc *src,
+				    struct type_state_reg **src_states,
+				    int mem_spacing)
+{
+	struct type_state_reg *tsr;
+	struct type_state_reg *src_tsr = src_states[0];
+	Dwarf_Die type_die;
+	u32 insn_offset = dl->al.offset;
+	int sreg = src->reg1;

tools/perf/util/annotate-arch/annotate-arm64.c static bool is_readonly_branch_or_cmp(const char *name) · line 482

Quoted 10 lines
tools/perf/util/annotate-arch/annotate-arm64.c +10 -0
+	int reg_offset;
+
+	if (!has_reg_type(state, dreg))
+		return -1;
+
+	tsr = &state->regs[dreg];
+	tsr->copied_from = -1;
+
+retry:
+	if (arch_get_reg_offset(dloc->arch, src, sreg, state, false, &reg_offset))

[Severity: Medium]
When acquiring the destination register state here, shouldn't the lifetime metadata be cleared (e.g., via tsr->lifetime_active = false;)?

If the register previously held a variable with an active DWARF lifetime, the newly loaded value inherits this stale lifetime. During function calls, caller-saved registers with active lifetimes are preserved rather than invalidated. This causes the register's new type to incorrectly survive the call, leading to stale type tracking downstream.

Quoted 3 lines
tools/perf/util/annotate-arch/annotate-arm64.c +3 -0
+		goto load_non_regoff;
+
+	reg_offset += mem_spacing;

[ ... ]

tools/perf/util/annotate-arch/annotate-arm64.c static bool is_readonly_branch_or_cmp(const char *name) · line 584

Quoted 9 lines
tools/perf/util/annotate-arch/annotate-arm64.c +9 -0
+	/* Handle dereference of TSR_KIND_POINTER registers */
+	if (src_tsr->kind == TSR_KIND_POINTER &&
+	    die_get_member_type(&src_tsr->type,
+				src_tsr->offset + reg_offset, &type_die)) {
+		tsr->type = src_tsr->type;
+		tsr->kind = TSR_KIND_TYPE;
+		tsr->offset = src_tsr->offset + reg_offset;
+		tsr->imm_value = 0;
+		tsr->ok = true;

[Severity: Medium]
Does this incorrectly assign the parent struct's type (src_tsr->type) instead of the member's type (type_die), corrupting type tracking for the loaded value?

If the destination register is subsequently used as a base address, check_matching_type() will fail because it expects a TSR_KIND_TYPE register to hold a pointer type, whereas it now holds a struct type. This breaks downstream type inference for the loaded value.

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=16
author
#36 lore raw

Re: [PATCH v6 16/26] perf annotate-arm64: Support load instruction tracking

Quoted 48 lines · On 2026/9/16 9:51, [email protected] wrote:

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] perf annotate: stale lifetime metadata retained on load
- [Medium] perf annotate: incorrect type propagation for TSR_KIND_POINTER loads --

commit 9763211f6135d1b6c3e44acf4ac681c34ac60395
Author: Tengda Wu <[email protected]>

perf annotate-arm64: Support load instruction tracking

Extend update_insn_state_arm64() to handle load instructions, tracking register state changes when data is loaded from memory to registers.

Quoted 4 lines
tools/perf/util/annotate-arch/annotate-arm64.c +0 -0
index d3d994a22cf92..11b2bdceaacb1 100644

[ ... ]

Quoted 13 lines
tools/perf/util/annotate-arch/annotate-arm64.c +12 -0
@@ -461,11 +461,210 @@ static bool is_readonly_branch_or_cmp(const char *name)
+static int propagate_load_reg_state(struct type_state *state,
+				    struct data_loc_info *dloc,
+				    struct disasm_line *dl, int dreg,
+				    struct annotated_op_loc *src,
+				    struct type_state_reg **src_states,
+				    int mem_spacing)
+{
+	struct type_state_reg *tsr;
+	struct type_state_reg *src_tsr = src_states[0];
+	Dwarf_Die type_die;
+	u32 insn_offset = dl->al.offset;
+	int sreg = src->reg1;

tools/perf/util/annotate-arch/annotate-arm64.c static bool is_readonly_branch_or_cmp(const char *name) · line 482

Quoted 10 lines
tools/perf/util/annotate-arch/annotate-arm64.c +10 -0
+	int reg_offset;
+
+	if (!has_reg_type(state, dreg))
+		return -1;
+
+	tsr = &state->regs[dreg];
+	tsr->copied_from = -1;
+
+retry:
+	if (arch_get_reg_offset(dloc->arch, src, sreg, state, false, &reg_offset))

[Severity: Medium]
When acquiring the destination register state here, shouldn't the lifetime metadata be cleared (e.g., via tsr->lifetime_active = false;)?

Quoted 6 lines

If the register previously held a variable with an active DWARF lifetime, the newly loaded value inherits this stale lifetime. During function calls, caller-saved registers with active lifetimes are preserved rather than invalidated. This causes the register's new type to incorrectly survive the call, leading to stale type tracking downstream.

Normally DWARF handles this lifetime quite well (DW_AT_ranges will mark the lifetime end at the ldr instruction), but clearing it is fine too, and it will also get sashiko to stop complaining.

Quoted 21 lines
Quoted 3 lines
tools/perf/util/annotate-arch/annotate-arm64.c +3 -0
+		goto load_non_regoff;
+
+	reg_offset += mem_spacing;

[ ... ]

tools/perf/util/annotate-arch/annotate-arm64.c static bool is_readonly_branch_or_cmp(const char *name) · line 584

Quoted 9 lines
tools/perf/util/annotate-arch/annotate-arm64.c +9 -0
+	/* Handle dereference of TSR_KIND_POINTER registers */
+	if (src_tsr->kind == TSR_KIND_POINTER &&
+	    die_get_member_type(&src_tsr->type,
+				src_tsr->offset + reg_offset, &type_die)) {
+		tsr->type = src_tsr->type;
+		tsr->kind = TSR_KIND_TYPE;
+		tsr->offset = src_tsr->offset + reg_offset;
+		tsr->imm_value = 0;
+		tsr->ok = true;

[Severity: Medium]
Does this incorrectly assign the parent struct's type (src_tsr->type) instead of the member's type (type_die), corrupting type tracking for the loaded value?

Quoted 5 lines

If the destination register is subsequently used as a base address, check_matching_type() will fail because it expects a TSR_KIND_TYPE register to hold a pointer type, whereas it now holds a struct type. This breaks downstream type inference for the loaded value.

This code is consistent with x86, and it doesn't look like a problem.

The purpose of die_get_member_type is only to determine whether the load offset is valid, not to resolve the type.

TSR_KIND_POINTER is obtained from die_collect_vars() -> update_var_state(). Whether it is marked as TSR_KIND_POINTER mainly depends on var->is_reg_var_addr, and var->is_reg_var_addr is determined based on DW_OP_breg* or DW_OP_fbreg operations. These operations already describe where the DIE type of the current variable comes from (for example, DW_OP_breg26 W26+2920 describes that it is obtained by loading from x26 + 2920 offset, and stores the DIE into the x26 register). So when the actual load happens (for example, ldrsw x3, [x26, #2920]), it only needs to inherit the type from breg.

So I will keep the TSR_KIND_POINTER handling unchanged.

Thanks,
Tengda

author
#37 lore raw

PATCH v6 17/26 perf annotate-arm64: Support store instruction tracking

In reply to Tengda Wu

Extend update_insn_state_arm64() to handle standard store instructions (str, stur, stlr, stp). Exclusive stores (e.g., stxr, stxp) are omitted as they exhibit different operand semantics.

Unlike load instructions, a store instruction sets a value in the struct within the memory where the destination register resides, and does not alter its type. Therefore, no processing is required for the transfer.

The only point to note is that store instructions support pre-index and post-index addressing modes, so calling adjust_reg_index_state() is still necessary to handle their addressing.

---
 .../perf/util/annotate-arch/annotate-arm64.c  | 25 +++++++++++++++++++
 1 file changed, 25 insertions(+)
tools/perf/util/annotate-arch/annotate-arm64.c +25 -0
index 11b2bdceaacb..a72c7703dd6b 100644
@@ -659,6 +659,20 @@ static void update_load_insn_state(struct type_state *state,
 	adjust_reg_index_state(state, dloc, dl, src);
 }
 
+/*
+ * Match standard store variants (str, stur, stlr, stp) that follow
+ * straightforward store semantics: source registers on the left (read-only)
+ * and memory target on the right. Excludes exclusive stores (stxr, stxp, etc.)
+ * which have status/result register side effects.
+ */
+static bool is_standard_store_insn(const char *name)
+{
+	return !strncmp(name, "str", 3)  ||  /* str, strb, strh */
+	       !strncmp(name, "stur", 4) ||  /* stur, sturb, sturh */
+	       !strncmp(name, "stlr", 4) ||  /* stlr, stlrb, stlrh */
+	       !strncmp(name, "stp", 3);     /* stp */
+}
+
 static void update_insn_state_arm64(struct type_state *state,
 				    struct data_loc_info *dloc, Dwarf_Die *cu_die,
 				    struct disasm_line *dl)
@@ -740,6 +754,17 @@ static void update_insn_state_arm64(struct type_state *state,
 	/* Memory to register transfers */
 	if (is_standard_load_insn(dl->ins.name))
 		update_load_insn_state(state, dloc, dl, src, dst);
+	/* Register to memory transfers */
+	else if (is_standard_store_insn(dl->ins.name)) {
+		/*
+		 * Ignore transfers since it'd set a value in a struct
+		 * and won't change the type.
+		 *
+		 * Needs to update the pre-index and post-index addressing
+		 * modes for the destination register.
+		 */
+		adjust_reg_index_state(state, dloc, dl, dst);
+	}
 }
 #endif
 
Signature · 1 line
2.34.1
#38 lore raw

Re: [PATCH v6 17/26] perf annotate-arm64: Support store instruction tracking

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 8 lines

Extend update_insn_state_arm64() to handle standard store instructions (str, stur, stlr, stp). Exclusive stores (e.g., stxr, stxp) are omitted as they exhibit different operand semantics.

Unlike load instructions, a store instruction sets a value in the struct within the memory where the destination register resides, and does not alter its type. Therefore, no processing is required for the transfer.

Quoted 5 lines

The only point to note is that store instructions support pre-index and post-index addressing modes, so calling adjust_reg_index_state() is still necessary to handle their addressing.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=17
author
#39 lore raw

PATCH v6 18/26 perf annotate-data: Expand type_state_reg imm_value to u64

In reply to Tengda Wu

The arm64 'adrp + add/ldr' instruction pair involves global variable address calculation and passing, which requires storing the 64-bit address in type_state_reg. Currently, there is no field in type_state_reg that can hold such an address. The most likely candidate for reuse is imm_value, but it is defined as u32, which limits the size of values it can carry.

To address this, expand imm_value from u32 to u64. Also update related compilation warnings and fix the sign-extension issue introduced by this change.

---
 tools/perf/util/annotate-arch/annotate-x86.c | 4 ++--
 tools/perf/util/annotate-data.h              | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)
tools/perf/util/annotate-arch/annotate-x86.c +2 -2
index aa381d25a63b..f5e40cef08ab 100644
@@ -536,11 +536,11 @@ static void update_insn_state_x86(struct type_state *state,
 
 		if (src->imm) {
 			tsr->kind = TSR_KIND_CONST;
-			tsr->imm_value = src->offset;
+			tsr->imm_value = (s64)src->offset;
 			tsr->offset = 0;
 			tsr->ok = true;
 
-			pr_debug_dtp("mov [%x] imm=%#x -> reg%d\n",
+			pr_debug_dtp("mov [%x] imm=%#"PRIx64" -> reg%d\n",
 				     insn_offset, tsr->imm_value, dst->reg1);
 			return;
 		}
tools/perf/util/annotate-data.h +1 -1
index 68e3feda2eda..62efdcd8a79a 100644
@@ -173,7 +173,7 @@ extern struct annotated_data_stat ann_data_stat;
  */
 struct type_state_reg {
 	Dwarf_Die type;
-	u32 imm_value;
+	u64 imm_value;
 	/*
 	 * The offset within the struct that the register points to.
 	 * A value of 0 means the register points to the beginning.
Signature · 1 line
2.34.1
#40 lore raw

Re: [PATCH v6 18/26] perf annotate-data: Expand type_state_reg imm_value to u64

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 11 lines

The arm64 'adrp + add/ldr' instruction pair involves global variable address calculation and passing, which requires storing the 64-bit address in type_state_reg. Currently, there is no field in type_state_reg that can hold such an address. The most likely candidate for reuse is imm_value, but it is defined as u32, which limits the size of values it can carry.

To address this, expand imm_value from u32 to u64. Also update related compilation warnings and fix the sign-extension issue introduced by this change.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=18
author
#41 lore raw

PATCH v6 19/26 perf annotate-data: Track imm_value for stack variables

In reply to Tengda Wu

Currently, imm_value is tracked in registers but dropped when values are saved to or loaded from stack variables during type profiling. If a register of type TSR_KIND_CONST is stored to the stack, its value will be lost.

Add an imm_value field to struct type_state_stack and update set_stack_state() and findnew_stack_state() to accept and preserve the immediate value when updating stack state.

---
 tools/perf/util/annotate-arch/annotate-x86.c |  8 ++++++--
 tools/perf/util/annotate-data.c              | 13 +++++++------
 tools/perf/util/annotate-data.h              |  5 +++--
 3 files changed, 16 insertions(+), 10 deletions(-)
tools/perf/util/annotate-arch/annotate-x86.c +6 -2
index f5e40cef08ab..21729e153fa0 100644
@@ -591,6 +591,7 @@ static void update_insn_state_x86(struct type_state *state,
 				tsr->type = stack->type;
 				tsr->kind = stack->kind;
 				tsr->offset = stack->ptr_offset;
+				tsr->imm_value = stack->imm_value;
 				tsr->ok = true;
 			} else if (die_get_member_type(&stack->type,
 						       offset - stack->offset,
@@ -598,6 +599,7 @@ static void update_insn_state_x86(struct type_state *state,
 				tsr->type = type_die;
 				tsr->kind = TSR_KIND_TYPE;
 				tsr->offset = 0;
+				tsr->imm_value = 0;
 				tsr->ok = true;
 			} else {
 				invalidate_reg_state(tsr);
@@ -773,10 +775,12 @@ static void update_insn_state_x86(struct type_state *state,
 				 */
 				if (!stack->compound)
 					set_stack_state(stack, offset, tsr->kind,
-							&tsr->type, tsr->offset);
+							&tsr->type, tsr->offset,
+							tsr->imm_value);
 			} else {
 				findnew_stack_state(state, offset, tsr->kind,
-						    &tsr->type, tsr->offset);
+						    &tsr->type, tsr->offset,
+						    tsr->imm_value);
 			}
 
 			if (dst->reg1 == fbreg) {
tools/perf/util/annotate-data.c +7 -6
index d9f0bfd3f0a7..8bd9b6370277 100644
@@ -594,7 +594,7 @@ struct type_state_stack *find_stack_state(struct type_state *state,
 }
 
 void set_stack_state(struct type_state_stack *stack, int offset, u8 kind,
-			    Dwarf_Die *type_die, int ptr_offset)
+			    Dwarf_Die *type_die, int ptr_offset, u64 imm_value)
 {
 	int tag;
 	Dwarf_Word size;
@@ -611,8 +611,9 @@ void set_stack_state(struct type_state_stack *stack, int offset, u8 kind,
 	stack->offset = offset;
 	stack->ptr_offset = ptr_offset;
 	stack->kind = kind;
+	stack->imm_value = imm_value;
 
-	if (kind == TSR_KIND_POINTER) {
+	if (kind == TSR_KIND_POINTER || kind == TSR_KIND_CONST) {
 		stack->compound = false;
 		return;
 	}
@@ -633,18 +634,18 @@ void set_stack_state(struct type_state_stack *stack, int offset, u8 kind,
 struct type_state_stack *findnew_stack_state(struct type_state *state,
 						    int offset, u8 kind,
 						    Dwarf_Die *type_die,
-						    int ptr_offset)
+						    int ptr_offset, u64 imm_value)
 {
 	struct type_state_stack *stack = find_stack_state(state, offset);
 
 	if (stack) {
-		set_stack_state(stack, offset, kind, type_die, ptr_offset);
+		set_stack_state(stack, offset, kind, type_die, ptr_offset, imm_value);
 		return stack;
 	}
 
 	stack = malloc(sizeof(*stack));
 	if (stack) {
-		set_stack_state(stack, offset, kind, type_die, ptr_offset);
+		set_stack_state(stack, offset, kind, type_die, ptr_offset, imm_value);
 		list_add(&stack->list, &state->stack_vars);
 	}
 	return stack;
@@ -939,7 +940,7 @@ static void update_var_state(struct type_state *state, struct data_loc_info *dlo
 				continue;
 
 			findnew_stack_state(state, offset, TSR_KIND_TYPE,
-					    &mem_die, /*ptr_offset=*/0);
+					    &mem_die, /*ptr_offset=*/0, /*imm_value=*/0);
 
 			if (var->reg == state->stack_reg) {
 				pr_debug_dtp("var [%"PRIx64"] %#x(reg%d)",
tools/perf/util/annotate-data.h +3 -2
index 62efdcd8a79a..de92459e8502 100644
@@ -199,6 +199,7 @@ struct type_state_stack {
 	int size;
 	bool compound;
 	u8 kind;
+	u64 imm_value;
 };
 
 /*
@@ -253,9 +254,9 @@ bool has_reg_type(struct type_state *state, int reg);
 struct type_state_stack *findnew_stack_state(struct type_state *state,
 						int offset, u8 kind,
 						Dwarf_Die *type_die,
-						int ptr_offset);
+						int ptr_offset, u64 imm_value);
 void set_stack_state(struct type_state_stack *stack, int offset, u8 kind,
-				Dwarf_Die *type_die, int ptr_offset);
+				Dwarf_Die *type_die, int ptr_offset, u64 imm_value);
 struct type_state_stack *find_stack_state(struct type_state *state,
 						int offset);
 void invalidate_reg_state(struct type_state_reg *reg);
Signature · 1 line
2.34.1
#42 lore raw

Re: [PATCH v6 19/26] perf annotate-data: Track imm_value for stack variables

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 11 lines

Currently, imm_value is tracked in registers but dropped when values are saved to or loaded from stack variables during type profiling. If a register of type TSR_KIND_CONST is stored to the stack, its value will be lost.

Add an imm_value field to struct type_state_stack and update set_stack_state() and findnew_stack_state() to accept and preserve the immediate value when updating stack state.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=19
author
#43 lore raw

PATCH v6 20/26 perf annotate-x86: Delete stale stack state on store of untracked register

In reply to Tengda Wu

When a register without a tracked type is stored to a stack slot that previously held a valid type, the code currently performs an early return. This leaves the old type information intact in the stack state.

As a result, a stale type bug occurs:
1. A tracked register is stored into a stack slot.
2. The register becomes untracked/invalid (e.g., across a function call).
3. The now-untracked register is stored into the same stack slot.
4. Subsequent loads from that slot erroneously pick up the old stale type because the stack entry was never invalidated.

Fix this by introducing delete_stack_state() to search and free a stack entry for a given offset. When storing an untracked or invalid source register into a stack location, call delete_stack_state() to delete the stale entry before returning.

---
 tools/perf/util/annotate-arch/annotate-x86.c | 17 +++++++++++++----
 tools/perf/util/annotate-data.c              | 10 ++++++++++
 tools/perf/util/annotate-data.h              |  1 +
 3 files changed, 24 insertions(+), 4 deletions(-)
tools/perf/util/annotate-arch/annotate-x86.c +13 -4
index 21729e153fa0..3a6ea7e978e0 100644
@@ -753,15 +753,24 @@ static void update_insn_state_x86(struct type_state *state,
 	}
 	/* Case 3. register to memory transfers */
 	if (!src->mem_ref && dst->mem_ref) {
-		if (!has_reg_type(state, src->reg1) ||
-		    !state->regs[src->reg1].ok)
-			return;
-
 		/* Check stack variables with offset */
 		if (dst->reg1 == fbreg || dst->reg1 == state->stack_reg) {
 			struct type_state_stack *stack;
 			int offset = dst->offset - fboff;
 
+			if (!has_reg_type(state, src->reg1) ||
+			    !state->regs[src->reg1].ok) {
+				stack = find_stack_state(state, offset);
+				/*
+				 * Preserve compound states when only a member
+				 * is overwritten.
+				 */
+				if (stack && !stack->compound)
+					delete_stack_state(state, offset);
+
+				return;
+			}
+
 			tsr = &state->regs[src->reg1];
 
 			stack = find_stack_state(state, offset);
tools/perf/util/annotate-data.c +10 -0
index 8bd9b6370277..9ca59bc2f841 100644
@@ -651,6 +651,16 @@ struct type_state_stack *findnew_stack_state(struct type_state *state,
 	return stack;
 }
 
+void delete_stack_state(struct type_state *state, int offset)
+{
+	struct type_state_stack *stack = find_stack_state(state, offset);
+
+	if (stack) {
+		list_del(&stack->list);
+		free(stack);
+	}
+}
+
 void invalidate_reg_state(struct type_state_reg *reg)
 {
 	reg->kind = TSR_KIND_INVALID;
tools/perf/util/annotate-data.h +1 -0
index de92459e8502..a0d7e20ed725 100644
@@ -259,6 +259,7 @@ void set_stack_state(struct type_state_stack *stack, int offset, u8 kind,
 				Dwarf_Die *type_die, int ptr_offset, u64 imm_value);
 struct type_state_stack *find_stack_state(struct type_state *state,
 						int offset);
+void delete_stack_state(struct type_state *state, int offset);
 void invalidate_reg_state(struct type_state_reg *reg);
 bool get_global_var_type(Dwarf_Die *cu_die, struct data_loc_info *dloc,
 				u64 ip, u64 var_addr, int *var_offset,
Signature · 1 line
2.34.1
#44 lore raw

Re: [PATCH v6 20/26] perf annotate-x86: Delete stale stack state on store of untracked register

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 11 lines

When a register without a tracked type is stored to a stack slot that previously held a valid type, the code currently performs an early return. This leaves the old type information intact in the stack state.

As a result, a stale type bug occurs:
1. A tracked register is stored into a stack slot.
2. The register becomes untracked/invalid (e.g., across a function call).
3. The now-untracked register is stored into the same stack slot.
4. Subsequent loads from that slot erroneously pick up the old stale type because the stack entry was never invalidated.

Quoted 6 lines

Fix this by introducing delete_stack_state() to search and free a stack entry for a given offset. When storing an untracked or invalid source register into a stack location, call delete_stack_state() to delete the stale entry before returning.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=20
author
#45 lore raw

PATCH v6 21/26 perf annotate-arm64: Support stack variable tracking

In reply to Tengda Wu

Extend update_insn_state_arm64() to track data types stored on the stack.

The implementation handles:
1. Stack Loads: Identify when a register is loaded from a stack slot and update the register's type state based on the tracked stack content or compound member types.
2. Stack Stores: Update or create new stack state entries when a tracked register type is stored to the stack.

Similar to loads, stores also support saving one or two registers (in the case of 'stp'). Therefore, propagate_store_reg_state() is introduced.

With these changes, the instruction tracker can now follow data types as they move between registers and memory, specifically for function local variables and compiler-spilled values on arm64.

---
 .../perf/util/annotate-arch/annotate-arm64.c  | 168 ++++++++++++++++--
 1 file changed, 158 insertions(+), 10 deletions(-)
tools/perf/util/annotate-arch/annotate-arm64.c +158 -10
index a72c7703dd6b..8339aab0d314 100644
@@ -542,6 +542,8 @@ static int propagate_load_reg_state(struct type_state *state,
 	u32 insn_offset = dl->al.offset;
 	int sreg = src->reg1;
 	int reg_offset;
+	int fbreg = dloc->fbreg;
+	int fboff = 0;
 
 	if (!has_reg_type(state, dreg))
 		return -1;
@@ -549,12 +551,57 @@ static int propagate_load_reg_state(struct type_state *state,
 	tsr = &state->regs[dreg];
 	tsr->copied_from = -1;
 
+	if (dloc->fb_cfa) {
+		u64 ip = dloc->ms->sym->start + dl->al.offset;
+		u64 pc = map__rip_2objdump(dloc->ms->map, ip);
+
+		if (die_get_cfa(dloc->di->dbg, pc, &fbreg, &fboff) < 0)
+			fbreg = -1;
+	}
+
 retry:
 	if (arch_get_reg_offset(dloc->arch, src, sreg, state, false, &reg_offset))
 		goto load_non_regoff;
 
 	reg_offset += mem_spacing;
 
+	/* Check stack variables with offset */
+	if (sreg != -1 && (sreg == fbreg || sreg == state->stack_reg)) {
+		struct type_state_stack *stack;
+		int offset = sreg == fbreg ? reg_offset - fboff : reg_offset;
+
+		stack = find_stack_state(state, offset);
+		if (stack == NULL) {
+			return -1;
+		} else if (!stack->compound) {
+			tsr->type = stack->type;
+			tsr->kind = stack->kind;
+			tsr->offset = stack->ptr_offset;
+			tsr->imm_value = stack->imm_value;
+			tsr->ok = true;
+		} else if (die_get_member_type(&stack->type,
+					       offset - stack->offset,
+					       &type_die)) {
+			tsr->type = type_die;
+			tsr->kind = TSR_KIND_TYPE;
+			tsr->offset = 0;
+			tsr->imm_value = 0;
+			tsr->ok = true;
+		} else {
+			return -1;
+		}
+
+		if (sreg == fbreg) {
+			pr_debug_dtp("%s [%x] -%#x(stack) -> reg%d",
+				     dl->ins.name, insn_offset, -offset, dreg);
+		} else {
+			pr_debug_dtp("%s [%x] %#x(reg%d) -> reg%d",
+				     dl->ins.name, insn_offset, offset, sreg, dreg);
+		}
+		pr_debug_type_name(&tsr->type, tsr->kind);
+		return 0;
+	}
+
 	if (!src_tsr || !src_tsr->ok)
 		return -1;
 
@@ -673,6 +720,115 @@ static bool is_standard_store_insn(const char *name)
 	       !strncmp(name, "stp", 3);     /* stp */
 }
 
+/*
+ * For store insns: propagate type from @sreg to the memory location referenced
+ * by @dst, applying @mem_spacing to account for multi-regs stores (e.g. stp).
+ */
+static void propagate_store_reg_state(struct type_state *state,
+				      struct data_loc_info *dloc,
+				      struct disasm_line *dl, int sreg,
+				      struct annotated_op_loc *dst,
+				      int mem_spacing)
+{
+	struct type_state_reg *tsr;
+	u32 insn_offset = dl->al.offset;
+	int dreg = dst->reg1;
+	int reg_offset;
+	int fbreg = dloc->fbreg;
+	int fboff = 0;
+
+	if (arch_get_reg_offset(dloc->arch, dst, dreg, state, false, &reg_offset))
+		return;
+
+	reg_offset += mem_spacing;
+
+	if (dloc->fb_cfa) {
+		u64 ip = dloc->ms->sym->start + dl->al.offset;
+		u64 pc = map__rip_2objdump(dloc->ms->map, ip);
+
+		if (die_get_cfa(dloc->di->dbg, pc, &fbreg, &fboff) < 0)
+			fbreg = -1;
+	}
+
+	/* Check stack variables with offset */
+	if (dreg != -1 && (dreg == fbreg || dreg == state->stack_reg)) {
+		struct type_state_stack *stack;
+		int offset = dreg == fbreg ? reg_offset - fboff : reg_offset;
+
+		if (!has_reg_type(state, sreg) || !state->regs[sreg].ok) {
+			stack = find_stack_state(state, offset);
+			/* Preserve compound states when only a member is overwritten. */
+			if (stack && !stack->compound)
+				delete_stack_state(state, offset);
+
+			return;
+		}
+
+		tsr = &state->regs[sreg];
+
+		stack = find_stack_state(state, offset);
+		if (stack) {
+			if (!stack->compound)
+				set_stack_state(stack, offset, tsr->kind, &tsr->type,
+						tsr->offset, tsr->imm_value);
+			/*
+			 * If it's a compound type, it means attempting to
+			 * write to a member value of the compound type without
+			 * changing the compound type itself, so do nothing.
+			 */
+		} else {
+			findnew_stack_state(state, offset, tsr->kind, &tsr->type,
+					    tsr->offset, tsr->imm_value);
+		}
+
+		if (dreg == fbreg) {
+			pr_debug_dtp("%s [%x] reg%d -> -%#x(stack)",
+				     dl->ins.name, insn_offset, sreg, -offset);
+		} else {
+			pr_debug_dtp("%s [%x] reg%d -> %#x(reg%d)",
+				     dl->ins.name, insn_offset, sreg, offset, dreg);
+		}
+		if (tsr->offset != 0)
+			pr_debug_dtp(" reg%d offset %#x ->", sreg, tsr->offset);
+
+		pr_debug_type_name(&tsr->type, tsr->kind);
+	}
+	/*
+	 * Ignore other transfers since it'd set a value in a struct
+	 * and won't change the type.
+	 */
+}
+
+static void update_store_insn_state(struct type_state *state,
+				    struct data_loc_info *dloc,
+				    struct disasm_line *dl,
+				    struct annotated_op_loc *src,
+				    struct annotated_op_loc *dst)
+{
+	int mem_spacing = 0;
+
+	if (!has_reg_type(state, dst->reg1))
+		return;
+
+	/* Handle the first source register */
+	if (has_reg_type(state, src->reg1))
+		propagate_store_reg_state(state, dloc, dl, src->reg1,
+					  dst, mem_spacing);
+
+	if (!src->multi_regs || !has_reg_type(state, src->reg2))
+		goto out_adjust;
+
+	/* Handle the second source register */
+	mem_spacing = arm64__reg_size(dl->ops.source.raw);
+
+	if (mem_spacing >= 0)
+		propagate_store_reg_state(state, dloc, dl, src->reg2,
+					  dst, mem_spacing);
+
+out_adjust:
+	adjust_reg_index_state(state, dloc, dl, dst);
+}
+
 static void update_insn_state_arm64(struct type_state *state,
 				    struct data_loc_info *dloc, Dwarf_Die *cu_die,
 				    struct disasm_line *dl)
@@ -755,16 +911,8 @@ static void update_insn_state_arm64(struct type_state *state,
 	if (is_standard_load_insn(dl->ins.name))
 		update_load_insn_state(state, dloc, dl, src, dst);
 	/* Register to memory transfers */
-	else if (is_standard_store_insn(dl->ins.name)) {
-		/*
-		 * Ignore transfers since it'd set a value in a struct
-		 * and won't change the type.
-		 *
-		 * Needs to update the pre-index and post-index addressing
-		 * modes for the destination register.
-		 */
-		adjust_reg_index_state(state, dloc, dl, dst);
-	}
+	else if (is_standard_store_insn(dl->ins.name))
+		update_store_insn_state(state, dloc, dl, src, dst);
 }
 #endif
 
Signature · 1 line
2.34.1
#46 lore raw

Re: [PATCH v6 21/26] perf annotate-arm64: Support stack variable tracking

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] perf: stale stack state due to bypassed invalidation in update_store_insn_state --

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] perf: stale stack state due to bypassed invalidation in update_store_insn_state --

commit 2ddb0f384d44eeb70844d8c3adc06f8d55101f68
Author: Tengda Wu <[email protected]>

perf annotate-arm64: Support stack variable tracking

Extend update_insn_state_arm64() to track data types stored on the stack. The implementation handles stack loads and stores to follow data types as they move between registers and memory.

Quoted 4 lines
tools/perf/util/annotate-arch/annotate-arm64.c +0 -0
index a72c7703dd6b3..8339aab0d314f 100644

[ ... ]

Quoted 10 lines
tools/perf/util/annotate-arch/annotate-arm64.c +10 -0
+/*
+ * For store insns: propagate type from @sreg to the memory location referenced
+ * by @dst, applying @mem_spacing to account for multi-regs stores (e.g. stp).
+ */
+static void propagate_store_reg_state(struct type_state *state,
+				      struct data_loc_info *dloc,
+				      struct disasm_line *dl, int sreg,
+				      struct annotated_op_loc *dst,
+				      int mem_spacing)
+{

[ ... ]

Quoted 8 lines
tools/perf/util/annotate-arch/annotate-arm64.c +8 -0
+		if (!has_reg_type(state, sreg) || !state->regs[sreg].ok) {
+			stack = find_stack_state(state, offset);
+			/* Preserve compound states when only a member is overwritten. */
+			if (stack && !stack->compound)
+				delete_stack_state(state, offset);
+
+			return;
+		}

[ ... ]

Quoted 8 lines
tools/perf/util/annotate-arch/annotate-arm64.c +8 -0
+static void update_store_insn_state(struct type_state *state,
+				    struct data_loc_info *dloc,
+				    struct disasm_line *dl,
+				    struct annotated_op_loc *src,
+				    struct annotated_op_loc *dst)
+{
+	int mem_spacing = 0;
+

tools/perf/util/annotate-arch/annotate-arm64.c static bool is_standard_store_insn(const char *name) · line 819

Quoted 10 lines
tools/perf/util/annotate-arch/annotate-arm64.c +10 -0
+	if (!has_reg_type(state, dst->reg1))
+		return;
+
+	/* Handle the first source register */
+	if (has_reg_type(state, src->reg1))
+		propagate_store_reg_state(state, dloc, dl, src->reg1,
+					  dst, mem_spacing);
+
+	if (!src->multi_regs || !has_reg_type(state, src->reg2))
+		goto out_adjust;

[Severity: Medium]
Does the has_reg_type() guard here inadvertently skip the stack state invalidation logic?

Looking at propagate_store_reg_state() above, there is logic to delete the stack state if the source register is untracked. However, because update_store_insn_state() completely bypasses the call to propagate_store_reg_state() when the source register is untracked, this cleanup is never reached.

If an untracked register is stored into a stack slot previously used for a tracked variable, could this leave stale tracking state on the stack, causing subsequent loads from that slot to inherit the wrong type?

Quoted 11 lines
tools/perf/util/annotate-arch/annotate-arm64.c +11 -0
+
+	/* Handle the second source register */
+	mem_spacing = arm64__reg_size(dl->ops.source.raw);
+
+	if (mem_spacing >= 0)
+		propagate_store_reg_state(state, dloc, dl, src->reg2,
+					  dst, mem_spacing);
+
+out_adjust:
+	adjust_reg_index_state(state, dloc, dl, dst);
+}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=21
author
#47 lore raw

Re: [PATCH v6 21/26] perf annotate-arm64: Support stack variable tracking

Quoted 67 lines · On 2026/9/16 9:49, [email protected] wrote:

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] perf: stale stack state due to bypassed invalidation in update_store_insn_state --

commit 2ddb0f384d44eeb70844d8c3adc06f8d55101f68
Author: Tengda Wu <[email protected]>

perf annotate-arm64: Support stack variable tracking

Extend update_insn_state_arm64() to track data types stored on the stack. The implementation handles stack loads and stores to follow data types as they move between registers and memory.

Quoted 4 lines
tools/perf/util/annotate-arch/annotate-arm64.c +0 -0
index a72c7703dd6b3..8339aab0d314f 100644

[ ... ]

Quoted 10 lines
tools/perf/util/annotate-arch/annotate-arm64.c +10 -0
+/*
+ * For store insns: propagate type from @sreg to the memory location referenced
+ * by @dst, applying @mem_spacing to account for multi-regs stores (e.g. stp).
+ */
+static void propagate_store_reg_state(struct type_state *state,
+				      struct data_loc_info *dloc,
+				      struct disasm_line *dl, int sreg,
+				      struct annotated_op_loc *dst,
+				      int mem_spacing)
+{

[ ... ]

Quoted 8 lines
tools/perf/util/annotate-arch/annotate-arm64.c +8 -0
+		if (!has_reg_type(state, sreg) || !state->regs[sreg].ok) {
+			stack = find_stack_state(state, offset);
+			/* Preserve compound states when only a member is overwritten. */
+			if (stack && !stack->compound)
+				delete_stack_state(state, offset);
+
+			return;
+		}

[ ... ]

Quoted 8 lines
tools/perf/util/annotate-arch/annotate-arm64.c +8 -0
+static void update_store_insn_state(struct type_state *state,
+				    struct data_loc_info *dloc,
+				    struct disasm_line *dl,
+				    struct annotated_op_loc *src,
+				    struct annotated_op_loc *dst)
+{
+	int mem_spacing = 0;
+

tools/perf/util/annotate-arch/annotate-arm64.c static bool is_standard_store_insn(const char *name) · line 819

Quoted 10 lines
tools/perf/util/annotate-arch/annotate-arm64.c +10 -0
+	if (!has_reg_type(state, dst->reg1))
+		return;
+
+	/* Handle the first source register */
+	if (has_reg_type(state, src->reg1))
+		propagate_store_reg_state(state, dloc, dl, src->reg1,
+					  dst, mem_spacing);
+
+	if (!src->multi_regs || !has_reg_type(state, src->reg2))
+		goto out_adjust;

[Severity: Medium]
Does the has_reg_type() guard here inadvertently skip the stack state invalidation logic?

Looking at propagate_store_reg_state() above, there is logic to delete the stack state if the source register is untracked. However, because update_store_insn_state() completely bypasses the call to propagate_store_reg_state() when the source register is untracked, this cleanup is never reached.

Quoted 4 lines

If an untracked register is stored into a stack slot previously used for a tracked variable, could this leave stale tracking state on the stack, causing subsequent loads from that slot to inherit the wrong type?

Agreed.

Quoted 12 lines
Quoted 11 lines
tools/perf/util/annotate-arch/annotate-arm64.c +11 -0
+
+	/* Handle the second source register */
+	mem_spacing = arm64__reg_size(dl->ops.source.raw);
+
+	if (mem_spacing >= 0)
+		propagate_store_reg_state(state, dloc, dl, src->reg2,
+					  dst, mem_spacing);
+
+out_adjust:
+	adjust_reg_index_state(state, dloc, dl, dst);
+}
author
#48 lore raw

Re: [PATCH v6 21/26] perf annotate-arm64: Support stack variable tracking

Quoted 70 lines · Tengda Wu
Quoted 67 lines · On 2026/9/16 9:49, [email protected] wrote:

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] perf: stale stack state due to bypassed invalidation in update_store_insn_state --

commit 2ddb0f384d44eeb70844d8c3adc06f8d55101f68
Author: Tengda Wu <[email protected]>

perf annotate-arm64: Support stack variable tracking

Extend update_insn_state_arm64() to track data types stored on the stack. The implementation handles stack loads and stores to follow data types as they move between registers and memory.

Quoted 4 lines
tools/perf/util/annotate-arch/annotate-arm64.c +0 -0
index a72c7703dd6b3..8339aab0d314f 100644

[ ... ]

Quoted 10 lines
tools/perf/util/annotate-arch/annotate-arm64.c +10 -0
+/*
+ * For store insns: propagate type from @sreg to the memory location referenced
+ * by @dst, applying @mem_spacing to account for multi-regs stores (e.g. stp).
+ */
+static void propagate_store_reg_state(struct type_state *state,
+				      struct data_loc_info *dloc,
+				      struct disasm_line *dl, int sreg,
+				      struct annotated_op_loc *dst,
+				      int mem_spacing)
+{

[ ... ]

Quoted 8 lines
tools/perf/util/annotate-arch/annotate-arm64.c +8 -0
+		if (!has_reg_type(state, sreg) || !state->regs[sreg].ok) {
+			stack = find_stack_state(state, offset);
+			/* Preserve compound states when only a member is overwritten. */
+			if (stack && !stack->compound)
+				delete_stack_state(state, offset);
+
+			return;
+		}

[ ... ]

Quoted 8 lines
tools/perf/util/annotate-arch/annotate-arm64.c +8 -0
+static void update_store_insn_state(struct type_state *state,
+				    struct data_loc_info *dloc,
+				    struct disasm_line *dl,
+				    struct annotated_op_loc *src,
+				    struct annotated_op_loc *dst)
+{
+	int mem_spacing = 0;
+

tools/perf/util/annotate-arch/annotate-arm64.c static bool is_standard_store_insn(const char *name) · line 819

Quoted 10 lines
tools/perf/util/annotate-arch/annotate-arm64.c +10 -0
+	if (!has_reg_type(state, dst->reg1))
+		return;
+
+	/* Handle the first source register */
+	if (has_reg_type(state, src->reg1))
+		propagate_store_reg_state(state, dloc, dl, src->reg1,
+					  dst, mem_spacing);
+
+	if (!src->multi_regs || !has_reg_type(state, src->reg2))
+		goto out_adjust;

[Severity: Medium]
Does the has_reg_type() guard here inadvertently skip the stack state invalidation logic?

Looking at propagate_store_reg_state() above, there is logic to delete the stack state if the source register is untracked. However, because update_store_insn_state() completely bypasses the call to propagate_store_reg_state() when the source register is untracked, this cleanup is never reached.

Quoted 6 lines
Quoted 4 lines

If an untracked register is stored into a stack slot previously used for a tracked variable, could this leave stale tracking state on the stack, causing subsequent loads from that slot to inherit the wrong type?

Agreed.

I might change it to something like this:

+static void update_store_insn_state(struct type_state *state,

+				    struct data_loc_info *dloc,
+				    struct disasm_line *dl,
+				    struct annotated_op_loc *src,
+				    struct annotated_op_loc *dst)

+{
+ int mem_spacing = 0;
+
+ if (!has_reg_type(state, dst->reg1))
+ return;
+
+ /* Handle the first source register */
+ propagate_store_reg_state(state, dloc, dl, src->reg1, dst, mem_spacing);
+
+ /* Handle the second source register (if any) */
+ if (src->multi_regs) {
+ mem_spacing = arm64__reg_size(dl->ops.source.raw);
+
+ if (mem_spacing >= 0)
+ propagate_store_reg_state(state, dloc, dl, src->reg2,
+ dst, mem_spacing);
+ }
+
+ adjust_reg_index_state(state, dloc, dl, dst);
+}

Thanks,
Tengda

author
#49 lore raw

PATCH v6 22/26 perf annotate-arm64: Support 'mov' instruction tracking

In reply to Tengda Wu

Extend update_insn_state_arm64() to support register-to-register and immediate-to-register 'mov' instructions.

For register-to-register 'mov' (e.g., mov dreg, sreg), propagate data type information from the source register to the destination register.

For immediate-to-register 'mov' (e.g., mov dreg, #imm), store the immediate value in the destination register's imm_value field and set its kind to TSR_KIND_CONST, allowing subsequent instructions to resolve it as a constant.

A real-world example is shown below:

ffff8000803eebf8 <get_vma_policy>:

  ffff8000803eec20:  mov  x21, x0          // x0 (struct vm_area_struct*) -> x21
  ffff8000803eec28:  ldr  x2, [x0, #112]
  ffff8000803eec2c:  cbz  x2, ffff8000803eec94 <get_vma_policy+0x9c>
* ffff8000803eec94:  ldr  x0, [x21, #152]

Before this commit, the type of x21 was unknown, causing the subsequent inference to fail:

var [0] reg0 offset 0 type='struct vm_area_struct*' size=0x8 chk [9c] reg21 offset=0x98 ok=0 kind=0 cfa : no type information final result: no type information

After this commit, the type of x21 is correctly inferred as 'vm_area_struct':

var [0] reg0 offset 0 type='struct vm_area_struct*' size=0x8 mov [28] reg0 -> reg21 type='struct vm_area_struct*' size=0x8 chk [9c] reg21 offset=0x98 ok=1 kind=1 (struct vm_area_struct*) : Good! found by insn track: 0x98(reg21) type-offset=0x98
final result: type='struct vm_area_struct' size=0xb0

---
 .../perf/util/annotate-arch/annotate-arm64.c  | 56 ++++++++++++++++++-
 1 file changed, 55 insertions(+), 1 deletion(-)
tools/perf/util/annotate-arch/annotate-arm64.c +55 -1
index 8339aab0d314..3358562893b1 100644
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0
 #include <linux/compiler.h>
 #include <errno.h>
+#include <inttypes.h>
 #include <stdlib.h>
 #include <string.h>
 #include <linux/ctype.h>
@@ -612,6 +613,7 @@ static int propagate_load_reg_state(struct type_state *state,
 		tsr->type = type_die;
 		tsr->kind = TSR_KIND_TYPE;
 		tsr->offset = 0;
+		tsr->imm_value = 0;
 		tsr->ok = true;
 
 		pr_debug_dtp("%s [%x] %#x(reg%d) -> reg%d",
@@ -829,6 +831,54 @@ static void update_store_insn_state(struct type_state *state,
 	adjust_reg_index_state(state, dloc, dl, dst);
 }
 
+static void update_mov_insn_state(struct type_state *state,
+				  struct disasm_line *dl,
+				  struct annotated_op_loc *src,
+				  struct annotated_op_loc *dst)
+{
+	struct type_state_reg *tsr;
+	u32 insn_offset = dl->al.offset;
+	int sreg = src->reg1;
+	int dreg = dst->reg1;
+
+	if (!has_reg_type(state, dreg))
+		return;
+
+	tsr = &state->regs[dreg];
+	tsr->copied_from = -1;
+
+	if (src->imm) {
+		invalidate_reg_state(tsr);
+		tsr->kind = TSR_KIND_CONST;
+		tsr->imm_value = (s64)src->offset;
+		tsr->offset = 0;
+		tsr->ok = true;
+
+		pr_debug_dtp("mov [%x] imm=%#"PRIx64" -> reg%d\n",
+			     insn_offset, tsr->imm_value, dreg);
+		return;
+	}
+
+	if (!has_reg_type(state, sreg) || !state->regs[sreg].ok) {
+		invalidate_reg_state(tsr);
+		return;
+	}
+
+	tsr->type = state->regs[sreg].type;
+	tsr->kind = state->regs[sreg].kind;
+	tsr->imm_value = state->regs[sreg].imm_value;
+	tsr->offset = state->regs[sreg].offset;
+	tsr->lifetime_active = state->regs[sreg].lifetime_active;
+	tsr->lifetime_end = state->regs[sreg].lifetime_end;
+	tsr->ok = state->regs[sreg].ok;
+
+	if (tsr->kind == TSR_KIND_TYPE || tsr->kind == TSR_KIND_POINTER)
+		tsr->copied_from = sreg;
+
+	pr_debug_dtp("mov [%x] reg%d -> reg%d", insn_offset, sreg, dreg);
+	pr_debug_type_name(&tsr->type, tsr->kind);
+}
+
 static void update_insn_state_arm64(struct type_state *state,
 				    struct data_loc_info *dloc, Dwarf_Die *cu_die,
 				    struct disasm_line *dl)
@@ -895,6 +945,7 @@ static void update_insn_state_arm64(struct type_state *state,
 	 * prevent stale type info from propagating to subsequent instructions.
 	 */
 	if (has_reg_type(state, dst->reg1) && !dst->mem_ref &&
+	    strcmp(dl->ins.name, "mov") &&
 	    !is_standard_load_insn(dl->ins.name)) {
 		pr_debug_dtp("%s [%x] invalidate reg%d",
 			     dl->ins.name, insn_offset, dst->reg1);
@@ -907,8 +958,11 @@ static void update_insn_state_arm64(struct type_state *state,
 		return;
 	}
 
+	/* Register to register or imm value to register transfers */
+	if (!strcmp(dl->ins.name, "mov"))
+		update_mov_insn_state(state, dl, src, dst);
 	/* Memory to register transfers */
-	if (is_standard_load_insn(dl->ins.name))
+	else if (is_standard_load_insn(dl->ins.name))
 		update_load_insn_state(state, dloc, dl, src, dst);
 	/* Register to memory transfers */
 	else if (is_standard_store_insn(dl->ins.name))
Signature · 1 line
2.34.1
#50 lore raw

Re: [PATCH v6 22/26] perf annotate-arm64: Support 'mov' instruction tracking

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 13 lines

Extend update_insn_state_arm64() to support register-to-register and immediate-to-register 'mov' instructions.

For register-to-register 'mov' (e.g., mov dreg, sreg), propagate data type information from the source register to the destination register.

For immediate-to-register 'mov' (e.g., mov dreg, #imm), store the immediate value in the destination register's imm_value field and set its kind to TSR_KIND_CONST, allowing subsequent instructions to resolve it as a constant.

A real-world example is shown below:

Quoted 8 lines

ffff8000803eebf8 <get_vma_policy>:
ffff8000803eec20: mov x21, x0 // x0 (struct vm_area_struct*) -> x21
[ ... ]
chk [9c] reg21 offset=0x98 ok=1 kind=1 (struct vm_area_struct*) : Good! found by insn track: 0x98(reg21) type-offset=0x98
final result: type='struct vm_area_struct' size=0xb0

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=22
author
#51 lore raw

PATCH v6 23/26 perf annotate-arm64: Support 'add' instruction tracking

In reply to Tengda Wu

Extend update_insn_state_arm64() to track 'add' instructions for structure member address calculation.

Unlike x86, the arm64 'add' instruction has an extra base register among its source operands. Therefore, in terms of propagating the data type, it is essentially performing a 'mov', except that before the 'mov', it first needs to be updated by adding the offset or reg2.

A real-world example is shown below:

ffff80008001c9a8 <flush_ptrace_hw_breakpoint>:

  ffff80008001c9c4:  add  x19, x0, #0xeb8   // x0 (task_struct*) + 0xeb8 -> x19
* ffff80008001c9d0:  ldr  x0, [x19]

Before this commit, the type flow broke at the 'add' instruction, leaving the subsequent load with no type information:

chk [28] reg19 offset=0 ok=0 kind=0 cfa : no type information final result: no type information

After this commit, the tracker correctly follows the member address calculation:

var [0] reg0 offset 0 type='struct task_struct*'
add [1c] address of 0xeb8(reg0) -> reg19 type='struct task_struct*' chk [28] reg19 offset=0 ok=1 kind=1 (struct task_struct*) : Good! found by insn track: 0(reg19) type-offset=0xeb8
final result: type='struct task_struct'

---
 .../perf/util/annotate-arch/annotate-arm64.c  | 74 ++++++++++++++++++-
 1 file changed, 72 insertions(+), 2 deletions(-)
tools/perf/util/annotate-arch/annotate-arm64.c +72 -2
index 3358562893b1..68510813f08b 100644
@@ -879,6 +879,74 @@ static void update_mov_insn_state(struct type_state *state,
 	pr_debug_type_name(&tsr->type, tsr->kind);
 }
 
+static void update_add_insn_state(struct type_state *state,
+				  struct data_loc_info *dloc,
+				  struct disasm_line *dl,
+				  struct annotated_op_loc *src,
+				  struct annotated_op_loc *dst)
+{
+	struct type_state_reg *tsr;
+	struct type_state_reg src_tsr;
+	u32 insn_offset = dl->al.offset;
+	int sreg = src->reg1;
+	int dreg = dst->reg1;
+	int reg_offset;
+
+	if (!has_reg_type(state, dreg))
+		return;
+
+	tsr = &state->regs[dreg];
+	tsr->copied_from = -1;
+	tsr->lifetime_active = false;
+	tsr->lifetime_end = 0;
+
+retry:
+	if (!has_reg_type(state, sreg) || !state->regs[sreg].ok) {
+		invalidate_reg_state(tsr);
+		return;
+	}
+
+	src_tsr = state->regs[sreg];
+
+	if (arch_get_reg_offset(dloc->arch, src, sreg, state, false, &reg_offset))
+		goto add_non_regoff;
+
+	if (src_tsr.kind == TSR_KIND_CONST) {
+		tsr->kind = src_tsr.kind;
+		tsr->imm_value = src_tsr.imm_value + reg_offset;
+		tsr->offset = 0;
+		tsr->ok = src_tsr.ok;
+
+		pr_debug_dtp("add [%x] imm %#x(reg%d) -> reg%d\n",
+			     insn_offset, reg_offset, sreg, dreg);
+		return;
+	}
+
+	if (src_tsr.kind == TSR_KIND_POINTER ||
+	    (src_tsr.kind == TSR_KIND_TYPE &&
+	     dwarf_tag(&src_tsr.type) == DW_TAG_pointer_type)) {
+		tsr->type = src_tsr.type;
+		tsr->kind = src_tsr.kind;
+		tsr->imm_value = src_tsr.imm_value;
+		tsr->offset = src_tsr.offset + reg_offset;
+		tsr->ok = src_tsr.ok;
+
+		pr_debug_dtp("add [%x] address of %#x(reg%d) -> reg%d",
+			     insn_offset, reg_offset, sreg, dreg);
+		pr_debug_type_name(&tsr->type, tsr->kind);
+		return;
+	}
+
+add_non_regoff:
+	/* Try another register if any */
+	if (src->multi_regs && src->reg1 != src->reg2 && sreg != src->reg2 &&
+	    !(src->extend_type || src->shift_type)) {
+		sreg = src->reg2;
+		goto retry;
+	}
+	invalidate_reg_state(tsr);
+}
+
 static void update_insn_state_arm64(struct type_state *state,
 				    struct data_loc_info *dloc, Dwarf_Die *cu_die,
 				    struct disasm_line *dl)
@@ -945,7 +1013,7 @@ static void update_insn_state_arm64(struct type_state *state,
 	 * prevent stale type info from propagating to subsequent instructions.
 	 */
 	if (has_reg_type(state, dst->reg1) && !dst->mem_ref &&
-	    strcmp(dl->ins.name, "mov") &&
+	    strcmp(dl->ins.name, "add") && strcmp(dl->ins.name, "mov") &&
 	    !is_standard_load_insn(dl->ins.name)) {
 		pr_debug_dtp("%s [%x] invalidate reg%d",
 			     dl->ins.name, insn_offset, dst->reg1);
@@ -958,8 +1026,10 @@ static void update_insn_state_arm64(struct type_state *state,
 		return;
 	}
 
+	if (!strcmp(dl->ins.name, "add"))
+		update_add_insn_state(state, dloc, dl, src, dst);
 	/* Register to register or imm value to register transfers */
-	if (!strcmp(dl->ins.name, "mov"))
+	else if (!strcmp(dl->ins.name, "mov"))
 		update_mov_insn_state(state, dl, src, dst);
 	/* Memory to register transfers */
 	else if (is_standard_load_insn(dl->ins.name))
Signature · 1 line
2.34.1
#52 lore raw

Re: [PATCH v6 23/26] perf annotate-arm64: Support 'add' instruction tracking

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 23 lines

Extend update_insn_state_arm64() to track 'add' instructions for structure member address calculation.

Unlike x86, the arm64 'add' instruction has an extra base register among its source operands. Therefore, in terms of propagating the data type, it is essentially performing a 'mov', except that before the 'mov', it first needs to be updated by adding the offset or reg2.

A real-world example is shown below:

ffff80008001c9a8 <flush_ptrace_hw_breakpoint>:

  ffff80008001c9c4:  add  x19, x0, #0xeb8   // x0 (task_struct*) + 0xeb8 -> x19
* ffff80008001c9d0:  ldr  x0, [x19]

Before this commit, the type flow broke at the 'add' instruction, leaving the subsequent load with no type information:

chk [28] reg19 offset=0 ok=0 kind=0 cfa : no type information final result: no type information

After this commit, the tracker correctly follows the member address calculation:

Quoted 7 lines

var [0] reg0 offset 0 type='struct task_struct*'
add [1c] address of 0xeb8(reg0) -> reg19 type='struct task_struct*' chk [28] reg19 offset=0 ok=1 kind=1 (struct task_struct*) : Good! found by insn track: 0(reg19) type-offset=0xeb8
final result: type='struct task_struct'

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=23
author
#53 lore raw

PATCH v6 24/26 perf annotate-arm64: Support 'adrp' instruction to track global variables

In reply to Tengda Wu

Extend update_insn_state_arm64() to track global variable types calculated via page-relative addressing.

On arm64, global variables are typically accessed by first calculating the page address using 'adrp', followed by an 'add' or 'ldr' to get the specific symbol address. Without tracking 'adrp', the instruction tracker loses the base address, making it impossible to resolve global symbols and their associated DWARF types.

Introduce TSR_KIND_GLOBAL_ADDR to represent a partial global address state. When encountering 'adrp', store the page-aligned target address in the register's type state. Upon a subsequent 'add' or 'ldr' instruction that references a TSR_KIND_GLOBAL_ADDR register, combine the page address with the immediate offset.

A real-world example is shown below:

ffff80008032e008 <folios_put_refs>:

  ffff80008032e048:  adrp  x24, ffff80008202f000 <nr_cpu_ids>
  ffff80008032e050:  add   x24, x24, #0xd40
* ffff80008032e078:  ldr   x0, [x24]

Before this commit, x24 was unknown, leading to no type information:

chk [70] reg24 offset=0 ok=0 kind=0 cfa : no type information final result: no type information

After this commit, the tracker correctly follows the adrp/add flow:

adrp [40] global addr=0xffff80008202f000 -> reg24 add [48] global 0xd40(reg24) -> reg24
chk [70] reg24 offset=0 ok=1 kind=7 global addr : Good! final result: type='struct folio*'

---
 .../perf/util/annotate-arch/annotate-arm64.c  | 83 +++++++++++++++++--
 tools/perf/util/annotate-data.c               | 25 +++++-
 tools/perf/util/annotate-data.h               |  1 +
 3 files changed, 99 insertions(+), 10 deletions(-)
tools/perf/util/annotate-arch/annotate-arm64.c +74 -9
index 68510813f08b..b25a3c70675c 100644
@@ -531,7 +531,7 @@ static bool is_standard_load_insn(const char *name)
  * which is added to the reg offset.
  */
 static int propagate_load_reg_state(struct type_state *state,
-				    struct data_loc_info *dloc,
+				    struct data_loc_info *dloc, Dwarf_Die *cu_die,
 				    struct disasm_line *dl, int dreg,
 				    struct annotated_op_loc *src,
 				    struct type_state_reg **src_states,
@@ -638,6 +638,33 @@ static int propagate_load_reg_state(struct type_state *state,
 		return 0;
 	}
 
+	/* Or check if it's a global variable */
+	if (src_tsr->kind == TSR_KIND_GLOBAL_ADDR) {
+		u64 ip = dloc->ms->sym->start + dl->al.offset;
+		u64 addr = src_tsr->imm_value + reg_offset;
+		int offset;
+
+		if (!get_global_var_type(cu_die, dloc, ip, addr, &offset, &type_die) ||
+		    !die_get_member_type(&type_die, offset, &type_die))
+			return -1;
+
+		tsr->type = type_die;
+		tsr->kind = TSR_KIND_TYPE;
+		tsr->offset = 0;
+		tsr->imm_value = 0;
+		tsr->ok = true;
+
+		if (src->multi_regs) {
+			pr_debug_dtp("%s [%x] global (reg%d, reg%d) -> reg%d",
+				     dl->ins.name, insn_offset, src->reg1, src->reg2, dreg);
+		} else {
+			pr_debug_dtp("%s [%x] global (reg%d) -> reg%d",
+				     dl->ins.name, insn_offset, sreg, dreg);
+		}
+		pr_debug_type_name(&tsr->type, tsr->kind);
+		return 0;
+	}
+
 load_non_regoff:
 	/* Try another register if any */
 	if (src->multi_regs && src->reg1 != src->reg2 && sreg != src->reg2 &&
@@ -651,7 +678,7 @@ static int propagate_load_reg_state(struct type_state *state,
 }
 
 static void update_load_insn_state(struct type_state *state,
-				   struct data_loc_info *dloc,
+				   struct data_loc_info *dloc, Dwarf_Die *cu_die,
 				   struct disasm_line *dl,
 				   struct annotated_op_loc *src,
 				   struct annotated_op_loc *dst)
@@ -685,7 +712,7 @@ static void update_load_insn_state(struct type_state *state,
 
 	/* Handle the first destination register */
 	if (has_reg_type(state, dst->reg1)) {
-		if (propagate_load_reg_state(state, dloc, dl, dst->reg1,
+		if (propagate_load_reg_state(state, dloc, cu_die, dl, dst->reg1,
 					     src, src_states, mem_spacing))
 			invalidate_reg_state(&state->regs[dst->reg1]);
 	}
@@ -700,7 +727,7 @@ static void update_load_insn_state(struct type_state *state,
 		mem_spacing = arm64__reg_size(dl->ops.target.raw);
 
 	if (mem_spacing < 0 ||
-	    propagate_load_reg_state(state, dloc, dl, dst->reg2,
+	    propagate_load_reg_state(state, dloc, cu_die, dl, dst->reg2,
 				     src, src_states, mem_spacing))
 		invalidate_reg_state(&state->regs[dst->reg2]);
 
@@ -911,14 +938,16 @@ static void update_add_insn_state(struct type_state *state,
 	if (arch_get_reg_offset(dloc->arch, src, sreg, state, false, &reg_offset))
 		goto add_non_regoff;
 
-	if (src_tsr.kind == TSR_KIND_CONST) {
+	if (src_tsr.kind == TSR_KIND_CONST || src_tsr.kind == TSR_KIND_GLOBAL_ADDR) {
 		tsr->kind = src_tsr.kind;
+		/* For 'adrp + add' pair: resolve the full global variable address. */
 		tsr->imm_value = src_tsr.imm_value + reg_offset;
 		tsr->offset = 0;
 		tsr->ok = src_tsr.ok;
 
-		pr_debug_dtp("add [%x] imm %#x(reg%d) -> reg%d\n",
-			     insn_offset, reg_offset, sreg, dreg);
+		pr_debug_dtp("add [%x] %s %#x(reg%d) -> reg%d\n",
+			     insn_offset, src_tsr.kind == TSR_KIND_CONST ?
+			     "imm" : "global", reg_offset, sreg, dreg);
 		return;
 	}
 
@@ -947,6 +976,39 @@ static void update_add_insn_state(struct type_state *state,
 	invalidate_reg_state(tsr);
 }
 
+static void update_adrp_insn_state(struct type_state *state,
+				   struct disasm_line *dl,
+				   struct annotated_op_loc *dst)
+{
+	struct type_state_reg *tsr;
+	u32 insn_offset = dl->al.offset;
+	int dreg = dst->reg1;
+
+	if (!has_reg_type(state, dreg))
+		return;
+
+	tsr = &state->regs[dreg];
+	tsr->copied_from = -1;
+
+	if (!dl->ops.source.addr) {
+		invalidate_reg_state(tsr);
+		return;
+	}
+
+	tsr->kind = TSR_KIND_GLOBAL_ADDR;
+	/*
+	 * Stores a partial page-relative address. The full absolute address
+	 * of the global variable will be resolved when a subsequent 'add' or
+	 * 'ldr' instruction consumes this register.
+	 */
+	tsr->imm_value = dl->ops.source.addr;
+	tsr->offset = 0;
+	tsr->ok = true;
+
+	pr_debug_dtp("adrp [%x] global addr=%#"PRIx64" -> reg%d\n",
+		     insn_offset, tsr->imm_value, dreg);
+}
+
 static void update_insn_state_arm64(struct type_state *state,
 				    struct data_loc_info *dloc, Dwarf_Die *cu_die,
 				    struct disasm_line *dl)
@@ -1013,6 +1075,7 @@ static void update_insn_state_arm64(struct type_state *state,
 	 * prevent stale type info from propagating to subsequent instructions.
 	 */
 	if (has_reg_type(state, dst->reg1) && !dst->mem_ref &&
+	    strcmp(dl->ins.name, "adrp") &&
 	    strcmp(dl->ins.name, "add") && strcmp(dl->ins.name, "mov") &&
 	    !is_standard_load_insn(dl->ins.name)) {
 		pr_debug_dtp("%s [%x] invalidate reg%d",
@@ -1026,14 +1089,16 @@ static void update_insn_state_arm64(struct type_state *state,
 		return;
 	}
 
-	if (!strcmp(dl->ins.name, "add"))
+	if (!strcmp(dl->ins.name, "adrp"))
+		update_adrp_insn_state(state, dl, dst);
+	else if (!strcmp(dl->ins.name, "add"))
 		update_add_insn_state(state, dloc, dl, src, dst);
 	/* Register to register or imm value to register transfers */
 	else if (!strcmp(dl->ins.name, "mov"))
 		update_mov_insn_state(state, dl, src, dst);
 	/* Memory to register transfers */
 	else if (is_standard_load_insn(dl->ins.name))
-		update_load_insn_state(state, dloc, dl, src, dst);
+		update_load_insn_state(state, dloc, cu_die, dl, src, dst);
 	/* Register to memory transfers */
 	else if (is_standard_store_insn(dl->ins.name))
 		update_store_insn_state(state, dloc, dl, src, dst);
tools/perf/util/annotate-data.c +24 -1
index 9ca59bc2f841..592ad8b1bc96 100644
@@ -70,6 +70,9 @@ void pr_debug_type_name(Dwarf_Die *die, enum type_state_kind kind)
 	case TSR_KIND_CANARY:
 		pr_info(" stack canary\n");
 		return;
+	case TSR_KIND_GLOBAL_ADDR:
+		pr_info(" global address\n");
+		return;
 	case TSR_KIND_TYPE:
 	default:
 		break;
@@ -613,7 +616,8 @@ void set_stack_state(struct type_state_stack *stack, int offset, u8 kind,
 	stack->kind = kind;
 	stack->imm_value = imm_value;
 
-	if (kind == TSR_KIND_POINTER || kind == TSR_KIND_CONST) {
+	if (kind == TSR_KIND_POINTER || kind == TSR_KIND_CONST ||
+	    kind == TSR_KIND_GLOBAL_ADDR) {
 		stack->compound = false;
 		return;
 	}
@@ -1368,6 +1372,25 @@ static enum type_match_result check_matching_type(struct type_state *state,
 		if (offset < 0 && reg != state->stack_reg && reg != dloc->fbreg)
 			goto check_kernel;
 	}
+
+	if (state->regs[reg].kind == TSR_KIND_GLOBAL_ADDR) {
+		u64 var_addr = state->regs[reg].imm_value + offset;
+		int var_offset;
+
+		pr_debug_dtp("global addr");
+
+		/*
+		 * The register holds the address of a global variable. Try to
+		 * find the variable by the address and get its type.
+		 */
+		if (get_global_var_type(cu_die, dloc, dloc->ip, var_addr,
+					&var_offset, type_die)) {
+			dloc->type_offset = var_offset;
+			return PERF_TMR_OK;
+		}
+		/* No need to retry global variables */
+		return PERF_TMR_BAIL_OUT;
+	}
 check_non_register:
 	if (reg == dloc->fbreg || reg == state->stack_reg) {
 		struct type_state_stack *stack;
tools/perf/util/annotate-data.h +1 -0
index a0d7e20ed725..21ae6970cd13 100644
@@ -37,6 +37,7 @@ enum type_state_kind {
 	TSR_KIND_PERCPU_POINTER,
 	TSR_KIND_POINTER,
 	TSR_KIND_CANARY,
+	TSR_KIND_GLOBAL_ADDR,
 };
 
 /**
Signature · 1 line
2.34.1
#54 lore raw

Re: [PATCH v6 24/26] perf annotate-arm64: Support 'adrp' instruction to track global variables

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 15 lines

Extend update_insn_state_arm64() to track global variable types calculated via page-relative addressing.

On arm64, global variables are typically accessed by first calculating the page address using 'adrp', followed by an 'add' or 'ldr' to get the specific symbol address. Without tracking 'adrp', the instruction tracker loses the base address, making it impossible to resolve global symbols and their associated DWARF types.

Introduce TSR_KIND_GLOBAL_ADDR to represent a partial global address state. When encountering 'adrp', store the page-aligned target address in the register's type state. Upon a subsequent 'add' or 'ldr' instruction that references a TSR_KIND_GLOBAL_ADDR register, combine the page address with the immediate offset.

Quoted 6 lines

[ ... ]
chk [70] reg24 offset=0 ok=1 kind=7 global addr : Good! final result: type='struct folio*'

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=24
author
#55 lore raw

PATCH v6 25/26 perf annotate-arm64: Support per-cpu variable access tracking

In reply to Tengda Wu

Extend update_insn_state_arm64() to handle per-cpu variable addressing.

On arm64, per-cpu variables are accessed by adding a per-cpu offset (typically from the '__per_cpu_offset' array) to the address of a global variable or a structure field with '__percpu' modifier (e.g., 's32 __percpu *counters' in struct percpu_counter). This results in instruction patterns like:

  ldr   x0, [x6, x5]   // Pattern A: direct load per-cpu instance
  add   x0, x6, x5     // Pattern B: compute per-cpu address

where x6 holds the per-cpu offset retrieved from:

  adrp  x4, <page>
  add   x4, x4, #offset          // x4 = &__per_cpu_offset
  ldr   x6, [x4, w0, sxtw #3]    // x6 = __per_cpu_offset[cpu]

and x5 is one of the following:

case 1: global variable
adrp x5, <page>
add x5, x5, #offset // x5 = &global_var

case 2: '__percpu' field embedded in a structure

    ldr   x5, [x25, #32]         // x5 = &percpu_field

To handle such cases:

1. Identify per-cpu base initialization: Detect 'adrp + ldr' pairs that resolve to the '__per_cpu_offset' symbol and mark the destination register as TSR_KIND_PERCPU_BASE.
2. Propagate type information: During subsequent 'ldr' or 'add' instructions, if one register is TSR_KIND_PERCPU_BASE, attempt to resolve the type from the other register.

A real-world example is shown below:

ffff8000808f2d28 <cppc_set_perf>:

  ffff8000808f2d38:  adrp  x2, ffff800082033000
  ffff8000808f2d3c:  add   x5, x2, #0x3f8         // x5 = &__per_cpu_offset
  ffff8000808f2d44:  adrp  x2, ffff800081f73000
  ffff8000808f2d48:  add   x2, x2, #0x6b8         // x2 = &cpu_pcc_subspace_idx
  ffff8000808f2d6c:  ldr   x5, [x5, w0, sxtw #3]  // x5 = __per_cpu_offset[cpu]
* ffff8000808f2d80:  ldr   w23, [x5, x2]          // per_cpu_var(cpu_pcc_subspace_idx)

Before this commit, the tracker could not link x5 back to a per-cpu context, resulting in an incorrect data type resolution:

adrp [10] global addr=0xffff800082033000 -> reg2
add [14] global 0x3f8(reg2) -> reg5
adrp [1c] global addr=0xffff800081f73000 -> reg2
add [20] global 0x6b8(reg2) -> reg2
ldr [44] global (reg5, reg0) -> reg5 type='long unsigned int[]' size=0x1000 chk [58] reg5 offset=0 ok=1 kind=1 (long unsigned int[]) : Good! found by insn track: 0(reg5, reg2) type-offset=0
final result: type='long unsigned int' size=0x8

After this commit, the tracker correctly identifies the per-cpu flow and resolves the actual variable type:

ldr [44] global (reg5, reg0) -> reg5 percpu base chk [58] reg5 offset=0 ok=1 kind=2 percpu var : retry chk [58] reg2 offset=0 ok=1 kind=7 global addr : Good! found by insn track: 0(reg5, reg2) type-offset=0 final result: type='int' size=0x4

---
 .../perf/util/annotate-arch/annotate-arm64.c  | 47 ++++++++++++++++++-
 tools/perf/util/annotate-data.c               | 22 ++++++++-
 2 files changed, 67 insertions(+), 2 deletions(-)
tools/perf/util/annotate-arch/annotate-arm64.c +46 -1
index b25a3c70675c..f4b7b0eb44f4 100644
@@ -12,6 +12,7 @@
 #include "../disasm.h"
 #include "../annotate-data.h"
 #include "../debug.h"
+#include "../dso.h"
 #include "../map.h"
 #include "../symbol.h"
 
@@ -643,13 +644,23 @@ static int propagate_load_reg_state(struct type_state *state,
 		u64 ip = dloc->ms->sym->start + dl->al.offset;
 		u64 addr = src_tsr->imm_value + reg_offset;
 		int offset;
+		u8 kind;
+		const char *var_name = NULL;
+
+		/* it might be per-cpu offset */
+		if (dso__kernel(map__dso(dloc->ms->map)) &&
+		    get_global_var_info(dloc, addr, &var_name, &offset) &&
+		    !strcmp(var_name, "__per_cpu_offset"))
+			kind = TSR_KIND_PERCPU_BASE;
+		else
+			kind = TSR_KIND_TYPE;
 
 		if (!get_global_var_type(cu_die, dloc, ip, addr, &offset, &type_die) ||
 		    !die_get_member_type(&type_die, offset, &type_die))
 			return -1;
 
 		tsr->type = type_die;
-		tsr->kind = TSR_KIND_TYPE;
+		tsr->kind = kind;
 		tsr->offset = 0;
 		tsr->imm_value = 0;
 		tsr->ok = true;
@@ -666,6 +677,12 @@ static int propagate_load_reg_state(struct type_state *state,
 	}
 
 load_non_regoff:
+	/*
+	 * If src_tsr->kind is TSR_KIND_PERCPU_BASE, treat it as an array.
+	 * No special handling is needed here; just fall through to retry
+	 * and resolve the type from the second register.
+	 */
+
 	/* Try another register if any */
 	if (src->multi_regs && src->reg1 != src->reg2 && sreg != src->reg2 &&
 	    !(src->extend_type || src->shift_type)) {
@@ -967,6 +984,34 @@ static void update_add_insn_state(struct type_state *state,
 	}
 
 add_non_regoff:
+	if (src_tsr.kind == TSR_KIND_PERCPU_BASE) {
+		int reg2;
+
+		if (!src->multi_regs) {
+			invalidate_reg_state(tsr);
+			return;
+		}
+
+		reg2 = (sreg == src->reg1) ? src->reg2 : src->reg1;
+
+		if (!has_reg_type(state, reg2) || !state->regs[reg2].ok) {
+			invalidate_reg_state(tsr);
+			return;
+		}
+
+		/* Treat percpu as array: inherit type from reg2 */
+		tsr->type = state->regs[reg2].type;
+		tsr->kind = state->regs[reg2].kind;
+		tsr->offset = state->regs[reg2].offset;
+		tsr->imm_value = state->regs[reg2].imm_value;
+		tsr->ok = state->regs[reg2].ok;
+
+		pr_debug_dtp("add [%x] percpu (reg%d, reg%d) -> reg%d",
+			     insn_offset, src->reg1, src->reg2, dreg);
+		pr_debug_type_name(&tsr->type, tsr->kind);
+		return;
+	}
+
 	/* Try another register if any */
 	if (src->multi_regs && src->reg1 != src->reg2 && sreg != src->reg2 &&
 	    !(src->extend_type || src->shift_type)) {
tools/perf/util/annotate-data.c +21 -1
index 592ad8b1bc96..a2434b978ec7 100644
@@ -1342,11 +1342,31 @@ static enum type_match_result check_matching_type(struct type_state *state,
 	}
 
 	if (state->regs[reg].kind == TSR_KIND_PERCPU_BASE) {
-		u64 var_addr = (s64) offset;
+		u64 var_addr;
 		int var_offset;
 
 		pr_debug_dtp("percpu var");
 
+		if (arch__is_arm64(dloc->arch)) {
+			int reg2;
+
+			if (!retry || !dloc->op->multi_regs ||
+			    dloc->op->reg1 == dloc->op->reg2 ||
+			    dloc->op->extend_type || dloc->op->shift_type)
+				return PERF_TMR_BAIL_OUT;
+
+			reg2 = dloc->op->reg2;
+			if (!has_reg_type(state, reg2) || !state->regs[reg2].ok)
+				return PERF_TMR_NO_TYPE;
+
+			pr_debug_dtp(" : retry\n");
+			retry = false;
+			reg = reg2;
+			goto again;
+		}
+
+		var_addr = (s64) offset;
+
 		if (dloc->op->multi_regs) {
 			int reg2 = dloc->op->reg2;
 
Signature · 1 line
2.34.1
#56 lore raw

Re: [PATCH v6 25/26] perf annotate-arm64: Support per-cpu variable access tracking

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] perf: retry mechanism fails for per-cpu variable loads --

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] perf: retry mechanism fails for per-cpu variable loads
--

commit 0a8000b94df79930832d6fd4535fbe42f682b339
Author: Tengda Wu <[email protected]>

perf annotate-arm64: Support per-cpu variable access tracking

This commit extends update_insn_state_arm64() to handle per-cpu variable addressing. It detects pairs resolving to the '__per_cpu_offset' symbol and marks the destination register as TSR_KIND_PERCPU_BASE to propagate type information during subsequent instructions.

Quoted 4 lines
tools/perf/util/annotate-arch/annotate-arm64.c +0 -0
index b25a3c70675c1..f4b7b0eb44f42 100644

[ ... ]

tools/perf/util/annotate-arch/annotate-arm64.c static int propagate_load_reg_state(struct type_state *state, · line 688

Quoted 13 lines
tools/perf/util/annotate-arch/annotate-arm64.c +6 -0
@@ -666,6 +677,12 @@ static int propagate_load_reg_state(struct type_state *state,
 	}
 
 load_non_regoff:
+	/*
+	 * If src_tsr->kind is TSR_KIND_PERCPU_BASE, treat it as an array.
+	 * No special handling is needed here; just fall through to retry
+	 * and resolve the type from the second register.
+	 */
+
 	/* Try another register if any */
 	if (src->multi_regs && src->reg1 != src->reg2 && sreg != src->reg2 &&
 	    !(src->extend_type || src->shift_type)) {

[Severity: Medium]
Will this retry logic actually succeed for per-cpu variable loads?

Since both registers in the per-cpu load are non-constant (TSR_KIND_PERCPU_BASE and TSR_KIND_GLOBAL_ADDR), arch_get_reg_offset() returns -1 for both the initial attempt and the retry pass because it requires the 'other' register in a multi-register operand to be TSR_KIND_CONST.

Does this break the intended fallback logic here in propagate_load_reg_state(), resulting in an unhandled -1 return and silent type propagation failure?

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=25
author
#57 lore raw

Re: [PATCH v6 25/26] perf annotate-arm64: Support per-cpu variable access tracking

Quoted 42 lines · On 2026/9/16 9:53, [email protected] wrote:

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] perf: retry mechanism fails for per-cpu variable loads
--

commit 0a8000b94df79930832d6fd4535fbe42f682b339
Author: Tengda Wu <[email protected]>

perf annotate-arm64: Support per-cpu variable access tracking

This commit extends update_insn_state_arm64() to handle per-cpu variable addressing. It detects pairs resolving to the '__per_cpu_offset' symbol and marks the destination register as TSR_KIND_PERCPU_BASE to propagate type information during subsequent instructions.

Quoted 4 lines
tools/perf/util/annotate-arch/annotate-arm64.c +0 -0
index b25a3c70675c1..f4b7b0eb44f42 100644

[ ... ]

tools/perf/util/annotate-arch/annotate-arm64.c static int propagate_load_reg_state(struct type_state *state, · line 688

Quoted 13 lines
tools/perf/util/annotate-arch/annotate-arm64.c +6 -0
@@ -666,6 +677,12 @@ static int propagate_load_reg_state(struct type_state *state,
 	}
 
 load_non_regoff:
+	/*
+	 * If src_tsr->kind is TSR_KIND_PERCPU_BASE, treat it as an array.
+	 * No special handling is needed here; just fall through to retry
+	 * and resolve the type from the second register.
+	 */
+
 	/* Try another register if any */
 	if (src->multi_regs && src->reg1 != src->reg2 && sreg != src->reg2 &&
 	    !(src->extend_type || src->shift_type)) {

[Severity: Medium]
Will this retry logic actually succeed for per-cpu variable loads?

Since both registers in the per-cpu load are non-constant (TSR_KIND_PERCPU_BASE and TSR_KIND_GLOBAL_ADDR), arch_get_reg_offset() returns -1 for both the initial attempt and the retry pass because it requires the 'other' register in a multi-register operand to be TSR_KIND_CONST.

Quoted 4 lines

Does this break the intended fallback logic here in propagate_load_reg_state(), resulting in an unhandled -1 return and silent type propagation failure?

It looks like arch_get_reg_offset needs to be extended to recognize when reg2 is TSR_KIND_PERCPU_BASE and set the offset to 0.

Thanks,
Tengda

author
#58 lore raw

Re: [PATCH v6 25/26] perf annotate-arm64: Support per-cpu variable access tracking

Quoted 45 lines · Tengda Wu
Quoted 42 lines · On 2026/9/16 9:53, [email protected] wrote:

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] perf: retry mechanism fails for per-cpu variable loads
--

commit 0a8000b94df79930832d6fd4535fbe42f682b339
Author: Tengda Wu <[email protected]>

perf annotate-arm64: Support per-cpu variable access tracking

This commit extends update_insn_state_arm64() to handle per-cpu variable addressing. It detects pairs resolving to the '__per_cpu_offset' symbol and marks the destination register as TSR_KIND_PERCPU_BASE to propagate type information during subsequent instructions.

Quoted 4 lines
tools/perf/util/annotate-arch/annotate-arm64.c +0 -0
index b25a3c70675c1..f4b7b0eb44f42 100644

[ ... ]

tools/perf/util/annotate-arch/annotate-arm64.c static int propagate_load_reg_state(struct type_state *state, · line 688

Quoted 13 lines
tools/perf/util/annotate-arch/annotate-arm64.c +6 -0
@@ -666,6 +677,12 @@ static int propagate_load_reg_state(struct type_state *state,
 	}
 
 load_non_regoff:
+	/*
+	 * If src_tsr->kind is TSR_KIND_PERCPU_BASE, treat it as an array.
+	 * No special handling is needed here; just fall through to retry
+	 * and resolve the type from the second register.
+	 */
+
 	/* Try another register if any */
 	if (src->multi_regs && src->reg1 != src->reg2 && sreg != src->reg2 &&
 	    !(src->extend_type || src->shift_type)) {

[Severity: Medium]
Will this retry logic actually succeed for per-cpu variable loads?

Since both registers in the per-cpu load are non-constant (TSR_KIND_PERCPU_BASE and TSR_KIND_GLOBAL_ADDR), arch_get_reg_offset() returns -1 for both the initial attempt and the retry pass because it requires the 'other' register in a multi-register operand to be TSR_KIND_CONST.

Quoted 8 lines
Quoted 4 lines

Does this break the intended fallback logic here in propagate_load_reg_state(), resulting in an unhandled -1 return and silent type propagation failure?

It looks like arch_get_reg_offset needs to be extended to recognize when reg2 is TSR_KIND_PERCPU_BASE and set the offset to 0.

I might change it to something like this:

tools/perf/util/annotate-data.c +9 -5
index cfdebd50fb0d..ab86037aa527 100644
@@ -1188,13 +1188,17 @@ int arch_get_reg_offset(const struct arch *arch, struct annotated_op_loc *op_loc
 
 		/*
 		 * For register-based addressing without shift/extension,
-		 * reg2 may be a constant offset. Use its value as the offset
-		 * from the base register.
+		 * derive the offset from the type of reg2.
 		 */
 		reg2 = op_loc->reg1 == breg ? op_loc->reg2 : op_loc->reg1;
-		if (has_reg_type(state, reg2) && state->regs[reg2].ok &&
-			state->regs[reg2].kind == TSR_KIND_CONST) {
-			*offset = (s64)state->regs[reg2].imm_value;
+		if (has_reg_type(state, reg2) && state->regs[reg2].ok) {
+			if (state->regs[reg2].kind == TSR_KIND_CONST)
+				*offset = (s64)state->regs[reg2].imm_value;
+			else if (state->regs[reg2].kind == TSR_KIND_PERCPU_BASE)
+				*offset = 0;  /* it does not contribute to the offset */
+			else
+				return -1;
+
 			return 0;
 		}
 

Thanks,
Tengda

author
#59 lore raw

Re: [PATCH v6 25/26] perf annotate-arm64: Support per-cpu variable access tracking

In reply to Tengda Wu
Quoted 123 lines · Tengda Wu

Extend update_insn_state_arm64() to handle per-cpu variable addressing.

On arm64, per-cpu variables are accessed by adding a per-cpu offset (typically from the '__per_cpu_offset' array) to the address of a global variable or a structure field with '__percpu' modifier (e.g., 's32 __percpu *counters' in struct percpu_counter). This results in instruction patterns like:

  ldr   x0, [x6, x5]   // Pattern A: direct load per-cpu instance
  add   x0, x6, x5     // Pattern B: compute per-cpu address

where x6 holds the per-cpu offset retrieved from:

  adrp  x4, <page>
  add   x4, x4, #offset          // x4 = &__per_cpu_offset
  ldr   x6, [x4, w0, sxtw #3]    // x6 = __per_cpu_offset[cpu]

and x5 is one of the following:

case 1: global variable
adrp x5, <page>
add x5, x5, #offset // x5 = &global_var

case 2: '__percpu' field embedded in a structure

    ldr   x5, [x25, #32]         // x5 = &percpu_field

To handle such cases:

1. Identify per-cpu base initialization: Detect 'adrp + ldr' pairs that resolve to the '__per_cpu_offset' symbol and mark the destination register as TSR_KIND_PERCPU_BASE.
2. Propagate type information: During subsequent 'ldr' or 'add' instructions, if one register is TSR_KIND_PERCPU_BASE, attempt to resolve the type from the other register.

A real-world example is shown below:

ffff8000808f2d28 <cppc_set_perf>:

  ffff8000808f2d38:  adrp  x2, ffff800082033000
  ffff8000808f2d3c:  add   x5, x2, #0x3f8         // x5 = &__per_cpu_offset
  ffff8000808f2d44:  adrp  x2, ffff800081f73000
  ffff8000808f2d48:  add   x2, x2, #0x6b8         // x2 = &cpu_pcc_subspace_idx
  ffff8000808f2d6c:  ldr   x5, [x5, w0, sxtw #3]  // x5 = __per_cpu_offset[cpu]
* ffff8000808f2d80:  ldr   w23, [x5, x2]          // per_cpu_var(cpu_pcc_subspace_idx)

Before this commit, the tracker could not link x5 back to a per-cpu context, resulting in an incorrect data type resolution:

adrp [10] global addr=0xffff800082033000 -> reg2
add [14] global 0x3f8(reg2) -> reg5
adrp [1c] global addr=0xffff800081f73000 -> reg2
add [20] global 0x6b8(reg2) -> reg2
ldr [44] global (reg5, reg0) -> reg5 type='long unsigned int[]' size=0x1000 chk [58] reg5 offset=0 ok=1 kind=1 (long unsigned int[]) : Good! found by insn track: 0(reg5, reg2) type-offset=0
final result: type='long unsigned int' size=0x8

After this commit, the tracker correctly identifies the per-cpu flow and resolves the actual variable type:

ldr [44] global (reg5, reg0) -> reg5 percpu base chk [58] reg5 offset=0 ok=1 kind=2 percpu var : retry chk [58] reg2 offset=0 ok=1 kind=7 global addr : Good! found by insn track: 0(reg5, reg2) type-offset=0 final result: type='int' size=0x4

---
 .../perf/util/annotate-arch/annotate-arm64.c  | 47 ++++++++++++++++++-
 tools/perf/util/annotate-data.c               | 22 ++++++++-
 2 files changed, 67 insertions(+), 2 deletions(-)
tools/perf/util/annotate-arch/annotate-arm64.c +18 -1
index b25a3c70675c..f4b7b0eb44f4 100644
@@ -12,6 +12,7 @@
 #include "../disasm.h"
 #include "../annotate-data.h"
 #include "../debug.h"
+#include "../dso.h"
 #include "../map.h"
 #include "../symbol.h"
 
@@ -643,13 +644,23 @@ static int propagate_load_reg_state(struct type_state *state,
 		u64 ip = dloc->ms->sym->start + dl->al.offset;
 		u64 addr = src_tsr->imm_value + reg_offset;
 		int offset;
+		u8 kind;
+		const char *var_name = NULL;
+
+		/* it might be per-cpu offset */
+		if (dso__kernel(map__dso(dloc->ms->map)) &&
+		    get_global_var_info(dloc, addr, &var_name, &offset) &&
+		    !strcmp(var_name, "__per_cpu_offset"))
+			kind = TSR_KIND_PERCPU_BASE;
+		else
+			kind = TSR_KIND_TYPE;
 
 		if (!get_global_var_type(cu_die, dloc, ip, addr, &offset, &type_die) ||
 		    !die_get_member_type(&type_die, offset, &type_die))
 			return -1;
 
 		tsr->type = type_die;
-		tsr->kind = TSR_KIND_TYPE;
+		tsr->kind = kind;
 		tsr->offset = 0;
 		tsr->imm_value = 0;
 		tsr->ok = true;
@@ -666,6 +677,12 @@ static int propagate_load_reg_state(struct type_state *state,
 	}
 
 load_non_regoff:
+	/*
+	 * If src_tsr->kind is TSR_KIND_PERCPU_BASE, treat it as an array.
+	 * No special handling is needed here; just fall through to retry
+	 * and resolve the type from the second register.
+	 */
+
 	/* Try another register if any */
 	if (src->multi_regs && src->reg1 != src->reg2 && sreg != src->reg2 &&
 	    !(src->extend_type || src->shift_type)) {

tools/perf/util/annotate-arch/annotate-arm64.c static void update_add_insn_state(struct type_state *state, · line 987

Quoted 4 lines
tools/perf/util/annotate-arch/annotate-arm64.c +1 -0
@@ -967,6 +984,34 @@ static void update_add_insn_state(struct type_state *state,
 	}
 
 add_non_regoff:
+	if (src_tsr.kind == TSR_KIND_PERCPU_BASE) {

After fixing the issue mentioned by Sashiko (by adding recognition and handling in arch_get_reg_offset for when reg2 is TSR_KIND_PERCPU_BASE [1]), this branch no longer really needs explicit handling -- it can simply fall back to swapping the registers and retrying (just like the handling for store). I plan to remove it in v7.

[1] https://lore.kernel.org/all/[email protected]/

Thanks,
Tengda

Quoted 67 lines
tools/perf/util/annotate-arch/annotate-arm64.c +27 -0
+		int reg2;
+
+		if (!src->multi_regs) {
+			invalidate_reg_state(tsr);
+			return;
+		}
+
+		reg2 = (sreg == src->reg1) ? src->reg2 : src->reg1;
+
+		if (!has_reg_type(state, reg2) || !state->regs[reg2].ok) {
+			invalidate_reg_state(tsr);
+			return;
+		}
+
+		/* Treat percpu as array: inherit type from reg2 */
+		tsr->type = state->regs[reg2].type;
+		tsr->kind = state->regs[reg2].kind;
+		tsr->offset = state->regs[reg2].offset;
+		tsr->imm_value = state->regs[reg2].imm_value;
+		tsr->ok = state->regs[reg2].ok;
+
+		pr_debug_dtp("add [%x] percpu (reg%d, reg%d) -> reg%d",
+			     insn_offset, src->reg1, src->reg2, dreg);
+		pr_debug_type_name(&tsr->type, tsr->kind);
+		return;
+	}
+
 	/* Try another register if any */
 	if (src->multi_regs && src->reg1 != src->reg2 && sreg != src->reg2 &&
 	    !(src->extend_type || src->shift_type)) {
tools/perf/util/annotate-data.c +21 -1
index 592ad8b1bc96..a2434b978ec7 100644
@@ -1342,11 +1342,31 @@ static enum type_match_result check_matching_type(struct type_state *state,
 	}
 
 	if (state->regs[reg].kind == TSR_KIND_PERCPU_BASE) {
-		u64 var_addr = (s64) offset;
+		u64 var_addr;
 		int var_offset;
 
 		pr_debug_dtp("percpu var");
 
+		if (arch__is_arm64(dloc->arch)) {
+			int reg2;
+
+			if (!retry || !dloc->op->multi_regs ||
+			    dloc->op->reg1 == dloc->op->reg2 ||
+			    dloc->op->extend_type || dloc->op->shift_type)
+				return PERF_TMR_BAIL_OUT;
+
+			reg2 = dloc->op->reg2;
+			if (!has_reg_type(state, reg2) || !state->regs[reg2].ok)
+				return PERF_TMR_NO_TYPE;
+
+			pr_debug_dtp(" : retry\n");
+			retry = false;
+			reg = reg2;
+			goto again;
+		}
+
+		var_addr = (s64) offset;
+
 		if (dloc->op->multi_regs) {
 			int reg2 = dloc->op->reg2;
 
author
#60 lore raw

PATCH v6 26/26 perf annotate-arm64: Support 'mrs' instruction to track 'current' pointer

In reply to Tengda Wu

Extend update_insn_state_arm64() to handle the 'mrs' instruction, enabling the tracking of the 'current' task pointer in the kernel.

On arm64, the kernel uses the 'sp_el0' system register to store the address of the currently executing 'struct task_struct'. This is typically accessed via the 'get_current()' inline function, resulting in the instruction 'mrs xN, sp_el0'.

To resolve the data type of the target register, first verify the access is to 'sp_el0' within a kernel DSO. Then, locate the 'get_current()' inline function's DWARF Die at the current PC and extract its return type (which should be 'struct task_struct*').

A real-world example is shown below:

ffff8000800deee8 <kthread_blkcg>:

  ffff8000800deef0:  mrs  x0, sp_el0    // x0 = current
* ffff8000800deef4:  ldr  w1, [x0, #44]

Before this commit, the type flow starts with no information:

chk [c] reg0 offset=0x2c ok=0 kind=0 cfa : no type information final result: no type information

After this commit, the tracker identifies the 'current' pointer from the system register:

mrs [8] sp_el0 -> reg0 type='struct task_struct*'
chk [c] reg0 offset=0x2c ok=1 kind=1 (struct task_struct*) : Good! found by insn track: 0x2c(reg0) type-offset=0x2c
final result: type='struct task_struct'

---
 .../perf/util/annotate-arch/annotate-arm64.c  | 73 ++++++++++++++++++-
 1 file changed, 71 insertions(+), 2 deletions(-)
tools/perf/util/annotate-arch/annotate-arm64.c +71 -2
index f4b7b0eb44f4..a4e4a38310cc 100644
@@ -4,6 +4,7 @@
 #include <inttypes.h>
 #include <stdlib.h>
 #include <string.h>
+#include <strings.h>
 #include <linux/ctype.h>
 #include <linux/string.h>
 #include <linux/zalloc.h>
@@ -15,6 +16,7 @@
 #include "../dso.h"
 #include "../map.h"
 #include "../symbol.h"
+#include "../strbuf.h"
 
 struct arch_arm64 {
 	struct arch arch;
@@ -1054,6 +1056,71 @@ static void update_adrp_insn_state(struct type_state *state,
 		     insn_offset, tsr->imm_value, dreg);
 }
 
+static void update_mrs_insn_state(struct type_state *state,
+				  struct data_loc_info *dloc, Dwarf_Die *cu_die,
+				  struct disasm_line *dl,
+				  struct annotated_op_loc *dst)
+{
+	struct type_state_reg *tsr;
+	Dwarf_Die type_die;
+	u32 insn_offset = dl->al.offset;
+	int dreg = dst->reg1;
+	Dwarf_Die func_die;
+	Dwarf_Attribute attr;
+	u64 ip, pc;
+	struct strbuf sb;
+	char *type_name;
+
+	if (!has_reg_type(state, dreg))
+		return;
+
+	tsr = &state->regs[dreg];
+	tsr->copied_from = -1;
+
+	/* Handle case difference: LLVM (SP_EL0) vs objdump (sp_el0) */
+	if (!dso__kernel(map__dso(dloc->ms->map)) ||
+	    strcasecmp(dl->ops.source.raw, "sp_el0")) {
+		invalidate_reg_state(tsr);
+		return;
+	}
+
+	ip = dloc->ms->sym->start + dl->al.offset;
+	pc = map__rip_2objdump(dloc->ms->map, ip);
+
+	/*
+	 * Find the inline function 'get_current()' Dwarf_Die and obtain its
+	 * return value data type, which should be 'struct task_struct*'.
+	 */
+	if (!die_find_inlinefunc(cu_die, pc, &func_die) ||
+	    !die_compare_name(&func_die, "get_current") ||
+	    !dwarf_attr_integrate(&func_die, DW_AT_type, &attr) ||
+	    !dwarf_formref_die(&attr, &type_die)) {
+		invalidate_reg_state(tsr);
+		return;
+	}
+
+	strbuf_init(&sb, 32);
+	die_get_typename_from_type(&type_die, &sb);
+	type_name = strbuf_detach(&sb, NULL);
+
+	if (!type_name || strcmp(type_name, "struct task_struct*")) {
+		invalidate_reg_state(tsr);
+		goto out_free;
+	}
+
+	tsr->type = type_die;
+	tsr->kind = TSR_KIND_TYPE;
+	tsr->offset = 0;
+	tsr->imm_value = 0;
+	tsr->ok = true;
+
+	pr_debug_dtp("mrs [%x] sp_el0 -> reg%d", insn_offset, dreg);
+	pr_debug_type_name(&type_die, tsr->kind);
+
+out_free:
+	free(type_name);
+}
+
 static void update_insn_state_arm64(struct type_state *state,
 				    struct data_loc_info *dloc, Dwarf_Die *cu_die,
 				    struct disasm_line *dl)
@@ -1120,7 +1187,7 @@ static void update_insn_state_arm64(struct type_state *state,
 	 * prevent stale type info from propagating to subsequent instructions.
 	 */
 	if (has_reg_type(state, dst->reg1) && !dst->mem_ref &&
-	    strcmp(dl->ins.name, "adrp") &&
+	    strcmp(dl->ins.name, "mrs") && strcmp(dl->ins.name, "adrp") &&
 	    strcmp(dl->ins.name, "add") && strcmp(dl->ins.name, "mov") &&
 	    !is_standard_load_insn(dl->ins.name)) {
 		pr_debug_dtp("%s [%x] invalidate reg%d",
@@ -1134,7 +1201,9 @@ static void update_insn_state_arm64(struct type_state *state,
 		return;
 	}
 
-	if (!strcmp(dl->ins.name, "adrp"))
+	if (!strcmp(dl->ins.name, "mrs"))
+		update_mrs_insn_state(state, dloc, cu_die, dl, dst);
+	else if (!strcmp(dl->ins.name, "adrp"))
 		update_adrp_insn_state(state, dl, dst);
 	else if (!strcmp(dl->ins.name, "add"))
 		update_add_insn_state(state, dloc, dl, src, dst);
Signature · 1 line
2.34.1
#61 lore raw

Re: [PATCH v6 26/26] perf annotate-arm64: Support 'mrs' instruction to track 'current' pointer

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 13 lines

Extend update_insn_state_arm64() to handle the 'mrs' instruction, enabling the tracking of the 'current' task pointer in the kernel.

On arm64, the kernel uses the 'sp_el0' system register to store the address of the currently executing 'struct task_struct'. This is typically accessed via the 'get_current()' inline function, resulting in the instruction 'mrs xN, sp_el0'.

To resolve the data type of the target register, first verify the access is to 'sp_el0' within a kernel DSO. Then, locate the 'get_current()' inline function's DWARF Die at the current PC and extract its return type (which should be 'struct task_struct*').

Quoted 8 lines

A real-world example is shown below:

[ ... ]
found by insn track: 0x2c(reg0) type-offset=0x2c final result: type='struct task_struct'

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=26
reviewer Reviewed-by on the v6 series
#62 lore raw
In reply to Tengda Wu
Quoted 227 lines · Tengda Wu

This patch series implements data type profiling support for arm64, enabling 'perf annotate --data-type' to resolve memory locations and variable types on arm64 platforms.

The main changes since v5 include:
v5: https://lore.kernel.org/all/[email protected]/

* __get_dwarf_regnum_arm64 optimization: compute directly based on character judgment, avoiding strtol calls. (Ian Rogers)

* extract_op_location_arm64 issue fix: support parsing instructions such as 'cmp x0, x1, lsl #3'.

* Instruction tracking part, fixing issues pointed out by Sashiko, including:
- adding TSR_KIND_POINTER handling for ldr.
- premature returns in add and ldr causing percpu handling to be skipped.
- adding a reg != -1 check for stack type propagation, etc.

Patch organization
==================

The series is organized as follows:

1. Fix disassembly mismatches (Patches 01-02)
Current perf annotate supports three disassembly backends: llvm, capstone, and objdump. On arm64, inconsistencies between the output of these backends (specifically llvm/capstone vs. objdump) often prevent the tracker from correctly identifying registers and offsets. These patches resolve these mismatches, ensuring consistent instruction parsing across all supported backends.

2. Infrastructure for arm64 operand parsing (Patches 03-09) These patches establish the necessary infrastructure for arm64-specific operand handling. This includes implementing new callbacks and data structures to manage arm64's unique addressing modes and register sets. This foundation is essential for the subsequent type-tracking logic.

3. ARM SPE event handling (Patches 10-11)
Patch 10 automatically deduplicates overlapping ARM SPE events (e.g., l1d-miss, tlb-access) in 'perf annotate' by retaining only the "instructions" event when data type profiling is enabled. Patch 11 defaults the synthesized event period to 1 for ARM SPE to fix zero 'Percent' values in annotate output.

4. Core instruction tracking (Patches 12-26)
These patches implement the core logic for type tracking on arm64, covering several key types of instructions, including:
* Memory Access: ldr/str variants (including stack-based access).
* Arithmetic & Data Processing: mov, add, and adrp.
* Special Access: System register access (mrs) and per-cpu variable tracking.

The implementation draws inspiration from the existing x86 logic while adapting it to the nuances of the AArch64 ISA [2][3]. With these changes, perf annotate can successfully resolve memory locations and register types, providing basic support for data type profiling on arm64 platforms.

Example Result
==============

# perf mem record -a -K -- sleep 1
# perf annotate --data-type --stdio --type-stat
Annotate data type stats:
total 1138, ok 852 (74.9%), bad 286 (25.1%)
-----------------------------------------------------------
         6 : no_sym
        42 : no_var
       230 : no_typeinfo
         8 : bad_offset
       213 : insn_track

Annotate type: 'struct page' in [kernel.kallsyms] (66948 samples):
============================================================================

 Percent     offset       size  field
  100.00          0       0x40  struct page      {
    9.01          0        0x8      long unsigned int   flags;
   57.99        0x8       0x28      union        {
   57.99        0x8       0x28          struct   {
   33.00        0x8       0x10              union        {
   33.00        0x8       0x10                  struct list_head        lru {
   33.00        0x8        0x8                      struct list_head*   next;
    0.00       0x10        0x8                      struct list_head*   prev;
                                                };
   33.00        0x8       0x10                  struct   {
   33.00        0x8        0x8                      void*       __filler;
    0.00       0x10        0x4                      unsigned int        mlock_count;
   ...

Each patch's type profiling results are as follows:

Patch | Feature | no_sym | no_var | no_typeinfo | bad_offset | insn_track | ok(%)
------+----------------------------+--------+--------+-------------+------------+------------+------

0011  | base (default spe period)  | 6      | 493    | -           | -          | -          | 56.2%
0013  | enable insn tracking       | 6      | 42     | 437         | 2          | 12         | 57.2%
0016  | support 'load' insn        | 6      | 42     | 398         | 1          | 52         | 60.7%
0017  | support 'store' insn       | 6      | 42     | 398         | 1          | 52         | 60.7%
0021  | support stack variable     | 6      | 42     | 391         | 1          | 59         | 61.3%
0022  | support 'mov' insn         | 6      | 42     | 372         | 3          | 76         | 62.8%
0023  | support 'add' insn         | 6      | 42     | 318         | 7          | 126        | 67.2%
0024  | support 'adrp' insn        | 6      | 42     | 238         | 8          | 205        | 74.2%
0025  | support per-cpu variable   | 6      | 42     | 233         | 8          | 210        | 74.6%
0026  | support 'mrs' insn         | 6      | 42     | 230         | 8          | 213        | 74.9%

Limitations
===========

* SIMD/FP & SVE Vector Support:
Data type profiling currently focuses on General-Purpose (GP) register operations. Vector/SIMD registers (v0-v31, d0-d31, q0-q31) and Scalable Vector Extension (SVE/SME) instructions are not tracked yet.

* Regular Instructions Only:
Only regular instructions are currently supported. Instructions following special rules or non-standard patterns are not supported yet. For example, load/store tracking only matches standard variants (ldr/ldur/ldar/ldp and str/stur/stlr/stp) with straightforward semantics; exclusive loads/stores (ldxr, stxr, etc.) and acquire/exclusive combination variants are excluded.

* Compiler Prologue/Epilogue Code:
As shown by the previous test results, approximately 14% of the failed type profiling results originate from compiler-generated prologue or epilogue code (e.g., ldp x19, x20, [sp, #16]). These instructions manage callee-saved registers across function boundaries, propagating type context through these operations requires inter-procedural (cross-function) instruction analysis, which is currently unsupported by the local backward instruction tracker.

Testing
=======

Tested on arm64 (all passed):

  # perf test -v "perf data type profiling tests"
  81: perf data type profiling tests                                                                  : Ok

=== Test Summary ===
Passed main tests : 1

  Passed subtests   : 0
  Skipped tests     : 0
  Failed tests      : 0

Tested on x86. The profiling results show no change before/after applying this patch series:

before : total 880, ok 711 (80.8%), bad 169 (19.2%) after : total 880, ok 711 (80.8%), bad 169 (19.2%)

Changelog
=========
v5 -> v6:
- v5: https://lore.kernel.org/all/[email protected]/
- Fix a potential UAF issue with capstone sym_name.
- Optimize __get_dwarf_regnum_arm64 to compute directly based on character judgment, avoiding strtol calls. (Ian Rogers)
- Fix an issue in extract_op_location_arm64 when parsing instructions such as

    'cmp x0, x1, lsl #3'.

- Fix several minor issues in instruction tracking. (Sashiko)
v4 -> v5:
- v4: https://lore.kernel.org/all/[email protected]/
- Introduce arch_get_reg_offset() to uniformly handle reg offset.
- Add support for parsing extension type and shift amount in arm64 instructions.
- Refine which instructions to track or skip. (Shuai Xue)
- Rename dont_overlap to default_single_event_per_ip and use it in itrace_synth_opts__set_default(). (Adrian Hunter)
- Introduce delete_stack_state() to clean up obsolete stack state. (Shuai Xue)
- Normalize arch__dwarf_regnum() error return values. (Shuai Xue)
- Drop canary support due to unresolved bugs.
- Fix various minor issues, such as name memory leaks and header includes.
v3 -> v4:
- v3: https://lore.kernel.org/all/[email protected]/
- Fix Capstone compilation failure.
- Stop adding new pcrel_adrp_addr in LLVM; reuse pcrel_load_addr instead.
- Fix parsing issue in arm64_mov__parse.
- Add PC-relative load instruction parsing logic to arm64_ldst__parse, and introduce rstrip_space_and_comment to strip comments.
- Remove wzr/xzr register parsing (not planning to handle this yet).
- Add post-index addressing mode parsing for the '[base], reg' format.
- Update built-in implementation of --itrace=i1i to deduplicate early during arm_spe_process_auxtrace_info.
- Restrict the "default period to 1" behavior to ARM SPE, instead of applying it to all architectures.
- Add register type tracking for function call instructions.
- Add dual-register type tracking for load pair and store pair instructions.
- Correct stack variable offset calculations.
- Add type invalidation upon retry failure.
- Reuse imm_value instead of introducing addr for 'adrp' instruction tracking.
- Fix potential stale type resolution errors caused by TSR_KIND_GLOBAL_ADDR and TSR_KIND_CONST during stack passing.
- Fix a strbuf memory leak during 'mrs' instruction tracking.
- Fix stale dieoff issue when debug info changes.
- Simplify add type propagation: only propagate offset/imm updates, leave type parsing to chk.
v2 -> v3:
- v2: https://lore.kernel.org/all/[email protected]/
- Instead of always parsing the left operand as src and the right operand as dst, set them based on the actual instruction definition. (Namhyung Kim)
- Fix refcount leak in print_capstone_detail().
- Remove useless '<' check when parsing 'addr <symbol>' in arm64_mov__parse().
- Add example comments in arm64_ldst__parse().
- Split arch__dwarf_regnum() changes into a separate commit.
- Rename annotated_addr_mode enum: INSN_ADDR_* -> PERF_ADDR_MODE_*.
- Set caller-saved registers in init_type_state().
- For instructions with addressing mode, always goto adjust_reg_index_state() at the end to update the src register state.
- Handle TSR_KIND_CONST registers for 'mov' and 'add' instructions.
- Invalidate dst register for all other unsupported instructions.
- Verify type DIE is task_struct pointer before caching globally.
- Enable --itrace=i1i by default for ARM SPE data type profiling in 'perf annotate' to avoid overlapping event counting for the same instruction. (James Clark)
- Fix global variable type resolving error in check_matching_type(). (James Clark)
- Address review comments from sashiko [1]:
- Fix unconditional call to arch->extract_op_location()
- Handle multi_regs correctly
- Fix invalid register state in error path
- Other misc fixes
v1 -> v2:
- v1: https://lore.kernel.org/all/[email protected]/
- Fix inconsistencies in arm64 instruction output across llvm, capstone, and objdump disassembly backends.
- Support arm64-specific addressing modes and operand formats. (Leo Yan)
- Extend instruction tracking to support mov and add instructions, along with per-cpu and stack variables.
- Include real-world examples in commit messages to demonstrate practical effects. (Namhyung Kim)
- Improve type-tracking success rate (type stat) from 64.2% to 82.1%.

Quoted 1 line

Please let me know if you have any feedback.

There's a sashiko finding on patch 21 that looks like it should be addressed. For the series:

Thanks,
Ian

Quoted 70 lines

Thanks,
Tengda

[1] https://sashiko.dev/#/patchset/20260403094800.1418825-1-wutengda%40huaweicloud.com
[2] https://developer.arm.com/documentation/102374/0103
[3] https://github.com/flynd/asmsheets/releases/tag/v8

Tengda Wu (26):
perf capstone: Symbolize address operands to match objdump on arm64 perf llvm: Fix arm64 adrp instruction disassembly mismatch with
objdump
perf annotate-arm64: Generalize arm64_mov__parse to support more
instructions
perf annotate-arm64: Handle load and store instructions perf annotate: Normalize arch__dwarf_regnum() error return values perf annotate: Introduce extract_op_location callback for
arch-specific parsing
perf dwarf-regs: Adapt get_dwarf_regnum() for arm64
perf annotate: Adapt arch__dwarf_regnum() for arm64
perf annotate-arm64: Implement extract_op_location() callback perf annotate: Default to --itrace=i1i for data type profiling perf arm-spe: Set default synthesized event period to 1 perf annotate-data: Extract invalidate_reg_state() as a common helper perf annotate-arm64: Enable instruction tracking support perf annotate-data: Add arch_get_reg_offset helper
perf annotate-arm64: Track return type after call instructions perf annotate-arm64: Support load instruction tracking perf annotate-arm64: Support store instruction tracking perf annotate-data: Expand type_state_reg imm_value to u64 perf annotate-data: Track imm_value for stack variables perf annotate-x86: Delete stale stack state on store of untracked
register
perf annotate-arm64: Support stack variable tracking
perf annotate-arm64: Support 'mov' instruction tracking perf annotate-arm64: Support 'add' instruction tracking perf annotate-arm64: Support 'adrp' instruction to track global
variables
perf annotate-arm64: Support per-cpu variable access tracking perf annotate-arm64: Support 'mrs' instruction to track 'current'
pointer

 tools/perf/builtin-annotate.c                 |    8 +
 .../perf/util/annotate-arch/annotate-arm64.c  | 1211 ++++++++++++++++-
 .../util/annotate-arch/annotate-powerpc.c     |    9 +
 tools/perf/util/annotate-arch/annotate-x86.c  |  117 +-
 tools/perf/util/annotate-data.c               |  205 ++-
 tools/perf/util/annotate-data.h               |   14 +-
 tools/perf/util/annotate.c                    |  108 +-
 tools/perf/util/annotate.h                    |   63 +
 tools/perf/util/arm-spe.c                     |   16 +-
 tools/perf/util/auxtrace.c                    |   12 +-
 tools/perf/util/auxtrace.h                    |    7 +-
 tools/perf/util/capstone.c                    |  186 ++-
 tools/perf/util/cs-etm.c                      |    2 +-
 tools/perf/util/disasm.c                      |    5 +
 tools/perf/util/disasm.h                      |    5 +
 .../util/dwarf-regs-arch/dwarf-regs-arm64.c   |   25 +
 tools/perf/util/dwarf-regs.c                  |    2 +-
 tools/perf/util/include/dwarf-regs.h          |    1 +
 tools/perf/util/intel-bts.c                   |    2 +-
 tools/perf/util/intel-pt.c                    |    3 +-
 tools/perf/util/llvm.c                        |   52 +-
 21 files changed, 1831 insertions(+), 222 deletions(-)

base-commit: e6e35979777d646fe3c7c94dca7dd32fb25d45f4 --
2.34.1

author
#63 lore raw
Quoted 228 lines · Ian Rogers
Quoted 227 lines · Tengda Wu

This patch series implements data type profiling support for arm64, enabling 'perf annotate --data-type' to resolve memory locations and variable types on arm64 platforms.

The main changes since v5 include:
v5: https://lore.kernel.org/all/[email protected]/

* __get_dwarf_regnum_arm64 optimization: compute directly based on character judgment, avoiding strtol calls. (Ian Rogers)

* extract_op_location_arm64 issue fix: support parsing instructions such as 'cmp x0, x1, lsl #3'.

* Instruction tracking part, fixing issues pointed out by Sashiko, including:
- adding TSR_KIND_POINTER handling for ldr.
- premature returns in add and ldr causing percpu handling to be skipped.
- adding a reg != -1 check for stack type propagation, etc.

Patch organization
==================

The series is organized as follows:

1. Fix disassembly mismatches (Patches 01-02)
Current perf annotate supports three disassembly backends: llvm, capstone, and objdump. On arm64, inconsistencies between the output of these backends (specifically llvm/capstone vs. objdump) often prevent the tracker from correctly identifying registers and offsets. These patches resolve these mismatches, ensuring consistent instruction parsing across all supported backends.

2. Infrastructure for arm64 operand parsing (Patches 03-09) These patches establish the necessary infrastructure for arm64-specific operand handling. This includes implementing new callbacks and data structures to manage arm64's unique addressing modes and register sets. This foundation is essential for the subsequent type-tracking logic.

3. ARM SPE event handling (Patches 10-11)
Patch 10 automatically deduplicates overlapping ARM SPE events (e.g., l1d-miss, tlb-access) in 'perf annotate' by retaining only the "instructions" event when data type profiling is enabled. Patch 11 defaults the synthesized event period to 1 for ARM SPE to fix zero 'Percent' values in annotate output.

4. Core instruction tracking (Patches 12-26)
These patches implement the core logic for type tracking on arm64, covering several key types of instructions, including:
* Memory Access: ldr/str variants (including stack-based access).
* Arithmetic & Data Processing: mov, add, and adrp.
* Special Access: System register access (mrs) and per-cpu variable tracking.

The implementation draws inspiration from the existing x86 logic while adapting it to the nuances of the AArch64 ISA [2][3]. With these changes, perf annotate can successfully resolve memory locations and register types, providing basic support for data type profiling on arm64 platforms.

Example Result
==============

# perf mem record -a -K -- sleep 1
# perf annotate --data-type --stdio --type-stat
Annotate data type stats:
total 1138, ok 852 (74.9%), bad 286 (25.1%)
-----------------------------------------------------------
         6 : no_sym
        42 : no_var
       230 : no_typeinfo
         8 : bad_offset
       213 : insn_track

Annotate type: 'struct page' in [kernel.kallsyms] (66948 samples):
============================================================================

 Percent     offset       size  field
  100.00          0       0x40  struct page      {
    9.01          0        0x8      long unsigned int   flags;
   57.99        0x8       0x28      union        {
   57.99        0x8       0x28          struct   {
   33.00        0x8       0x10              union        {
   33.00        0x8       0x10                  struct list_head        lru {
   33.00        0x8        0x8                      struct list_head*   next;
    0.00       0x10        0x8                      struct list_head*   prev;
                                                };
   33.00        0x8       0x10                  struct   {
   33.00        0x8        0x8                      void*       __filler;
    0.00       0x10        0x4                      unsigned int        mlock_count;
   ...

Each patch's type profiling results are as follows:

Patch | Feature | no_sym | no_var | no_typeinfo | bad_offset | insn_track | ok(%)
------+----------------------------+--------+--------+-------------+------------+------------+------

0011  | base (default spe period)  | 6      | 493    | -           | -          | -          | 56.2%
0013  | enable insn tracking       | 6      | 42     | 437         | 2          | 12         | 57.2%
0016  | support 'load' insn        | 6      | 42     | 398         | 1          | 52         | 60.7%
0017  | support 'store' insn       | 6      | 42     | 398         | 1          | 52         | 60.7%
0021  | support stack variable     | 6      | 42     | 391         | 1          | 59         | 61.3%
0022  | support 'mov' insn         | 6      | 42     | 372         | 3          | 76         | 62.8%
0023  | support 'add' insn         | 6      | 42     | 318         | 7          | 126        | 67.2%
0024  | support 'adrp' insn        | 6      | 42     | 238         | 8          | 205        | 74.2%
0025  | support per-cpu variable   | 6      | 42     | 233         | 8          | 210        | 74.6%
0026  | support 'mrs' insn         | 6      | 42     | 230         | 8          | 213        | 74.9%

Limitations
===========

* SIMD/FP & SVE Vector Support:
Data type profiling currently focuses on General-Purpose (GP) register operations. Vector/SIMD registers (v0-v31, d0-d31, q0-q31) and Scalable Vector Extension (SVE/SME) instructions are not tracked yet.

* Regular Instructions Only:
Only regular instructions are currently supported. Instructions following special rules or non-standard patterns are not supported yet. For example, load/store tracking only matches standard variants (ldr/ldur/ldar/ldp and str/stur/stlr/stp) with straightforward semantics; exclusive loads/stores (ldxr, stxr, etc.) and acquire/exclusive combination variants are excluded.

* Compiler Prologue/Epilogue Code:
As shown by the previous test results, approximately 14% of the failed type profiling results originate from compiler-generated prologue or epilogue code (e.g., ldp x19, x20, [sp, #16]). These instructions manage callee-saved registers across function boundaries, propagating type context through these operations requires inter-procedural (cross-function) instruction analysis, which is currently unsupported by the local backward instruction tracker.

Testing
=======

Tested on arm64 (all passed):

  # perf test -v "perf data type profiling tests"
  81: perf data type profiling tests                                                                  : Ok

=== Test Summary ===
Passed main tests : 1

  Passed subtests   : 0
  Skipped tests     : 0
  Failed tests      : 0

Tested on x86. The profiling results show no change before/after applying this patch series:

before : total 880, ok 711 (80.8%), bad 169 (19.2%) after : total 880, ok 711 (80.8%), bad 169 (19.2%)

Changelog
=========
v5 -> v6:
- v5: https://lore.kernel.org/all/[email protected]/
- Fix a potential UAF issue with capstone sym_name.
- Optimize __get_dwarf_regnum_arm64 to compute directly based on character judgment, avoiding strtol calls. (Ian Rogers)
- Fix an issue in extract_op_location_arm64 when parsing instructions such as

    'cmp x0, x1, lsl #3'.

- Fix several minor issues in instruction tracking. (Sashiko)
v4 -> v5:
- v4: https://lore.kernel.org/all/[email protected]/
- Introduce arch_get_reg_offset() to uniformly handle reg offset.
- Add support for parsing extension type and shift amount in arm64 instructions.
- Refine which instructions to track or skip. (Shuai Xue)
- Rename dont_overlap to default_single_event_per_ip and use it in itrace_synth_opts__set_default(). (Adrian Hunter)
- Introduce delete_stack_state() to clean up obsolete stack state. (Shuai Xue)
- Normalize arch__dwarf_regnum() error return values. (Shuai Xue)
- Drop canary support due to unresolved bugs.
- Fix various minor issues, such as name memory leaks and header includes.
v3 -> v4:
- v3: https://lore.kernel.org/all/[email protected]/
- Fix Capstone compilation failure.
- Stop adding new pcrel_adrp_addr in LLVM; reuse pcrel_load_addr instead.
- Fix parsing issue in arm64_mov__parse.
- Add PC-relative load instruction parsing logic to arm64_ldst__parse, and introduce rstrip_space_and_comment to strip comments.
- Remove wzr/xzr register parsing (not planning to handle this yet).
- Add post-index addressing mode parsing for the '[base], reg' format.
- Update built-in implementation of --itrace=i1i to deduplicate early during arm_spe_process_auxtrace_info.
- Restrict the "default period to 1" behavior to ARM SPE, instead of applying it to all architectures.
- Add register type tracking for function call instructions.
- Add dual-register type tracking for load pair and store pair instructions.
- Correct stack variable offset calculations.
- Add type invalidation upon retry failure.
- Reuse imm_value instead of introducing addr for 'adrp' instruction tracking.
- Fix potential stale type resolution errors caused by TSR_KIND_GLOBAL_ADDR and TSR_KIND_CONST during stack passing.
- Fix a strbuf memory leak during 'mrs' instruction tracking.
- Fix stale dieoff issue when debug info changes.
- Simplify add type propagation: only propagate offset/imm updates, leave type parsing to chk.
v2 -> v3:
- v2: https://lore.kernel.org/all/[email protected]/
- Instead of always parsing the left operand as src and the right operand as dst, set them based on the actual instruction definition. (Namhyung Kim)
- Fix refcount leak in print_capstone_detail().
- Remove useless '<' check when parsing 'addr <symbol>' in arm64_mov__parse().
- Add example comments in arm64_ldst__parse().
- Split arch__dwarf_regnum() changes into a separate commit.
- Rename annotated_addr_mode enum: INSN_ADDR_* -> PERF_ADDR_MODE_*.
- Set caller-saved registers in init_type_state().
- For instructions with addressing mode, always goto adjust_reg_index_state() at the end to update the src register state.
- Handle TSR_KIND_CONST registers for 'mov' and 'add' instructions.
- Invalidate dst register for all other unsupported instructions.
- Verify type DIE is task_struct pointer before caching globally.
- Enable --itrace=i1i by default for ARM SPE data type profiling in 'perf annotate' to avoid overlapping event counting for the same instruction. (James Clark)
- Fix global variable type resolving error in check_matching_type(). (James Clark)
- Address review comments from sashiko [1]:
- Fix unconditional call to arch->extract_op_location()
- Handle multi_regs correctly
- Fix invalid register state in error path
- Other misc fixes
v1 -> v2:
- v1: https://lore.kernel.org/all/[email protected]/
- Fix inconsistencies in arm64 instruction output across llvm, capstone, and objdump disassembly backends.
- Support arm64-specific addressing modes and operand formats. (Leo Yan)
- Extend instruction tracking to support mov and add instructions, along with per-cpu and stack variables.
- Include real-world examples in commit messages to demonstrate practical effects. (Namhyung Kim)
- Improve type-tracking success rate (type stat) from 64.2% to 82.1%.

Quoted 7 lines
Quoted 1 line

Please let me know if you have any feedback.

There's a sashiko finding on patch 21 that looks like it should be addressed. For the series:

Thanks Ian! Will address the patch 21 sashiko finding in next version.

Thanks,
Tengda

maintainer
#64 lore raw
In reply to Tengda Wu

Hello,

Quoted 37 lines · Tengda Wu

This patch series implements data type profiling support for arm64, enabling 'perf annotate --data-type' to resolve memory locations and variable types on arm64 platforms.

The main changes since v5 include:
v5: https://lore.kernel.org/all/[email protected]/

* __get_dwarf_regnum_arm64 optimization: compute directly based on character judgment, avoiding strtol calls. (Ian Rogers)

* extract_op_location_arm64 issue fix: support parsing instructions such as 'cmp x0, x1, lsl #3'.

* Instruction tracking part, fixing issues pointed out by Sashiko, including:
- adding TSR_KIND_POINTER handling for ldr.
- premature returns in add and ldr causing percpu handling to be skipped.
- adding a reg != -1 check for stack type propagation, etc.

Patch organization
==================

The series is organized as follows:

1. Fix disassembly mismatches (Patches 01-02)
Current perf annotate supports three disassembly backends: llvm, capstone, and objdump. On arm64, inconsistencies between the output of these backends (specifically llvm/capstone vs. objdump) often prevent the tracker from correctly identifying registers and offsets. These patches resolve these mismatches, ensuring consistent instruction parsing across all supported backends.

2. Infrastructure for arm64 operand parsing (Patches 03-09) These patches establish the necessary infrastructure for arm64-specific operand handling. This includes implementing new callbacks and data structures to manage arm64's unique addressing modes and register sets. This foundation is essential for the subsequent type-tracking logic.

Quoted 6 lines

3. ARM SPE event handling (Patches 10-11)
Patch 10 automatically deduplicates overlapping ARM SPE events (e.g., l1d-miss, tlb-access) in 'perf annotate' by retaining only the "instructions" event when data type profiling is enabled. Patch 11 defaults the synthesized event period to 1 for ARM SPE to fix zero 'Percent' values in annotate output.

Thanks for working on this!

I think it's ready to merge up to this point. The instruction tracking part needs more review. I'll do that later but it'd be nice if ARM folks and Shuai could review it too.

Thanks,
Namhyung

Quoted 255 lines

4. Core instruction tracking (Patches 12-26)
These patches implement the core logic for type tracking on arm64, covering several key types of instructions, including:
* Memory Access: ldr/str variants (including stack-based access).
* Arithmetic & Data Processing: mov, add, and adrp.
* Special Access: System register access (mrs) and per-cpu variable tracking.

The implementation draws inspiration from the existing x86 logic while adapting it to the nuances of the AArch64 ISA [2][3]. With these changes, perf annotate can successfully resolve memory locations and register types, providing basic support for data type profiling on arm64 platforms.

Example Result
==============

# perf mem record -a -K -- sleep 1
# perf annotate --data-type --stdio --type-stat
Annotate data type stats:
total 1138, ok 852 (74.9%), bad 286 (25.1%)
-----------------------------------------------------------
         6 : no_sym
        42 : no_var
       230 : no_typeinfo
         8 : bad_offset
       213 : insn_track

Annotate type: 'struct page' in [kernel.kallsyms] (66948 samples):
============================================================================

 Percent     offset       size  field
  100.00          0       0x40  struct page      {
    9.01          0        0x8      long unsigned int   flags;
   57.99        0x8       0x28      union        {
   57.99        0x8       0x28          struct   {
   33.00        0x8       0x10              union        {
   33.00        0x8       0x10                  struct list_head        lru {
   33.00        0x8        0x8                      struct list_head*   next;
    0.00       0x10        0x8                      struct list_head*   prev;
                                                };
   33.00        0x8       0x10                  struct   {
   33.00        0x8        0x8                      void*       __filler;
    0.00       0x10        0x4                      unsigned int        mlock_count;
   ...

Each patch's type profiling results are as follows:

Patch | Feature | no_sym | no_var | no_typeinfo | bad_offset | insn_track | ok(%)
------+----------------------------+--------+--------+-------------+------------+------------+------

0011  | base (default spe period)  | 6      | 493    | -           | -          | -          | 56.2%
0013  | enable insn tracking       | 6      | 42     | 437         | 2          | 12         | 57.2%
0016  | support 'load' insn        | 6      | 42     | 398         | 1          | 52         | 60.7%
0017  | support 'store' insn       | 6      | 42     | 398         | 1          | 52         | 60.7%
0021  | support stack variable     | 6      | 42     | 391         | 1          | 59         | 61.3%
0022  | support 'mov' insn         | 6      | 42     | 372         | 3          | 76         | 62.8%
0023  | support 'add' insn         | 6      | 42     | 318         | 7          | 126        | 67.2%
0024  | support 'adrp' insn        | 6      | 42     | 238         | 8          | 205        | 74.2%
0025  | support per-cpu variable   | 6      | 42     | 233         | 8          | 210        | 74.6%
0026  | support 'mrs' insn         | 6      | 42     | 230         | 8          | 213        | 74.9%

Limitations
===========

* SIMD/FP & SVE Vector Support:
Data type profiling currently focuses on General-Purpose (GP) register operations. Vector/SIMD registers (v0-v31, d0-d31, q0-q31) and Scalable Vector Extension (SVE/SME) instructions are not tracked yet.

* Regular Instructions Only:
Only regular instructions are currently supported. Instructions following special rules or non-standard patterns are not supported yet. For example, load/store tracking only matches standard variants (ldr/ldur/ldar/ldp and str/stur/stlr/stp) with straightforward semantics; exclusive loads/stores (ldxr, stxr, etc.) and acquire/exclusive combination variants are excluded.

* Compiler Prologue/Epilogue Code:
As shown by the previous test results, approximately 14% of the failed type profiling results originate from compiler-generated prologue or epilogue code (e.g., ldp x19, x20, [sp, #16]). These instructions manage callee-saved registers across function boundaries, propagating type context through these operations requires inter-procedural (cross-function) instruction analysis, which is currently unsupported by the local backward instruction tracker.

Testing
=======

Tested on arm64 (all passed):

  # perf test -v "perf data type profiling tests"
  81: perf data type profiling tests                                                                  : Ok

=== Test Summary ===
Passed main tests : 1

  Passed subtests   : 0
  Skipped tests     : 0
  Failed tests      : 0

Tested on x86. The profiling results show no change before/after applying this patch series:

before : total 880, ok 711 (80.8%), bad 169 (19.2%) after : total 880, ok 711 (80.8%), bad 169 (19.2%)

Changelog
=========
v5 -> v6:
- v5: https://lore.kernel.org/all/[email protected]/
- Fix a potential UAF issue with capstone sym_name.
- Optimize __get_dwarf_regnum_arm64 to compute directly based on character judgment, avoiding strtol calls. (Ian Rogers)
- Fix an issue in extract_op_location_arm64 when parsing instructions such as

    'cmp x0, x1, lsl #3'.

- Fix several minor issues in instruction tracking. (Sashiko)
v4 -> v5:
- v4: https://lore.kernel.org/all/[email protected]/
- Introduce arch_get_reg_offset() to uniformly handle reg offset.
- Add support for parsing extension type and shift amount in arm64 instructions.
- Refine which instructions to track or skip. (Shuai Xue)
- Rename dont_overlap to default_single_event_per_ip and use it in itrace_synth_opts__set_default(). (Adrian Hunter)
- Introduce delete_stack_state() to clean up obsolete stack state. (Shuai Xue)
- Normalize arch__dwarf_regnum() error return values. (Shuai Xue)
- Drop canary support due to unresolved bugs.
- Fix various minor issues, such as name memory leaks and header includes.
v3 -> v4:
- v3: https://lore.kernel.org/all/[email protected]/
- Fix Capstone compilation failure.
- Stop adding new pcrel_adrp_addr in LLVM; reuse pcrel_load_addr instead.
- Fix parsing issue in arm64_mov__parse.
- Add PC-relative load instruction parsing logic to arm64_ldst__parse, and introduce rstrip_space_and_comment to strip comments.
- Remove wzr/xzr register parsing (not planning to handle this yet).
- Add post-index addressing mode parsing for the '[base], reg' format.
- Update built-in implementation of --itrace=i1i to deduplicate early during arm_spe_process_auxtrace_info.
- Restrict the "default period to 1" behavior to ARM SPE, instead of applying it to all architectures.
- Add register type tracking for function call instructions.
- Add dual-register type tracking for load pair and store pair instructions.
- Correct stack variable offset calculations.
- Add type invalidation upon retry failure.
- Reuse imm_value instead of introducing addr for 'adrp' instruction tracking.
- Fix potential stale type resolution errors caused by TSR_KIND_GLOBAL_ADDR and TSR_KIND_CONST during stack passing.
- Fix a strbuf memory leak during 'mrs' instruction tracking.
- Fix stale dieoff issue when debug info changes.
- Simplify add type propagation: only propagate offset/imm updates, leave type parsing to chk.
v2 -> v3:
- v2: https://lore.kernel.org/all/[email protected]/
- Instead of always parsing the left operand as src and the right operand as dst, set them based on the actual instruction definition. (Namhyung Kim)
- Fix refcount leak in print_capstone_detail().
- Remove useless '<' check when parsing 'addr <symbol>' in arm64_mov__parse().
- Add example comments in arm64_ldst__parse().
- Split arch__dwarf_regnum() changes into a separate commit.
- Rename annotated_addr_mode enum: INSN_ADDR_* -> PERF_ADDR_MODE_*.
- Set caller-saved registers in init_type_state().
- For instructions with addressing mode, always goto adjust_reg_index_state() at the end to update the src register state.
- Handle TSR_KIND_CONST registers for 'mov' and 'add' instructions.
- Invalidate dst register for all other unsupported instructions.
- Verify type DIE is task_struct pointer before caching globally.
- Enable --itrace=i1i by default for ARM SPE data type profiling in 'perf annotate' to avoid overlapping event counting for the same instruction. (James Clark)
- Fix global variable type resolving error in check_matching_type(). (James Clark)
- Address review comments from sashiko [1]:
- Fix unconditional call to arch->extract_op_location()
- Handle multi_regs correctly
- Fix invalid register state in error path
- Other misc fixes
v1 -> v2:
- v1: https://lore.kernel.org/all/[email protected]/
- Fix inconsistencies in arm64 instruction output across llvm, capstone, and objdump disassembly backends.
- Support arm64-specific addressing modes and operand formats. (Leo Yan)
- Extend instruction tracking to support mov and add instructions, along with per-cpu and stack variables.
- Include real-world examples in commit messages to demonstrate practical effects. (Namhyung Kim)
- Improve type-tracking success rate (type stat) from 64.2% to 82.1%.

Please let me know if you have any feedback.

Thanks,
Tengda

[1] https://sashiko.dev/#/patchset/20260403094800.1418825-1-wutengda%40huaweicloud.com
[2] https://developer.arm.com/documentation/102374/0103
[3] https://github.com/flynd/asmsheets/releases/tag/v8

Tengda Wu (26):
perf capstone: Symbolize address operands to match objdump on arm64 perf llvm: Fix arm64 adrp instruction disassembly mismatch with
objdump
perf annotate-arm64: Generalize arm64_mov__parse to support more
instructions
perf annotate-arm64: Handle load and store instructions perf annotate: Normalize arch__dwarf_regnum() error return values perf annotate: Introduce extract_op_location callback for
arch-specific parsing
perf dwarf-regs: Adapt get_dwarf_regnum() for arm64
perf annotate: Adapt arch__dwarf_regnum() for arm64
perf annotate-arm64: Implement extract_op_location() callback perf annotate: Default to --itrace=i1i for data type profiling perf arm-spe: Set default synthesized event period to 1 perf annotate-data: Extract invalidate_reg_state() as a common helper perf annotate-arm64: Enable instruction tracking support perf annotate-data: Add arch_get_reg_offset helper
perf annotate-arm64: Track return type after call instructions perf annotate-arm64: Support load instruction tracking perf annotate-arm64: Support store instruction tracking perf annotate-data: Expand type_state_reg imm_value to u64 perf annotate-data: Track imm_value for stack variables perf annotate-x86: Delete stale stack state on store of untracked
register
perf annotate-arm64: Support stack variable tracking
perf annotate-arm64: Support 'mov' instruction tracking perf annotate-arm64: Support 'add' instruction tracking perf annotate-arm64: Support 'adrp' instruction to track global
variables
perf annotate-arm64: Support per-cpu variable access tracking perf annotate-arm64: Support 'mrs' instruction to track 'current'
pointer

 tools/perf/builtin-annotate.c                 |    8 +
 .../perf/util/annotate-arch/annotate-arm64.c  | 1211 ++++++++++++++++-
 .../util/annotate-arch/annotate-powerpc.c     |    9 +
 tools/perf/util/annotate-arch/annotate-x86.c  |  117 +-
 tools/perf/util/annotate-data.c               |  205 ++-
 tools/perf/util/annotate-data.h               |   14 +-
 tools/perf/util/annotate.c                    |  108 +-
 tools/perf/util/annotate.h                    |   63 +
 tools/perf/util/arm-spe.c                     |   16 +-
 tools/perf/util/auxtrace.c                    |   12 +-
 tools/perf/util/auxtrace.h                    |    7 +-
 tools/perf/util/capstone.c                    |  186 ++-
 tools/perf/util/cs-etm.c                      |    2 +-
 tools/perf/util/disasm.c                      |    5 +
 tools/perf/util/disasm.h                      |    5 +
 .../util/dwarf-regs-arch/dwarf-regs-arm64.c   |   25 +
 tools/perf/util/dwarf-regs.c                  |    2 +-
 tools/perf/util/include/dwarf-regs.h          |    1 +
 tools/perf/util/intel-bts.c                   |    2 +-
 tools/perf/util/intel-pt.c                    |    3 +-
 tools/perf/util/llvm.c                        |   52 +-
 21 files changed, 1831 insertions(+), 222 deletions(-)

base-commit: e6e35979777d646fe3c7c94dca7dd32fb25d45f4

Signature · 1 line
2.34.1
#65 lore raw
Quoted 47 lines · Namhyung Kim

Hello,

Quoted 37 lines · Tengda Wu

This patch series implements data type profiling support for arm64, enabling 'perf annotate --data-type' to resolve memory locations and variable types on arm64 platforms.

The main changes since v5 include:
v5: https://lore.kernel.org/all/[email protected]/

* __get_dwarf_regnum_arm64 optimization: compute directly based on character judgment, avoiding strtol calls. (Ian Rogers)

* extract_op_location_arm64 issue fix: support parsing instructions such as 'cmp x0, x1, lsl #3'.

* Instruction tracking part, fixing issues pointed out by Sashiko, including:
- adding TSR_KIND_POINTER handling for ldr.
- premature returns in add and ldr causing percpu handling to be skipped.
- adding a reg != -1 check for stack type propagation, etc.

Patch organization
==================

The series is organized as follows:

1. Fix disassembly mismatches (Patches 01-02)
Current perf annotate supports three disassembly backends: llvm, capstone, and objdump. On arm64, inconsistencies between the output of these backends (specifically llvm/capstone vs. objdump) often prevent the tracker from correctly identifying registers and offsets. These patches resolve these mismatches, ensuring consistent instruction parsing across all supported backends.

2. Infrastructure for arm64 operand parsing (Patches 03-09) These patches establish the necessary infrastructure for arm64-specific operand handling. This includes implementing new callbacks and data structures to manage arm64's unique addressing modes and register sets. This foundation is essential for the subsequent type-tracking logic.

Quoted 6 lines

3. ARM SPE event handling (Patches 10-11)
Patch 10 automatically deduplicates overlapping ARM SPE events (e.g., l1d-miss, tlb-access) in 'perf annotate' by retaining only the "instructions" event when data type profiling is enabled. Patch 11 defaults the synthesized event period to 1 for ARM SPE to fix zero 'Percent' values in annotate output.

Quoted 3 lines

Thanks for working on this!

I think it's ready to merge up to this point.

You mean patches 1-11?

- Arnaldo

The instruction tracking

Quoted 261 lines

part needs more review. I'll do that later but it'd be nice if ARM folks and Shuai could review it too.

Thanks,
Namhyung

Quoted 255 lines

4. Core instruction tracking (Patches 12-26)
These patches implement the core logic for type tracking on arm64, covering several key types of instructions, including:
* Memory Access: ldr/str variants (including stack-based access).
* Arithmetic & Data Processing: mov, add, and adrp.
* Special Access: System register access (mrs) and per-cpu variable tracking.

The implementation draws inspiration from the existing x86 logic while adapting it to the nuances of the AArch64 ISA [2][3]. With these changes, perf annotate can successfully resolve memory locations and register types, providing basic support for data type profiling on arm64 platforms.

Example Result
==============

# perf mem record -a -K -- sleep 1
# perf annotate --data-type --stdio --type-stat
Annotate data type stats:
total 1138, ok 852 (74.9%), bad 286 (25.1%)
-----------------------------------------------------------
         6 : no_sym
        42 : no_var
       230 : no_typeinfo
         8 : bad_offset
       213 : insn_track

Annotate type: 'struct page' in [kernel.kallsyms] (66948 samples):
============================================================================

 Percent     offset       size  field
  100.00          0       0x40  struct page      {
    9.01          0        0x8      long unsigned int   flags;
   57.99        0x8       0x28      union        {
   57.99        0x8       0x28          struct   {
   33.00        0x8       0x10              union        {
   33.00        0x8       0x10                  struct list_head        lru {
   33.00        0x8        0x8                      struct list_head*   next;
    0.00       0x10        0x8                      struct list_head*   prev;
                                                };
   33.00        0x8       0x10                  struct   {
   33.00        0x8        0x8                      void*       __filler;
    0.00       0x10        0x4                      unsigned int        mlock_count;
   ...

Each patch's type profiling results are as follows:

Patch | Feature | no_sym | no_var | no_typeinfo | bad_offset | insn_track | ok(%)
------+----------------------------+--------+--------+-------------+------------+------------+------

0011  | base (default spe period)  | 6      | 493    | -           | -          | -          | 56.2%
0013  | enable insn tracking       | 6      | 42     | 437         | 2          | 12         | 57.2%
0016  | support 'load' insn        | 6      | 42     | 398         | 1          | 52         | 60.7%
0017  | support 'store' insn       | 6      | 42     | 398         | 1          | 52         | 60.7%
0021  | support stack variable     | 6      | 42     | 391         | 1          | 59         | 61.3%
0022  | support 'mov' insn         | 6      | 42     | 372         | 3          | 76         | 62.8%
0023  | support 'add' insn         | 6      | 42     | 318         | 7          | 126        | 67.2%
0024  | support 'adrp' insn        | 6      | 42     | 238         | 8          | 205        | 74.2%
0025  | support per-cpu variable   | 6      | 42     | 233         | 8          | 210        | 74.6%
0026  | support 'mrs' insn         | 6      | 42     | 230         | 8          | 213        | 74.9%

Limitations
===========

* SIMD/FP & SVE Vector Support:
Data type profiling currently focuses on General-Purpose (GP) register operations. Vector/SIMD registers (v0-v31, d0-d31, q0-q31) and Scalable Vector Extension (SVE/SME) instructions are not tracked yet.

* Regular Instructions Only:
Only regular instructions are currently supported. Instructions following special rules or non-standard patterns are not supported yet. For example, load/store tracking only matches standard variants (ldr/ldur/ldar/ldp and str/stur/stlr/stp) with straightforward semantics; exclusive loads/stores (ldxr, stxr, etc.) and acquire/exclusive combination variants are excluded.

* Compiler Prologue/Epilogue Code:
As shown by the previous test results, approximately 14% of the failed type profiling results originate from compiler-generated prologue or epilogue code (e.g., ldp x19, x20, [sp, #16]). These instructions manage callee-saved registers across function boundaries, propagating type context through these operations requires inter-procedural (cross-function) instruction analysis, which is currently unsupported by the local backward instruction tracker.

Testing
=======

Tested on arm64 (all passed):

  # perf test -v "perf data type profiling tests"
  81: perf data type profiling tests                                                                  : Ok

=== Test Summary ===
Passed main tests : 1

  Passed subtests   : 0
  Skipped tests     : 0
  Failed tests      : 0

Tested on x86. The profiling results show no change before/after applying this patch series:

before : total 880, ok 711 (80.8%), bad 169 (19.2%) after : total 880, ok 711 (80.8%), bad 169 (19.2%)

Changelog
=========
v5 -> v6:
- v5: https://lore.kernel.org/all/[email protected]/
- Fix a potential UAF issue with capstone sym_name.
- Optimize __get_dwarf_regnum_arm64 to compute directly based on character judgment, avoiding strtol calls. (Ian Rogers)
- Fix an issue in extract_op_location_arm64 when parsing instructions such as

    'cmp x0, x1, lsl #3'.

- Fix several minor issues in instruction tracking. (Sashiko)
v4 -> v5:
- v4: https://lore.kernel.org/all/[email protected]/
- Introduce arch_get_reg_offset() to uniformly handle reg offset.
- Add support for parsing extension type and shift amount in arm64 instructions.
- Refine which instructions to track or skip. (Shuai Xue)
- Rename dont_overlap to default_single_event_per_ip and use it in itrace_synth_opts__set_default(). (Adrian Hunter)
- Introduce delete_stack_state() to clean up obsolete stack state. (Shuai Xue)
- Normalize arch__dwarf_regnum() error return values. (Shuai Xue)
- Drop canary support due to unresolved bugs.
- Fix various minor issues, such as name memory leaks and header includes.
v3 -> v4:
- v3: https://lore.kernel.org/all/[email protected]/
- Fix Capstone compilation failure.
- Stop adding new pcrel_adrp_addr in LLVM; reuse pcrel_load_addr instead.
- Fix parsing issue in arm64_mov__parse.
- Add PC-relative load instruction parsing logic to arm64_ldst__parse, and introduce rstrip_space_and_comment to strip comments.
- Remove wzr/xzr register parsing (not planning to handle this yet).
- Add post-index addressing mode parsing for the '[base], reg' format.
- Update built-in implementation of --itrace=i1i to deduplicate early during arm_spe_process_auxtrace_info.
- Restrict the "default period to 1" behavior to ARM SPE, instead of applying it to all architectures.
- Add register type tracking for function call instructions.
- Add dual-register type tracking for load pair and store pair instructions.
- Correct stack variable offset calculations.
- Add type invalidation upon retry failure.
- Reuse imm_value instead of introducing addr for 'adrp' instruction tracking.
- Fix potential stale type resolution errors caused by TSR_KIND_GLOBAL_ADDR and TSR_KIND_CONST during stack passing.
- Fix a strbuf memory leak during 'mrs' instruction tracking.
- Fix stale dieoff issue when debug info changes.
- Simplify add type propagation: only propagate offset/imm updates, leave type parsing to chk.
v2 -> v3:
- v2: https://lore.kernel.org/all/[email protected]/
- Instead of always parsing the left operand as src and the right operand as dst, set them based on the actual instruction definition. (Namhyung Kim)
- Fix refcount leak in print_capstone_detail().
- Remove useless '<' check when parsing 'addr <symbol>' in arm64_mov__parse().
- Add example comments in arm64_ldst__parse().
- Split arch__dwarf_regnum() changes into a separate commit.
- Rename annotated_addr_mode enum: INSN_ADDR_* -> PERF_ADDR_MODE_*.
- Set caller-saved registers in init_type_state().
- For instructions with addressing mode, always goto adjust_reg_index_state() at the end to update the src register state.
- Handle TSR_KIND_CONST registers for 'mov' and 'add' instructions.
- Invalidate dst register for all other unsupported instructions.
- Verify type DIE is task_struct pointer before caching globally.
- Enable --itrace=i1i by default for ARM SPE data type profiling in 'perf annotate' to avoid overlapping event counting for the same instruction. (James Clark)
- Fix global variable type resolving error in check_matching_type(). (James Clark)
- Address review comments from sashiko [1]:
- Fix unconditional call to arch->extract_op_location()
- Handle multi_regs correctly
- Fix invalid register state in error path
- Other misc fixes
v1 -> v2:
- v1: https://lore.kernel.org/all/[email protected]/
- Fix inconsistencies in arm64 instruction output across llvm, capstone, and objdump disassembly backends.
- Support arm64-specific addressing modes and operand formats. (Leo Yan)
- Extend instruction tracking to support mov and add instructions, along with per-cpu and stack variables.
- Include real-world examples in commit messages to demonstrate practical effects. (Namhyung Kim)
- Improve type-tracking success rate (type stat) from 64.2% to 82.1%.

Please let me know if you have any feedback.

Thanks,
Tengda

[1] https://sashiko.dev/#/patchset/20260403094800.1418825-1-wutengda%40huaweicloud.com
[2] https://developer.arm.com/documentation/102374/0103
[3] https://github.com/flynd/asmsheets/releases/tag/v8

Tengda Wu (26):
perf capstone: Symbolize address operands to match objdump on arm64 perf llvm: Fix arm64 adrp instruction disassembly mismatch with
objdump
perf annotate-arm64: Generalize arm64_mov__parse to support more
instructions
perf annotate-arm64: Handle load and store instructions perf annotate: Normalize arch__dwarf_regnum() error return values perf annotate: Introduce extract_op_location callback for
arch-specific parsing
perf dwarf-regs: Adapt get_dwarf_regnum() for arm64
perf annotate: Adapt arch__dwarf_regnum() for arm64
perf annotate-arm64: Implement extract_op_location() callback perf annotate: Default to --itrace=i1i for data type profiling perf arm-spe: Set default synthesized event period to 1 perf annotate-data: Extract invalidate_reg_state() as a common helper perf annotate-arm64: Enable instruction tracking support perf annotate-data: Add arch_get_reg_offset helper
perf annotate-arm64: Track return type after call instructions perf annotate-arm64: Support load instruction tracking perf annotate-arm64: Support store instruction tracking perf annotate-data: Expand type_state_reg imm_value to u64 perf annotate-data: Track imm_value for stack variables perf annotate-x86: Delete stale stack state on store of untracked
register
perf annotate-arm64: Support stack variable tracking
perf annotate-arm64: Support 'mov' instruction tracking perf annotate-arm64: Support 'add' instruction tracking perf annotate-arm64: Support 'adrp' instruction to track global
variables
perf annotate-arm64: Support per-cpu variable access tracking perf annotate-arm64: Support 'mrs' instruction to track 'current'
pointer

 tools/perf/builtin-annotate.c                 |    8 +
 .../perf/util/annotate-arch/annotate-arm64.c  | 1211 ++++++++++++++++-
 .../util/annotate-arch/annotate-powerpc.c     |    9 +
 tools/perf/util/annotate-arch/annotate-x86.c  |  117 +-
 tools/perf/util/annotate-data.c               |  205 ++-
 tools/perf/util/annotate-data.h               |   14 +-
 tools/perf/util/annotate.c                    |  108 +-
 tools/perf/util/annotate.h                    |   63 +
 tools/perf/util/arm-spe.c                     |   16 +-
 tools/perf/util/auxtrace.c                    |   12 +-
 tools/perf/util/auxtrace.h                    |    7 +-
 tools/perf/util/capstone.c                    |  186 ++-
 tools/perf/util/cs-etm.c                      |    2 +-
 tools/perf/util/disasm.c                      |    5 +
 tools/perf/util/disasm.h                      |    5 +
 .../util/dwarf-regs-arch/dwarf-regs-arm64.c   |   25 +
 tools/perf/util/dwarf-regs.c                  |    2 +-
 tools/perf/util/include/dwarf-regs.h          |    1 +
 tools/perf/util/intel-bts.c                   |    2 +-
 tools/perf/util/intel-pt.c                    |    3 +-
 tools/perf/util/llvm.c                        |   52 +-
 21 files changed, 1831 insertions(+), 222 deletions(-)

base-commit: e6e35979777d646fe3c7c94dca7dd32fb25d45f4

Signature · 1 line
2.34.1

- Arnaldo

maintainer
#66 lore raw
Quoted 50 lines · Arnaldo Melo
Quoted 47 lines · Namhyung Kim

Hello,

Quoted 43 lines · Tengda Wu

This patch series implements data type profiling support for arm64, enabling 'perf annotate --data-type' to resolve memory locations and variable types on arm64 platforms.

The main changes since v5 include:
v5: https://lore.kernel.org/all/[email protected]/

* __get_dwarf_regnum_arm64 optimization: compute directly based on character judgment, avoiding strtol calls. (Ian Rogers)

* extract_op_location_arm64 issue fix: support parsing instructions such as 'cmp x0, x1, lsl #3'.

* Instruction tracking part, fixing issues pointed out by Sashiko, including:
- adding TSR_KIND_POINTER handling for ldr.
- premature returns in add and ldr causing percpu handling to be skipped.
- adding a reg != -1 check for stack type propagation, etc.

Patch organization
==================

The series is organized as follows:

1. Fix disassembly mismatches (Patches 01-02)
Current perf annotate supports three disassembly backends: llvm, capstone, and objdump. On arm64, inconsistencies between the output of these backends (specifically llvm/capstone vs. objdump) often prevent the tracker from correctly identifying registers and offsets. These patches resolve these mismatches, ensuring consistent instruction parsing across all supported backends.

2. Infrastructure for arm64 operand parsing (Patches 03-09) These patches establish the necessary infrastructure for arm64-specific operand handling. This includes implementing new callbacks and data structures to manage arm64's unique addressing modes and register sets. This foundation is essential for the subsequent type-tracking logic.

3. ARM SPE event handling (Patches 10-11)
Patch 10 automatically deduplicates overlapping ARM SPE events (e.g., l1d-miss, tlb-access) in 'perf annotate' by retaining only the "instructions" event when data type profiling is enabled. Patch 11 defaults the synthesized event period to 1 for ARM SPE to fix zero 'Percent' values in annotate output.

Quoted 5 lines
Quoted 3 lines

Thanks for working on this!

I think it's ready to merge up to this point.

You mean patches 1-11?

Yes.

Thanks,
Namhyung

author
#67 lore raw
In reply to Namhyung Kim
Quoted 47 lines · Namhyung Kim

Hello,

Quoted 37 lines · Tengda Wu

This patch series implements data type profiling support for arm64, enabling 'perf annotate --data-type' to resolve memory locations and variable types on arm64 platforms.

The main changes since v5 include:
v5: https://lore.kernel.org/all/[email protected]/

* __get_dwarf_regnum_arm64 optimization: compute directly based on character judgment, avoiding strtol calls. (Ian Rogers)

* extract_op_location_arm64 issue fix: support parsing instructions such as 'cmp x0, x1, lsl #3'.

* Instruction tracking part, fixing issues pointed out by Sashiko, including:
- adding TSR_KIND_POINTER handling for ldr.
- premature returns in add and ldr causing percpu handling to be skipped.
- adding a reg != -1 check for stack type propagation, etc.

Patch organization
==================

The series is organized as follows:

1. Fix disassembly mismatches (Patches 01-02)
Current perf annotate supports three disassembly backends: llvm, capstone, and objdump. On arm64, inconsistencies between the output of these backends (specifically llvm/capstone vs. objdump) often prevent the tracker from correctly identifying registers and offsets. These patches resolve these mismatches, ensuring consistent instruction parsing across all supported backends.

2. Infrastructure for arm64 operand parsing (Patches 03-09) These patches establish the necessary infrastructure for arm64-specific operand handling. This includes implementing new callbacks and data structures to manage arm64's unique addressing modes and register sets. This foundation is essential for the subsequent type-tracking logic.

Quoted 6 lines

3. ARM SPE event handling (Patches 10-11)
Patch 10 automatically deduplicates overlapping ARM SPE events (e.g., l1d-miss, tlb-access) in 'perf annotate' by retaining only the "instructions" event when data type profiling is enabled. Patch 11 defaults the synthesized event period to 1 for ARM SPE to fix zero 'Percent' values in annotate output.

Quoted 6 lines

Thanks for working on this!

I think it's ready to merge up to this point. The instruction tracking part needs more review. I'll do that later but it'd be nice if ARM folks and Shuai could review it too.

Yes, patches 01-11 are already ready.

For the instruction tracking part (patches 12-26), apart from the issues with patches 21 and 25 mentioned by Sashiko, the rest is also ready for review.

Regarding patches 21 and 25, I've posted the fixes I plan to make under the respective patches. Please let me know if there are any concerns.

Looking forward to your review.

Best regards,
Tengda

Quoted 258 lines

Thanks,
Namhyung

Quoted 255 lines

4. Core instruction tracking (Patches 12-26)
These patches implement the core logic for type tracking on arm64, covering several key types of instructions, including:
* Memory Access: ldr/str variants (including stack-based access).
* Arithmetic & Data Processing: mov, add, and adrp.
* Special Access: System register access (mrs) and per-cpu variable tracking.

The implementation draws inspiration from the existing x86 logic while adapting it to the nuances of the AArch64 ISA [2][3]. With these changes, perf annotate can successfully resolve memory locations and register types, providing basic support for data type profiling on arm64 platforms.

Example Result
==============

# perf mem record -a -K -- sleep 1
# perf annotate --data-type --stdio --type-stat
Annotate data type stats:
total 1138, ok 852 (74.9%), bad 286 (25.1%)
-----------------------------------------------------------
         6 : no_sym
        42 : no_var
       230 : no_typeinfo
         8 : bad_offset
       213 : insn_track

Annotate type: 'struct page' in [kernel.kallsyms] (66948 samples):
============================================================================

 Percent     offset       size  field
  100.00          0       0x40  struct page      {
    9.01          0        0x8      long unsigned int   flags;
   57.99        0x8       0x28      union        {
   57.99        0x8       0x28          struct   {
   33.00        0x8       0x10              union        {
   33.00        0x8       0x10                  struct list_head        lru {
   33.00        0x8        0x8                      struct list_head*   next;
    0.00       0x10        0x8                      struct list_head*   prev;
                                                };
   33.00        0x8       0x10                  struct   {
   33.00        0x8        0x8                      void*       __filler;
    0.00       0x10        0x4                      unsigned int        mlock_count;
   ...

Each patch's type profiling results are as follows:

Patch | Feature | no_sym | no_var | no_typeinfo | bad_offset | insn_track | ok(%)
------+----------------------------+--------+--------+-------------+------------+------------+------

0011  | base (default spe period)  | 6      | 493    | -           | -          | -          | 56.2%
0013  | enable insn tracking       | 6      | 42     | 437         | 2          | 12         | 57.2%
0016  | support 'load' insn        | 6      | 42     | 398         | 1          | 52         | 60.7%
0017  | support 'store' insn       | 6      | 42     | 398         | 1          | 52         | 60.7%
0021  | support stack variable     | 6      | 42     | 391         | 1          | 59         | 61.3%
0022  | support 'mov' insn         | 6      | 42     | 372         | 3          | 76         | 62.8%
0023  | support 'add' insn         | 6      | 42     | 318         | 7          | 126        | 67.2%
0024  | support 'adrp' insn        | 6      | 42     | 238         | 8          | 205        | 74.2%
0025  | support per-cpu variable   | 6      | 42     | 233         | 8          | 210        | 74.6%
0026  | support 'mrs' insn         | 6      | 42     | 230         | 8          | 213        | 74.9%

Limitations
===========

* SIMD/FP & SVE Vector Support:
Data type profiling currently focuses on General-Purpose (GP) register operations. Vector/SIMD registers (v0-v31, d0-d31, q0-q31) and Scalable Vector Extension (SVE/SME) instructions are not tracked yet.

* Regular Instructions Only:
Only regular instructions are currently supported. Instructions following special rules or non-standard patterns are not supported yet. For example, load/store tracking only matches standard variants (ldr/ldur/ldar/ldp and str/stur/stlr/stp) with straightforward semantics; exclusive loads/stores (ldxr, stxr, etc.) and acquire/exclusive combination variants are excluded.

* Compiler Prologue/Epilogue Code:
As shown by the previous test results, approximately 14% of the failed type profiling results originate from compiler-generated prologue or epilogue code (e.g., ldp x19, x20, [sp, #16]). These instructions manage callee-saved registers across function boundaries, propagating type context through these operations requires inter-procedural (cross-function) instruction analysis, which is currently unsupported by the local backward instruction tracker.

Testing
=======

Tested on arm64 (all passed):

  # perf test -v "perf data type profiling tests"
  81: perf data type profiling tests                                                                  : Ok

=== Test Summary ===
Passed main tests : 1

  Passed subtests   : 0
  Skipped tests     : 0
  Failed tests      : 0

Tested on x86. The profiling results show no change before/after applying this patch series:

before : total 880, ok 711 (80.8%), bad 169 (19.2%) after : total 880, ok 711 (80.8%), bad 169 (19.2%)

Changelog
=========
v5 -> v6:
- v5: https://lore.kernel.org/all/[email protected]/
- Fix a potential UAF issue with capstone sym_name.
- Optimize __get_dwarf_regnum_arm64 to compute directly based on character judgment, avoiding strtol calls. (Ian Rogers)
- Fix an issue in extract_op_location_arm64 when parsing instructions such as

    'cmp x0, x1, lsl #3'.

- Fix several minor issues in instruction tracking. (Sashiko)
v4 -> v5:
- v4: https://lore.kernel.org/all/[email protected]/
- Introduce arch_get_reg_offset() to uniformly handle reg offset.
- Add support for parsing extension type and shift amount in arm64 instructions.
- Refine which instructions to track or skip. (Shuai Xue)
- Rename dont_overlap to default_single_event_per_ip and use it in itrace_synth_opts__set_default(). (Adrian Hunter)
- Introduce delete_stack_state() to clean up obsolete stack state. (Shuai Xue)
- Normalize arch__dwarf_regnum() error return values. (Shuai Xue)
- Drop canary support due to unresolved bugs.
- Fix various minor issues, such as name memory leaks and header includes.
v3 -> v4:
- v3: https://lore.kernel.org/all/[email protected]/
- Fix Capstone compilation failure.
- Stop adding new pcrel_adrp_addr in LLVM; reuse pcrel_load_addr instead.
- Fix parsing issue in arm64_mov__parse.
- Add PC-relative load instruction parsing logic to arm64_ldst__parse, and introduce rstrip_space_and_comment to strip comments.
- Remove wzr/xzr register parsing (not planning to handle this yet).
- Add post-index addressing mode parsing for the '[base], reg' format.
- Update built-in implementation of --itrace=i1i to deduplicate early during arm_spe_process_auxtrace_info.
- Restrict the "default period to 1" behavior to ARM SPE, instead of applying it to all architectures.
- Add register type tracking for function call instructions.
- Add dual-register type tracking for load pair and store pair instructions.
- Correct stack variable offset calculations.
- Add type invalidation upon retry failure.
- Reuse imm_value instead of introducing addr for 'adrp' instruction tracking.
- Fix potential stale type resolution errors caused by TSR_KIND_GLOBAL_ADDR and TSR_KIND_CONST during stack passing.
- Fix a strbuf memory leak during 'mrs' instruction tracking.
- Fix stale dieoff issue when debug info changes.
- Simplify add type propagation: only propagate offset/imm updates, leave type parsing to chk.
v2 -> v3:
- v2: https://lore.kernel.org/all/[email protected]/
- Instead of always parsing the left operand as src and the right operand as dst, set them based on the actual instruction definition. (Namhyung Kim)
- Fix refcount leak in print_capstone_detail().
- Remove useless '<' check when parsing 'addr <symbol>' in arm64_mov__parse().
- Add example comments in arm64_ldst__parse().
- Split arch__dwarf_regnum() changes into a separate commit.
- Rename annotated_addr_mode enum: INSN_ADDR_* -> PERF_ADDR_MODE_*.
- Set caller-saved registers in init_type_state().
- For instructions with addressing mode, always goto adjust_reg_index_state() at the end to update the src register state.
- Handle TSR_KIND_CONST registers for 'mov' and 'add' instructions.
- Invalidate dst register for all other unsupported instructions.
- Verify type DIE is task_struct pointer before caching globally.
- Enable --itrace=i1i by default for ARM SPE data type profiling in 'perf annotate' to avoid overlapping event counting for the same instruction. (James Clark)
- Fix global variable type resolving error in check_matching_type(). (James Clark)
- Address review comments from sashiko [1]:
- Fix unconditional call to arch->extract_op_location()
- Handle multi_regs correctly
- Fix invalid register state in error path
- Other misc fixes
v1 -> v2:
- v1: https://lore.kernel.org/all/[email protected]/
- Fix inconsistencies in arm64 instruction output across llvm, capstone, and objdump disassembly backends.
- Support arm64-specific addressing modes and operand formats. (Leo Yan)
- Extend instruction tracking to support mov and add instructions, along with per-cpu and stack variables.
- Include real-world examples in commit messages to demonstrate practical effects. (Namhyung Kim)
- Improve type-tracking success rate (type stat) from 64.2% to 82.1%.

Please let me know if you have any feedback.

Thanks,
Tengda

[1] https://sashiko.dev/#/patchset/20260403094800.1418825-1-wutengda%40huaweicloud.com
[2] https://developer.arm.com/documentation/102374/0103
[3] https://github.com/flynd/asmsheets/releases/tag/v8

Tengda Wu (26):
perf capstone: Symbolize address operands to match objdump on arm64 perf llvm: Fix arm64 adrp instruction disassembly mismatch with
objdump
perf annotate-arm64: Generalize arm64_mov__parse to support more
instructions
perf annotate-arm64: Handle load and store instructions perf annotate: Normalize arch__dwarf_regnum() error return values perf annotate: Introduce extract_op_location callback for
arch-specific parsing
perf dwarf-regs: Adapt get_dwarf_regnum() for arm64
perf annotate: Adapt arch__dwarf_regnum() for arm64
perf annotate-arm64: Implement extract_op_location() callback perf annotate: Default to --itrace=i1i for data type profiling perf arm-spe: Set default synthesized event period to 1 perf annotate-data: Extract invalidate_reg_state() as a common helper perf annotate-arm64: Enable instruction tracking support perf annotate-data: Add arch_get_reg_offset helper
perf annotate-arm64: Track return type after call instructions perf annotate-arm64: Support load instruction tracking perf annotate-arm64: Support store instruction tracking perf annotate-data: Expand type_state_reg imm_value to u64 perf annotate-data: Track imm_value for stack variables perf annotate-x86: Delete stale stack state on store of untracked
register
perf annotate-arm64: Support stack variable tracking
perf annotate-arm64: Support 'mov' instruction tracking perf annotate-arm64: Support 'add' instruction tracking perf annotate-arm64: Support 'adrp' instruction to track global
variables
perf annotate-arm64: Support per-cpu variable access tracking perf annotate-arm64: Support 'mrs' instruction to track 'current'
pointer

 tools/perf/builtin-annotate.c                 |    8 +
 .../perf/util/annotate-arch/annotate-arm64.c  | 1211 ++++++++++++++++-
 .../util/annotate-arch/annotate-powerpc.c     |    9 +
 tools/perf/util/annotate-arch/annotate-x86.c  |  117 +-
 tools/perf/util/annotate-data.c               |  205 ++-
 tools/perf/util/annotate-data.h               |   14 +-
 tools/perf/util/annotate.c                    |  108 +-
 tools/perf/util/annotate.h                    |   63 +
 tools/perf/util/arm-spe.c                     |   16 +-
 tools/perf/util/auxtrace.c                    |   12 +-
 tools/perf/util/auxtrace.h                    |    7 +-
 tools/perf/util/capstone.c                    |  186 ++-
 tools/perf/util/cs-etm.c                      |    2 +-
 tools/perf/util/disasm.c                      |    5 +
 tools/perf/util/disasm.h                      |    5 +
 .../util/dwarf-regs-arch/dwarf-regs-arm64.c   |   25 +
 tools/perf/util/dwarf-regs.c                  |    2 +-
 tools/perf/util/include/dwarf-regs.h          |    1 +
 tools/perf/util/intel-bts.c                   |    2 +-
 tools/perf/util/intel-pt.c                    |    3 +-
 tools/perf/util/llvm.c                        |   52 +-
 21 files changed, 1831 insertions(+), 222 deletions(-)

base-commit: e6e35979777d646fe3c7c94dca7dd32fb25d45f4

Signature · 1 line
2.34.1
maintainer Applied
#68 lore raw
Quoted 50 lines · Tengda Wu
Quoted 47 lines · Namhyung Kim

Hello,

Quoted 43 lines · Tengda Wu

This patch series implements data type profiling support for arm64, enabling 'perf annotate --data-type' to resolve memory locations and variable types on arm64 platforms.

The main changes since v5 include:
v5: https://lore.kernel.org/all/[email protected]/

* __get_dwarf_regnum_arm64 optimization: compute directly based on character judgment, avoiding strtol calls. (Ian Rogers)

* extract_op_location_arm64 issue fix: support parsing instructions such as 'cmp x0, x1, lsl #3'.

* Instruction tracking part, fixing issues pointed out by Sashiko, including:
- adding TSR_KIND_POINTER handling for ldr.
- premature returns in add and ldr causing percpu handling to be skipped.
- adding a reg != -1 check for stack type propagation, etc.

Patch organization
==================

The series is organized as follows:

1. Fix disassembly mismatches (Patches 01-02)
Current perf annotate supports three disassembly backends: llvm, capstone, and objdump. On arm64, inconsistencies between the output of these backends (specifically llvm/capstone vs. objdump) often prevent the tracker from correctly identifying registers and offsets. These patches resolve these mismatches, ensuring consistent instruction parsing across all supported backends.

2. Infrastructure for arm64 operand parsing (Patches 03-09) These patches establish the necessary infrastructure for arm64-specific operand handling. This includes implementing new callbacks and data structures to manage arm64's unique addressing modes and register sets. This foundation is essential for the subsequent type-tracking logic.

3. ARM SPE event handling (Patches 10-11)
Patch 10 automatically deduplicates overlapping ARM SPE events (e.g., l1d-miss, tlb-access) in 'perf annotate' by retaining only the "instructions" event when data type profiling is enabled. Patch 11 defaults the synthesized event period to 1 for ARM SPE to fix zero 'Percent' values in annotate output.

Quoted 8 lines
Quoted 6 lines

Thanks for working on this!

I think it's ready to merge up to this point. The instruction tracking part needs more review. I'll do that later but it'd be nice if ARM folks and Shuai could review it too.

Yes, patches 01-11 are already ready.

I had to just do a minor fixup:

util/annotate-arch/annotate-arm64.c: In function ‘extract_op_location_arm64’:
util/annotate-arch/annotate-arm64.c:385:19: error: assignment discards ‘const’ qualifier from pointer target type [-Werror=discarded-qualifiers]
385 | p = strchr(s, ']');
| ^
cc1: all warnings being treated as errors

That 'p' var can't be made const so I casted the return of strchr instead, one-liner.

Thanks, applied to perf-tools-next, for v7.4.

- Arnaldo

Quoted 270 lines

For the instruction tracking part (patches 12-26), apart from the issues with patches 21 and 25 mentioned by Sashiko, the rest is also ready for review.

Regarding patches 21 and 25, I've posted the fixes I plan to make under the respective patches. Please let me know if there are any concerns.

Looking forward to your review.

Best regards,
Tengda

Quoted 258 lines

Thanks,
Namhyung

Quoted 255 lines

4. Core instruction tracking (Patches 12-26)
These patches implement the core logic for type tracking on arm64, covering several key types of instructions, including:
* Memory Access: ldr/str variants (including stack-based access).
* Arithmetic & Data Processing: mov, add, and adrp.
* Special Access: System register access (mrs) and per-cpu variable tracking.

The implementation draws inspiration from the existing x86 logic while adapting it to the nuances of the AArch64 ISA [2][3]. With these changes, perf annotate can successfully resolve memory locations and register types, providing basic support for data type profiling on arm64 platforms.

Example Result
==============

# perf mem record -a -K -- sleep 1
# perf annotate --data-type --stdio --type-stat
Annotate data type stats:
total 1138, ok 852 (74.9%), bad 286 (25.1%)
-----------------------------------------------------------
         6 : no_sym
        42 : no_var
       230 : no_typeinfo
         8 : bad_offset
       213 : insn_track

Annotate type: 'struct page' in [kernel.kallsyms] (66948 samples):
============================================================================

 Percent     offset       size  field
  100.00          0       0x40  struct page      {
    9.01          0        0x8      long unsigned int   flags;
   57.99        0x8       0x28      union        {
   57.99        0x8       0x28          struct   {
   33.00        0x8       0x10              union        {
   33.00        0x8       0x10                  struct list_head        lru {
   33.00        0x8        0x8                      struct list_head*   next;
    0.00       0x10        0x8                      struct list_head*   prev;
                                                };
   33.00        0x8       0x10                  struct   {
   33.00        0x8        0x8                      void*       __filler;
    0.00       0x10        0x4                      unsigned int        mlock_count;
   ...

Each patch's type profiling results are as follows:

Patch | Feature | no_sym | no_var | no_typeinfo | bad_offset | insn_track | ok(%)
------+----------------------------+--------+--------+-------------+------------+------------+------

0011  | base (default spe period)  | 6      | 493    | -           | -          | -          | 56.2%
0013  | enable insn tracking       | 6      | 42     | 437         | 2          | 12         | 57.2%
0016  | support 'load' insn        | 6      | 42     | 398         | 1          | 52         | 60.7%
0017  | support 'store' insn       | 6      | 42     | 398         | 1          | 52         | 60.7%
0021  | support stack variable     | 6      | 42     | 391         | 1          | 59         | 61.3%
0022  | support 'mov' insn         | 6      | 42     | 372         | 3          | 76         | 62.8%
0023  | support 'add' insn         | 6      | 42     | 318         | 7          | 126        | 67.2%
0024  | support 'adrp' insn        | 6      | 42     | 238         | 8          | 205        | 74.2%
0025  | support per-cpu variable   | 6      | 42     | 233         | 8          | 210        | 74.6%
0026  | support 'mrs' insn         | 6      | 42     | 230         | 8          | 213        | 74.9%

Limitations
===========

* SIMD/FP & SVE Vector Support:
Data type profiling currently focuses on General-Purpose (GP) register operations. Vector/SIMD registers (v0-v31, d0-d31, q0-q31) and Scalable Vector Extension (SVE/SME) instructions are not tracked yet.

* Regular Instructions Only:
Only regular instructions are currently supported. Instructions following special rules or non-standard patterns are not supported yet. For example, load/store tracking only matches standard variants (ldr/ldur/ldar/ldp and str/stur/stlr/stp) with straightforward semantics; exclusive loads/stores (ldxr, stxr, etc.) and acquire/exclusive combination variants are excluded.

* Compiler Prologue/Epilogue Code:
As shown by the previous test results, approximately 14% of the failed type profiling results originate from compiler-generated prologue or epilogue code (e.g., ldp x19, x20, [sp, #16]). These instructions manage callee-saved registers across function boundaries, propagating type context through these operations requires inter-procedural (cross-function) instruction analysis, which is currently unsupported by the local backward instruction tracker.

Testing
=======

Tested on arm64 (all passed):

  # perf test -v "perf data type profiling tests"
  81: perf data type profiling tests                                                                  : Ok

=== Test Summary ===
Passed main tests : 1

  Passed subtests   : 0
  Skipped tests     : 0
  Failed tests      : 0

Tested on x86. The profiling results show no change before/after applying this patch series:

before : total 880, ok 711 (80.8%), bad 169 (19.2%) after : total 880, ok 711 (80.8%), bad 169 (19.2%)

Changelog
=========
v5 -> v6:
- v5: https://lore.kernel.org/all/[email protected]/
- Fix a potential UAF issue with capstone sym_name.
- Optimize __get_dwarf_regnum_arm64 to compute directly based on character judgment, avoiding strtol calls. (Ian Rogers)
- Fix an issue in extract_op_location_arm64 when parsing instructions such as

    'cmp x0, x1, lsl #3'.

- Fix several minor issues in instruction tracking. (Sashiko)
v4 -> v5:
- v4: https://lore.kernel.org/all/[email protected]/
- Introduce arch_get_reg_offset() to uniformly handle reg offset.
- Add support for parsing extension type and shift amount in arm64 instructions.
- Refine which instructions to track or skip. (Shuai Xue)
- Rename dont_overlap to default_single_event_per_ip and use it in itrace_synth_opts__set_default(). (Adrian Hunter)
- Introduce delete_stack_state() to clean up obsolete stack state. (Shuai Xue)
- Normalize arch__dwarf_regnum() error return values. (Shuai Xue)
- Drop canary support due to unresolved bugs.
- Fix various minor issues, such as name memory leaks and header includes.
v3 -> v4:
- v3: https://lore.kernel.org/all/[email protected]/
- Fix Capstone compilation failure.
- Stop adding new pcrel_adrp_addr in LLVM; reuse pcrel_load_addr instead.
- Fix parsing issue in arm64_mov__parse.
- Add PC-relative load instruction parsing logic to arm64_ldst__parse, and introduce rstrip_space_and_comment to strip comments.
- Remove wzr/xzr register parsing (not planning to handle this yet).
- Add post-index addressing mode parsing for the '[base], reg' format.
- Update built-in implementation of --itrace=i1i to deduplicate early during arm_spe_process_auxtrace_info.
- Restrict the "default period to 1" behavior to ARM SPE, instead of applying it to all architectures.
- Add register type tracking for function call instructions.
- Add dual-register type tracking for load pair and store pair instructions.
- Correct stack variable offset calculations.
- Add type invalidation upon retry failure.
- Reuse imm_value instead of introducing addr for 'adrp' instruction tracking.
- Fix potential stale type resolution errors caused by TSR_KIND_GLOBAL_ADDR and TSR_KIND_CONST during stack passing.
- Fix a strbuf memory leak during 'mrs' instruction tracking.
- Fix stale dieoff issue when debug info changes.
- Simplify add type propagation: only propagate offset/imm updates, leave type parsing to chk.
v2 -> v3:
- v2: https://lore.kernel.org/all/[email protected]/
- Instead of always parsing the left operand as src and the right operand as dst, set them based on the actual instruction definition. (Namhyung Kim)
- Fix refcount leak in print_capstone_detail().
- Remove useless '<' check when parsing 'addr <symbol>' in arm64_mov__parse().
- Add example comments in arm64_ldst__parse().
- Split arch__dwarf_regnum() changes into a separate commit.
- Rename annotated_addr_mode enum: INSN_ADDR_* -> PERF_ADDR_MODE_*.
- Set caller-saved registers in init_type_state().
- For instructions with addressing mode, always goto adjust_reg_index_state() at the end to update the src register state.
- Handle TSR_KIND_CONST registers for 'mov' and 'add' instructions.
- Invalidate dst register for all other unsupported instructions.
- Verify type DIE is task_struct pointer before caching globally.
- Enable --itrace=i1i by default for ARM SPE data type profiling in 'perf annotate' to avoid overlapping event counting for the same instruction. (James Clark)
- Fix global variable type resolving error in check_matching_type(). (James Clark)
- Address review comments from sashiko [1]:
- Fix unconditional call to arch->extract_op_location()
- Handle multi_regs correctly
- Fix invalid register state in error path
- Other misc fixes
v1 -> v2:
- v1: https://lore.kernel.org/all/[email protected]/
- Fix inconsistencies in arm64 instruction output across llvm, capstone, and objdump disassembly backends.
- Support arm64-specific addressing modes and operand formats. (Leo Yan)
- Extend instruction tracking to support mov and add instructions, along with per-cpu and stack variables.
- Include real-world examples in commit messages to demonstrate practical effects. (Namhyung Kim)
- Improve type-tracking success rate (type stat) from 64.2% to 82.1%.

Please let me know if you have any feedback.

Thanks,
Tengda

[1] https://sashiko.dev/#/patchset/20260403094800.1418825-1-wutengda%40huaweicloud.com
[2] https://developer.arm.com/documentation/102374/0103
[3] https://github.com/flynd/asmsheets/releases/tag/v8

Tengda Wu (26):
perf capstone: Symbolize address operands to match objdump on arm64 perf llvm: Fix arm64 adrp instruction disassembly mismatch with
objdump
perf annotate-arm64: Generalize arm64_mov__parse to support more
instructions
perf annotate-arm64: Handle load and store instructions perf annotate: Normalize arch__dwarf_regnum() error return values perf annotate: Introduce extract_op_location callback for
arch-specific parsing
perf dwarf-regs: Adapt get_dwarf_regnum() for arm64
perf annotate: Adapt arch__dwarf_regnum() for arm64
perf annotate-arm64: Implement extract_op_location() callback perf annotate: Default to --itrace=i1i for data type profiling perf arm-spe: Set default synthesized event period to 1 perf annotate-data: Extract invalidate_reg_state() as a common helper perf annotate-arm64: Enable instruction tracking support perf annotate-data: Add arch_get_reg_offset helper
perf annotate-arm64: Track return type after call instructions perf annotate-arm64: Support load instruction tracking perf annotate-arm64: Support store instruction tracking perf annotate-data: Expand type_state_reg imm_value to u64 perf annotate-data: Track imm_value for stack variables perf annotate-x86: Delete stale stack state on store of untracked
register
perf annotate-arm64: Support stack variable tracking
perf annotate-arm64: Support 'mov' instruction tracking perf annotate-arm64: Support 'add' instruction tracking perf annotate-arm64: Support 'adrp' instruction to track global
variables
perf annotate-arm64: Support per-cpu variable access tracking perf annotate-arm64: Support 'mrs' instruction to track 'current'
pointer

 tools/perf/builtin-annotate.c                 |    8 +
 .../perf/util/annotate-arch/annotate-arm64.c  | 1211 ++++++++++++++++-
 .../util/annotate-arch/annotate-powerpc.c     |    9 +
 tools/perf/util/annotate-arch/annotate-x86.c  |  117 +-
 tools/perf/util/annotate-data.c               |  205 ++-
 tools/perf/util/annotate-data.h               |   14 +-
 tools/perf/util/annotate.c                    |  108 +-
 tools/perf/util/annotate.h                    |   63 +
 tools/perf/util/arm-spe.c                     |   16 +-
 tools/perf/util/auxtrace.c                    |   12 +-
 tools/perf/util/auxtrace.h                    |    7 +-
 tools/perf/util/capstone.c                    |  186 ++-
 tools/perf/util/cs-etm.c                      |    2 +-
 tools/perf/util/disasm.c                      |    5 +
 tools/perf/util/disasm.h                      |    5 +
 .../util/dwarf-regs-arch/dwarf-regs-arm64.c   |   25 +
 tools/perf/util/dwarf-regs.c                  |    2 +-
 tools/perf/util/include/dwarf-regs.h          |    1 +
 tools/perf/util/intel-bts.c                   |    2 +-
 tools/perf/util/intel-pt.c                    |    3 +-
 tools/perf/util/llvm.c                        |   52 +-
 21 files changed, 1831 insertions(+), 222 deletions(-)

base-commit: e6e35979777d646fe3c7c94dca7dd32fb25d45f4

Signature · 1 line
2.34.1
author
#69 lore raw

Hi Arnaldo,

Quoted 68 lines · Arnaldo Carvalho de Melo
Quoted 50 lines · Tengda Wu
Quoted 47 lines · Namhyung Kim

Hello,

Quoted 43 lines · Tengda Wu

This patch series implements data type profiling support for arm64, enabling 'perf annotate --data-type' to resolve memory locations and variable types on arm64 platforms.

The main changes since v5 include:
v5: https://lore.kernel.org/all/[email protected]/

* __get_dwarf_regnum_arm64 optimization: compute directly based on character judgment, avoiding strtol calls. (Ian Rogers)

* extract_op_location_arm64 issue fix: support parsing instructions such as 'cmp x0, x1, lsl #3'.

* Instruction tracking part, fixing issues pointed out by Sashiko, including:
- adding TSR_KIND_POINTER handling for ldr.
- premature returns in add and ldr causing percpu handling to be skipped.
- adding a reg != -1 check for stack type propagation, etc.

Patch organization
==================

The series is organized as follows:

1. Fix disassembly mismatches (Patches 01-02)
Current perf annotate supports three disassembly backends: llvm, capstone, and objdump. On arm64, inconsistencies between the output of these backends (specifically llvm/capstone vs. objdump) often prevent the tracker from correctly identifying registers and offsets. These patches resolve these mismatches, ensuring consistent instruction parsing across all supported backends.

2. Infrastructure for arm64 operand parsing (Patches 03-09) These patches establish the necessary infrastructure for arm64-specific operand handling. This includes implementing new callbacks and data structures to manage arm64's unique addressing modes and register sets. This foundation is essential for the subsequent type-tracking logic.

3. ARM SPE event handling (Patches 10-11)
Patch 10 automatically deduplicates overlapping ARM SPE events (e.g., l1d-miss, tlb-access) in 'perf annotate' by retaining only the "instructions" event when data type profiling is enabled. Patch 11 defaults the synthesized event period to 1 for ARM SPE to fix zero 'Percent' values in annotate output.

Quoted 8 lines
Quoted 6 lines

Thanks for working on this!

I think it's ready to merge up to this point. The instruction tracking part needs more review. I'll do that later but it'd be nice if ARM folks and Shuai could review it too.

Yes, patches 01-11 are already ready.

I had to just do a minor fixup:

util/annotate-arch/annotate-arm64.c: In function ‘extract_op_location_arm64’:
util/annotate-arch/annotate-arm64.c:385:19: error: assignment discards ‘const’ qualifier from pointer target type [-Werror=discarded-qualifiers]
385 | p = strchr(s, ']');
| ^
cc1: all warnings being treated as errors

Quoted 6 lines

That 'p' var can't be made const so I casted the return of strchr instead, one-liner.

Thanks, applied to perf-tools-next, for v7.4.

- Arnaldo

Thanks for applying 01–11. Glad those are in.

Cheers,
Tengda

Quoted 271 lines
Quoted 271 lines

For the instruction tracking part (patches 12-26), apart from the issues with patches 21 and 25 mentioned by Sashiko, the rest is also ready for review.

Regarding patches 21 and 25, I've posted the fixes I plan to make under the respective patches. Please let me know if there are any concerns.

Looking forward to your review.

Best regards,
Tengda

Quoted 258 lines

Thanks,
Namhyung

Quoted 255 lines

4. Core instruction tracking (Patches 12-26)
These patches implement the core logic for type tracking on arm64, covering several key types of instructions, including:
* Memory Access: ldr/str variants (including stack-based access).
* Arithmetic & Data Processing: mov, add, and adrp.
* Special Access: System register access (mrs) and per-cpu variable tracking.

The implementation draws inspiration from the existing x86 logic while adapting it to the nuances of the AArch64 ISA [2][3]. With these changes, perf annotate can successfully resolve memory locations and register types, providing basic support for data type profiling on arm64 platforms.

Example Result
==============

# perf mem record -a -K -- sleep 1
# perf annotate --data-type --stdio --type-stat
Annotate data type stats:
total 1138, ok 852 (74.9%), bad 286 (25.1%)
-----------------------------------------------------------
         6 : no_sym
        42 : no_var
       230 : no_typeinfo
         8 : bad_offset
       213 : insn_track

Annotate type: 'struct page' in [kernel.kallsyms] (66948 samples):
============================================================================

 Percent     offset       size  field
  100.00          0       0x40  struct page      {
    9.01          0        0x8      long unsigned int   flags;
   57.99        0x8       0x28      union        {
   57.99        0x8       0x28          struct   {
   33.00        0x8       0x10              union        {
   33.00        0x8       0x10                  struct list_head        lru {
   33.00        0x8        0x8                      struct list_head*   next;
    0.00       0x10        0x8                      struct list_head*   prev;
                                                };
   33.00        0x8       0x10                  struct   {
   33.00        0x8        0x8                      void*       __filler;
    0.00       0x10        0x4                      unsigned int        mlock_count;
   ...

Each patch's type profiling results are as follows:

Patch | Feature | no_sym | no_var | no_typeinfo | bad_offset | insn_track | ok(%)
------+----------------------------+--------+--------+-------------+------------+------------+------

0011  | base (default spe period)  | 6      | 493    | -           | -          | -          | 56.2%
0013  | enable insn tracking       | 6      | 42     | 437         | 2          | 12         | 57.2%
0016  | support 'load' insn        | 6      | 42     | 398         | 1          | 52         | 60.7%
0017  | support 'store' insn       | 6      | 42     | 398         | 1          | 52         | 60.7%
0021  | support stack variable     | 6      | 42     | 391         | 1          | 59         | 61.3%
0022  | support 'mov' insn         | 6      | 42     | 372         | 3          | 76         | 62.8%
0023  | support 'add' insn         | 6      | 42     | 318         | 7          | 126        | 67.2%
0024  | support 'adrp' insn        | 6      | 42     | 238         | 8          | 205        | 74.2%
0025  | support per-cpu variable   | 6      | 42     | 233         | 8          | 210        | 74.6%
0026  | support 'mrs' insn         | 6      | 42     | 230         | 8          | 213        | 74.9%

Limitations
===========

* SIMD/FP & SVE Vector Support:
Data type profiling currently focuses on General-Purpose (GP) register operations. Vector/SIMD registers (v0-v31, d0-d31, q0-q31) and Scalable Vector Extension (SVE/SME) instructions are not tracked yet.

* Regular Instructions Only:
Only regular instructions are currently supported. Instructions following special rules or non-standard patterns are not supported yet. For example, load/store tracking only matches standard variants (ldr/ldur/ldar/ldp and str/stur/stlr/stp) with straightforward semantics; exclusive loads/stores (ldxr, stxr, etc.) and acquire/exclusive combination variants are excluded.

* Compiler Prologue/Epilogue Code:
As shown by the previous test results, approximately 14% of the failed type profiling results originate from compiler-generated prologue or epilogue code (e.g., ldp x19, x20, [sp, #16]). These instructions manage callee-saved registers across function boundaries, propagating type context through these operations requires inter-procedural (cross-function) instruction analysis, which is currently unsupported by the local backward instruction tracker.

Testing
=======

Tested on arm64 (all passed):

  # perf test -v "perf data type profiling tests"
  81: perf data type profiling tests                                                                  : Ok

=== Test Summary ===
Passed main tests : 1

  Passed subtests   : 0
  Skipped tests     : 0
  Failed tests      : 0

Tested on x86. The profiling results show no change before/after applying this patch series:

before : total 880, ok 711 (80.8%), bad 169 (19.2%) after : total 880, ok 711 (80.8%), bad 169 (19.2%)

Changelog
=========
v5 -> v6:
- v5: https://lore.kernel.org/all/[email protected]/
- Fix a potential UAF issue with capstone sym_name.
- Optimize __get_dwarf_regnum_arm64 to compute directly based on character judgment, avoiding strtol calls. (Ian Rogers)
- Fix an issue in extract_op_location_arm64 when parsing instructions such as

    'cmp x0, x1, lsl #3'.

- Fix several minor issues in instruction tracking. (Sashiko)
v4 -> v5:
- v4: https://lore.kernel.org/all/[email protected]/
- Introduce arch_get_reg_offset() to uniformly handle reg offset.
- Add support for parsing extension type and shift amount in arm64 instructions.
- Refine which instructions to track or skip. (Shuai Xue)
- Rename dont_overlap to default_single_event_per_ip and use it in itrace_synth_opts__set_default(). (Adrian Hunter)
- Introduce delete_stack_state() to clean up obsolete stack state. (Shuai Xue)
- Normalize arch__dwarf_regnum() error return values. (Shuai Xue)
- Drop canary support due to unresolved bugs.
- Fix various minor issues, such as name memory leaks and header includes.
v3 -> v4:
- v3: https://lore.kernel.org/all/[email protected]/
- Fix Capstone compilation failure.
- Stop adding new pcrel_adrp_addr in LLVM; reuse pcrel_load_addr instead.
- Fix parsing issue in arm64_mov__parse.
- Add PC-relative load instruction parsing logic to arm64_ldst__parse, and introduce rstrip_space_and_comment to strip comments.
- Remove wzr/xzr register parsing (not planning to handle this yet).
- Add post-index addressing mode parsing for the '[base], reg' format.
- Update built-in implementation of --itrace=i1i to deduplicate early during arm_spe_process_auxtrace_info.
- Restrict the "default period to 1" behavior to ARM SPE, instead of applying it to all architectures.
- Add register type tracking for function call instructions.
- Add dual-register type tracking for load pair and store pair instructions.
- Correct stack variable offset calculations.
- Add type invalidation upon retry failure.
- Reuse imm_value instead of introducing addr for 'adrp' instruction tracking.
- Fix potential stale type resolution errors caused by TSR_KIND_GLOBAL_ADDR and TSR_KIND_CONST during stack passing.
- Fix a strbuf memory leak during 'mrs' instruction tracking.
- Fix stale dieoff issue when debug info changes.
- Simplify add type propagation: only propagate offset/imm updates, leave type parsing to chk.
v2 -> v3:
- v2: https://lore.kernel.org/all/[email protected]/
- Instead of always parsing the left operand as src and the right operand as dst, set them based on the actual instruction definition. (Namhyung Kim)
- Fix refcount leak in print_capstone_detail().
- Remove useless '<' check when parsing 'addr <symbol>' in arm64_mov__parse().
- Add example comments in arm64_ldst__parse().
- Split arch__dwarf_regnum() changes into a separate commit.
- Rename annotated_addr_mode enum: INSN_ADDR_* -> PERF_ADDR_MODE_*.
- Set caller-saved registers in init_type_state().
- For instructions with addressing mode, always goto adjust_reg_index_state() at the end to update the src register state.
- Handle TSR_KIND_CONST registers for 'mov' and 'add' instructions.
- Invalidate dst register for all other unsupported instructions.
- Verify type DIE is task_struct pointer before caching globally.
- Enable --itrace=i1i by default for ARM SPE data type profiling in 'perf annotate' to avoid overlapping event counting for the same instruction. (James Clark)
- Fix global variable type resolving error in check_matching_type(). (James Clark)
- Address review comments from sashiko [1]:
- Fix unconditional call to arch->extract_op_location()
- Handle multi_regs correctly
- Fix invalid register state in error path
- Other misc fixes
v1 -> v2:
- v1: https://lore.kernel.org/all/[email protected]/
- Fix inconsistencies in arm64 instruction output across llvm, capstone, and objdump disassembly backends.
- Support arm64-specific addressing modes and operand formats. (Leo Yan)
- Extend instruction tracking to support mov and add instructions, along with per-cpu and stack variables.
- Include real-world examples in commit messages to demonstrate practical effects. (Namhyung Kim)
- Improve type-tracking success rate (type stat) from 64.2% to 82.1%.

Please let me know if you have any feedback.

Thanks,
Tengda

[1] https://sashiko.dev/#/patchset/20260403094800.1418825-1-wutengda%40huaweicloud.com
[2] https://developer.arm.com/documentation/102374/0103
[3] https://github.com/flynd/asmsheets/releases/tag/v8

Tengda Wu (26):
perf capstone: Symbolize address operands to match objdump on arm64 perf llvm: Fix arm64 adrp instruction disassembly mismatch with
objdump
perf annotate-arm64: Generalize arm64_mov__parse to support more
instructions
perf annotate-arm64: Handle load and store instructions perf annotate: Normalize arch__dwarf_regnum() error return values perf annotate: Introduce extract_op_location callback for
arch-specific parsing
perf dwarf-regs: Adapt get_dwarf_regnum() for arm64
perf annotate: Adapt arch__dwarf_regnum() for arm64
perf annotate-arm64: Implement extract_op_location() callback perf annotate: Default to --itrace=i1i for data type profiling perf arm-spe: Set default synthesized event period to 1 perf annotate-data: Extract invalidate_reg_state() as a common helper perf annotate-arm64: Enable instruction tracking support perf annotate-data: Add arch_get_reg_offset helper
perf annotate-arm64: Track return type after call instructions perf annotate-arm64: Support load instruction tracking perf annotate-arm64: Support store instruction tracking perf annotate-data: Expand type_state_reg imm_value to u64 perf annotate-data: Track imm_value for stack variables perf annotate-x86: Delete stale stack state on store of untracked
register
perf annotate-arm64: Support stack variable tracking
perf annotate-arm64: Support 'mov' instruction tracking perf annotate-arm64: Support 'add' instruction tracking perf annotate-arm64: Support 'adrp' instruction to track global
variables
perf annotate-arm64: Support per-cpu variable access tracking perf annotate-arm64: Support 'mrs' instruction to track 'current'
pointer

 tools/perf/builtin-annotate.c                 |    8 +
 .../perf/util/annotate-arch/annotate-arm64.c  | 1211 ++++++++++++++++-
 .../util/annotate-arch/annotate-powerpc.c     |    9 +
 tools/perf/util/annotate-arch/annotate-x86.c  |  117 +-
 tools/perf/util/annotate-data.c               |  205 ++-
 tools/perf/util/annotate-data.h               |   14 +-
 tools/perf/util/annotate.c                    |  108 +-
 tools/perf/util/annotate.h                    |   63 +
 tools/perf/util/arm-spe.c                     |   16 +-
 tools/perf/util/auxtrace.c                    |   12 +-
 tools/perf/util/auxtrace.h                    |    7 +-
 tools/perf/util/capstone.c                    |  186 ++-
 tools/perf/util/cs-etm.c                      |    2 +-
 tools/perf/util/disasm.c                      |    5 +
 tools/perf/util/disasm.h                      |    5 +
 .../util/dwarf-regs-arch/dwarf-regs-arm64.c   |   25 +
 tools/perf/util/dwarf-regs.c                  |    2 +-
 tools/perf/util/include/dwarf-regs.h          |    1 +
 tools/perf/util/intel-bts.c                   |    2 +-
 tools/perf/util/intel-pt.c                    |    3 +-
 tools/perf/util/llvm.c                        |   52 +-
 21 files changed, 1831 insertions(+), 222 deletions(-)

base-commit: e6e35979777d646fe3c7c94dca7dd32fb25d45f4

Signature · 1 line
2.34.1