/* SPDX-License-Identifier: GPL-2.0 */ /* * Convert sample address to data type using DWARF debug info. * * Written by Namhyung Kim <namhyung@kernel.org>
*/
switch (kind) { case TSR_KIND_INVALID:
pr_info("\n"); return; case TSR_KIND_PERCPU_BASE:
pr_info(" percpu base\n"); return; case TSR_KIND_CONST:
pr_info(" constant\n"); return; case TSR_KIND_POINTER:
pr_info(" pointer"); /* it also prints the type info */ break; case TSR_KIND_CANARY:
pr_info(" stack canary\n"); return; case TSR_KIND_TYPE: default: break;
}
/* * Compare type name and size to maintain them in a tree. * I'm not sure if DWARF would have information of a single type in many * different places (compilation units). If not, it could compare the * offset of the type entry in the .debug_info section.
*/ staticint data_type_cmp(constvoid *_key, conststruct rb_node *node)
{ conststruct annotated_data_type *key = _key; struct annotated_data_type *type;
type = rb_entry(node, struct annotated_data_type, node);
/* It can have anonymous struct/union members */ if (child->var_name) {
len = scnprintf(buf, sz, "%s%s",
first ? "" : ".", child->var_name);
first = false;
} else {
len = 0;
}
if (dwarf_addrdie(di->dbg, pc, cu_die) != NULL) return cu_die;
/* * There are some kernels don't have full aranges and contain only a few * aranges entries. Fallback to iterate all CU entries in .debug_info * in case it's missing.
*/
off = 0; while (dwarf_nextcu(di->dbg, off, &next_off, &header_size,
NULL, NULL, NULL) == 0) { if (dwarf_offdie(di->dbg, off + header_size, cu_die) &&
dwarf_haspc(cu_die, pc)) returntrue;
staticconstchar *match_result_str(enum type_match_result tmr)
{ switch (tmr) { case PERF_TMR_OK: return"Good!"; case PERF_TMR_NO_TYPE: return"no type information"; case PERF_TMR_NO_POINTER: return"no/void pointer"; case PERF_TMR_NO_SIZE: return"type size is unknown"; case PERF_TMR_BAD_OFFSET: return"offset bigger than size"; case PERF_TMR_UNKNOWN: case PERF_TMR_BAIL_OUT: default: return"invalid state";
}
}
staticbool is_pointer_type(Dwarf_Die *type_die)
{ int tag = dwarf_tag(type_die);
return tag == DW_TAG_pointer_type || tag == DW_TAG_array_type;
}
staticbool is_compound_type(Dwarf_Die *type_die)
{ int tag = dwarf_tag(type_die);
return tag == DW_TAG_structure_type || tag == DW_TAG_union_type;
}
/* returns if Type B has better information than Type A */ staticbool is_better_type(Dwarf_Die *type_a, Dwarf_Die *type_b)
{
Dwarf_Word size_a, size_b;
Dwarf_Die die_a, die_b;
/* pointer type is preferred */ if (is_pointer_type(type_a) != is_pointer_type(type_b)) return is_pointer_type(type_b);
if (is_pointer_type(type_b)) { /* * We want to compare the target type, but 'void *' can fail to * get the target type.
*/ if (die_get_real_type(type_a, &die_a) == NULL) returntrue; if (die_get_real_type(type_b, &die_b) == NULL) returnfalse;
type_a = &die_a;
type_b = &die_b;
}
/* bigger type is preferred */ if (dwarf_aggregate_size(type_a, &size_a) < 0 ||
dwarf_aggregate_size(type_b, &size_b) < 0) returnfalse;
if (size_a != size_b) return size_a < size_b;
/* struct or union is preferred */ if (is_compound_type(type_a) != is_compound_type(type_b)) return is_compound_type(type_b);
/* typedef is preferred */ if (dwarf_tag(type_b) == DW_TAG_typedef) returntrue;
returnfalse;
}
/* The type info will be saved in @type_die */ staticenum type_match_result check_variable(struct data_loc_info *dloc,
Dwarf_Die *var_die,
Dwarf_Die *type_die, int reg, int offset, bool is_fbreg)
{
Dwarf_Word size; bool needs_pointer = true;
Dwarf_Die sized_type;
/* Get the type of the variable */ if (__die_get_real_type(var_die, type_die) == NULL) return PERF_TMR_NO_TYPE;
/* * Usually it expects a pointer type for a memory access. * Convert to a real type it points to. But global variables * and local variables are accessed directly without a pointer.
*/ if (needs_pointer) { if (!is_pointer_type(type_die) ||
__die_get_real_type(type_die, type_die) == NULL) return PERF_TMR_NO_POINTER;
}
if (dwarf_tag(type_die) == DW_TAG_typedef)
die_get_real_type(type_die, &sized_type); else
sized_type = *type_die;
/* Get the size of the actual type */ if (dwarf_aggregate_size(&sized_type, &size) < 0) return PERF_TMR_NO_SIZE;
/* Iterate all CU and collect global variables that have no location in a register. */
off = 0; while (dwarf_nextcu(dwarf, off, &next_off, &header_size,
NULL, NULL, NULL) == 0) { struct die_var_type *var_types = NULL; struct die_var_type *pos;
if (dwarf_offdie(dwarf, off + header_size, &cu_die) == NULL) {
off = next_off; continue;
}
die_collect_global_vars(&cu_die, &var_types);
for (pos = var_types; pos; pos = pos->next) { constchar *var_name = NULL; int var_offset = 0;
if (pos->reg != -1) continue;
if (!dwarf_offdie(dwarf, pos->die_off, &type_die)) continue;
if (!get_global_var_info(dloc, pos->addr, &var_name,
&var_offset)) continue;
/* Try to get the variable by address first */ if (die_find_variable_by_addr(cu_die, var_addr, &var_die, &offset) &&
check_variable(dloc, &var_die, type_die, DWARF_REG_PC, offset, /*is_fbreg=*/false) == PERF_TMR_OK) {
var_name = dwarf_diename(&var_die);
*var_offset = offset; goto ok;
}
if (!get_global_var_info(dloc, var_addr, &var_name, var_offset)) returnfalse;
pc = map__rip_2objdump(dloc->ms->map, ip);
/* Try to get the name of global variable */ if (die_find_variable_at(cu_die, var_name, pc, &var_die) &&
check_variable(dloc, &var_die, type_die, DWARF_REG_PC, *var_offset, /*is_fbreg=*/false) == PERF_TMR_OK) goto ok;
returnfalse;
ok: /* The address should point to the start of the variable */
global_var__add(dloc, var_addr - *var_offset, var_name, type_die); returntrue;
}
/** * update_var_state - Update type state using given variables * @state: type state table * @dloc: data location info * @addr: instruction address to match with variable * @insn_offset: instruction offset (for debug) * @var_types: list of variables with type info * * This function fills the @state table using @var_types info. Each variable * is used only at the given location and updates an entry in the table.
*/ staticvoid update_var_state(struct type_state *state, struct data_loc_info *dloc,
u64 addr, u64 insn_offset, struct die_var_type *var_types)
{
Dwarf_Die mem_die; struct die_var_type *var; int fbreg = dloc->fbreg; int fb_offset = 0;
if (dloc->fb_cfa) { if (die_get_cfa(dloc->di->dbg, addr, &fbreg, &fb_offset) < 0)
fbreg = -1;
}
for (var = var_types; var != NULL; var = var->next) { if (var->addr != addr) continue; /* Get the type DIE using the offset */ if (!dwarf_offdie(dloc->di->dbg, var->die_off, &mem_die)) continue;
if (var->reg == DWARF_REG_FB || var->reg == fbreg || var->reg == state->stack_reg) { int offset = var->offset; struct type_state_stack *stack;
if (var->reg != DWARF_REG_FB)
offset -= fb_offset;
/* * If this register is directly copied from another and it gets a * better type, also update the type of the source register. This * is usually the case of container_of() macro with offset of 0.
*/ if (has_reg_type(state, reg->copied_from)) { struct type_state_reg *copy_reg;
/** * update_insn_state - Update type state for an instruction * @state: type state table * @dloc: data location info * @cu_die: compile unit debug entry * @dl: disasm line for the instruction * * This function updates the @state table for the target operand of the * instruction at @dl if it transfers the type like MOV on x86. Since it * tracks the type, it won't care about the values like in arithmetic * instructions like ADD/SUB/MUL/DIV and INC/DEC. * * Note that ops->reg2 is only available when both mem_ref and multi_regs * are true.
*/ staticvoid update_insn_state(struct type_state *state, struct data_loc_info *dloc,
Dwarf_Die *cu_die, struct disasm_line *dl)
{ if (dloc->arch->update_insn_state)
dloc->arch->update_insn_state(state, dloc, cu_die, dl);
}
/* Last insn in this_blocks should be same as first insn in full_blocks */ if (last_bb->end != first_bb->begin) {
pr_debug("prepend basic blocks: mismatched disasm line %"PRIx64" -> %"PRIx64"\n",
last_bb->end->al.offset, first_bb->begin->al.offset); goto out;
}
/* Is the basic block have only one disasm_line? */ if (last_bb->begin == last_bb->end) {
list_del(&last_bb->list);
free(last_bb); goto out;
}
/* Point to the insn before the last when adding this block to full_blocks */
last_bb->end = list_prev_entry(last_bb->end, al.node);
/* Make sure all variables have a valid start address */ staticvoid fixup_var_address(struct die_var_type *var_types, u64 addr)
{ while (var_types) { /* * Some variables have no address range meaning it's always * available in the whole scope. Let's adjust the start * address to the start of the scope.
*/ if (var_types->addr == 0)
var_types->addr = addr;
/* should match to is_stack_canary() in util/annotate.c */ staticvoid setup_stack_canary(struct data_loc_info *dloc)
{ if (arch__is(dloc->arch, "x86")) {
dloc->op->segment = INSN_SEG_X86_GS;
dloc->op->imm = true;
dloc->op->offset = 40;
}
}
/* * 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 * are similar to global variables and no additional info is needed.
*/ staticenum type_match_result check_matching_type(struct type_state *state, struct data_loc_info *dloc,
Dwarf_Die *cu_die, struct disasm_line *dl,
Dwarf_Die *type_die)
{
Dwarf_Word size;
u32 insn_offset = dl->al.offset; int reg = dloc->op->reg1; int offset = dloc->op->offset; constchar *offset_sign = ""; bool retry = true;
/* * Normal registers should hold a pointer (or array) to * dereference a memory location.
*/ if (!is_pointer_type(&state->regs[reg].type)) { if (dloc->op->offset < 0 && reg != state->stack_reg) goto check_kernel;
return PERF_TMR_NO_POINTER;
}
/* Remove the pointer and get the target type */ if (__die_get_real_type(&state->regs[reg].type, type_die) == NULL) return PERF_TMR_NO_POINTER;
dloc->type_offset = dloc->op->offset;
if (dwarf_tag(type_die) == DW_TAG_typedef)
die_get_real_type(type_die, &sized_type); else
sized_type = *type_die;
/* Get the size of the actual type */ if (dwarf_aggregate_size(&sized_type, &size) < 0 ||
(unsigned)dloc->type_offset >= size) return PERF_TMR_BAD_OFFSET;
return PERF_TMR_OK;
}
if (state->regs[reg].kind == TSR_KIND_POINTER) {
pr_debug_dtp("percpu ptr");
/* * It's actaully pointer but the address was calculated using * some arithmetic. So it points to the actual type already.
*/
*type_die = state->regs[reg].type;
dloc->type_offset = dloc->op->offset;
/* Get the size of the actual type */ if (dwarf_aggregate_size(type_die, &size) < 0 ||
(unsigned)dloc->type_offset >= size) return PERF_TMR_BAIL_OUT;
return PERF_TMR_OK;
}
if (state->regs[reg].kind == TSR_KIND_CANARY) {
pr_debug_dtp("stack canary");
/* * This is a saved value of the stack canary which will be handled * in the outer logic when it returns failure here. Pretend it's * from the stack canary directly.
*/
setup_stack_canary(dloc);
return PERF_TMR_BAIL_OUT;
}
if (state->regs[reg].kind == TSR_KIND_PERCPU_BASE) {
u64 var_addr = dloc->op->offset; int var_offset;
pr_debug_dtp("percpu var");
if (dloc->op->multi_regs) { int reg2 = dloc->op->reg2;
/* * Construct a list of basic blocks for each scope with variables and try to find * the data type by updating a type state table through instructions.
*/ staticenum type_match_result find_data_type_block(struct data_loc_info *dloc,
Dwarf_Die *cu_die,
Dwarf_Die *scopes, int nr_scopes,
Dwarf_Die *type_die)
{
LIST_HEAD(basic_blocks); struct die_var_type *var_types = NULL;
u64 src_ip, dst_ip, prev_dst_ip; enum type_match_result ret = PERF_TMR_UNKNOWN;
/* TODO: other architecture support */ if (!arch_supports_insn_tracking(dloc)) return PERF_TMR_BAIL_OUT;
prev_dst_ip = dst_ip = dloc->ip; for (int i = nr_scopes - 1; i >= 0; i--) {
Dwarf_Addr base, start, end;
LIST_HEAD(this_blocks);
if (dwarf_ranges(&scopes[i], 0, &base, &start, &end) < 0) break;
pr_debug_dtp("scope: [%d/%d] ", i + 1, nr_scopes);
pr_debug_scope(&scopes[i]);
src_ip = map__objdump_2rip(dloc->ms->map, start);
again: /* Get basic blocks for this scope */ if (annotate_get_basic_blocks(dloc->ms->sym, src_ip, dst_ip,
&this_blocks) < 0) { /* Try previous block if they are not connected */ if (prev_dst_ip != dst_ip) {
dst_ip = prev_dst_ip; goto again;
}
pr_debug_dtp("cannot find a basic block from %"PRIx64" to %"PRIx64"\n",
src_ip - dloc->ms->sym->start,
dst_ip - dloc->ms->sym->start); continue;
}
prepend_basic_blocks(&this_blocks, &basic_blocks);
/* Get variable info for this scope and add to var_types list */
die_collect_vars(&scopes[i], &var_types);
fixup_var_address(var_types, start);
/* Find from start of this scope to the target instruction */
ret = find_data_type_insn(dloc, &basic_blocks, var_types,
cu_die, type_die); if (ret == PERF_TMR_OK) { char buf[64]; int offset = dloc->op->offset; constchar *offset_sign = "";
/* The result will be saved in @type_die */ staticint find_data_type_die(struct data_loc_info *dloc, Dwarf_Die *type_die)
{ struct annotated_op_loc *loc = dloc->op;
Dwarf_Die cu_die, var_die;
Dwarf_Die *scopes = NULL; int reg, offset = loc->offset; int ret = -1; int i, nr_scopes; int fbreg = -1; int fb_offset = 0; bool is_fbreg = false; bool found = false;
u64 pc; char buf[64]; enum type_match_result result = PERF_TMR_UNKNOWN; constchar *offset_sign = "";
pr_debug_dtp("-----------------------------------------------------------\n");
pr_debug_dtp("find data type for %s%#x(%s) at %s+%#"PRIx64"\n",
offset_sign, offset, buf,
dloc->ms->sym->name, dloc->ip - dloc->ms->sym->start);
/* * IP is a relative instruction address from the start of the map, as * it can be randomized/relocated, it needs to translate to PC which is * a file address for DWARF processing.
*/
pc = map__rip_2objdump(dloc->ms->map, dloc->ip);
/* Get a compile_unit for this address */ if (!find_cu_die(dloc->di, pc, &cu_die)) {
pr_debug_dtp("cannot find CU for address %"PRIx64"\n", pc);
ann_data_stat.no_cuinfo++; return -1;
}
reg = loc->reg1;
offset = loc->offset;
pr_debug_dtp("CU for %s (die:%#lx)\n",
dwarf_diename(&cu_die), (long)dwarf_dieoffset(&cu_die));
if (reg == DWARF_REG_PC) { if (get_global_var_type(&cu_die, dloc, dloc->ip, dloc->var_addr,
&offset, type_die)) {
dloc->type_offset = offset;
pr_debug_dtp("found by addr=%#"PRIx64" type_offset=%#x\n",
dloc->var_addr, offset);
pr_debug_type_name(type_die, TSR_KIND_TYPE);
found = true; goto out;
}
}
/* Get a list of nested scopes - i.e. (inlined) functions and blocks. */
nr_scopes = die_get_scopes(&cu_die, pc, &scopes);
if (!found && reg != DWARF_REG_PC) {
result = find_data_type_block(dloc, &cu_die, scopes,
nr_scopes, type_die); if (result == PERF_TMR_OK) {
ann_data_stat.insn_track++;
found = true;
}
}
out:
pr_debug_dtp("final result: "); if (found) {
pr_debug_type_name(type_die, TSR_KIND_TYPE);
ret = 0;
} else { switch (result) { case PERF_TMR_NO_TYPE: case PERF_TMR_NO_POINTER:
pr_debug_dtp("%s\n", match_result_str(result));
ann_data_stat.no_typeinfo++; break; case PERF_TMR_NO_SIZE:
pr_debug_dtp("%s\n", match_result_str(result));
ann_data_stat.invalid_size++; break; case PERF_TMR_BAD_OFFSET:
pr_debug_dtp("%s\n", match_result_str(result));
ann_data_stat.bad_offset++; break; case PERF_TMR_UNKNOWN: case PERF_TMR_BAIL_OUT: case PERF_TMR_OK: /* should not reach here */ default:
pr_debug_dtp("no variable found\n");
ann_data_stat.no_var++; break;
}
ret = -1;
}
free(scopes); return ret;
}
/** * find_data_type - Return a data type at the location * @dloc: data location * * This functions searches the debug information of the binary to get the data * type it accesses. The exact location is expressed by (ip, reg, offset) * for pointer variables or (ip, addr) for global variables. Note that global * variables might update the @dloc->type_offset after finding the start of the * variable. If it cannot find a global variable by address, it tried to find * a declaration of the variable using var_name. In that case, @dloc->offset * won't be updated. * * It return %NULL if not found.
*/ struct annotated_data_type *find_data_type(struct data_loc_info *dloc)
{ struct dso *dso = map__dso(dloc->ms->map);
Dwarf_Die type_die;
/* * The type offset is the same as instruction offset by default. * But when finding a global variable, the offset won't be valid.
*/
dloc->type_offset = dloc->op->offset;
dloc->fbreg = -1;
if (find_data_type_die(dloc, &type_die) < 0) return NULL;
return dso__findnew_data_type(dso, &type_die);
}
staticint alloc_data_type_histograms(struct annotated_data_type *adt, int nr_entries)
{ int i;
size_t sz = sizeof(struct type_hist);
/* Allocate a table of pointers for each event */
adt->histograms = calloc(nr_entries, sizeof(*adt->histograms)); if (adt->histograms == NULL) return -ENOMEM;
/* * Each histogram is allocated for the whole size of the type. * TODO: Probably we can move the histogram to members.
*/ for (i = 0; i < nr_entries; i++) {
adt->histograms[i] = zalloc(sz); if (adt->histograms[i] == NULL) goto err;
}
adt->nr_histograms = nr_entries; return 0;
err: while (--i >= 0)
zfree(&(adt->histograms[i]));
zfree(&adt->histograms); return -ENOMEM;
}
staticvoid delete_data_type_histograms(struct annotated_data_type *adt)
{ for (int i = 0; i < adt->nr_histograms; i++)
zfree(&(adt->histograms[i]));
/** * annotated_data_type__update_samples - Update histogram * @adt: Data type to update * @evsel: Event to update * @offset: Offset in the type * @nr_samples: Number of samples at this offset * @period: Event count at this offset * * This function updates type histogram at @ofs for @evsel. Samples are * aggregated before calling this function so it can be called with more * than one samples at a certain offset.
*/ int annotated_data_type__update_samples(struct annotated_data_type *adt, struct evsel *evsel, int offset, int nr_samples, u64 period)
{ struct type_hist *h;
if (adt == NULL) return 0;
if (adt->histograms == NULL) { int nr = evsel->evlist->core.nr_entries;
if (alloc_data_type_histograms(adt, nr) < 0) return -1;
}
if (offset < 0 || offset >= adt->self.size) return -1;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.