diff options
Diffstat (limited to 'tools/perf/util/annotate-data.c')
| -rw-r--r-- | tools/perf/util/annotate-data.c | 209 |
1 files changed, 182 insertions, 27 deletions
diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c index 19a6ecd67f28..b3de1ca688a2 100644 --- a/tools/perf/util/annotate-data.c +++ b/tools/perf/util/annotate-data.c @@ -29,6 +29,7 @@ /* register number of the stack pointer */ #define X86_REG_SP 7 +#define ARM64_REG_SP 31 static void delete_var_types(struct die_var_type *var_types); @@ -70,6 +71,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; @@ -179,6 +183,17 @@ static void init_type_state(struct type_state *state, const struct arch *arch) state->regs[11].caller_saved = true; state->ret_reg = 0; state->stack_reg = X86_REG_SP; + } else if (arch__is_arm64(arch)) { + /* + * Mark x0..x18 as caller-saved. x30 (LR) is intentionally + * omitted from the caller_saved set: it is clobbered by + * calls, but holds only the return address and is not used + * to carry data type information. + */ + for (int i = 0; i < 19; i++) + state->regs[i].caller_saved = true; + state->ret_reg = 0; + state->stack_reg = ARM64_REG_SP; } } @@ -692,7 +707,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 imm_value) { int tag; Dwarf_Word size; @@ -709,8 +724,10 @@ 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->imm_value = imm_value; - if (kind == TSR_KIND_POINTER) { + if (kind == TSR_KIND_POINTER || kind == TSR_KIND_CONST || + kind == TSR_KIND_GLOBAL_ADDR) { stack->compound = false; return; } @@ -731,23 +748,42 @@ 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 imm_value) { 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, imm_value); 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, imm_value); list_add(&stack->list, &state->stack_vars); } return stack; } +void delete_stack_state(struct type_state *state, int offset) +{ + struct type_state_stack *stack = find_stack_state(state, offset); + + if (stack) { + list_del(&stack->list); + free(stack); + } +} + +void invalidate_reg_state(struct type_state_reg *reg) +{ + reg->kind = TSR_KIND_INVALID; + reg->ok = false; + reg->lifetime_active = false; + reg->lifetime_end = 0; + reg->copied_from = -1; +} + /* Maintain a cache for quick global variable lookup */ struct global_var_entry { struct rb_node node; @@ -1037,7 +1073,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, /*imm_value=*/0); if (var->reg == state->stack_reg) { pr_debug_dtp("var [%"PRIx64"] %#x(reg%d)", @@ -1220,6 +1256,78 @@ static void setup_stack_canary(struct data_loc_info *dloc) } } +int arch_get_reg_offset(const struct arch *arch, struct annotated_op_loc *op_loc, + int breg, struct type_state *state, bool apply_index, + int *offset) +{ + if (arch__is_arm64(arch)) { + int reg2; + + /* Post-indexed addressing: offset is 0 when not applying index */ + if (op_loc->addr_mode == PERF_AAM_POST_INDEX && !apply_index) { + *offset = 0; + return 0; + } + + /* Single-register addressing: return the stored offset directly */ + if (!op_loc->multi_regs) { + *offset = op_loc->offset; + return 0; + } + + /* + * SIB (Scale-Index-Base) style indexed addressing, e.g.: + * Array element load: ldr dst, [base, index, lsl #3] + * Array element load: ldr dst, [base, index, uxtw #3] + * Array address calc: add dst, base, index lsl #3 + * + * The index reg only computes element offset without modifying + * the underlying data type; the dst type is inherited from base. + * Therefore, the base reg offset can be treated as 0. + */ + if (op_loc->extend_type || op_loc->shift_type) { + /* A memory reference implies breg is a pointer. */ + if (op_loc->mem_ref) { + *offset = 0; + return 0; + } + /* + * Otherwise, require breg to be a known pointer to avoid + * treating integer arithmetic as indexed address calculation. + */ + if (has_reg_type(state, breg) && state->regs[breg].ok && + (state->regs[breg].kind == TSR_KIND_POINTER || + (state->regs[breg].kind == TSR_KIND_TYPE && + dwarf_tag(&state->regs[breg].type) == DW_TAG_pointer_type))) { + *offset = 0; + return 0; + } + return -1; + } + + /* + * For register-based addressing without shift/extension, + * derive the offset from the type of reg2. + */ + reg2 = op_loc->reg1 == breg ? op_loc->reg2 : op_loc->reg1; + if (has_reg_type(state, reg2) && state->regs[reg2].ok) { + if (state->regs[reg2].kind == TSR_KIND_CONST) + *offset = (s64)state->regs[reg2].imm_value; + else if (state->regs[reg2].kind == TSR_KIND_PERCPU_BASE) + *offset = 0; /* it does not contribute to the offset */ + else + return -1; + + return 0; + } + + return -1; + } + + *offset = op_loc->offset; + return 0; +} + /* * It's at the target address, check if it has a matching type. * It returns PERF_TMR_BAIL_OUT when it looks up per-cpu variables which @@ -1233,18 +1341,25 @@ static enum type_match_result check_matching_type(struct type_state *state, { u32 insn_offset = dl->al.offset; int reg = dloc->op->reg1; - int offset = dloc->op->offset; - const char *offset_sign = ""; + int offset; + const char *offset_sign; bool retry = true; - if (offset < 0) { - offset = -offset; - offset_sign = "-"; +again: + if (arch_get_reg_offset(dloc->arch, dloc->op, reg, state, false, &offset)) { + /* + * Fall back to the instruction's offset. This prevents the + * register type of the instruction from being completely lost. + * Compared to dropping this type inference entirely, providing + * a rough type hint is likely more useful. + */ + offset = dloc->op->offset; } -again: + offset_sign = offset < 0 ? "-" : ""; + pr_debug_dtp("chk [%x] reg%d offset=%s%#x ok=%d kind=%d ", - insn_offset, reg, offset_sign, offset, + insn_offset, reg, offset_sign, abs(offset), state->regs[reg].ok, state->regs[reg].kind); if (!state->regs[reg].ok) @@ -1267,7 +1382,7 @@ again: */ ptr_type = die_get_pointer_type(&state->regs[reg].type, &ptr_die); if (!ptr_type) { - if (dloc->op->offset < 0 && reg != state->stack_reg) + if (offset < 0 && reg != state->stack_reg) goto check_kernel; return PERF_TMR_NO_POINTER; @@ -1277,7 +1392,7 @@ again: if (__die_get_real_type(ptr_type, type_die) == NULL) return PERF_TMR_NO_POINTER; - dloc->type_offset = dloc->op->offset + state->regs[reg].offset; + dloc->type_offset = offset + state->regs[reg].offset; if (dwarf_tag(type_die) == DW_TAG_typedef) die_get_real_type(type_die, &sized_type); @@ -1301,7 +1416,7 @@ again: */ *type_die = state->regs[reg].type; - dloc->type_offset = dloc->op->offset + state->regs[reg].offset; + dloc->type_offset = offset + state->regs[reg].offset; return check_type_offset(type_die, dloc->type_offset); } @@ -1315,7 +1430,7 @@ again: */ *type_die = state->regs[reg].type; - dloc->type_offset = dloc->op->offset; + dloc->type_offset = offset; if (check_type_offset(type_die, dloc->type_offset) != PERF_TMR_OK) return PERF_TMR_BAIL_OUT; @@ -1337,11 +1452,31 @@ again: } 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 (!retry || !dloc->op->multi_regs || + dloc->op->reg1 == dloc->op->reg2 || + dloc->op->extend_type || dloc->op->shift_type) + return PERF_TMR_BAIL_OUT; + + reg2 = dloc->op->reg2; + if (!has_reg_type(state, reg2) || !state->regs[reg2].ok) + return PERF_TMR_NO_TYPE; + + pr_debug_dtp(" : retry\n"); + retry = false; + reg = reg2; + goto again; + } + + var_addr = (s64) offset; + if (dloc->op->multi_regs) { int reg2 = dloc->op->reg2; @@ -1364,16 +1499,35 @@ again: if (state->regs[reg].kind == TSR_KIND_CONST && dso__kernel(map__dso(dloc->ms->map))) { - if (dloc->op->offset < 0 && reg != state->stack_reg && reg != dloc->fbreg) + if (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].imm_value + 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; pr_debug_dtp("%s", reg == dloc->fbreg ? "fbreg" : "stack"); - stack = find_stack_state(state, dloc->type_offset); + stack = find_stack_state(state, offset); if (stack == NULL) { if (retry) { pr_debug_dtp(" : retry\n"); @@ -1396,7 +1550,7 @@ check_non_register: *type_die = stack->type; /* Update the type offset from the start of slot */ - dloc->type_offset -= stack->offset; + dloc->type_offset = offset - stack->offset; return PERF_TMR_OK; } @@ -1414,7 +1568,7 @@ check_non_register: if (reg != fbreg) return PERF_TMR_NO_TYPE; - stack = find_stack_state(state, dloc->type_offset - fboff); + stack = find_stack_state(state, offset - fboff); if (stack == NULL) { if (retry) { pr_debug_dtp(" : retry\n"); @@ -1437,7 +1591,7 @@ check_non_register: *type_die = stack->type; /* Update the type offset from the start of slot */ - dloc->type_offset -= fboff + stack->offset; + dloc->type_offset = offset - fboff - stack->offset; return PERF_TMR_OK; } @@ -1451,7 +1605,7 @@ check_kernel: arch__is_x86(dloc->arch)) { pr_debug_dtp("this-cpu var"); - addr = dloc->op->offset; + addr = (s64) offset; if (get_global_var_type(cu_die, dloc, dloc->ip, addr, &offset, type_die)) { @@ -1462,8 +1616,8 @@ check_kernel: } /* Access to global variable like "-0x7dcf0500(,%rdx,8)" */ - if (dloc->op->offset < 0 && reg != state->stack_reg) { - addr = (s64) dloc->op->offset; + if (offset < 0 && reg != state->stack_reg) { + addr = (s64) offset; if (get_global_var_type(cu_die, dloc, dloc->ip, addr, &offset, type_die)) { @@ -1534,7 +1688,8 @@ out: static int arch_supports_insn_tracking(struct data_loc_info *dloc) { - if ((arch__is_x86(dloc->arch)) || (arch__is_powerpc(dloc->arch))) + if (arch__is_x86(dloc->arch) || arch__is_powerpc(dloc->arch) || + arch__is_arm64(dloc->arch)) return 1; return 0; } |
