[PATCH v3 18/21] perf annotate-arm64: Support 'add' instruction tracking

From: Tengda Wu

Date: Wed Jul 01 2026 - 00:01:10 EST


Extend update_insn_state() for arm64 to track 'add' instructions for
structure member address calculation, which commonly appear as:

add dreg, base, #offset
add dreg, base, reg2 (reg2 holds a constant)

For such instructions, determine the final offset value from either the
immediate or the constant in reg2. If the base register contains a
pointer or structure type, compute the resulting offset and invoke
die_get_member_type() to verify it corresponds to a valid member within
that type. On success, update the destination register's type state with
the new offset while retaining the base type information.

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'

Signed-off-by: Tengda Wu <wutengda@xxxxxxxxxxxxxxx>
---
.../perf/util/annotate-arch/annotate-arm64.c | 91 ++++++++++++++++++-
1 file changed, 90 insertions(+), 1 deletion(-)

diff --git a/tools/perf/util/annotate-arch/annotate-arm64.c b/tools/perf/util/annotate-arch/annotate-arm64.c
index be07a85057c8..1fed18811719 100644
--- a/tools/perf/util/annotate-arch/annotate-arm64.c
+++ b/tools/perf/util/annotate-arch/annotate-arm64.c
@@ -572,6 +572,90 @@ 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 disasm_line *dl,
+ struct annotated_op_loc *src,
+ struct annotated_op_loc *dst)
+{
+ struct type_state_reg *tsr;
+ struct type_state_reg src_tsr;
+ Dwarf_Die type_die;
+ 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;
+
+retry:
+ if (!has_reg_type(state, sreg) || !state->regs[sreg].ok) {
+ invalidate_reg_state(tsr);
+ return;
+ }
+
+ src_tsr = state->regs[sreg];
+
+ /*
+ * Handle 'add' instructions of the form:
+ * add dreg, base, #offset (immediate offset)
+ * add dreg, base, reg2 (reg2 holds a constant)
+ *
+ * For case 2, retrieve the constant value from reg2
+ * and use it as the offset.
+ */
+ reg_offset = src->offset;
+ if (src->multi_regs) {
+ int reg2 = (sreg == src->reg1) ? src->reg2 : src->reg1;
+
+ if (has_reg_type(state, reg2) && state->regs[reg2].ok &&
+ state->regs[reg2].kind == TSR_KIND_CONST)
+ reg_offset = state->regs[reg2].imm_value;
+ }
+
+ /* Handle calculation of a register holding a typed pointer */
+ if (src_tsr.kind == TSR_KIND_POINTER ||
+ (src_tsr.kind == TSR_KIND_TYPE &&
+ dwarf_tag(&src_tsr.type) == DW_TAG_pointer_type)) {
+ s32 offset;
+
+ if (src_tsr.kind == TSR_KIND_TYPE &&
+ __die_get_real_type(&src_tsr.type, &type_die) == NULL) {
+ invalidate_reg_state(tsr);
+ return;
+ }
+
+ if (src_tsr.kind == TSR_KIND_POINTER)
+ type_die = src_tsr.type;
+
+ /* Check if the target type has a member at the new offset */
+ offset = reg_offset + src_tsr.offset;
+ if (die_get_member_type(&type_die, offset, &type_die) == NULL) {
+ invalidate_reg_state(tsr);
+ return;
+ }
+
+ tsr->type = src_tsr.type;
+ tsr->kind = src_tsr.kind;
+ tsr->offset = offset;
+ tsr->ok = src_tsr.ok;
+
+ pr_debug_dtp("add [%x] address of %s%#x(reg%d) -> reg%d",
+ insn_offset, reg_offset < 0 ? "-" : "",
+ abs(reg_offset), sreg, dreg);
+
+ pr_debug_type_name(&tsr->type, tsr->kind);
+ }
+ /* Or try another register if any */
+ else if (src->multi_regs && src->reg1 != src->reg2 && sreg != src->reg2) {
+ sreg = src->reg2;
+ goto retry;
+ }
+}
+
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)
@@ -589,7 +673,7 @@ static void update_insn_state_arm64(struct type_state *state,
* the destination register itself to prevent incorrect type propagation.
*/
if (has_reg_type(state, dst->reg1) &&
- strcmp(dl->ins.name, "mov") &&
+ strcmp(dl->ins.name, "add") && strcmp(dl->ins.name, "mov") &&
strncmp(dl->ins.name, "ld", 2) && strncmp(dl->ins.name, "st", 2)) {
pr_debug_dtp("%s [%x] invalidate reg%d\n",
dl->ins.name, insn_offset, dst->reg1);
@@ -597,6 +681,11 @@ static void update_insn_state_arm64(struct type_state *state,
return;
}

+ if (!strcmp(dl->ins.name, "add")) {
+ update_add_insn_state(state, dl, src, dst);
+ return;
+ }
+
/* Register to register or imm value to register transfers */
if (!strcmp(dl->ins.name, "mov")) {
update_mov_insn_state(state, dl, src, dst);
--
2.34.1