[PATCH v4 23/23] perf annotate-arm64: Support 'mrs' instruction to track 'current' pointer
Tengda Wu <[email protected]>
| Newsgroups | org.kernel.vger.linux-perf-users,dev.linux.lists.llvm,org.kernel.vger.linux-kernel |
|---|---|
| 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