Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

perf annotate-data: Support stack variables

Local variables are allocated in the stack and the location list
should look like base register(s) and an offset. Extend the
die_find_variable_by_reg() to handle the following expressions

* DW_OP_breg{0..31}
* DW_OP_bregx
* DW_OP_fbreg

Ususally DWARF subprogram entries have frame base information and
use it to locate stack variable like below:

<2><43d1575>: Abbrev Number: 62 (DW_TAG_variable)
<43d1576> DW_AT_location : 2 byte block: 91 7c (DW_OP_fbreg: -4) <--- here
<43d1579> DW_AT_name : (indirect string, offset: 0x2c00c9): i
<43d157d> DW_AT_decl_file : 1
<43d157e> DW_AT_decl_line : 78
<43d157f> DW_AT_type : <0x43d19d7>

I found some differences on saving the frame base between gcc and clang.
The gcc uses the CFA to get the base so it needs to check the current
frame's CFI info. In this case, stack offset needs to be adjusted from
the start of the CFA.

<1><1bb8d>: Abbrev Number: 102 (DW_TAG_subprogram)
<1bb8e> DW_AT_name : (indirect string, offset: 0x74d41): kernel_init
<1bb92> DW_AT_decl_file : 2
<1bb92> DW_AT_decl_line : 1440
<1bb94> DW_AT_decl_column : 18
<1bb95> DW_AT_prototyped : 1
<1bb95> DW_AT_type : <0xcc>
<1bb99> DW_AT_low_pc : 0xffffffff81bab9e0
<1bba1> DW_AT_high_pc : 0x1b2
<1bba9> DW_AT_frame_base : 1 byte block: 9c (DW_OP_call_frame_cfa) <------ here
<1bbab> DW_AT_call_all_calls: 1
<1bbab> DW_AT_sibling : <0x1bf5a>

While clang sets it to a register directly and it can check the register
and offset in the instruction directly.

<1><43d1542>: Abbrev Number: 60 (DW_TAG_subprogram)
<43d1543> DW_AT_low_pc : 0xffffffff816a7c60
<43d154b> DW_AT_high_pc : 0x98
<43d154f> DW_AT_frame_base : 1 byte block: 56 (DW_OP_reg6 (rbp)) <---------- here
<43d1551> DW_AT_GNU_all_call_sites: 1
<43d1551> DW_AT_name : (indirect string, offset: 0x3bce91): foo
<43d1555> DW_AT_decl_file : 1
<43d1556> DW_AT_decl_line : 75
<43d1557> DW_AT_prototyped : 1
<43d1557> DW_AT_type : <0x43c7332>
<43d155b> DW_AT_external : 1

Also it needs to update the offset after finding the type like global
variables since the offset was from the frame base. Factor out
match_var_offset() to check global and local variables in the same way.

The type stats are improved too:

Annotate data type stats:
total 294, ok 160 (54.4%), bad 134 (45.6%)
-----------------------------------------------------------
30 : no_sym
32 : no_mem_ops
51 : no_var
14 : no_typeinfo
7 : bad_offset

Reviewed-by: Ian Rogers <irogers@google.com>
Cc: Stephane Eranian <eranian@google.com>
Cc: Masami Hiramatsu <mhiramat@kernel.org>
Link: https://lore.kernel.org/r/20240117062657.985479-9-namhyung@kernel.org
Signed-off-by: Namhyung Kim <namhyung@kernel.org>

