[PATCH v4 23/23] perf annotate-arm64: Support 'mrs' instruction to track 'current' pointer

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 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 is 'struct task_struct *').

Introduce 'task_struct_cache' to cache the DWARF offset of task_struct
alongside its corresponding dbg handle. This is particularly important
because the compiler-generated stack canary check code (which loads from
'current->stack_canary') often exists in code sections or leaf functions
where the local Compilation Unit (CU) lacks a full 'struct task_struct'
definition. Caching the offset allows 'perf annotate' to consistently
resolve task-related fields across the entire kernel binary.

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'

Signed-off-by: Li Huafei <[email protected]>
Signed-off-by: Tengda Wu <[email protected]>
---
 .../perf/util/annotate-arch/annotate-arm64.c  | 97 ++++++++++++++++++-
 1 file changed, 95 insertions(+), 2 deletions(-)

diff --git a/tools/perf/util/annotate-arch/annotate-arm64.c b/tools/perf/util/annotate-arch/annotate-arm64.c
index b01fa6cf865f..9d1f315dcb28 100644
--- a/tools/perf/util/annotate-arch/annotate-arm64.c
+++ b/tools/perf/util/annotate-arch/annotate-arm64.c
@@ -15,6 +15,7 @@
 #include "../dso.h"
 #include "../map.h"
 #include "../symbol.h"
+#include "../strbuf.h"
 
 struct arch_arm64 {
 	struct arch arch;
@@ -889,6 +890,96 @@ static void update_adrp_insn_state(struct type_state *state,
 		     insn_offset, tsr->imm_value, dreg);
 }
 
+static struct {
+	Dwarf_Off dieoff;
+	Dwarf *dbg;
+} task_struct_cache;
+
+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;
+
+	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);
+
+	/*
+	 * Invalidate cached DIE offset if the debug info handle changed
+	 * (e.g. switching between vmlinux and a kernel module).
+	 */
+	if (task_struct_cache.dbg != dloc->di->dbg) {
+		task_struct_cache.dieoff = 0;
+		task_struct_cache.dbg = dloc->di->dbg;
+	}
+
+	if (!task_struct_cache.dieoff ||
+	    !dwarf_offdie(dloc->di->dbg, task_struct_cache.dieoff, &type_die)) {
+		struct strbuf sb;
+		char *type_name;
+		/*
+		 * 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);
+			free(type_name);
+			return;
+		}
+
+		/*
+		 * Cache the 'struct task_struct*' die offset globally.
+		 * This allows us to resolve stack canary accesses even
+		 * in CUs that lack a full task_struct definition (e.g.,
+		 * compiler-generated entry/exit code).
+		 */
+		task_struct_cache.dieoff = dwarf_dieoffset(&type_die);
+		free(type_name);
+	}
+
+	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);
+}
+
 static void update_insn_state_arm64(struct type_state *state,
 				    struct data_loc_info *dloc, Dwarf_Die *cu_die,
 				    struct disasm_line *dl)
@@ -951,7 +1042,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) &&
-	    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") &&
 	    strncmp(dl->ins.name, "ld", 2) && strncmp(dl->ins.name, "st", 2)) {
 		pr_debug_dtp("%s [%x] invalidate reg%d",
@@ -965,7 +1056,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, dl, src, dst);
-- 
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.