diff options
Diffstat (limited to 'tools/perf/util')
34 files changed, 1556 insertions, 363 deletions
diff --git a/tools/perf/util/addr2line.c b/tools/perf/util/addr2line.c index 6f64f6b42442..960dd48701bb 100644 --- a/tools/perf/util/addr2line.c +++ b/tools/perf/util/addr2line.c @@ -298,6 +298,7 @@ int cmd__addr2line(const char *dso_name, u64 addr, struct io io = { .eof = false }; enum cmd_a2l_style cmd_a2l_style; const char *current_dso_name; + bool a2l_synced = false; mutex_lock(dso__lock(dso)); current_dso_name = dso__symsrc_filename(dso) ?: dso_name; @@ -316,11 +317,14 @@ int cmd__addr2line(const char *dso_name, u64 addr, if (a2l == NULL) { if (!symbol_conf.addr2line_disable_warn) pr_warning("%s %s: addr2line_subprocess_init failed\n", __func__, dso_name); + ret = -1; goto out_unlock; } cmd_a2l_style = cmd_addr2line_configure(a2l, dso_name); - if (cmd_a2l_style == BROKEN) + if (cmd_a2l_style == BROKEN) { + ret = -1; goto out_unlock; + } /* * Take ownership of the a2l subprocess so we can safely perform @@ -344,6 +348,7 @@ int cmd__addr2line(const char *dso_name, u64 addr, if (written != len) { if (!symbol_conf.addr2line_disable_warn) pr_warning("%s %s: could not send request\n", __func__, dso_name); + ret = -1; goto out; } io__init(&io, a2l->out, buf, sizeof(buf)); @@ -353,6 +358,7 @@ int cmd__addr2line(const char *dso_name, u64 addr, case -1: if (!symbol_conf.addr2line_disable_warn) pr_warning("%s %s: could not read first record\n", __func__, dso_name); + ret = -1; goto out; case 0: /* @@ -373,6 +379,7 @@ int cmd__addr2line(const char *dso_name, u64 addr, break; case 0: /* The sentinel as expected. */ + a2l_synced = true; break; default: if (!symbol_conf.addr2line_disable_warn) @@ -388,17 +395,23 @@ int cmd__addr2line(const char *dso_name, u64 addr, if (file) { *file = strdup(record_filename); - ret = 1; + if (!*file) { + ret = -ENOMEM; + goto out; + } } if (line_nr) *line_nr = record_line_nr; + ret = 1; if (unwind_inlines) { if (node && inline_list__append_record(dso, node, sym, record_function, record_filename, record_line_nr)) { - ret = 0; + if (file && *file) + zfree(file); + ret = -ENOMEM; goto out; } } @@ -421,12 +434,17 @@ int cmd__addr2line(const char *dso_name, u64 addr, record_function, record_filename, record_line_nr)) { - ret = 0; + if (file && *file) + zfree(file); + inline_node__clear_frames(node); + ret = -ENOMEM; goto out; } ret = 1; /* found at least one inline frame */ } } + if (record_status == 0) + a2l_synced = true; out: mutex_lock(dso__lock(dso)); @@ -434,7 +452,7 @@ out: free(record_filename); current_dso_name = dso__symsrc_filename(dso) ?: dso__long_name(dso); - if (!io.eof && dso__a2l(dso) == NULL && current_dso_name && + if (a2l_synced && !io.eof && dso__a2l(dso) == NULL && current_dso_name && !strcmp(current_dso_name, dso_name)) dso__set_a2l(dso, a2l); else diff --git a/tools/perf/util/annotate-arch/annotate-arm64.c b/tools/perf/util/annotate-arch/annotate-arm64.c index 6549535961a9..81369ae47290 100644 --- a/tools/perf/util/annotate-arch/annotate-arm64.c +++ b/tools/perf/util/annotate-arch/annotate-arm64.c @@ -1,14 +1,22 @@ // SPDX-License-Identifier: GPL-2.0 #include <linux/compiler.h> #include <errno.h> +#include <inttypes.h> #include <stdlib.h> #include <string.h> +#include <strings.h> #include <linux/ctype.h> #include <linux/string.h> #include <linux/zalloc.h> #include <regex.h> #include "../annotate.h" #include "../disasm.h" +#include "../annotate-data.h" +#include "../debug.h" +#include "../dso.h" +#include "../map.h" +#include "../symbol.h" +#include "../strbuf.h" struct arch_arm64 { struct arch arch; @@ -448,6 +456,769 @@ check_modifiers: } } +#ifdef HAVE_LIBDW_SUPPORT +/* Check if the instruction is read-only (does not modify register state). */ +static bool is_readonly_insn(const char *name) +{ + return !strncmp(name, "cb", 2) || !strncmp(name, "tb", 2) || + !strcmp(name, "tst") || !strcmp(name, "teq") || + !strcmp(name, "cmp") || !strcmp(name, "cmn") || + !strcmp(name, "ccmp") || !strcmp(name, "ccmn"); +} + +static void update_call_insn_state(struct type_state *state, + struct data_loc_info *dloc, Dwarf_Die *cu_die, + struct disasm_line *dl) +{ + struct symbol *func = dl->ops.target.sym; + const char *call_name; + u64 call_addr; + struct type_state_reg *tsr; + Dwarf_Die type_die; + u32 insn_offset = dl->al.offset; + + call_name = func ? func->name : dl->ops.target.name; + pr_debug_dtp("call [%x] %s\n", insn_offset, call_name ?: "<unknown>"); + + /* Invalidate caller-saved registers after call */ + call_addr = map__rip_2objdump(dloc->ms->map, + dloc->ms->sym->start + dl->al.offset); + for (unsigned int i = 0; i < ARRAY_SIZE(state->regs); i++) { + struct type_state_reg *reg = &state->regs[i]; + + if (!reg->caller_saved) + continue; + /* Keep register valid within DWARF location lifetime */ + if (reg->lifetime_active && call_addr < reg->lifetime_end) + continue; + invalidate_reg_state(reg); + } + + /* Update register with the return type (if any) */ + if (call_name && die_find_func_rettype(cu_die, call_name, &type_die)) { + tsr = &state->regs[state->ret_reg]; + invalidate_reg_state(tsr); + tsr->type = type_die; + tsr->kind = TSR_KIND_TYPE; + tsr->offset = 0; + tsr->ok = true; + + pr_debug_dtp("call [%x] return -> reg%d", insn_offset, state->ret_reg); + pr_debug_type_name(&type_die, tsr->kind); + } +} + +/* Invalidate registers explicitly written by an unhandled instruction. */ +static void invalidate_unhandled_insn_regs(struct type_state *state, + struct disasm_line *dl, + struct annotated_op_loc *src, + struct annotated_op_loc *dst) +{ + u32 insn_offset = dl->al.offset; + + /* Invalidate destination registers */ + if (has_reg_type(state, dst->reg1) && !dst->mem_ref) { + pr_debug_dtp("%s [%x] invalidate reg%d", + dl->ins.name, insn_offset, dst->reg1); + invalidate_reg_state(&state->regs[dst->reg1]); + + if (dst->multi_regs && has_reg_type(state, dst->reg2)) { + pr_debug_dtp(" and reg%d", dst->reg2); + invalidate_reg_state(&state->regs[dst->reg2]); + } + pr_debug_dtp("\n"); + } + + /* + * Exclusive store instructions (stxr, stxp, stlxr, stlxp, etc.) + * write their execution status (0 or 1) into the status register + * (parsed into src->reg1). Invalidate this status register to prevent + * stale type propagation. + */ + if (!strncmp(dl->ins.name, "stx", 3) || /* stxr, stxp, stxrb, stxrh */ + !strncmp(dl->ins.name, "stlx", 4)) { /* stlxr, stlxp, stlxrb, stlxrh */ + if (has_reg_type(state, src->reg1)) { + pr_debug_dtp("%s [%x] invalidate reg%d\n", + dl->ins.name, insn_offset, src->reg1); + invalidate_reg_state(&state->regs[src->reg1]); + } + } + + /* TODO: Extend invalidation support to more special instructions. */ +} + +static int arm64__reg_size(const char *reg) +{ + if (!reg || !*reg || !arm64__is_reg(reg)) + return -1; + + if (reg[0] == 'w') + return 4; + + if (reg[0] == 'x' || !strncmp(reg, "sp", 2)) + return 8; + + return -1; +} + +/* Apply addressing mode (pre-index, post-index) to register state */ +static void adjust_reg_index_state(struct type_state *state, + struct data_loc_info *dloc, + struct disasm_line *dl, + struct annotated_op_loc *op_loc) +{ + struct type_state_reg *tsr; + int reg = op_loc->reg1; + int offset; + + if (op_loc->addr_mode != PERF_AAM_PRE_INDEX && + op_loc->addr_mode != PERF_AAM_POST_INDEX) + return; + + if (!has_reg_type(state, reg) || !state->regs[reg].ok) + return; + + tsr = &state->regs[reg]; + tsr->copied_from = -1; + + if (arch_get_reg_offset(dloc->arch, op_loc, reg, state, true, &offset)) { + invalidate_reg_state(tsr); + return; + } + + tsr->offset += offset; + + pr_debug_dtp("%s [%x] %s-index %#x(reg%d) -> reg%d", dl->ins.name, + (u32) dl->al.offset, op_loc->addr_mode == PERF_AAM_PRE_INDEX ? + "pre" : "post", offset, reg, reg); + pr_debug_type_name(&tsr->type, tsr->kind); +} + +/* + * Match standard load variants (ldr, ldur, ldar, ldp) that follow + * straightforward load semantics: memory source on the right + * and target registers on the left (written). Excludes exclusive loads + * (ldxr, ldxp, etc.) and acquire/exclusive combination variants. + */ +static bool is_standard_load_insn(const char *name) +{ + return !strncmp(name, "ldr", 3) || /* ldr, ldrb, ldrh, ldrsb, ldrsh, ldrsw */ + !strncmp(name, "ldur", 4) || /* ldur, ldurb, ldurh, ldursb, ldursh, ldursw */ + !strncmp(name, "ldar", 4) || /* ldar, ldarb, ldarh */ + !strncmp(name, "ldp", 3); /* ldp, ldpsw */ +} + +/* + * For load insns: propagate type from source reg state @src_states to @dreg. + * + * @mem_spacing accounts for the additional in-memory spacing between the two + * loads when handling multi-reg loads (e.g. the second target reg in 'ldp'), + * which is added to the reg offset. + */ +static int propagate_load_reg_state(struct type_state *state, + struct data_loc_info *dloc, Dwarf_Die *cu_die, + struct disasm_line *dl, int dreg, + struct annotated_op_loc *src, + struct type_state_reg **src_states, + int mem_spacing) +{ + struct type_state_reg *tsr; + struct type_state_reg *src_tsr = src_states[0]; + Dwarf_Die type_die; + u32 insn_offset = dl->al.offset; + int sreg = src->reg1; + int reg_offset; + int fbreg = dloc->fbreg; + int fboff = 0; + + if (!has_reg_type(state, dreg)) + return -1; + + tsr = &state->regs[dreg]; + tsr->copied_from = -1; + tsr->lifetime_active = false; + tsr->lifetime_end = 0; + + if (dloc->fb_cfa) { + u64 ip = dloc->ms->sym->start + dl->al.offset; + u64 pc = map__rip_2objdump(dloc->ms->map, ip); + + if (die_get_cfa(dloc->di->dbg, pc, &fbreg, &fboff) < 0) + fbreg = -1; + } + +retry: + if (arch_get_reg_offset(dloc->arch, src, sreg, state, false, ®_offset)) + goto load_non_regoff; + + reg_offset += mem_spacing; + + /* Check stack variables with offset */ + if (sreg != -1 && (sreg == fbreg || sreg == state->stack_reg)) { + struct type_state_stack *stack; + int offset = sreg == fbreg ? reg_offset - fboff : reg_offset; + + stack = find_stack_state(state, offset); + if (stack == NULL) { + return -1; + } else if (!stack->compound) { + tsr->type = stack->type; + tsr->kind = stack->kind; + tsr->offset = stack->ptr_offset; + tsr->imm_value = stack->imm_value; + tsr->ok = true; + } else if (die_get_member_type(&stack->type, + offset - stack->offset, + &type_die)) { + tsr->type = type_die; + tsr->kind = TSR_KIND_TYPE; + tsr->offset = 0; + tsr->imm_value = 0; + tsr->ok = true; + } else { + return -1; + } + + if (sreg == fbreg) { + pr_debug_dtp("%s [%x] -%#x(stack) -> reg%d", + dl->ins.name, insn_offset, -offset, dreg); + } else { + pr_debug_dtp("%s [%x] %#x(reg%d) -> reg%d", + dl->ins.name, insn_offset, offset, sreg, dreg); + } + pr_debug_type_name(&tsr->type, tsr->kind); + return 0; + } + + if (!src_tsr || !src_tsr->ok) + return -1; + + /* Dereference the pointer if it has one */ + if (src_tsr->kind == TSR_KIND_TYPE && + die_deref_ptr_type(&src_tsr->type, + src_tsr->offset + reg_offset, &type_die)) { + tsr->type = type_die; + tsr->kind = TSR_KIND_TYPE; + tsr->offset = 0; + tsr->imm_value = 0; + tsr->ok = true; + + pr_debug_dtp("%s [%x] %#x(reg%d) -> reg%d", + dl->ins.name, insn_offset, reg_offset, sreg, dreg); + pr_debug_type_name(&tsr->type, tsr->kind); + return 0; + } + + /* Handle dereference of TSR_KIND_POINTER registers */ + if (src_tsr->kind == TSR_KIND_POINTER && + die_get_member_type(&src_tsr->type, + src_tsr->offset + reg_offset, &type_die)) { + tsr->type = src_tsr->type; + tsr->kind = TSR_KIND_TYPE; + tsr->offset = src_tsr->offset + reg_offset; + tsr->imm_value = 0; + tsr->ok = true; + + pr_debug_dtp("%s [%x] addr %#x(reg%d) -> reg%d", + dl->ins.name, insn_offset, reg_offset, sreg, dreg); + pr_debug_type_name(&tsr->type, tsr->kind); + return 0; + } + + /* Or check if it's a global variable */ + if (src_tsr->kind == TSR_KIND_GLOBAL_ADDR) { + 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 = kind; + tsr->offset = 0; + tsr->imm_value = 0; + tsr->ok = true; + + if (src->multi_regs) { + pr_debug_dtp("%s [%x] global (reg%d, reg%d) -> reg%d", + dl->ins.name, insn_offset, src->reg1, src->reg2, dreg); + } else { + pr_debug_dtp("%s [%x] global (reg%d) -> reg%d", + dl->ins.name, insn_offset, sreg, dreg); + } + pr_debug_type_name(&tsr->type, tsr->kind); + return 0; + } + + /* + * If src_tsr->kind is TSR_KIND_PERCPU_BASE, treat it as an array. + * No special handling is needed here; just fall through to retry + * and resolve the type from the second register. + */ + +load_non_regoff: + /* Try another register if any */ + if (src->multi_regs && src->reg1 != src->reg2 && sreg != src->reg2 && + !(src->extend_type || src->shift_type)) { + sreg = src->reg2; + src_tsr = src_states[1]; + goto retry; + } + + return -1; +} + +static void update_load_insn_state(struct type_state *state, + struct data_loc_info *dloc, Dwarf_Die *cu_die, + struct disasm_line *dl, + struct annotated_op_loc *src, + struct annotated_op_loc *dst) +{ + struct type_state_reg snapshots[2]; + struct type_state_reg *src_states[2] = {}; + int mem_spacing = 0; + + if (!src->mem_ref || /* exclude PC-relative loads */ + !has_reg_type(state, src->reg1)) { + /* Invalidate tracked destination registers */ + if (has_reg_type(state, dst->reg1)) + invalidate_reg_state(&state->regs[dst->reg1]); + if (dst->multi_regs && has_reg_type(state, dst->reg2)) + invalidate_reg_state(&state->regs[dst->reg2]); + goto out_adjust; + } + + /* + * Snapshot source register states before updating destination registers. + * This preserves the original source state for aliased loads (e.g., + * ldp x0, x1, [x0]), where updating the first destination may overwrite + * the source register. + */ + snapshots[0] = state->regs[src->reg1]; + src_states[0] = &snapshots[0]; + if (src->multi_regs && has_reg_type(state, src->reg2)) { + snapshots[1] = state->regs[src->reg2]; + src_states[1] = &snapshots[1]; + } + + /* Handle the first destination register */ + if (has_reg_type(state, dst->reg1)) { + if (propagate_load_reg_state(state, dloc, cu_die, dl, dst->reg1, + src, src_states, mem_spacing)) + invalidate_reg_state(&state->regs[dst->reg1]); + } + + if (!dst->multi_regs || !has_reg_type(state, dst->reg2)) + goto out_adjust; + + /* Handle the second destination register */ + if (!strcmp(dl->ins.name, "ldpsw")) + mem_spacing = 4; /* ldpsw loads two 32-bit signed words (4-byte spacing) */ + else + mem_spacing = arm64__reg_size(dl->ops.target.raw); + + if (mem_spacing < 0 || + propagate_load_reg_state(state, dloc, cu_die, dl, dst->reg2, + src, src_states, mem_spacing)) + invalidate_reg_state(&state->regs[dst->reg2]); + +out_adjust: + adjust_reg_index_state(state, dloc, dl, src); +} + +/* + * Match standard store variants (str, stur, stlr, stp) that follow + * straightforward store semantics: source registers on the left (read-only) + * and memory target on the right. Excludes exclusive stores (stxr, stxp, etc.) + * which have status/result register side effects. + */ +static bool is_standard_store_insn(const char *name) +{ + return !strncmp(name, "str", 3) || /* str, strb, strh */ + !strncmp(name, "stur", 4) || /* stur, sturb, sturh */ + !strncmp(name, "stlr", 4) || /* stlr, stlrb, stlrh */ + !strncmp(name, "stp", 3); /* stp */ +} + +/* + * For store insns: propagate type from @sreg to the memory location referenced + * by @dst, applying @mem_spacing to account for multi-regs stores (e.g. stp). + */ +static void propagate_store_reg_state(struct type_state *state, + struct data_loc_info *dloc, + struct disasm_line *dl, int sreg, + struct annotated_op_loc *dst, + int mem_spacing) +{ + struct type_state_reg *tsr; + u32 insn_offset = dl->al.offset; + int dreg = dst->reg1; + int reg_offset; + int fbreg = dloc->fbreg; + int fboff = 0; + + if (arch_get_reg_offset(dloc->arch, dst, dreg, state, false, ®_offset)) + return; + + reg_offset += mem_spacing; + + if (dloc->fb_cfa) { + u64 ip = dloc->ms->sym->start + dl->al.offset; + u64 pc = map__rip_2objdump(dloc->ms->map, ip); + + if (die_get_cfa(dloc->di->dbg, pc, &fbreg, &fboff) < 0) + fbreg = -1; + } + + /* Check stack variables with offset */ + if (dreg != -1 && (dreg == fbreg || dreg == state->stack_reg)) { + struct type_state_stack *stack; + int offset = dreg == fbreg ? reg_offset - fboff : reg_offset; + + if (!has_reg_type(state, sreg) || !state->regs[sreg].ok) { + stack = find_stack_state(state, offset); + /* Preserve compound states when only a member is overwritten. */ + if (stack && !stack->compound) + delete_stack_state(state, offset); + + return; + } + + tsr = &state->regs[sreg]; + + stack = find_stack_state(state, offset); + if (stack) { + if (!stack->compound) + set_stack_state(stack, offset, tsr->kind, &tsr->type, + tsr->offset, tsr->imm_value); + /* + * If it's a compound type, it means attempting to + * write to a member value of the compound type without + * changing the compound type itself, so do nothing. + */ + } else { + findnew_stack_state(state, offset, tsr->kind, &tsr->type, + tsr->offset, tsr->imm_value); + } + + if (dreg == fbreg) { + pr_debug_dtp("%s [%x] reg%d -> -%#x(stack)", + dl->ins.name, insn_offset, sreg, -offset); + } else { + pr_debug_dtp("%s [%x] reg%d -> %#x(reg%d)", + dl->ins.name, insn_offset, sreg, offset, dreg); + } + if (tsr->offset != 0) + pr_debug_dtp(" reg%d offset %#x ->", sreg, tsr->offset); + + pr_debug_type_name(&tsr->type, tsr->kind); + } + /* + * Ignore other transfers since it'd set a value in a struct + * and won't change the type. + */ +} + +static void update_store_insn_state(struct type_state *state, + struct data_loc_info *dloc, + struct disasm_line *dl, + struct annotated_op_loc *src, + struct annotated_op_loc *dst) +{ + int mem_spacing = 0; + + if (!has_reg_type(state, dst->reg1)) + return; + + /* Handle the first source register */ + propagate_store_reg_state(state, dloc, dl, src->reg1, dst, mem_spacing); + + /* Handle the second source register (if any) */ + if (src->multi_regs) { + mem_spacing = arm64__reg_size(dl->ops.source.raw); + + if (mem_spacing >= 0) + propagate_store_reg_state(state, dloc, dl, src->reg2, + dst, mem_spacing); + } + + adjust_reg_index_state(state, dloc, dl, dst); +} + +static void update_mov_insn_state(struct type_state *state, + struct disasm_line *dl, + struct annotated_op_loc *src, + struct annotated_op_loc *dst) +{ + struct type_state_reg *tsr; + u32 insn_offset = dl->al.offset; + int sreg = src->reg1; + int dreg = dst->reg1; + + if (!has_reg_type(state, dreg)) + return; + + tsr = &state->regs[dreg]; + tsr->copied_from = -1; + + if (src->imm) { + invalidate_reg_state(tsr); + tsr->kind = TSR_KIND_CONST; + tsr->imm_value = (s64)src->offset; + tsr->offset = 0; + tsr->ok = true; + + pr_debug_dtp("mov [%x] imm=%#"PRIx64" -> reg%d\n", + insn_offset, tsr->imm_value, dreg); + return; + } + + if (!has_reg_type(state, sreg) || !state->regs[sreg].ok) { + invalidate_reg_state(tsr); + return; + } + + tsr->type = state->regs[sreg].type; + tsr->kind = state->regs[sreg].kind; + tsr->imm_value = state->regs[sreg].imm_value; + tsr->offset = state->regs[sreg].offset; + tsr->lifetime_active = state->regs[sreg].lifetime_active; + tsr->lifetime_end = state->regs[sreg].lifetime_end; + tsr->ok = state->regs[sreg].ok; + + if (tsr->kind == TSR_KIND_TYPE || tsr->kind == TSR_KIND_POINTER) + tsr->copied_from = sreg; + + pr_debug_dtp("mov [%x] reg%d -> reg%d", insn_offset, sreg, dreg); + pr_debug_type_name(&tsr->type, tsr->kind); +} + +static void update_add_insn_state(struct type_state *state, + struct data_loc_info *dloc, + struct disasm_line *dl, + struct annotated_op_loc *src, + struct annotated_op_loc *dst) +{ + struct type_state_reg *tsr; + struct type_state_reg src_tsr; + u32 insn_offset = dl->al.offset; + int sreg = src->reg1; + int dreg = dst->reg1; + int reg_offset; + + if (!has_reg_type(state, dreg)) + return; + + tsr = &state->regs[dreg]; + tsr->copied_from = -1; + tsr->lifetime_active = false; + tsr->lifetime_end = 0; + +retry: + if (!has_reg_type(state, sreg) || !state->regs[sreg].ok) { + invalidate_reg_state(tsr); + return; + } + + src_tsr = state->regs[sreg]; + + if (arch_get_reg_offset(dloc->arch, src, sreg, state, false, ®_offset)) + goto add_non_regoff; + + if (src_tsr.kind == TSR_KIND_CONST || src_tsr.kind == TSR_KIND_GLOBAL_ADDR) { + tsr->kind = src_tsr.kind; + /* For 'adrp + add' pair: resolve the full global variable address. */ + tsr->imm_value = src_tsr.imm_value + reg_offset; + tsr->offset = 0; + tsr->ok = src_tsr.ok; + + pr_debug_dtp("add [%x] %s %#x(reg%d) -> reg%d\n", + insn_offset, src_tsr.kind == TSR_KIND_CONST ? + "imm" : "global", reg_offset, sreg, dreg); + return; + } + + if (src_tsr.kind == TSR_KIND_POINTER || + (src_tsr.kind == TSR_KIND_TYPE && + dwarf_tag(&src_tsr.type) == DW_TAG_pointer_type)) { + tsr->type = src_tsr.type; + tsr->kind = src_tsr.kind; + tsr->imm_value = src_tsr.imm_value; + tsr->offset = src_tsr.offset + reg_offset; + tsr->ok = src_tsr.ok; + + pr_debug_dtp("add [%x] address of %#x(reg%d) -> reg%d", + insn_offset, reg_offset, sreg, dreg); + pr_debug_type_name(&tsr->type, tsr->kind); + return; + } + + /* + * If src_tsr->kind is TSR_KIND_PERCPU_BASE, treat it as an array. + * No special handling is needed here; just fall through to retry + * and resolve the type from the second register. + */ + +add_non_regoff: + /* Try another register if any */ + if (src->multi_regs && src->reg1 != src->reg2 && sreg != src->reg2 && + !(src->extend_type || src->shift_type)) { + sreg = src->reg2; + goto retry; + } + invalidate_reg_state(tsr); +} + +static void update_adrp_insn_state(struct type_state *state, + struct disasm_line *dl, + struct annotated_op_loc *dst) +{ + struct type_state_reg *tsr; + u32 insn_offset = dl->al.offset; + int dreg = dst->reg1; + + if (!has_reg_type(state, dreg)) + return; + + tsr = &state->regs[dreg]; + tsr->copied_from = -1; + + if (!dl->ops.source.addr) { + invalidate_reg_state(tsr); + return; + } + + tsr->kind = TSR_KIND_GLOBAL_ADDR; + /* + * Stores a partial page-relative address. The full absolute address + * of the global variable will be resolved when a subsequent 'add' or + * 'ldr' instruction consumes this register. + */ + tsr->imm_value = dl->ops.source.addr; + tsr->offset = 0; + tsr->lifetime_active = false; + tsr->lifetime_end = 0; + tsr->ok = true; + + pr_debug_dtp("adrp [%x] global addr=%#"PRIx64" -> reg%d\n", + insn_offset, tsr->imm_value, dreg); +} + +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; + struct strbuf sb; + char *type_name; + + 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); + + /* + * 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); + goto out_free; + } + + tsr->type = type_die; + tsr->kind = TSR_KIND_TYPE; + tsr->offset = 0; + tsr->imm_value = 0; + tsr->lifetime_active = false; + tsr->lifetime_end = 0; + tsr->ok = true; + + pr_debug_dtp("mrs [%x] sp_el0 -> reg%d", insn_offset, dreg); + pr_debug_type_name(&type_die, tsr->kind); + +out_free: + free(type_name); +} + +static void update_insn_state_arm64(struct type_state *state, + struct data_loc_info *dloc, Dwarf_Die *cu_die, + struct disasm_line *dl) +{ + struct annotated_insn_loc loc; + struct annotated_op_loc *src = &loc.ops[INSN_OP_SOURCE]; + struct annotated_op_loc *dst = &loc.ops[INSN_OP_TARGET]; + + if (is_readonly_insn(dl->ins.name)) + return; + + if (annotate_get_insn_location(dloc->arch, dl, &loc) < 0) + return; + + if (ins__is_call(&dl->ins)) + update_call_insn_state(state, dloc, cu_die, dl); + else 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, dloc, dl, src, dst); + /* Register to register or imm value to register transfers */ + else if (!strcmp(dl->ins.name, "mov")) + update_mov_insn_state(state, dl, src, dst); + /* Memory to register transfers */ + else if (is_standard_load_insn(dl->ins.name)) + update_load_insn_state(state, dloc, cu_die, dl, src, dst); + /* Register to memory transfers */ + else if (is_standard_store_insn(dl->ins.name)) + update_store_insn_state(state, dloc, dl, src, dst); + else + invalidate_unhandled_insn_regs(state, dl, src, dst); +} +#endif + const struct arch *arch__new_arm64(const struct e_machine_and_e_flags *id, const char *cpuid __maybe_unused) { @@ -467,6 +1238,9 @@ const struct arch *arch__new_arm64(const struct e_machine_and_e_flags *id, arch->objdump.imm_char = '#'; arch->associate_instruction_ops = arm64__associate_instruction_ops; arch->extract_op_location = extract_op_location_arm64; +#ifdef HAVE_LIBDW_SUPPORT + arch->update_insn_state = update_insn_state_arm64; +#endif /* bl, blr */ err = regcomp(&arm->call_insn, "^blr?$", REG_EXTENDED); diff --git a/tools/perf/util/annotate-arch/annotate-x86.c b/tools/perf/util/annotate-arch/annotate-x86.c index 1acf31a2c759..3a6ea7e978e0 100644 --- a/tools/perf/util/annotate-arch/annotate-x86.c +++ b/tools/perf/util/annotate-arch/annotate-x86.c @@ -207,15 +207,6 @@ static int x86__cpuid_parse(struct arch *arch, const char *cpuid) } #ifdef HAVE_LIBDW_SUPPORT -static 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; -} - static void update_insn_state_x86(struct type_state *state, struct data_loc_info *dloc, Dwarf_Die *cu_die, struct disasm_line *dl) @@ -545,11 +536,11 @@ static void update_insn_state_x86(struct type_state *state, if (src->imm) { tsr->kind = TSR_KIND_CONST; - tsr->imm_value = src->offset; + tsr->imm_value = (s64)src->offset; tsr->offset = 0; tsr->ok = true; - pr_debug_dtp("mov [%x] imm=%#x -> reg%d\n", + pr_debug_dtp("mov [%x] imm=%#"PRIx64" -> reg%d\n", insn_offset, tsr->imm_value, dst->reg1); return; } @@ -600,6 +591,7 @@ retry: tsr->type = stack->type; tsr->kind = stack->kind; tsr->offset = stack->ptr_offset; + tsr->imm_value = stack->imm_value; tsr->ok = true; } else if (die_get_member_type(&stack->type, offset - stack->offset, @@ -607,6 +599,7 @@ retry: tsr->type = type_die; tsr->kind = TSR_KIND_TYPE; tsr->offset = 0; + tsr->imm_value = 0; tsr->ok = true; } else { invalidate_reg_state(tsr); @@ -760,15 +753,24 @@ retry: } /* Case 3. register to memory transfers */ if (!src->mem_ref && dst->mem_ref) { - if (!has_reg_type(state, src->reg1) || - !state->regs[src->reg1].ok) - return; - /* Check stack variables with offset */ if (dst->reg1 == fbreg || dst->reg1 == state->stack_reg) { struct type_state_stack *stack; int offset = dst->offset - fboff; + if (!has_reg_type(state, src->reg1) || + !state->regs[src->reg1].ok) { + stack = find_stack_state(state, offset); + /* + * Preserve compound states when only a member + * is overwritten. + */ + if (stack && !stack->compound) + delete_stack_state(state, offset); + + return; + } + tsr = &state->regs[src->reg1]; stack = find_stack_state(state, offset); @@ -782,10 +784,12 @@ retry: */ if (!stack->compound) set_stack_state(stack, offset, tsr->kind, - &tsr->type, tsr->offset); + &tsr->type, tsr->offset, + tsr->imm_value); } else { findnew_stack_state(state, offset, tsr->kind, - &tsr->type, tsr->offset); + &tsr->type, tsr->offset, + tsr->imm_value); } if (dst->reg1 == fbreg) { 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; } diff --git a/tools/perf/util/annotate-data.h b/tools/perf/util/annotate-data.h index bbf4fd35c1d7..eee1f8dae3dc 100644 --- a/tools/perf/util/annotate-data.h +++ b/tools/perf/util/annotate-data.h @@ -37,6 +37,7 @@ enum type_state_kind { TSR_KIND_PERCPU_POINTER, TSR_KIND_POINTER, TSR_KIND_CANARY, + TSR_KIND_GLOBAL_ADDR, }; /** @@ -182,7 +183,7 @@ extern struct annotated_data_stat ann_data_stat; */ struct type_state_reg { Dwarf_Die type; - u32 imm_value; + u64 imm_value; /* * The offset within the struct that the register points to. * A value of 0 means the register points to the beginning. @@ -208,6 +209,7 @@ struct type_state_stack { int size; bool compound; u8 kind; + u64 imm_value; }; /* @@ -262,18 +264,22 @@ bool has_reg_type(struct type_state *state, int reg); 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); 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); struct type_state_stack *find_stack_state(struct type_state *state, int offset); +void delete_stack_state(struct type_state *state, int offset); +void invalidate_reg_state(struct type_state_reg *reg); bool get_global_var_type(Dwarf_Die *cu_die, struct data_loc_info *dloc, u64 ip, u64 var_addr, int *var_offset, Dwarf_Die *type_die); bool get_global_var_info(struct data_loc_info *dloc, u64 addr, const char **var_name, int *var_offset); void pr_debug_type_name(Dwarf_Die *die, enum type_state_kind kind); - +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); #else /* HAVE_LIBDW_SUPPORT */ static inline struct annotated_data_type * diff --git a/tools/perf/util/auxtrace.c b/tools/perf/util/auxtrace.c index f30f86156a3a..8bb491d389a2 100644 --- a/tools/perf/util/auxtrace.c +++ b/tools/perf/util/auxtrace.c @@ -123,7 +123,7 @@ static int evlist__regroup(struct evlist *evlist, struct evsel *leader, struct e return 0; } -static bool auxtrace__dont_decode(struct perf_session *session) +bool auxtrace__dont_decode(struct perf_session *session) { return !session->itrace_synth_opts || session->itrace_synth_opts->dont_decode; diff --git a/tools/perf/util/auxtrace.h b/tools/perf/util/auxtrace.h index 5012bead1b92..c29a8444f08e 100644 --- a/tools/perf/util/auxtrace.h +++ b/tools/perf/util/auxtrace.h @@ -650,6 +650,7 @@ void auxtrace__free(struct perf_session *session); bool auxtrace__evsel_is_auxtrace(struct perf_session *session, struct evsel *evsel); u64 auxtrace_synth_id_range_start(struct evsel *evsel); +bool auxtrace__dont_decode(struct perf_session *session); #define ITRACE_HELP \ " i[period]: synthesize instructions events\n" \ diff --git a/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c b/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c index 26940f1f1b0b..b56cf57a68c8 100644 --- a/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c +++ b/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c @@ -741,7 +741,7 @@ cs_etm_decoder__new(int decoders, struct cs_etm_decoder_params *d_params, return decoder; err_free_decoder: - cs_etm_decoder__free(decoder); + cs_etm_decoder__zfree(&decoder); return NULL; } @@ -793,14 +793,14 @@ int cs_etm_decoder__process_data_block(struct cs_etm_decoder *decoder, return ret; } -void cs_etm_decoder__free(struct cs_etm_decoder *decoder) +void cs_etm_decoder__zfree(struct cs_etm_decoder **decoder) { - if (!decoder) + if (!(*decoder)) return; - ocsd_destroy_dcd_tree(decoder->dcd_tree); - decoder->dcd_tree = NULL; - free(decoder); + ocsd_destroy_dcd_tree((*decoder)->dcd_tree); + (*decoder)->dcd_tree = NULL; + zfree(decoder); } const char *cs_etm_decoder__get_name(struct cs_etm_decoder *decoder) diff --git a/tools/perf/util/cs-etm-decoder/cs-etm-decoder.h b/tools/perf/util/cs-etm-decoder/cs-etm-decoder.h index 12c782fa6db2..2a8214f17958 100644 --- a/tools/perf/util/cs-etm-decoder/cs-etm-decoder.h +++ b/tools/perf/util/cs-etm-decoder/cs-etm-decoder.h @@ -96,7 +96,7 @@ cs_etm_decoder__new(int num_cpu, struct cs_etm_decoder_params *d_params, struct cs_etm_trace_params t_params[]); -void cs_etm_decoder__free(struct cs_etm_decoder *decoder); +void cs_etm_decoder__zfree(struct cs_etm_decoder **decoder); int cs_etm_decoder__add_mem_access_cb(struct cs_etm_decoder *decoder, u64 start, u64 end, diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c index 2d1ab34f7b6b..bdcf94813129 100644 --- a/tools/perf/util/cs-etm.c +++ b/tools/perf/util/cs-etm.c @@ -243,8 +243,12 @@ enum cs_etm_pid_fmt cs_etm__get_pid_fmt(struct cs_etm_queue *etmq) return etmq->etm->pid_fmt; } -static int cs_etm__insert_trace_id_node(struct cs_etm_queue *etmq, - u8 trace_chan_id, u64 *cpu_metadata) +/* + * Add a traceid_list entry that maps a trace ID to a CPU metadata for this etmq + * and update the trace ID in the cpu_metadata. + */ +static int cs_etm__map_trace_id(struct cs_etm_queue *etmq, u8 trace_chan_id, + u64 *cpu_metadata) { /* Get an RB node for this CPU */ struct int_node *inode = intlist__findnew(etmq->traceid_list, trace_chan_id); @@ -289,8 +293,7 @@ static int cs_etm__insert_trace_id_node(struct cs_etm_queue *etmq, /* Not one we've seen before, associate the traceID with the metadata pointer */ inode->priv = cpu_metadata; - - return 0; + return cs_etm__metadata_set_trace_id(trace_chan_id, cpu_metadata); } static struct cs_etm_queue *cs_etm__get_queue(struct cs_etm_auxtrace *etm, int cpu) @@ -309,31 +312,29 @@ static int cs_etm__map_trace_id_v0(struct cs_etm_auxtrace *etm, u8 trace_chan_id { struct cs_etm_queue *etmq; - /* - * If the queue is unformatted then only save one mapping in the - * queue associated with that CPU so only one decoder is made. - */ etmq = cs_etm__get_queue(etm, cpu_metadata[CS_ETM_CPU]); if (!etmq) return -EINVAL; + /* + * Ignore HW_IDs for unformatted, a decoder will be made + * unconditionally later. + */ if (etmq->format == UNFORMATTED) - return cs_etm__insert_trace_id_node(etmq, trace_chan_id, - cpu_metadata); + return 0; /* * Otherwise, version 0 trace IDs are global so save them into every - * queue. + * formatted queue. */ for (unsigned int i = 0; i < etm->queues.nr_queues; ++i) { int ret; etmq = etm->queues.queue_array[i].priv; - if (!etmq) + if (!etmq || etmq->format == UNFORMATTED) continue; - ret = cs_etm__insert_trace_id_node(etmq, trace_chan_id, - cpu_metadata); + ret = cs_etm__map_trace_id(etmq, trace_chan_id, cpu_metadata); if (ret) return ret; } @@ -344,7 +345,6 @@ static int cs_etm__map_trace_id_v0(struct cs_etm_auxtrace *etm, u8 trace_chan_id static int cs_etm__process_trace_id_v0(struct cs_etm_auxtrace *etm, int cpu, u64 hw_id) { - int err; u64 *cpu_data; u8 trace_chan_id = FIELD_GET(CS_AUX_HW_ID_TRACE_ID_MASK, hw_id); @@ -352,22 +352,13 @@ static int cs_etm__process_trace_id_v0(struct cs_etm_auxtrace *etm, int cpu, if (cpu_data == NULL) return -EINVAL; - err = cs_etm__map_trace_id_v0(etm, trace_chan_id, cpu_data); - if (err) - return err; - - /* - * if we are picking up the association from the packet, need to plug - * the correct trace ID into the metadata for setting up decoders later. - */ - return cs_etm__metadata_set_trace_id(trace_chan_id, cpu_data); + return cs_etm__map_trace_id_v0(etm, trace_chan_id, cpu_data); } static int cs_etm__process_trace_id_v0_1(struct cs_etm_auxtrace *etm, int cpu, u64 hw_id) { struct cs_etm_queue *etmq = cs_etm__get_queue(etm, cpu); - int ret; u64 *cpu_data; u32 sink_id = FIELD_GET(CS_AUX_HW_ID_SINK_ID_MASK, hw_id); u8 trace_id = FIELD_GET(CS_AUX_HW_ID_TRACE_ID_MASK, hw_id); @@ -376,6 +367,13 @@ static int cs_etm__process_trace_id_v0_1(struct cs_etm_auxtrace *etm, int cpu, return -EINVAL; /* + * Ignore HW_IDs for unformatted, a decoder will be made + * unconditionally later. + */ + if (etmq->format == UNFORMATTED) + return 0; + + /* * Check sink id hasn't changed in per-cpu mode. In per-thread mode, * let it pass for now until an actual overlapping trace ID is hit. In * most cases IDs won't overlap even if the sink changes. @@ -419,13 +417,41 @@ static int cs_etm__process_trace_id_v0_1(struct cs_etm_auxtrace *etm, int cpu, if (!cpu_data) return -EINVAL; - ret = cs_etm__insert_trace_id_node(etmq, trace_id, cpu_data); - if (ret) - return ret; + return cs_etm__map_trace_id(etmq, trace_id, cpu_data); +} - ret = cs_etm__metadata_set_trace_id(trace_id, cpu_data); - if (ret) - return ret; +static int cs_etm__map_unformatted_trace_ids(struct cs_etm_auxtrace *etm) +{ + struct auxtrace_queues *queues = &etm->queues; + + for (unsigned int i = 0; i < queues->nr_queues; i++) { + struct auxtrace_queue *queue = &queues->queue_array[i]; + struct cs_etm_queue *etmq = queue->priv; + u64 *cpu_data; + int ret; + + if (list_empty(&queue->head) || !etmq || etmq->format != UNFORMATTED) + continue; + + if (!intlist__empty(etmq->traceid_list)) { + pr_err("CS_ETM: Unformatted queue should't have trace ID\n"); + return -EINVAL; + } + + if (etm->per_thread_decoding) + cpu_data = etm->metadata[0]; + else + cpu_data = get_cpu_data(etm, queue->cpu); + + if (!cpu_data) { + pr_err("CS_ETM: Unformatted queue missing CPU metadata\n"); + return -EINVAL; + } + + ret = cs_etm__map_trace_id(etmq, 1, cpu_data); + if (ret) + return ret; + } return 0; } @@ -939,7 +965,7 @@ static int cs_etm__flush_events(struct perf_session *session, struct cs_etm_auxtrace *etm = container_of(session->auxtrace, struct cs_etm_auxtrace, auxtrace); - if (dump_trace) + if (dump_trace || auxtrace__dont_decode(session)) return 0; if (!tool->ordered_events) @@ -1002,7 +1028,7 @@ static void cs_etm__free_queue(void *priv) if (!etmq) return; - cs_etm_decoder__free(etmq->decoder); + cs_etm_decoder__zfree(&etmq->decoder); cs_etm__free_traceid_queues(etmq); if (etmq->own_traceid_list) { @@ -1017,13 +1043,10 @@ static void cs_etm__free_queue(void *priv) free(etmq); } -static void cs_etm__free_events(struct perf_session *session) +static void cs_etm__free_queues(struct cs_etm_auxtrace *etm) { unsigned int i; - struct cs_etm_auxtrace *aux = container_of(session->auxtrace, - struct cs_etm_auxtrace, - auxtrace); - struct auxtrace_queues *queues = &aux->queues; + struct auxtrace_queues *queues = &etm->queues; for (i = 0; i < queues->nr_queues; i++) { cs_etm__free_queue(queues->queue_array[i].priv); @@ -1033,13 +1056,23 @@ static void cs_etm__free_events(struct perf_session *session) auxtrace_queues__free(queues); } +static void cs_etm__free_events(struct perf_session *session) +{ + struct cs_etm_auxtrace *aux = container_of(session->auxtrace, + struct cs_etm_auxtrace, + auxtrace); + + cs_etm__free_queues(aux); +} + static void cs_etm__free(struct perf_session *session) { int i; struct cs_etm_auxtrace *aux = container_of(session->auxtrace, struct cs_etm_auxtrace, auxtrace); - cs_etm__free_events(session); + + cs_etm__free_queues(aux); session->auxtrace = NULL; for (i = 0; i < aux->num_cpu; i++) @@ -2969,7 +3002,7 @@ static int cs_etm__process_event(struct perf_session *session, struct cs_etm_auxtrace, auxtrace); - if (dump_trace) + if (dump_trace || auxtrace__dont_decode(session)) return 0; if (!tool->ordered_events) { @@ -3476,7 +3509,7 @@ static int cs_etm__create_queue_decoders(struct cs_etm_queue *etmq) return 0; out_free_decoder: - cs_etm_decoder__free(etmq->decoder); + cs_etm_decoder__zfree(&etmq->decoder); out_free: zfree(&t_params); return -EINVAL; @@ -3519,27 +3552,49 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event, int err = 0; int aux_hw_id_found; int i; - u64 *ptr = NULL; + u64 *ptr = (u64 *) auxtrace_info->priv; u64 **metadata = NULL; - /* First the global part */ - ptr = (u64 *) auxtrace_info->priv; - num_cpu = ptr[CS_PMU_TYPE_CPUS] & 0xffffffff; + etm = zalloc(sizeof(*etm)); + if (!etm) + return -ENOMEM; + + session->auxtrace = &etm->auxtrace; + etm->auxtrace.free = cs_etm__free; + etm->auxtrace.evsel_is_auxtrace = cs_etm__evsel_is_auxtrace; + etm->auxtrace.process_event = cs_etm__process_event; + etm->auxtrace.process_auxtrace_event = cs_etm__process_auxtrace_event; + etm->auxtrace.flush_events = cs_etm__flush_events; + etm->auxtrace.free_events = cs_etm__free_events; + etm->pmu_type = (unsigned int) ((ptr[CS_PMU_TYPE_CPUS] >> 32) & 0xffffffff); + + /* + * Don't go further than the minimum required to identify this event as + * auxtrace with cs_etm__evsel_is_auxtrace() so unleader_auxtrace() + * works. + */ + if (auxtrace__dont_decode(session)) + return 0; /* * Bound num_cpu by the event size: the global header consumes * CS_ETM_HEADER_SIZE bytes, and each CPU needs at least one u64 * metadata entry after that. */ + num_cpu = ptr[CS_PMU_TYPE_CPUS] & 0xffffffff; priv_size = total_size - event_header_size - INFO_HEADER_SIZE - CS_ETM_HEADER_SIZE; if (num_cpu <= 0 || priv_size <= 0 || - num_cpu > priv_size / (int)sizeof(u64)) - return -EINVAL; + num_cpu > priv_size / (int)sizeof(u64)) { + err = -EINVAL; + goto err_free_etm; + } metadata = zalloc(sizeof(*metadata) * num_cpu); - if (!metadata) - return -ENOMEM; + if (!metadata) { + err = -ENOMEM; + goto err_free_etm; + } /* Start parsing after the common part of the header */ i = CS_HEADER_VERSION_MAX; @@ -3598,13 +3653,6 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event, goto err_free_metadata; } - etm = zalloc(sizeof(*etm)); - - if (!etm) { - err = -ENOMEM; - goto err_free_metadata; - } - /* * As all the ETMs run at the same exception level, the system should * have the same PID format crossing CPUs. So cache the PID format @@ -3614,7 +3662,7 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event, err = auxtrace_queues__init_nr(&etm->queues, max_cpu + 1); if (err) - goto err_free_etm; + goto err_free_metadata; for (unsigned int j = 0; j < etm->queues.nr_queues; ++j) { err = cs_etm__setup_queue(etm, &etm->queues.queue_array[j], j); @@ -3652,7 +3700,6 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event, etm->session = session; etm->num_cpu = num_cpu; - etm->pmu_type = (unsigned int) ((ptr[CS_PMU_TYPE_CPUS] >> 32) & 0xffffffff); etm->snapshot_mode = (ptr[CS_ETM_SNAPSHOT] != 0); etm->metadata = metadata; etm->auxtrace_type = auxtrace_info->type; @@ -3679,14 +3726,6 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event, "you can specify the itrace option 'T' for timestamp decoding\n" "if the Coresight timestamp on the platform is same with the kernel time.\n\n"); - etm->auxtrace.process_event = cs_etm__process_event; - etm->auxtrace.process_auxtrace_event = cs_etm__process_auxtrace_event; - etm->auxtrace.flush_events = cs_etm__flush_events; - etm->auxtrace.free_events = cs_etm__free_events; - etm->auxtrace.free = cs_etm__free; - etm->auxtrace.evsel_is_auxtrace = cs_etm__evsel_is_auxtrace; - session->auxtrace = &etm->auxtrace; - cs_etm__setup_timeless_decoding(etm); etm->tc.time_shift = tc->time_shift; @@ -3750,6 +3789,11 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event, goto err_free_queues; } + /* Create IDs for TRBE sinks so that a decoder is made for each one. */ + err = cs_etm__map_unformatted_trace_ids(etm); + if (err) + goto err_free_queues; + err = cs_etm__create_decoders(etm); if (err) goto err_free_queues; @@ -3758,14 +3802,14 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event, return 0; err_free_queues: - auxtrace_queues__free(&etm->queues); - session->auxtrace = NULL; -err_free_etm: - zfree(&etm); + cs_etm__free_queues(etm); err_free_metadata: /* No need to check @metadata[j], free(NULL) is supported */ for (int j = 0; j < num_cpu; j++) zfree(&metadata[j]); zfree(&metadata); +err_free_etm: + session->auxtrace = NULL; + zfree(&etm); return err; } diff --git a/tools/perf/util/debug.h b/tools/perf/util/debug.h index 6b737e195ce1..7552c9171d90 100644 --- a/tools/perf/util/debug.h +++ b/tools/perf/util/debug.h @@ -33,10 +33,9 @@ extern int debug_type_profile; }) #define pr_info(fmt, ...) \ eprintf(0, verbose, pr_fmt(fmt), ##__VA_ARGS__) -#define pr_debug(fmt, ...) \ - eprintf(1, verbose, pr_fmt(fmt), ##__VA_ARGS__) #define pr_debugN(n, fmt, ...) \ - eprintf(n, verbose, pr_fmt(fmt), ##__VA_ARGS__) + (unlikely(verbose >= (n)) ? eprintf(n, verbose, pr_fmt(fmt), ##__VA_ARGS__) : 0) +#define pr_debug(fmt, ...) pr_debugN(1, pr_fmt(fmt), ##__VA_ARGS__) #define pr_debug2(fmt, ...) pr_debugN(2, pr_fmt(fmt), ##__VA_ARGS__) #define pr_debug3(fmt, ...) pr_debugN(3, pr_fmt(fmt), ##__VA_ARGS__) #define pr_debug4(fmt, ...) pr_debugN(4, pr_fmt(fmt), ##__VA_ARGS__) diff --git a/tools/perf/util/disasm.c b/tools/perf/util/disasm.c index 5478c134e7e3..7c289b67b5bd 100644 --- a/tools/perf/util/disasm.c +++ b/tools/perf/util/disasm.c @@ -1176,84 +1176,6 @@ int symbol__strerror_disassemble(struct map_symbol *ms, int errnum, char *buf, s return 0; } -static int dso__disassemble_filename(struct dso *dso, char *filename, size_t filename_size) -{ - char linkname[PATH_MAX]; - char *build_id_filename; - char *build_id_path = NULL; - char *pos; - int len; - - if (dso__symtab_type(dso) == DSO_BINARY_TYPE__KALLSYMS && - !dso__is_kcore(dso)) - return SYMBOL_ANNOTATE_ERRNO__NO_VMLINUX; - - build_id_filename = dso__build_id_filename(dso, NULL, 0, false); - if (build_id_filename) { - /* - * This is a path in perf's own build id cache, not a path on - * the profiled system, so the symfs layout does not apply. - */ - path__join(filename, filename_size, symbol_conf.symfs, - build_id_filename); - free(build_id_filename); - } else { - if (dso__has_build_id(dso)) - return ENOMEM; - goto fallback; - } - - build_id_path = strdup(filename); - if (!build_id_path) - return ENOMEM; - - /* - * old style build-id cache has name of XX/XXXXXXX.. while - * new style has XX/XXXXXXX../{elf,kallsyms,vdso}. - * extract the build-id part of dirname in the new style only. - */ - pos = strrchr(build_id_path, '/'); - if (pos && strlen(pos) < SBUILD_ID_SIZE - 2) - dirname(build_id_path); - - if (dso__is_kcore(dso)) - goto fallback; - - len = readlink(build_id_path, linkname, sizeof(linkname) - 1); - if (len < 0) - goto fallback; - - linkname[len] = '\0'; - if (strstr(linkname, DSO__NAME_KALLSYMS) || - access(filename, R_OK)) { -fallback: - /* - * If we don't have build-ids or the build-id file isn't in the - * cache, or is just a kallsyms file, well, lets hope that this - * DSO is the same as when 'perf record' ran. - */ - if (dso__kernel(dso) && dso__long_name(dso)[0] == '/') - snprintf(filename, filename_size, "%s", dso__long_name(dso)); - else - __symbol__join_symfs(filename, filename_size, dso__long_name(dso)); - - mutex_lock(dso__lock(dso)); - if (access(filename, R_OK) && errno == ENOENT && dso__nsinfo(dso)) { - char *new_name = dso__filename_with_chroot(dso, filename); - if (new_name) { - strlcpy(filename, new_name, filename_size); - free(new_name); - } - } - mutex_unlock(dso__lock(dso)); - } else if (dso__binary_type(dso) == DSO_BINARY_TYPE__NOT_FOUND) { - dso__set_binary_type(dso, DSO_BINARY_TYPE__BUILD_ID_CACHE); - } - - free(build_id_path); - return 0; -} - static int symbol__disassemble_raw(char *filename, struct symbol *sym, struct annotate_args *args) { @@ -1566,14 +1488,29 @@ int symbol__disassemble(struct symbol *sym, struct annotate_args *args) struct annotation_options *options = args->options; struct map *map = args->ms->map; struct dso *dso = map__dso(map); - char symfs_filename[PATH_MAX]; + char *symfs_filename; bool delete_extract = false; struct kcore_extract kce; + enum dso_binary_type dbt; bool decomp = false; - int err = dso__disassemble_filename(dso, symfs_filename, sizeof(symfs_filename)); + int err; - if (err) - return err; + dbt = dso__find_binary_type(dso); + + if (dbt == DSO_BINARY_TYPE__KALLSYMS) + return SYMBOL_ANNOTATE_ERRNO__NO_VMLINUX; + + mutex_lock(dso__lock(dso)); + symfs_filename = dso__get_filename(dso, "", &decomp, dbt); + mutex_unlock(dso__lock(dso)); + + if (symfs_filename == NULL) { + if (dbt == DSO_BINARY_TYPE__BPF_PROG_INFO || + dbt == DSO_BINARY_TYPE__BPF_IMAGE) + symfs_filename = (char *)dso__long_name(dso); + else + return SYMBOL_ANNOTATE_ERRNO__COULDNT_DETERMINE_FILE_TYPE; + } pr_debug("%s: filename=%s, sym=%s, start=%#" PRIx64 ", end=%#" PRIx64 "\n", __func__, symfs_filename, sym->name, map__unmap_ip(map, sym->start), @@ -1581,9 +1518,7 @@ int symbol__disassemble(struct symbol *sym, struct annotate_args *args) pr_debug("annotating [%p] %30s : [%p] %30s\n", dso, dso__long_name(dso), sym, sym->name); - if (dso__binary_type(dso) == DSO_BINARY_TYPE__NOT_FOUND) { - return SYMBOL_ANNOTATE_ERRNO__COULDNT_DETERMINE_FILE_TYPE; - } else if (dso__is_kcore(dso)) { + if (dso__is_kcore(dso)) { kce.addr = map__rip_2objdump(map, sym->start); kce.kcore_filename = symfs_filename; kce.len = sym->end - sym->start; @@ -1591,18 +1526,8 @@ int symbol__disassemble(struct symbol *sym, struct annotate_args *args) if (!kcore_extract__create(&kce)) { delete_extract = true; - strlcpy(symfs_filename, kce.extract_filename, sizeof(symfs_filename)); - } - } else if (dso__needs_decompress(dso)) { - char tmp[KMOD_DECOMP_LEN]; - - if (dso__decompress_kmodule_path(dso, symfs_filename, tmp, sizeof(tmp)) < 0) - return -1; - - /* empty pathname means file wasn't actually compressed */ - if (tmp[0] != '\0') { - decomp = true; - strcpy(symfs_filename, tmp); + dso__put_filename(dso, symfs_filename, decomp); + symfs_filename = kce.extract_filename; } } @@ -1660,11 +1585,10 @@ int symbol__disassemble(struct symbol *sym, struct annotate_args *args) pr_debug("Disassembled with %s\n", perf_disassembler__strs[dis]); } out_remove_tmp: - if (decomp) - unlink(symfs_filename); - if (delete_extract) kcore_extract__delete(&kce); + else if (dbt != DSO_BINARY_TYPE__BPF_PROG_INFO && dbt != DSO_BINARY_TYPE__BPF_IMAGE) + dso__put_filename(dso, symfs_filename, decomp); return err; } diff --git a/tools/perf/util/dso.c b/tools/perf/util/dso.c index d3017c82ffb5..ce98613868e7 100644 --- a/tools/perf/util/dso.c +++ b/tools/perf/util/dso.c @@ -833,34 +833,149 @@ void dso__data_close(struct dso *dso) mutex_unlock(dso__data_open_lock()); } -static void try_to_open_dso(struct dso *dso, struct machine *machine) - EXCLUSIVE_LOCKS_REQUIRED(_dso__data_open_lock) +static int dso__find_binary_type_locked(struct dso *dso) + EXCLUSIVE_LOCKS_REQUIRED(dso__lock(dso)) { enum dso_binary_type binary_type_data[] = { DSO_BINARY_TYPE__BUILD_ID_CACHE, DSO_BINARY_TYPE__SYSTEM_PATH_DSO, + dso__symtab_type(dso), DSO_BINARY_TYPE__NOT_FOUND, }; int i = 0; - struct dso_data *dso_data = dso__data(dso); + char *path; + bool found = false; + bool decomp = false; + int saved_errno; - if (dso_data->fd >= 0) - return; + if (dso__bintype_searched(dso)) + return dso__binary_type(dso); + dso__set_bintype_searched(dso); - if (dso__binary_type(dso) != DSO_BINARY_TYPE__NOT_FOUND) { - dso_data->fd = open_dso(dso, machine); - goto out; + switch (dso__binary_type(dso)) { + case DSO_BINARY_TYPE__KALLSYMS: + case DSO_BINARY_TYPE__GUEST_KALLSYMS: + case DSO_BINARY_TYPE__VMLINUX: + case DSO_BINARY_TYPE__GUEST_VMLINUX: + case DSO_BINARY_TYPE__KCORE: + case DSO_BINARY_TYPE__GUEST_KCORE: + /* Nothing to do with kernel images */ + WARN_ON(!dso__kernel(dso)); + return dso__binary_type(dso); + case DSO_BINARY_TYPE__BPF_PROG_INFO: + case DSO_BINARY_TYPE__BPF_IMAGE: + case DSO_BINARY_TYPE__OOL: + case DSO_BINARY_TYPE__JAVA_JIT: + /* Same for the special DSOs */ + return dso__binary_type(dso); + case DSO_BINARY_TYPE__GUEST_KMODULE: + case DSO_BINARY_TYPE__GUEST_KMODULE_COMP: + case DSO_BINARY_TYPE__SYSTEM_PATH_KMODULE: + case DSO_BINARY_TYPE__SYSTEM_PATH_KMODULE_COMP: + case DSO_BINARY_TYPE__SYSTEM_PATH_DSO: + case DSO_BINARY_TYPE__BUILD_ID_CACHE: + /* We don't expect these are set; fall through */ + case DSO_BINARY_TYPE__NOT_FOUND: + /* Let's find it out (for user DSOs or kernel modules) */ + break; + case DSO_BINARY_TYPE__FEDORA_DEBUGINFO: + case DSO_BINARY_TYPE__UBUNTU_DEBUGINFO: + case DSO_BINARY_TYPE__MIXEDUP_UBUNTU_DEBUGINFO: + case DSO_BINARY_TYPE__OPENEMBEDDED_DEBUGINFO: + case DSO_BINARY_TYPE__BUILD_ID_CACHE_DEBUGINFO: + case DSO_BINARY_TYPE__BUILDID_DEBUGINFO: + case DSO_BINARY_TYPE__GNU_DEBUGDATA: + case DSO_BINARY_TYPE__DEBUGLINK: + default: + /* Unexpected binary types (usually debug only) */ + break; } do { dso__set_binary_type(dso, binary_type_data[i++]); - dso_data->fd = open_dso(dso, machine); - if (dso_data->fd >= 0) - goto out; + errno = 0; + path = dso__get_filename(dso, "", &decomp, dso__binary_type(dso)); + if (path == NULL) { + if (errno == EMFILE) + goto open_err; + continue; + } + + errno = 0; + found = filename__has_section(path, ".text"); + if (!found) + saved_errno = errno; + dso__put_filename(dso, path, decomp); + + if (found) + return dso__binary_type(dso); + else if (saved_errno == EMFILE) + goto open_err; } while (dso__binary_type(dso) != DSO_BINARY_TYPE__NOT_FOUND); -out: + + return dso__binary_type(dso); + +open_err: + /* Too many open files. Allow retry after closing one. */ + RC_CHK_ACCESS(dso)->bintype_searched = 0; + dso__set_binary_type(dso, DSO_BINARY_TYPE__NOT_FOUND); + return -1; +} + +#define FIND_MAX_RETRY 3 +static int __dso__find_binary_type(struct dso *dso) + EXCLUSIVE_LOCKS_REQUIRED(_dso__data_open_lock) +{ + int ret; + int retry = 0; + +again: + mutex_lock(dso__lock(dso)); + ret = dso__find_binary_type_locked(dso); + mutex_unlock(dso__lock(dso)); + + if (ret == -1 && retry++ < FIND_MAX_RETRY) { + if (dso__data_open_cnt) { + close_first_dso(); + goto again; + } + } + + if (ret == -1) { + mutex_lock(dso__lock(dso)); + ret = dso__binary_type(dso); + dso__set_bintype_searched(dso); + mutex_unlock(dso__lock(dso)); + } + + return ret; +} +#undef FIND_MAX_RETRY + +enum dso_binary_type dso__find_binary_type(struct dso *dso) +{ + int ret; + + mutex_lock(dso__data_open_lock()); + ret = __dso__find_binary_type(dso); + mutex_unlock(dso__data_open_lock()); + + return ret; +} + +static void try_to_open_dso(struct dso *dso, struct machine *machine) + EXCLUSIVE_LOCKS_REQUIRED(_dso__data_open_lock) +{ + struct dso_data *dso_data = dso__data(dso); + + if (dso_data->fd >= 0) + return; + + __dso__find_binary_type(dso); + dso_data->fd = open_dso(dso, machine); + if (dso_data->fd >= 0) dso_data->status = DSO_DATA_STATUS_OK; else @@ -1377,9 +1492,10 @@ uint16_t dso__e_machine_endian(struct dso *dso, struct machine *machine, uint32_ bool *is_big_endian) { uint16_t e_machine = EM_NONE; + enum dso_binary_type dbt = dso__find_binary_type(dso); int fd; - switch (dso__binary_type(dso)) { + switch (dbt) { case DSO_BINARY_TYPE__KALLSYMS: case DSO_BINARY_TYPE__GUEST_KALLSYMS: case DSO_BINARY_TYPE__VMLINUX: @@ -1722,8 +1838,6 @@ struct dso *dso__new_id(const char *name, const struct dso_id *id) dso->srclines = RB_ROOT_CACHED; dso->data_types = RB_ROOT; dso->global_vars = RB_ROOT; - dso->data.fd = -1; - dso->data.status = DSO_DATA_STATUS_UNKNOWN; dso->symtab_type = DSO_BINARY_TYPE__NOT_FOUND; dso->binary_type = DSO_BINARY_TYPE__NOT_FOUND; dso->dbginfo_type = DSO_BINARY_TYPE__NOT_FOUND; @@ -2113,6 +2227,7 @@ void dso__find_dbginfo_type(struct dso *dso) DSO_BINARY_TYPE__OPENEMBEDDED_DEBUGINFO, DSO_BINARY_TYPE__BUILDID_DEBUGINFO, DSO_BINARY_TYPE__SYSTEM_PATH_DSO, + DSO_BINARY_TYPE__DEBUGLINK, DSO_BINARY_TYPE__NOT_FOUND, }; const enum dso_binary_type *type; diff --git a/tools/perf/util/dso.h b/tools/perf/util/dso.h index 3f08d45e7f53..d5f7382d4e13 100644 --- a/tools/perf/util/dso.h +++ b/tools/perf/util/dso.h @@ -349,6 +349,7 @@ DECLARE_RC_STRUCT(dso) { u8 short_name_allocated:1; u8 long_name_allocated:1; u8 is_64_bit:1; + u8 bintype_searched:1; u8 debuginfo_searched:1; bool sorted_by_name; bool loaded; @@ -759,7 +760,7 @@ static inline const char *dso__symsrc_filename(const struct dso *dso) return RC_CHK_ACCESS(dso)->symsrc_filename; } -void dso__set_symsrc_filename(struct dso *dso, char *val); +void dso__set_symsrc_filename(struct dso *dso, char *val) EXCLUSIVE_LOCKS_REQUIRED(dso__lock(dso)); static inline void dso__free_symsrc_filename(struct dso *dso) { @@ -957,6 +958,8 @@ void dso__reset_find_symbol_cache(struct dso *dso); size_t dso__fprintf_symbols_by_name(struct dso *dso, FILE *fp); size_t dso__fprintf(struct dso *dso, FILE *fp); +enum dso_binary_type dso__find_binary_type(struct dso *dso); + static inline enum dso_binary_type dso__binary_type(const struct dso *dso) { return RC_CHK_ACCESS(dso)->binary_type; @@ -967,6 +970,16 @@ static inline void dso__set_binary_type(struct dso *dso, enum dso_binary_type bt RC_CHK_ACCESS(dso)->binary_type = bt; } +static inline bool dso__bintype_searched(const struct dso *dso) +{ + return RC_CHK_ACCESS(dso)->bintype_searched; +} + +static inline void dso__set_bintype_searched(struct dso *dso) +{ + RC_CHK_ACCESS(dso)->bintype_searched = 1; +} + static inline bool dso__is_vmlinux(const struct dso *dso) { enum dso_binary_type bt = dso__binary_type(dso); diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c index 9c5e7510f0c0..d060fca8090f 100644 --- a/tools/perf/util/evsel.c +++ b/tools/perf/util/evsel.c @@ -3005,6 +3005,19 @@ check: return false; } +static void evsel__close_fds(struct evsel *evsel, int start_cpu_map_idx, + int cpu_map_idx, int thread, int nthreads) +{ + do { + while (--thread >= 0) { + if (FD(evsel, cpu_map_idx, thread) >= 0) + close(FD(evsel, cpu_map_idx, thread)); + FD(evsel, cpu_map_idx, thread) = -1; + } + thread = nthreads; + } while (--cpu_map_idx >= start_cpu_map_idx); +} + static int evsel__open_cpu(struct evsel *evsel, struct perf_cpu_map *cpus, struct perf_thread_map *threads, int start_cpu_map_idx, int end_cpu_map_idx) @@ -3014,6 +3027,12 @@ static int evsel__open_cpu(struct evsel *evsel, struct perf_cpu_map *cpus, enum rlimit_action set_rlimit = NO_CHANGE; struct perf_cpu cpu; + /* + * A failed attempt being retried after evsel__fallback() would + * otherwise keep group members out of their group in get_group_fd(). + */ + evsel->supported = true; + if (evsel__is_retire_lat(evsel)) { err = evsel__tpebs_open(evsel); goto out; @@ -3098,7 +3117,11 @@ retry_open: bpf_counter__install_pe(evsel, idx, fd); - if (unlikely(test_attr__enabled())) { + /* + * Support probes aren't what was asked for, leave them + * out of the PERF_TEST_ATTR dump. + */ + if (unlikely(test_attr__enabled() && !evsel->support_probe)) { test_attr__open(&evsel->core.attr, pid, cpu, fd, group_fd, evsel->open_flags); } @@ -3140,8 +3163,14 @@ try_fallback: if (err == -EMFILE && rlimit__increase_nofile(&set_rlimit)) goto retry_open; - if (err == -EINVAL && evsel__detect_missing_features(evsel, cpu)) + if (err == -EINVAL && evsel__detect_missing_features(evsel, cpu)) { + /* + * The restart opens the events again, so close what this + * attempt opened before its entries are overwritten. + */ + evsel__close_fds(evsel, start_cpu_map_idx, idx, thread, nthreads); goto fallback_missing_features; + } if (evsel__precise_ip_fallback(evsel)) goto retry_open; @@ -3151,14 +3180,7 @@ out_close: threads->err_thread = thread; old_errno = errno; - do { - while (--thread >= 0) { - if (FD(evsel, idx, thread) >= 0) - close(FD(evsel, idx, thread)); - FD(evsel, idx, thread) = -1; - } - thread = nthreads; - } while (--idx >= start_cpu_map_idx); + evsel__close_fds(evsel, start_cpu_map_idx, idx, thread, nthreads); errno = old_errno; out: if (err) diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h index 174f3414fd3c..53f3641b66e1 100644 --- a/tools/perf/util/evsel.h +++ b/tools/perf/util/evsel.h @@ -120,6 +120,7 @@ struct evsel { u64 db_id; bool uniquified_name; bool supported; + bool support_probe; /* Opened just to check for support. */ bool needs_swap; bool disabled; bool no_aux_samples; diff --git a/tools/perf/util/hist.c b/tools/perf/util/hist.c index 443694926f1a..e75a4fcce975 100644 --- a/tools/perf/util/hist.c +++ b/tools/perf/util/hist.c @@ -409,7 +409,8 @@ static bool hists__decay_entry(struct hists *hists, struct hist_entry *he) u64 prev_period = he->stat.period; u64 prev_latency = he->stat.latency; - if (prev_period == 0) + if (prev_period == 0 && + (!symbol_conf.cumulate_callchain || he->stat_acc->period == 0)) return true; he_stat__decay(&he->stat); @@ -442,7 +443,8 @@ static bool hists__decay_entry(struct hists *hists, struct hist_entry *he) } } - return he->stat.period == 0 && he->stat.latency == 0; + return he->stat.period == 0 && he->stat.latency == 0 && + (!symbol_conf.cumulate_callchain || he->stat_acc->period == 0); } static void hists__delete_entry(struct hists *hists, struct hist_entry *he) diff --git a/tools/perf/util/libbfd.c b/tools/perf/util/libbfd.c index a907d7c1ac94..992e2c2a7198 100644 --- a/tools/perf/util/libbfd.c +++ b/tools/perf/util/libbfd.c @@ -288,7 +288,7 @@ int libbfd__addr2line(const char *dso_name, u64 addr, if (node && inline_list__append_dso_a2l(dso, node, sym)) { inline_node__clear_frames(node); - ret = 0; + ret = -ENOMEM; goto out; } @@ -302,7 +302,7 @@ int libbfd__addr2line(const char *dso_name, u64 addr, if (node != NULL) { if (inline_list__append_dso_a2l(dso, node, sym)) { inline_node__clear_frames(node); - ret = 0; + ret = -ENOMEM; goto out; } } @@ -312,7 +312,7 @@ int libbfd__addr2line(const char *dso_name, u64 addr, if (file) { *file = a2l->filename ? strdup(a2l->filename) : NULL; if (!*file) { - /* Leave ret as 0 so that another addr2line is tried. */ + ret = a2l->filename ? -ENOMEM : 0; goto out; } } @@ -463,7 +463,7 @@ int dso__load_bfd_symbols(struct dso *dso, const char *debugfile) symbols__fixup_duplicate(dso__symbols(dso)); dso__set_adjust_symbols(dso, true); - err = 0; + err = dso__has_symbols(dso); out_free: free(symbols); out_close: diff --git a/tools/perf/util/libdw.c b/tools/perf/util/libdw.c index c4504cceb313..e660623338d5 100644 --- a/tools/perf/util/libdw.c +++ b/tools/perf/util/libdw.c @@ -7,6 +7,7 @@ #include "callchain.h" #include <fcntl.h> #include <unistd.h> +#include <string.h> #include <elfutils/libdwfl.h> static const Dwfl_Callbacks offline_callbacks = { @@ -175,10 +176,15 @@ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr, int lineno = 0; int ret = 0; + if (!dso__has_srcline(dso)) + return 0; + mutex_lock(dso__lock(dso)); dwfl = dso__libdw_dwfl(dso); - if (!dwfl) + if (!dwfl) { + ret = -1; goto out; + } mod = dwfl_addrmodule(dwfl, addr); if (!mod) { @@ -192,14 +198,23 @@ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr, * libdw. */ if (!dwfl_module_getdwarf(mod, &bias)) { - ret = -1; + /* + * Free the Dwfl session so the mmap'd ELF binary is unmapped + * immediately, and mark the DSO as lacking srcline/DWARF info + * so neither libdw nor slower fallback backends (addr2line + * subprocess, libbfd) are repeatedly invoked on stripped DSOs. + */ + dso__free_libdw(dso); + dso__set_has_srcline(dso, false); + ret = 0; goto out; } /* Find source line information for the address. */ dwline = dwfl_module_getsrc(mod, addr + bias); if (!dwline) { - ret = -1; + /* conclusively missing information -> abort fallbacks */ + ret = 0; goto out; } @@ -228,7 +243,7 @@ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr, free(*file); *file = NULL; } - ret = 0; + ret = -ENOMEM; goto out; } @@ -245,7 +260,7 @@ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr, *file = NULL; } inline_node__clear_frames(node); - ret = 0; + ret = args.err; goto out; } } diff --git a/tools/perf/util/libdw.h b/tools/perf/util/libdw.h index b12094737415..c8f319ce35df 100644 --- a/tools/perf/util/libdw.h +++ b/tools/perf/util/libdw.h @@ -23,7 +23,8 @@ struct symbol; * finds the source line information for the given address, and optionally * resolves inline function call chains. * - * Returns 1 on success (found), 0 on failure (not found). + * Returns 1 on success (found), 0 when DWARF/line info is conclusively + * missing, and < 0 on initialization or uncompiled error. */ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr, struct dso *dso, @@ -47,7 +48,7 @@ static inline int libdw__addr2line(u64 addr __maybe_unused, char **file __maybe_ struct inline_node *node __maybe_unused, struct symbol *sym __maybe_unused) { - return 0; + return -1; } static inline void dso__free_libdw(struct dso *dso __maybe_unused) diff --git a/tools/perf/util/llvm-c-helpers.cpp b/tools/perf/util/llvm-c-helpers.cpp index 004081bd12c9..d990166100f0 100644 --- a/tools/perf/util/llvm-c-helpers.cpp +++ b/tools/perf/util/llvm-c-helpers.cpp @@ -50,20 +50,20 @@ static LLVMSymbolizer *get_symbolizer() return instance; } -/* Returns 0 on error, 1 on success. */ +/* Returns < 0 on error, 0 when line info is missing, 1 on success. */ static int extract_file_and_line(const DILineInfo &line_info, char **file, unsigned int *line) { - if (file) { - if (line_info.FileName == "<invalid>") { - /* Match the convention of libbfd. */ + if (line_info.FileName == "<invalid>") { + if (file) *file = nullptr; - } else { - /* The caller expects to get something it can free(). */ - *file = strdup(line_info.FileName.c_str()); - if (*file == nullptr) - return 0; - } + return 0; + } + if (file) { + /* The caller expects to get something it can free(). */ + *file = strdup(line_info.FileName.c_str()); + if (*file == nullptr) + return -ENOMEM; } if (line) *line = line_info.Line; @@ -86,30 +86,42 @@ int llvm_addr2line(const char *dso_name, u64 addr, Expected<DIInliningInfo> res_or_err = symbolizer->symbolizeInlinedCode(dso_name, sectioned_addr); - if (!res_or_err) - return 0; + if (!res_or_err) { + consumeError(res_or_err.takeError()); + return -1; + } unsigned num_frames = res_or_err->getNumberOfFrames(); if (num_frames == 0) return 0; - if (extract_file_and_line(res_or_err->getFrame(0), - file, line) == 0) - return 0; + int ret = extract_file_and_line(res_or_err->getFrame(0), + file, line); + if (ret <= 0) + return ret; *inline_frames = (llvm_a2l_frame *)calloc( num_frames, sizeof(**inline_frames)); - if (*inline_frames == nullptr) - return 0; + if (*inline_frames == nullptr) { + if (file) + zfree(file); + return -ENOMEM; + } for (unsigned i = 0; i < num_frames; ++i) { const DILineInfo &src = res_or_err->getFrame(i); llvm_a2l_frame &dst = (*inline_frames)[i]; - if (src.FileName == "<invalid>") - /* Match the convention of libbfd. */ - dst.filename = nullptr; - else - dst.filename = strdup(src.FileName.c_str()); + if (src.FileName == "<invalid>") { + for (unsigned j = 0; j < i; ++j) { + zfree(&(*inline_frames)[j].filename); + zfree(&(*inline_frames)[j].funcname); + } + zfree(inline_frames); + if (file) + zfree(file); + return 0; + } + dst.filename = strdup(src.FileName.c_str()); dst.funcname = strdup(src.FunctionName.c_str()); dst.line = src.Line; @@ -120,7 +132,9 @@ int llvm_addr2line(const char *dso_name, u64 addr, zfree(&(*inline_frames)[j].funcname); } zfree(inline_frames); - return 0; + if (file) + zfree(file); + return -ENOMEM; } } @@ -131,8 +145,10 @@ int llvm_addr2line(const char *dso_name, u64 addr, Expected<DILineInfo> res_or_err = symbolizer->symbolizeCode(dso_name, sectioned_addr); - if (!res_or_err) - return 0; + if (!res_or_err) { + consumeError(res_or_err.takeError()); + return -1; + } return extract_file_and_line(*res_or_err, file, line); } } diff --git a/tools/perf/util/llvm.c b/tools/perf/util/llvm.c index 722e71aab100..0ecb1a53359e 100644 --- a/tools/perf/util/llvm.c +++ b/tools/perf/util/llvm.c @@ -44,8 +44,11 @@ int llvm__addr2line(const char *dso_name __maybe_unused, u64 addr __maybe_unused int num_frames = llvm_addr2line(dso_name, addr, file, line, node && unwind_inlines, &inline_frames); - if (num_frames == 0 || !inline_frames) { - /* Error, or we didn't want inlines. */ + if (num_frames <= 0 || !inline_frames) { + /* + * Error (< 0 -> iterate fallbacks), missing line info (0), or + * we resolved lines without wanting inlines (1). + */ return num_frames; } @@ -60,8 +63,11 @@ int llvm__addr2line(const char *dso_name __maybe_unused, u64 addr __maybe_unused inline_frames[i].line); } if (inline_list__append(inline_sym, srcline, node) != 0) { + if (file) + zfree(file); + inline_node__clear_frames(node); free_llvm_inline_frames(inline_frames, num_frames); - return 0; + return -1; } } free_llvm_inline_frames(inline_frames, num_frames); diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c index 1d9d3bf57720..076d167380f2 100644 --- a/tools/perf/util/machine.c +++ b/tools/perf/util/machine.c @@ -2860,7 +2860,7 @@ static int thread__resolve_callchain_sample(struct thread *thread, if (chain) chain_nr = chain->nr; - if (evsel__has_branch_callstack(evsel)) { + if (chain && evsel__has_branch_callstack(evsel)) { struct perf_env *env = evsel__env(evsel); err = resolve_lbr_callchain_sample(thread, cursor, sample, parent, diff --git a/tools/perf/util/mmap.c b/tools/perf/util/mmap.c index 358e70c4f3ed..6dcc4a9304b9 100644 --- a/tools/perf/util/mmap.c +++ b/tools/perf/util/mmap.c @@ -330,7 +330,7 @@ int mmap__mmap(struct mmap *map, struct mmap_params *mp, int fd, struct perf_cpu int perf_mmap__push(struct mmap *md, void *to, int push(struct mmap *map, void *to, void *buf, size_t size)) { - u64 head = perf_mmap__read_head(&md->core); + u64 head; unsigned char *data = md->core.base + page_size; unsigned long size; void *buf; @@ -340,6 +340,7 @@ int perf_mmap__push(struct mmap *md, void *to, if (rc < 0) return (rc == -EAGAIN) ? 1 : -1; + head = md->core.overwrite ? md->core.start : md->core.end; size = md->core.end - md->core.start; if ((md->core.start & md->core.mask) + size != (md->core.end & md->core.mask)) { diff --git a/tools/perf/util/mutex.c b/tools/perf/util/mutex.c index 7aa1f3f55a7d..df3b7cce3d5a 100644 --- a/tools/perf/util/mutex.c +++ b/tools/perf/util/mutex.c @@ -17,37 +17,106 @@ static void check_err(const char *fn, int err) #define CHECK_ERR(err) check_err(__func__, err) -static void __mutex_init(struct mutex *mtx, bool pshared, bool recursive) +#ifdef HAVE_PTHREAD_MUTEX_ROBUST +/* + * A robust mutex whose owner died while holding it comes back as EOWNERDEAD, + * with the caller holding it: make it consistent, which takes it over; failing + * that, a give_up caller unlocks it, leaving it ENOTRECOVERABLE, not leaked. + */ +static int mutex_take_over(struct mutex *mtx, int err, bool give_up) +{ + if (err != EOWNERDEAD) + return err; + + pr_debug("mutex: taking over one left by a dead owner\n"); + err = pthread_mutex_consistent(&mtx->lock); + if (err && give_up) + CHECK_ERR(pthread_mutex_unlock(&mtx->lock)); + + return err; +} +#else +#define mutex_take_over(mtx, err, give_up) ((void)(mtx), (void)(give_up), (err)) +#endif + +static bool __mutex_init(struct mutex *mtx, bool pshared, bool recursive, bool robust) { pthread_mutexattr_t attr; + bool ok = false, robust_unavailable = false; + int err; - CHECK_ERR(pthread_mutexattr_init(&attr)); + err = pthread_mutexattr_init(&attr); + if (err) + goto out; #ifndef NDEBUG /* In normal builds enable error checking, such as recursive usage. */ - CHECK_ERR(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK)); + err = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); + if (err) + goto out_destroy; #endif - if (recursive) - CHECK_ERR(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)); - if (pshared) - CHECK_ERR(pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED)); - CHECK_ERR(pthread_mutex_init(&mtx->lock, &attr)); + if (recursive) { + err = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + if (err) + goto out_destroy; + } + if (pshared) { + err = pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); + if (err) + goto out_destroy; + } + if (robust) { +#ifdef HAVE_PTHREAD_MUTEX_ROBUST + err = pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST); + if (err) + robust_unavailable = true; +#else + robust_unavailable = true; +#endif + if (robust_unavailable) + goto out_destroy; + } + + err = pthread_mutex_init(&mtx->lock, &attr); + if (!err) + ok = true; + else if (robust && pshared && err == ENOTSUP) + /* + * glibc refuses a robust process shared mutex with ENOTSUP when + * set_robust_list(2) isn't usable, i.e. when the machine provides + * no robust mutexes for the process shared case: an unavailable + * API, not a bad request. + */ + robust_unavailable = true; +out_destroy: CHECK_ERR(pthread_mutexattr_destroy(&attr)); +out: + if (robust_unavailable) + pr_debug("mutex: robust mutexes aren't available\n"); + else if (err) + CHECK_ERR(err); + + return ok; } void mutex_init(struct mutex *mtx) { - __mutex_init(mtx, /*pshared=*/false, /*recursive=*/false); + __mutex_init(mtx, /*pshared=*/false, /*recursive=*/false, /*robust=*/false); } void mutex_init_pshared(struct mutex *mtx) { - __mutex_init(mtx, /*pshared=*/true, /*recursive=*/false); + __mutex_init(mtx, /*pshared=*/true, /*recursive=*/false, /*robust=*/false); } void mutex_init_recursive(struct mutex *mtx) { - __mutex_init(mtx, /*pshared=*/false, /*recursive=*/true); + __mutex_init(mtx, /*pshared=*/false, /*recursive=*/true, /*robust=*/false); +} + +bool mutex_init_pshared_robust(struct mutex *mtx) +{ + return __mutex_init(mtx, /*pshared=*/true, /*recursive=*/false, /*robust=*/true); } void mutex_destroy(struct mutex *mtx) @@ -58,7 +127,8 @@ void mutex_destroy(struct mutex *mtx) void mutex_lock(struct mutex *mtx) NO_THREAD_SAFETY_ANALYSIS { - CHECK_ERR(pthread_mutex_lock(&mtx->lock)); + CHECK_ERR(mutex_take_over(mtx, pthread_mutex_lock(&mtx->lock), + /*give_up=*/false)); } void mutex_unlock(struct mutex *mtx) @@ -69,7 +139,8 @@ void mutex_unlock(struct mutex *mtx) bool mutex_trylock(struct mutex *mtx) { - int ret = pthread_mutex_trylock(&mtx->lock); + int ret = mutex_take_over(mtx, pthread_mutex_trylock(&mtx->lock), + /*give_up=*/true); if (ret == 0) return true; /* Lock acquired. */ @@ -111,7 +182,9 @@ void cond_destroy(struct cond *cnd) void cond_wait(struct cond *cnd, struct mutex *mtx) { - CHECK_ERR(pthread_cond_wait(&cnd->cond, &mtx->lock)); + CHECK_ERR(mutex_take_over(mtx, + pthread_cond_wait(&cnd->cond, &mtx->lock), + /*give_up=*/false)); } void cond_signal(struct cond *cnd) diff --git a/tools/perf/util/mutex.h b/tools/perf/util/mutex.h index 555493767588..5de1065ea2b9 100644 --- a/tools/perf/util/mutex.h +++ b/tools/perf/util/mutex.h @@ -142,6 +142,12 @@ void mutex_init(struct mutex *mtx); void mutex_init_pshared(struct mutex *mtx); /* Initializes a mutex that may be recursively held on the same thread. */ void mutex_init_recursive(struct mutex *mtx); +/* + * As mutex_init_pshared(), plus the robust attribute: a process dying while + * holding it doesn't leave it locked, but the takeover isn't reported and the + * data it protects may be mid update. Returns false when not available. + */ +bool mutex_init_pshared_robust(struct mutex *mtx); void mutex_destroy(struct mutex *mtx); void mutex_lock(struct mutex *mtx) EXCLUSIVE_LOCK_FUNCTION(*mtx); diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c index 2f3317ce17f0..f3ba70d4f76b 100644 --- a/tools/perf/util/parse-events.c +++ b/tools/perf/util/parse-events.c @@ -1,10 +1,10 @@ // SPDX-License-Identifier: GPL-2.0 +#include <errno.h> #include <linux/hw_breakpoint.h> #include <linux/err.h> #include <linux/list_sort.h> #include <linux/zalloc.h> #include <dirent.h> -#include <errno.h> #include <sys/ioctl.h> #include <sys/param.h> #include "cpumap.h" diff --git a/tools/perf/util/pfm.c b/tools/perf/util/pfm.c index f80d6b0df47a..ad9bc481da70 100644 --- a/tools/perf/util/pfm.c +++ b/tools/perf/util/pfm.c @@ -141,6 +141,7 @@ static bool is_libpfm_event_supported(const char *name, struct perf_cpu_map *cpu return false; evsel->is_libpfm_event = true; + evsel->support_probe = true; ret = evsel__open(evsel, cpus, threads); if (ret == -EACCES) { diff --git a/tools/perf/util/print-events.c b/tools/perf/util/print-events.c index 0242243681b6..77428d9e73c4 100644 --- a/tools/perf/util/print-events.c +++ b/tools/perf/util/print-events.c @@ -147,6 +147,7 @@ bool is_event_supported(u8 type, u64 config) evsel = evsel__new(&attr); if (evsel) { + evsel->support_probe = true; ret = evsel__open(evsel, NULL, tmap) >= 0; if (!ret) { diff --git a/tools/perf/util/print_insn.h b/tools/perf/util/print_insn.h index cefa5c5f246e..3de9438af9cd 100644 --- a/tools/perf/util/print_insn.h +++ b/tools/perf/util/print_insn.h @@ -4,6 +4,7 @@ #include <stddef.h> #include <stdio.h> +#include <sys/types.h> #include <linux/types.h> diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c index 4eb17bdc5fd1..60dbe2dad5eb 100644 --- a/tools/perf/util/python.c +++ b/tools/perf/util/python.c @@ -3824,10 +3824,12 @@ static PyObject *pyrf__parse_events(PyObject *self, PyObject *args) perf_evlist__set_maps(evlist__core(evlist), cpus, threads); if (parse_events(evlist, input, &err)) { parse_events_error__print(&err, input); + parse_events_error__exit(&err); PyErr_SetFromErrno(PyExc_OSError); evlist__put(evlist); return NULL; } + parse_events_error__exit(&err); result = pyrf_evlist__from_evlist(evlist); evlist__put(evlist); return result; diff --git a/tools/perf/util/srcline.c b/tools/perf/util/srcline.c index a8d16106f619..23951c35fb66 100644 --- a/tools/perf/util/srcline.c +++ b/tools/perf/util/srcline.c @@ -181,9 +181,9 @@ static int addr2line(const char *dso_name, u64 addr, char **file, unsigned int * break; case A2L_STYLE_UNKNOWN: default: - break; + return 0; } - if (ret > 0) + if (ret >= 0) return ret; } @@ -250,6 +250,10 @@ static struct inline_node *addr2inlines(const char *dso_name, u64 addr, addr2line(dso_name, addr, /*file=*/NULL, /*line_nr=*/NULL, dso, /*unwind_inlines=*/true, node, sym); + if (!dso__has_srcline(dso) && list_empty(&node->val)) { + inline_node__delete(node); + return NULL; + } return node; } @@ -276,8 +280,8 @@ char *__get_srcline(struct dso *dso, u64 addr, struct symbol *sym, if (dso_name == NULL) goto out_err; - if (!addr2line(dso_name, addr, &file, &line, dso, - unwind_inlines, /*node=*/NULL, sym)) { + if (addr2line(dso_name, addr, &file, &line, dso, + unwind_inlines, /*node=*/NULL, sym) <= 0) { free(dso_name); goto out_err; } @@ -302,6 +306,7 @@ out_err: dso__set_has_srcline(dso, false); dso__free_a2l(dso); dso__free_a2l_libbfd(dso); + dso__free_libdw(dso); } mutex_unlock(dso__lock(dso)); out: @@ -331,8 +336,8 @@ char *get_srcline_split(struct dso *dso, u64 addr, unsigned *line) if (dso_name == NULL) goto out_err; - if (!addr2line(dso_name, addr, &file, line, dso, /*unwind_inlines=*/true, - /*node=*/NULL, /*sym=*/NULL)) { + if (addr2line(dso_name, addr, &file, line, dso, /*unwind_inlines=*/true, + /*node=*/NULL, /*sym=*/NULL) <= 0) { free(dso_name); goto out_err; } @@ -350,6 +355,7 @@ out_err: dso__set_has_srcline(dso, false); dso__free_a2l(dso); dso__free_a2l_libbfd(dso); + dso__free_libdw(dso); } mutex_unlock(dso__lock(dso)); @@ -448,6 +454,9 @@ struct inline_node *dso__parse_addr_inlines(struct dso *dso, u64 addr, char *dso_name; struct inline_node *node; + if (!dso__has_srcline(dso)) + return NULL; + dso_name = srcline_dso_name(dso); if (dso_name == NULL) return NULL; diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c index e955c3feddcd..1e2716c6af45 100644 --- a/tools/perf/util/symbol-elf.c +++ b/tools/perf/util/symbol-elf.c @@ -228,7 +228,8 @@ bool filename__has_section(const char *filename, const char *sec) if (gelf_getehdr(elf, &ehdr) == NULL) goto elf_out; - found = !!elf_section_by_name(elf, &ehdr, &shdr, sec, NULL); + found = !!elf_section_by_name(elf, &ehdr, &shdr, sec, NULL) && + shdr.sh_type == SHT_PROGBITS; elf_out: elf_end(elf); diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c index 5d98888d068c..b61a574ce9a3 100644 --- a/tools/perf/util/symbol.c +++ b/tools/perf/util/symbol.c @@ -967,23 +967,6 @@ static int maps__split_kallsyms_for_kcore(struct maps *kmaps, struct dso *dso) return count; } -static uint16_t machine_or_dso_e_machine(struct machine *machine, struct dso *dso) -{ - uint16_t e_machine = EM_NONE; - /* DSO should be most accurate */ - if (dso) - e_machine = dso__e_machine(dso, machine, /*e_flags=*/NULL); - - if (e_machine != EM_NONE) - return e_machine; - - /* Check the global environment next. */ - if (machine && machine->env && machine->env->e_machine != EM_NONE) - return machine->env->e_machine; - - return perf_env__e_machine(machine ? machine->env : NULL, /*e_flags=*/NULL); -} - /* * Split the symbols into maps, making sure there are no overlaps, i.e. the * kernel range is broken in several maps, named [kernel].N, as we don't have @@ -1005,7 +988,7 @@ static int maps__split_kallsyms(struct maps *kmaps, struct dso *dso, u64 delta, return -1; machine = maps__machine(kmaps); - e_machine = machine_or_dso_e_machine(machine, dso); + e_machine = perf_env__e_machine(machine->env, /*e_flags=*/NULL); while (next) { char *module; @@ -1099,6 +1082,13 @@ static int maps__split_kallsyms(struct maps *kmaps, struct dso *dso, u64 delta, return -1; dso__set_kernel(ndso, dso__kernel(dso)); + if (dso__kernel(dso) == DSO_SPACE__KERNEL_GUEST) { + dso__set_binary_type(ndso, DSO_BINARY_TYPE__GUEST_KALLSYMS); + dso__set_symtab_type(ndso, DSO_BINARY_TYPE__GUEST_KALLSYMS); + } else { + dso__set_binary_type(ndso, DSO_BINARY_TYPE__KALLSYMS); + dso__set_symtab_type(ndso, DSO_BINARY_TYPE__KALLSYMS); + } dso__set_loaded(ndso); curr_map = map__new2(pos->start, ndso); @@ -1560,7 +1550,7 @@ static int dso__load_kcore(struct dso *dso, struct map *map, free(new_node); } - if (machine_or_dso_e_machine(machine, dso) == EM_X86_64) { + if (perf_env__e_machine(machine->env, /*e_flags=*/NULL) == EM_X86_64) { u64 addr; /* @@ -1848,7 +1838,7 @@ int dso__load(struct dso *dso, struct map *map) ret = dso__load_guest_kernel_sym(dso, map); machine = maps__machine(map__kmaps(map)); - if (machine && machine_or_dso_e_machine(machine, dso) == EM_X86_64) + if (perf_env__e_machine(machine->env, /*e_flags=*/NULL) == EM_X86_64) machine__map_x86_64_entry_trampolines(machine, dso); goto out; } @@ -1857,6 +1847,7 @@ int dso__load(struct dso *dso, struct map *map) if (perfmap) { ret = dso__load_perf_map(map_path, dso); + dso__set_binary_type(dso, DSO_BINARY_TYPE__JAVA_JIT); dso__set_symtab_type(dso, ret > 0 ? DSO_BINARY_TYPE__JAVA_JIT : DSO_BINARY_TYPE__NOT_FOUND); @@ -1930,9 +1921,10 @@ int dso__load(struct dso *dso, struct map *map) if (nsexit) nsinfo__mountns_enter(dso__nsinfo(dso), &nsc); - if (bfdrc == 0) { - ret = 0; - break; + if (bfdrc > 0) { + dso__set_symtab_type(dso, symtab_type); + ret = 1; + goto out_destroy; } if (!is_reg || sirc < 0) @@ -1952,19 +1944,6 @@ int dso__load(struct dso *dso, struct map *map) if (next_slot) { ss_pos++; - - /* - * The binary type is used to find the file containing - * the executed instructions, so prefer the types that - * refer to the actual object over debug-only files such - * as DSO_BINARY_TYPE__DEBUGLINK. - */ - if (dso__binary_type(dso) == DSO_BINARY_TYPE__NOT_FOUND || - symtab_type == DSO_BINARY_TYPE__BUILD_ID_CACHE || - (symtab_type == DSO_BINARY_TYPE__SYSTEM_PATH_DSO && - dso__binary_type(dso) != DSO_BINARY_TYPE__BUILD_ID_CACHE)) - dso__set_binary_type(dso, symtab_type); - if (syms_ss && runtime_ss) break; } else { @@ -1997,6 +1976,7 @@ int dso__load(struct dso *dso, struct map *map) ret += nr_plt; } +out_destroy: for (; ss_pos > 0; ss_pos--) symsrc__destroy(&ss_[ss_pos - 1]); out_free: @@ -2079,7 +2059,7 @@ int dso__load_vmlinux_path(struct dso *dso, struct map *map) if (filename != NULL) { err = dso__load_vmlinux(dso, map, filename, true); if (err > 0) - goto out; + dso__set_symtab_type(dso, DSO_BINARY_TYPE__BUILD_ID_CACHE); } out: return err; @@ -2235,8 +2215,10 @@ static int dso__load_kernel_sym(struct dso *dso, struct map *map) filename = __dso__build_id_filename(dso, NULL, 0, false, false); if (filename != NULL) { err = dso__load_vmlinux(dso, map, filename, true); - if (err > 0) + if (err > 0) { + dso__set_symtab_type(dso, DSO_BINARY_TYPE__BUILD_ID_CACHE); return err; + } } if (!symbol_conf.ignore_vmlinux && vmlinux_path != NULL) { |