+93 -24
+32 -3
tools/perf/util/annotate-data.c
··· 209 209 /* 210 210 * Usually it expects a pointer type for a memory access. 211 211 * Convert to a real type it points to. But global variables 212 - * are accessed directly without a pointer. 212 + * and local variables are accessed directly without a pointer. 213 213 */ 214 214 if (is_pointer) { 215 215 if ((dwarf_tag(type_die) != DW_TAG_pointer_type && ··· 248 248 int reg, offset; 249 249 int ret = -1; 250 250 int i, nr_scopes; 251 + int fbreg = -1; 252 + bool is_fbreg = false; 253 + int fb_offset = 0; 251 254 252 255 /* Get a compile_unit for this address */ 253 256 if (!find_cu_die(di, pc, &cu_die)) { ··· 282 279 /* Get a list of nested scopes - i.e. (inlined) functions and blocks. */ 283 280 nr_scopes = die_get_scopes(&cu_die, pc, &scopes); 284 281 282 + if (reg != DWARF_REG_PC && dwarf_hasattr(&scopes[0], DW_AT_frame_base)) { 283 + Dwarf_Attribute attr; 284 + Dwarf_Block block; 285 + 286 + /* Check if the 'reg' is assigned as frame base register */ 287 + if (dwarf_attr(&scopes[0], DW_AT_frame_base, &attr) != NULL && 288 + dwarf_formblock(&attr, &block) == 0 && block.length == 1) { 289 + switch (*block.data) { 290 + case DW_OP_reg0 ... DW_OP_reg31: 291 + fbreg = *block.data - DW_OP_reg0; 292 + break; 293 + case DW_OP_call_frame_cfa: 294 + if (die_get_cfa(di->dbg, pc, &fbreg, 295 + &fb_offset) < 0) 296 + fbreg = -1; 297 + break; 298 + default: 299 + break; 300 + } 301 + } 302 + } 303 + 285 304 retry: 305 + is_fbreg = (reg == fbreg); 306 + if (is_fbreg) 307 + offset = loc->offset - fb_offset; 308 + 286 309 /* Search from the inner-most scope to the outer */ 287 310 for (i = nr_scopes - 1; i >= 0; i--) { 288 311 if (reg == DWARF_REG_PC) { ··· 318 289 } else { 319 290 /* Look up variables/parameters in this scope */ 320 291 if (!die_find_variable_by_reg(&scopes[i], pc, reg, 321 - &var_die)) 292 + &offset, is_fbreg, &var_die)) 322 293 continue; 323 294 } 324 295 325 296 /* Found a variable, see if it's correct */ 326 297 ret = check_variable(&var_die, type_die, offset, 327 - reg != DWARF_REG_PC); 298 + reg != DWARF_REG_PC && !is_fbreg); 328 299 loc->offset = offset; 329 300 goto out; 330 301 }
+58 -21
tools/perf/util/dwarf-aux.c
··· 1272 1272 unsigned reg; 1273 1273 /* Access offset, set for global data */ 1274 1274 int offset; 1275 + /* True if the current register is the frame base */ 1276 + bool is_fbreg; 1275 1277 }; 1276 1278 1277 1279 /* Max number of registers DW_OP_regN supports */ 1278 1280 #define DWARF_OP_DIRECT_REGS 32 1281 + 1282 + static bool match_var_offset(Dwarf_Die *die_mem, struct find_var_data *data, 1283 + u64 addr_offset, u64 addr_type) 1284 + { 1285 + Dwarf_Die type_die; 1286 + Dwarf_Word size; 1287 + 1288 + if (addr_offset == addr_type) { 1289 + /* Update offset relative to the start of the variable */ 1290 + data->offset = 0; 1291 + return true; 1292 + } 1293 + 1294 + if (die_get_real_type(die_mem, &type_die) == NULL) 1295 + return false; 1296 + 1297 + if (dwarf_aggregate_size(&type_die, &size) < 0) 1298 + return false; 1299 + 1300 + if (addr_offset >= addr_type + size) 1301 + return false; 1302 + 1303 + /* Update offset relative to the start of the variable */ 1304 + data->offset = addr_offset - addr_type; 1305 + return true; 1306 + } 1279 1307 1280 1308 /* Only checks direct child DIEs in the given scope. */ 1281 1309 static int __die_find_var_reg_cb(Dwarf_Die *die_mem, void *arg) ··· 1329 1301 if (start > data->pc) 1330 1302 break; 1331 1303 1304 + /* Local variables accessed using frame base register */ 1305 + if (data->is_fbreg && ops->atom == DW_OP_fbreg && 1306 + data->offset >= (int)ops->number && 1307 + match_var_offset(die_mem, data, data->offset, ops->number)) 1308 + return DIE_FIND_CB_END; 1309 + 1332 1310 /* Only match with a simple case */ 1333 1311 if (data->reg < DWARF_OP_DIRECT_REGS) { 1334 1312 if (ops->atom == (DW_OP_reg0 + data->reg) && nops == 1) 1335 1313 return DIE_FIND_CB_END; 1314 + 1315 + /* Local variables accessed by a register + offset */ 1316 + if (ops->atom == (DW_OP_breg0 + data->reg) && 1317 + match_var_offset(die_mem, data, data->offset, ops->number)) 1318 + return DIE_FIND_CB_END; 1336 1319 } else { 1337 1320 if (ops->atom == DW_OP_regx && ops->number == data->reg && 1338 1321 nops == 1) 1322 + return DIE_FIND_CB_END; 1323 + 1324 + /* Local variables accessed by a register + offset */ 1325 + if (ops->atom == DW_OP_bregx && data->reg == ops->number && 1326 + match_var_offset(die_mem, data, data->offset, ops->number2)) 1339 1327 return DIE_FIND_CB_END; 1340 1328 } 1341 1329 } ··· 1363 1319 * @sc_die: a scope DIE 1364 1320 * @pc: the program address to find 1365 1321 * @reg: the register number to find 1322 + * @poffset: pointer to offset, will be updated for fbreg case 1323 + * @is_fbreg: boolean value if the current register is the frame base 1366 1324 * @die_mem: a buffer to save the resulting DIE 1367 1325 * 1368 - * Find the variable DIE accessed by the given register. 1326 + * Find the variable DIE accessed by the given register. It'll update the @offset 1327 + * when the variable is in the stack. 1369 1328 */ 1370 1329 Dwarf_Die *die_find_variable_by_reg(Dwarf_Die *sc_die, Dwarf_Addr pc, int reg, 1330 + int *poffset, bool is_fbreg, 1371 1331 Dwarf_Die *die_mem) 1372 1332 { 1373 1333 struct find_var_data data = { 1374 1334 .pc = pc, 1375 1335 .reg = reg, 1336 + .offset = *poffset, 1337 + .is_fbreg = is_fbreg, 1376 1338 }; 1377 - return die_find_child(sc_die, __die_find_var_reg_cb, &data, die_mem); 1339 + Dwarf_Die *result; 1340 + 1341 + result = die_find_child(sc_die, __die_find_var_reg_cb, &data, die_mem); 1342 + if (result) 1343 + *poffset = data.offset; 1344 + return result; 1378 1345 } 1379 1346 1380 1347 /* Only checks direct child DIEs in the given scope */ ··· 1396 1341 ptrdiff_t off = 0; 1397 1342 Dwarf_Attribute attr; 1398 1343 Dwarf_Addr base, start, end; 1399 - Dwarf_Word size; 1400 - Dwarf_Die type_die; 1401 1344 Dwarf_Op *ops; 1402 1345 size_t nops; 1403 1346 ··· 1412 1359 if (data->addr < ops->number) 1413 1360 continue; 1414 1361 1415 - if (data->addr == ops->number) { 1416 - /* Update offset relative to the start of the variable */ 1417 - data->offset = 0; 1362 + if (match_var_offset(die_mem, data, data->addr, ops->number)) 1418 1363 return DIE_FIND_CB_END; 1419 - } 1420 - 1421 - if (die_get_real_type(die_mem, &type_die) == NULL) 1422 - continue; 1423 - 1424 - if (dwarf_aggregate_size(&type_die, &size) < 0) 1425 - continue; 1426 - 1427 - if (data->addr >= ops->number + size) 1428 - continue; 1429 - 1430 - /* Update offset relative to the start of the variable */ 1431 - data->offset = data->addr - ops->number; 1432 - return DIE_FIND_CB_END; 1433 1364 } 1434 1365 return DIE_FIND_CB_SIBLING; 1435 1366 }
+3
tools/perf/util/dwarf-aux.h
··· 142 142 143 143 /* Find a variable saved in the 'reg' at given address */ 144 144 Dwarf_Die *die_find_variable_by_reg(Dwarf_Die *sc_die, Dwarf_Addr pc, int reg, 145 + int *poffset, bool is_fbreg, 145 146 Dwarf_Die *die_mem); 146 147 147 148 /* Find a (global) variable located in the 'addr' */ ··· 162 161 static inline Dwarf_Die *die_find_variable_by_reg(Dwarf_Die *sc_die __maybe_unused, 163 162 Dwarf_Addr pc __maybe_unused, 164 163 int reg __maybe_unused, 164 + int *poffset __maybe_unused, 165 + bool is_fbreg __maybe_unused, 165 166 Dwarf_Die *die_mem __maybe_unused) 166 167 { 167 168 return NULL;