[PATCH v4 22/23] perf annotate-arm64: Support per-cpu variable access tracking

Tengda Wu <[email protected]>
Newsgroups dev.linux.lists.llvm,org.kernel.vger.linux-kernel,org.kernel.vger.linux-perf-users
Message-ID <[email protected]>
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 + 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(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  | 61 ++++++++++++++++++-
 tools/perf/util/annotate-data.c               | 24 +++++++-
 2 files changed, 83 insertions(+), 2 deletions(-)

diff --git a/tools/perf/util/annotate-arch/annotate-arm64.c b/tools/perf/util/annotate-arch/annotate-arm64.c
index 7eb3bef26a64..b01fa6cf865f 100644
--- a/tools/perf/util/annotate-arch/annotate-arm64.c
+++ b/tools/perf/util/annotate-arch/annotate-arm64.c
@@ -12,6 +12,7 @@
 #include "../disasm.h"
 #include "../annotate-data.h"
 #include "../debug.h"
+#include "../dso.h"
 #include "../map.h"
 #include "../symbol.h"
 
@@ -505,13 +506,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;
@@ -526,6 +537,24 @@ static int propagate_load_reg_state(struct type_state *state,
 		pr_debug_type_name(&tsr->type, tsr->kind);
 		return 0;
 	}
+	/* 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 */)
+			return -1;
+
+		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))
+			return -1;
+
+		/* 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;
@@ -789,6 +818,36 @@ static void update_add_insn_state(struct type_state *state,
 		return;
 	}
 
+	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 ||
+		    (state->regs[reg2].kind != TSR_KIND_GLOBAL_ADDR &&
+		     state->regs[reg2].kind != TSR_KIND_TYPE)) {
+			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) {
 		sreg = src->reg2;
diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c
index 7fe5b979563f..8b0584486cce 100644
--- a/tools/perf/util/annotate-data.c
+++ b/tools/perf/util/annotate-data.c
@@ -1253,11 +1253,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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.