Re: [PATCH v3 19/21] perf annotate-arm64: Support 'adrp' instruction to track global variables
From: Namhyung Kim
Date: Thu Jul 09 2026 - 03:45:55 EST
On Wed, Jul 01, 2026 at 03:53:53AM +0000, Tengda Wu wrote:
> Extend update_insn_state() for 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*'
>
> Signed-off-by: Li Huafei <lihuafei1@xxxxxxxxxx>
> Signed-off-by: Tengda Wu <wutengda@xxxxxxxxxxxxxxx>
> ---
> .../perf/util/annotate-arch/annotate-arm64.c | 89 +++++++++++++++++--
> tools/perf/util/annotate-arch/annotate-x86.c | 6 +-
> tools/perf/util/annotate-data.c | 33 +++++--
> tools/perf/util/annotate-data.h | 7 +-
> 4 files changed, 121 insertions(+), 14 deletions(-)
>
> diff --git a/tools/perf/util/annotate-arch/annotate-arm64.c b/tools/perf/util/annotate-arch/annotate-arm64.c
> index 1fed18811719..6f96e75d313d 100644
> --- a/tools/perf/util/annotate-arch/annotate-arm64.c
> +++ b/tools/perf/util/annotate-arch/annotate-arm64.c
> @@ -7,6 +7,7 @@
> #include <linux/string.h>
> #include <linux/ctype.h>
> #include <regex.h>
> +#include <inttypes.h>
> #include "../annotate.h"
> #include "../disasm.h"
> #include "../annotate-data.h"
> @@ -359,7 +360,7 @@ static void adjust_reg_index_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)
> @@ -402,6 +403,7 @@ static void update_load_insn_state(struct type_state *state,
> tsr->type = stack->type;
> tsr->kind = stack->kind;
> tsr->offset = stack->ptr_offset;
> + tsr->addr = stack->addr;
> tsr->ok = true;
> } else if (die_get_member_type(&stack->type,
> offset - stack->offset,
> @@ -409,6 +411,7 @@ static void update_load_insn_state(struct type_state *state,
> tsr->type = type_die;
> tsr->kind = TSR_KIND_TYPE;
> tsr->offset = 0;
> + tsr->addr = 0;
> tsr->ok = true;
> } else {
> invalidate_reg_state(tsr);
> @@ -441,6 +444,7 @@ static void update_load_insn_state(struct type_state *state,
> tsr->type = type_die;
> tsr->kind = TSR_KIND_TYPE;
> tsr->offset = 0;
> + tsr->addr = 0;
> tsr->ok = true;
>
> if (src->multi_regs) {
> @@ -453,6 +457,33 @@ static void update_load_insn_state(struct type_state *state,
> }
> pr_debug_type_name(&tsr->type, tsr->kind);
> }
> + /* Or check if it's a global variable */
> + else if (src_tsr.kind == TSR_KIND_GLOBAL_ADDR) {
> + u64 ip = dloc->ms->sym->start + dl->al.offset;
> + u64 addr = src_tsr.addr + 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)) {
> + invalidate_reg_state(tsr);
> + goto out_adjust;
> + }
> +
> + tsr->type = type_die;
> + tsr->kind = TSR_KIND_TYPE;
> + tsr->offset = 0;
> + tsr->addr = 0;
> + tsr->ok = true;
> +
> + if (src->multi_regs) {
> + pr_debug_dtp("ldr [%x] global (reg%d, reg%d) -> reg%d",
> + insn_offset, src->reg1, src->reg2, dreg);
> + } else {
> + pr_debug_dtp("ldr [%x] global (reg%d) -> reg%d",
> + insn_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;
> @@ -498,10 +529,10 @@ static void update_store_insn_state(struct type_state *state,
> if (stack) {
> if (!stack->compound)
> set_stack_state(stack, offset, tsr->kind,
> - &tsr->type, tsr->offset);
> + &tsr->type, tsr->offset, tsr->addr);
> } else {
> findnew_stack_state(state, offset, tsr->kind,
> - &tsr->type, tsr->offset);
> + &tsr->type, tsr->offset, tsr->addr);
> }
>
> if (dst->reg1 == fbreg) {
> @@ -547,6 +578,7 @@ static void update_mov_insn_state(struct type_state *state,
> tsr->kind = TSR_KIND_CONST;
> tsr->imm_value = src->offset;
> tsr->offset = 0;
> + tsr->addr = 0;
> tsr->ok = true;
>
> pr_debug_dtp("mov [%x] imm=%#x -> reg%d\n",
> @@ -563,6 +595,7 @@ static void update_mov_insn_state(struct type_state *state,
> tsr->kind = state->regs[sreg].kind;
> tsr->imm_value = state->regs[sreg].imm_value;
> tsr->offset = state->regs[sreg].offset;
> + tsr->addr = state->regs[sreg].addr;
> tsr->ok = state->regs[sreg].ok;
>
> if (tsr->kind == TSR_KIND_TYPE || tsr->kind == TSR_KIND_POINTER)
> @@ -641,6 +674,7 @@ static void update_add_insn_state(struct type_state *state,
> tsr->type = src_tsr.type;
> tsr->kind = src_tsr.kind;
> tsr->offset = offset;
> + tsr->addr = 0;
> tsr->ok = src_tsr.ok;
>
> pr_debug_dtp("add [%x] address of %s%#x(reg%d) -> reg%d",
> @@ -649,6 +683,16 @@ static void update_add_insn_state(struct type_state *state,
>
> pr_debug_type_name(&tsr->type, tsr->kind);
> }
> + /* Handle page-relative global address calculation */
> + else if (src_tsr.kind == TSR_KIND_GLOBAL_ADDR) {
> + tsr->kind = src_tsr.kind;
> + tsr->addr = src_tsr.addr + reg_offset;
> + tsr->offset = 0;
> + tsr->ok = true;
> +
> + pr_debug_dtp("add [%x] global %#x(reg%d) -> reg%d\n",
> + insn_offset, reg_offset, sreg, dreg);
> + }
> /* Or try another register if any */
> else if (src->multi_regs && src->reg1 != src->reg2 && sreg != src->reg2) {
> sreg = src->reg2;
> @@ -656,8 +700,37 @@ static void update_add_insn_state(struct type_state *state,
> }
> }
>
> +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;
I was wondering if it could use TSR_KIND_CONST but imm_value is u32.
Anyway, probably we need to increase it to u64 to handle 64-bit
immediate values regardless of this.
Thanks,
Namhyung
> + /* Partial page-relative address, finalized in next 'add/ldr' */
> + tsr->addr = dl->ops.source.addr;
> + tsr->offset = 0;
> + tsr->ok = true;
> +
> + pr_debug_dtp("adrp [%x] global addr=%#"PRIx64" -> reg%d\n",
> + insn_offset, tsr->addr, dreg);
> +}
> +
> 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;
> @@ -673,6 +746,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, "adrp") &&
> 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",
> @@ -681,6 +755,11 @@ static void update_insn_state_arm64(struct type_state *state,
> return;
> }
>
> + if (!strcmp(dl->ins.name, "adrp")) {
> + update_adrp_insn_state(state, dl, dst);
> + return;
> + }
> +
> if (!strcmp(dl->ins.name, "add")) {
> update_add_insn_state(state, dl, src, dst);
> return;
> @@ -694,7 +773,7 @@ static void update_insn_state_arm64(struct type_state *state,
>
> /* Memory to register transfers */
> if (!strncmp(dl->ins.name, "ld", 2)) {
> - update_load_insn_state(state, dloc, dl, src, dst);
> + update_load_insn_state(state, dloc, cu_die, dl, src, dst);
> return;
> }
>
> diff --git a/tools/perf/util/annotate-arch/annotate-x86.c b/tools/perf/util/annotate-arch/annotate-x86.c
> index ef84b9593a39..9977ca079143 100644
> --- a/tools/perf/util/annotate-arch/annotate-x86.c
> +++ b/tools/perf/util/annotate-arch/annotate-x86.c
> @@ -772,10 +772,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->addr);
> } else {
> findnew_stack_state(state, offset, tsr->kind,
> - &tsr->type, tsr->offset);
> + &tsr->type, tsr->offset,
> + tsr->addr);
> }
>
> if (dst->reg1 == fbreg) {
> diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c
> index c51b20a7af9b..6fa5cd373a46 100644
> --- a/tools/perf/util/annotate-data.c
> +++ b/tools/perf/util/annotate-data.c
> @@ -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;
> @@ -590,7 +593,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 addr)
> {
> int tag;
> Dwarf_Word size;
> @@ -607,6 +610,7 @@ 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->addr = addr;
>
> if (kind == TSR_KIND_POINTER) {
> stack->compound = false;
> @@ -629,18 +633,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 addr)
> {
> 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, addr);
> 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, addr);
> list_add(&stack->list, &state->stack_vars);
> }
> return stack;
> @@ -935,7 +939,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, /*addr=*/0);
>
> if (var->reg == state->stack_reg) {
> pr_debug_dtp("var [%"PRIx64"] %#x(reg%d)",
> @@ -1278,6 +1282,25 @@ static enum type_match_result check_matching_type(struct type_state *state,
> if (dloc->op->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].addr + dloc->op->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;
> diff --git a/tools/perf/util/annotate-data.h b/tools/perf/util/annotate-data.h
> index 453e13bbe3e2..a3e1ef7097f0 100644
> --- a/tools/perf/util/annotate-data.h
> +++ b/tools/perf/util/annotate-data.h
> @@ -37,6 +37,7 @@ enum type_state_kind {
> TSR_KIND_PERCPU_POINTER,
> TSR_KIND_POINTER,
> TSR_KIND_CANARY,
> + TSR_KIND_GLOBAL_ADDR,
> };
>
> /**
> @@ -187,6 +188,7 @@ struct type_state_reg {
> u64 lifetime_end;
> u8 kind;
> u8 copied_from;
> + u64 addr;
> };
>
> /* Type information in a stack location, dynamically allocated */
> @@ -199,6 +201,7 @@ struct type_state_stack {
> int size;
> bool compound;
> u8 kind;
> + u64 addr;
> };
>
> /*
> @@ -253,9 +256,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 addr);
> 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 addr);
> struct type_state_stack *find_stack_state(struct type_state *state,
> int offset);
> void invalidate_reg_state(struct type_state_reg *reg);
> --
> 2.34.1
>