Re: [PATCH v3 20/21] perf annotate-arm64: Support per-cpu variable access tracking
Namhyung Kim <[email protected]>
| Newsgroups | dev.linux.lists.llvm,org.kernel.vger.linux-kernel,org.kernel.vger.linux-perf-users |
|---|---|
| Message-ID | <ak9N0CY1sAtGt47I@z2> |
On Wed, Jul 01, 2026 at 03:53:54AM +0000, Tengda Wu wrote: > Extend update_insn_state() for 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 local variable 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: local variable with '__percpu' modifier > ldr x1, [x25, #32] // x1 = local_percpu_ptr > > To handle such cases: > > 1. Identify per-cpu base initialization: Detect 'adrp+add' 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(cpu_pcc_subspace_idx, cpu) > > 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 > > Signed-off-by: Tengda Wu <[email protected]> > --- > .../perf/util/annotate-arch/annotate-arm64.c | 60 ++++++++++++++++++- > tools/perf/util/annotate-data.c | 24 +++++++- > 2 files changed, 82 insertions(+), 2 deletions(-) > > diff --git a/tools/perf/util/annotate-arch/annotate-arm64.c b/tools/perf/util/annotate-arch/annotate-arm64.c > index 6f96e75d313d..ec6fd59d51a2 100644 > --- a/tools/perf/util/annotate-arch/annotate-arm64.c > +++ b/tools/perf/util/annotate-arch/annotate-arm64.c > @@ -462,6 +462,15 @@ static void update_load_insn_state(struct type_state *state, > u64 ip = dloc->ms->sym->start + dl->al.offset; > u64 addr = src_tsr.addr + reg_offset; > int offset; > + u8 kind; > + const char *var_name = NULL; > + > + /* it might be per-cpu offset */ > + if (get_global_var_info(dloc, addr, &var_name, &offset) && > + !strcmp(var_name, "__per_cpu_offset")) > + kind = TSR_KIND_PERCPU_BASE; It seems you need to check if the target DSO is a kernel or a module by calling `dso__kernel(map__dso(dloc->ms->map))` first. Thanks, Namhyung > + 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)) { > @@ -470,7 +479,7 @@ static void update_load_insn_state(struct type_state *state, > } > > tsr->type = type_die; > - tsr->kind = TSR_KIND_TYPE; > + tsr->kind = kind; > tsr->offset = 0; > tsr->addr = 0; > tsr->ok = true; > @@ -484,6 +493,28 @@ static void update_load_insn_state(struct type_state *state, > } > pr_debug_type_name(&tsr->type, tsr->kind); > } > + /* Or check if it's a per-cpu access */ > + else if (src_tsr.kind == TSR_KIND_PERCPU_BASE) { > + int reg2; > + > + if (!src->multi_regs || src->reg1 == src->reg2 || > + sreg == src->reg2 /* retried */) { > + invalidate_reg_state(tsr); > + goto out_adjust; > + } > + > + reg2 = src->reg2; > + if (!has_reg_type(state, reg2) || !state->regs[reg2].ok || > + (state->regs[reg2].kind != TSR_KIND_GLOBAL_ADDR && > + state->regs[reg2].kind != TSR_KIND_TYPE)) { > + invalidate_reg_state(tsr); > + goto out_adjust; > + } else { > + /* Treat percpu as array: resolve type from reg2 */ > + sreg = src->reg2; > + goto retry; > + } > + } > /* Or try another register if any */ > else if (src->multi_regs && src->reg1 != src->reg2 && sreg != src->reg2) { > sreg = src->reg2; > @@ -693,6 +724,33 @@ static void update_add_insn_state(struct type_state *state, > pr_debug_dtp("add [%x] global %#x(reg%d) -> reg%d\n", > insn_offset, reg_offset, sreg, dreg); > } > + /* Handle per-cpu base address calculation for per-cpu variables */ > + else if (src_tsr.kind == TSR_KIND_PERCPU_BASE) { > + /* > + * A per-cpu base acts like an array index. Adding it to > + * a global variable or typed register should preserve > + * the original variable's type. Inherit the type from > + * the typed register. > + */ > + if (!src->multi_regs || !has_reg_type(state, src->reg2) || > + !state->regs[src->reg2].ok || > + (state->regs[src->reg2].kind != TSR_KIND_GLOBAL_ADDR && > + state->regs[src->reg2].kind != TSR_KIND_TYPE)) { > + invalidate_reg_state(tsr); > + return; > + } > + > + tsr->type = state->regs[src->reg2].type; > + tsr->kind = state->regs[src->reg2].kind; > + tsr->offset = state->regs[src->reg2].offset; > + tsr->addr = state->regs[src->reg2].addr; > + tsr->ok = state->regs[src->reg2].ok; > + > + pr_debug_dtp("add [%x] percpu (reg%d) -> reg%d", > + insn_offset, sreg, dreg); > + pr_debug_type_name(&tsr->type, tsr->kind); > + return; > + } > /* Or try another register if any */ > else if (src->multi_regs && src->reg1 != src->reg2 && sreg != src->reg2) { > sreg = src->reg2; > diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c > index 6fa5cd373a46..b0bf0437168b 100644 > --- a/tools/perf/util/annotate-data.c > +++ b/tools/perf/util/annotate-data.c > @@ -1252,11 +1252,33 @@ 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; > int var_offset; > > pr_debug_dtp("percpu var"); > > + if (arch__is_arm64(dloc->arch)) { > + int reg2; > + > + if (!dloc->op->multi_regs || > + dloc->op->reg1 == dloc->op->reg2 || !retry) > + return PERF_TMR_BAIL_OUT; > + > + reg2 = dloc->op->reg2; > + if (!has_reg_type(state, reg2) || > + !state->regs[reg2].ok || > + (state->regs[reg2].kind != TSR_KIND_GLOBAL_ADDR && > + state->regs[reg2].kind != TSR_KIND_TYPE)) > + return PERF_TMR_NO_TYPE; > + > + pr_debug_dtp(" : retry\n"); > + retry = false; > + reg = reg2; > + goto again; > + } > + > + var_addr = dloc->op->offset; > + > if (dloc->op->multi_regs) { > int reg2 = dloc->op->reg2; > > -- > 2.34.1 >