* [PATCH v2 00/11] perf tools: Improvements to data type profiler
@ 2026-03-09 17:55 Zecheng Li
2026-03-09 17:55 ` [PATCH v2 01/11] perf dwarf-aux: Add die_get_pointer_type to get pointer types Zecheng Li
` (12 more replies)
0 siblings, 13 replies; 14+ messages in thread
From: Zecheng Li @ 2026-03-09 17:55 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Mark Rutland, Alexander Shishkin, Jiri Olsa, Ian Rogers,
Adrian Hunter, James Clark, Zecheng Li, xliuprof,
linux-perf-users, linux-kernel
This patch series improves the coverage and correctness of data type
annotations in the perf tools.
Here's a breakdown of the patches:
Patches 1-4 improve variable type matching:
- Add die_get_pointer_type() to properly handle typedef'd pointer types
- Preserve typedefs in match_var_offset for consistent type handling
- Skip redundant check_variable for die_find_variable_by_reg and
die_find_variable_by_addr since match_var_offset already performs
sufficient checking
- Improve type comparison when variables are found in different scopes
Patch 5 handles array types in die_get_member_type, allowing proper
resolution of struct members that are arrays.
Patches 6-7 improve global variable handling:
- Allow collecting global variables without symbol names (DWARF provides
the address directly via DW_OP_addr)
- Handle global variable access when a register holds a const value
with negative offset
Patches 8-9 improve caller-saved register handling:
- Add invalidate_reg_state() helper for consistent register invalidation
- Always invalidate caller-saved registers for call instructions per ABI
requirements, even when the call target is unknown
Patches 10-11 use DWARF location ranges to improve type tracking:
- Track DWARF location lifetime to preserve register state across calls
when the debug info indicates the value is still valid
- Collect all variable location entries instead of just the first one
Tested with the Linux kernel vmlinux with sampled functions and five
programs from binutils (as, ld, nm, objdump, readelf) with all
functions. Coverage rate includes all memory access instructions,
excluding stack memory accesses.
`lost` means coverage loss (only including annotated -> none);
`chg` means type name change. Net coverage gain is new_rate - prev_rate.
-------- vmlinux -------- ------- binutils -------
Patch rate lost chg rate lost chg
----------------------------------------------------------------
base 88.87% - - 72.55% - -
1 88.87% 0% 0% 74.22% 0% .03%
2 88.88% 0% .05% 74.32% 0% 0%
3 88.90% 0% .69% 74.38% 0% .31%
4 88.90% 0% .83% 74.38% 0% .17%
5 89.11% .01% .08% 74.41% 0% .01%
6 89.12% 0% .05% 75.33% 0% 1.62%
7 89.18% 0% 0% 75.33% 0% 0%
8 89.18% 0% 0% 75.30% .02% 0%
9 89.09% .09% 0% 75.24% .05% 0%
10 89.05% .08% .09% 75.53% .03% .03%
11 89.97% 0% .10% 76.63% .26% .07%
Total: vmlinux 88.87% -> 89.97% (+1.10%)
binutils 72.55% -> 76.63% (+4.08%)
Changes in v2:
- Reorder patches 1-3 to avoid temporary regression: pointer_type and
typedef patches now come before skip check_variable (Namhyung)
- Skip check_variable for both die_find_variable_by_reg and
die_find_variable_by_addr paths (Namhyung)
- Add dso__kernel() guard for const register global variable access
to restrict it to kernel DSOs (Namhyung)
Zecheng Li (11):
perf dwarf-aux: Add die_get_pointer_type to get pointer types
perf dwarf-aux: Preserve typedefs in match_var_offset
perf dwarf-aux: Skip check_variable for variable lookup
perf annotate-data: Improve type comparison from different scopes
perf dwarf-aux: Handle array types in die_get_member_type
perf annotate-data: Collect global variables without name
perf annotate-data: Handle global variable access with const register
perf annotate-data: Add invalidate_reg_state() helper for x86
perf annotate-data: Invalidate caller-saved regs for all calls
perf annotate-data: Use DWARF location ranges to preserve reg state
perf dwarf-aux: Collect all variable locations for insn tracking
tools/perf/util/annotate-arch/annotate-x86.c | 69 ++++++---
tools/perf/util/annotate-data.c | 119 ++++++++-------
tools/perf/util/annotate-data.h | 3 +
tools/perf/util/dwarf-aux.c | 145 +++++++++++++------
tools/perf/util/dwarf-aux.h | 9 +-
5 files changed, 230 insertions(+), 115 deletions(-)
--
2.53.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v2 01/11] perf dwarf-aux: Add die_get_pointer_type to get pointer types
2026-03-09 17:55 [PATCH v2 00/11] perf tools: Improvements to data type profiler Zecheng Li
@ 2026-03-09 17:55 ` Zecheng Li
2026-03-09 17:55 ` [PATCH v2 02/11] perf dwarf-aux: Preserve typedefs in match_var_offset Zecheng Li
` (11 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Zecheng Li @ 2026-03-09 17:55 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Mark Rutland, Alexander Shishkin, Jiri Olsa, Ian Rogers,
Adrian Hunter, James Clark, Zecheng Li, xliuprof,
linux-perf-users, linux-kernel
When a variable type is wrapped in typedef/qualifiers, callers may need
to first resolve it to the underlying DW_TAG_pointer_type or
DW_TAG_array_type. A simple tag check is not enough and directly calling
__die_get_real_type() can stop at the pointer type (e.g. typedef ->
pointer) instead of the pointee type.
Add die_get_pointer_type() helper that follows typedef/qualifier chains
and returns the underlying pointer DIE. Use it in annotate-data.c so
pointer checks and dereference work correctly for typedef'd pointers.
Signed-off-by: Zecheng Li <zli94@ncsu.edu>
---
tools/perf/util/annotate-data.c | 39 +++++++++++++++++++--------------
tools/perf/util/dwarf-aux.c | 27 +++++++++++++++++++++++
tools/perf/util/dwarf-aux.h | 2 ++
3 files changed, 51 insertions(+), 17 deletions(-)
diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c
index 44fbd41e3845..cda020ea18d5 100644
--- a/tools/perf/util/annotate-data.c
+++ b/tools/perf/util/annotate-data.c
@@ -455,13 +455,6 @@ static const char *match_result_str(enum type_match_result tmr)
}
}
-static bool is_pointer_type(Dwarf_Die *type_die)
-{
- int tag = dwarf_tag(type_die);
-
- return tag == DW_TAG_pointer_type || tag == DW_TAG_array_type;
-}
-
static bool is_compound_type(Dwarf_Die *type_die)
{
int tag = dwarf_tag(type_die);
@@ -474,19 +467,24 @@ static bool is_better_type(Dwarf_Die *type_a, Dwarf_Die *type_b)
{
Dwarf_Word size_a, size_b;
Dwarf_Die die_a, die_b;
+ Dwarf_Die ptr_a, ptr_b;
+ Dwarf_Die *ptr_type_a, *ptr_type_b;
+
+ ptr_type_a = die_get_pointer_type(type_a, &ptr_a);
+ ptr_type_b = die_get_pointer_type(type_b, &ptr_b);
/* pointer type is preferred */
- if (is_pointer_type(type_a) != is_pointer_type(type_b))
- return is_pointer_type(type_b);
+ if ((ptr_type_a != NULL) != (ptr_type_b != NULL))
+ return ptr_type_b != NULL;
- if (is_pointer_type(type_b)) {
+ if (ptr_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)
+ if (die_get_real_type(ptr_type_a, &die_a) == NULL)
return true;
- if (die_get_real_type(type_b, &die_b) == NULL)
+ if (die_get_real_type(ptr_type_b, &die_b) == NULL)
return false;
type_a = &die_a;
@@ -539,7 +537,7 @@ static enum type_match_result check_variable(struct data_loc_info *dloc,
* and local variables are accessed directly without a pointer.
*/
if (needs_pointer) {
- if (!is_pointer_type(type_die) ||
+ if (die_get_pointer_type(type_die, type_die) == NULL ||
__die_get_real_type(type_die, type_die) == NULL)
return PERF_TMR_NO_POINTER;
}
@@ -880,12 +878,16 @@ static void update_var_state(struct type_state *state, struct data_loc_info *dlo
continue;
if (var->reg == DWARF_REG_FB || var->reg == fbreg || var->reg == state->stack_reg) {
+ Dwarf_Die ptr_die;
+ Dwarf_Die *ptr_type;
int offset = var->offset;
struct type_state_stack *stack;
+ ptr_type = die_get_pointer_type(&mem_die, &ptr_die);
+
/* If the reg location holds the pointer value, dereference the type */
- if (!var->is_reg_var_addr && is_pointer_type(&mem_die) &&
- __die_get_real_type(&mem_die, &mem_die) == NULL)
+ if (!var->is_reg_var_addr && ptr_type &&
+ __die_get_real_type(ptr_type, &mem_die) == NULL)
continue;
if (var->reg != DWARF_REG_FB)
@@ -1110,7 +1112,9 @@ static enum type_match_result check_matching_type(struct type_state *state,
goto check_non_register;
if (state->regs[reg].kind == TSR_KIND_TYPE) {
+ Dwarf_Die ptr_die;
Dwarf_Die sized_type;
+ Dwarf_Die *ptr_type;
struct strbuf sb;
strbuf_init(&sb, 32);
@@ -1122,7 +1126,8 @@ static enum type_match_result check_matching_type(struct type_state *state,
* Normal registers should hold a pointer (or array) to
* dereference a memory location.
*/
- if (!is_pointer_type(&state->regs[reg].type)) {
+ ptr_type = die_get_pointer_type(&state->regs[reg].type, &ptr_die);
+ if (!ptr_type) {
if (dloc->op->offset < 0 && reg != state->stack_reg)
goto check_kernel;
@@ -1130,7 +1135,7 @@ static enum type_match_result check_matching_type(struct type_state *state,
}
/* Remove the pointer and get the target type */
- if (__die_get_real_type(&state->regs[reg].type, type_die) == NULL)
+ 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;
diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
index 9267af204c7d..38142062d6e5 100644
--- a/tools/perf/util/dwarf-aux.c
+++ b/tools/perf/util/dwarf-aux.c
@@ -303,6 +303,33 @@ Dwarf_Die *die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem)
return vr_die;
}
+/**
+ * die_get_pointer_type - Get a pointer/array type die
+ * @type_die: a DIE of a type
+ * @die_mem: where to store a type DIE
+ *
+ * Get a pointer/array type DIE from @type_die. If the type is a typedef or
+ * qualifier (const, volatile, etc.), follow the chain to find the underlying
+ * pointer type.
+ */
+Dwarf_Die *die_get_pointer_type(Dwarf_Die *type_die, Dwarf_Die *die_mem)
+{
+ int tag;
+
+ do {
+ tag = dwarf_tag(type_die);
+ if (tag == DW_TAG_pointer_type || tag == DW_TAG_array_type)
+ return type_die;
+ if (tag != DW_TAG_typedef && tag != DW_TAG_const_type &&
+ tag != DW_TAG_restrict_type && tag != DW_TAG_volatile_type &&
+ tag != DW_TAG_shared_type)
+ return NULL;
+ type_die = die_get_type(type_die, die_mem);
+ } while (type_die);
+
+ return NULL;
+}
+
/* Get attribute and translate it as a udata */
static int die_get_attr_udata(Dwarf_Die *tp_die, unsigned int attr_name,
Dwarf_Word *result)
diff --git a/tools/perf/util/dwarf-aux.h b/tools/perf/util/dwarf-aux.h
index cd481ec9c5a1..99d2735122d5 100644
--- a/tools/perf/util/dwarf-aux.h
+++ b/tools/perf/util/dwarf-aux.h
@@ -60,6 +60,8 @@ Dwarf_Die *die_get_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem);
Dwarf_Die *__die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem);
/* Get a type die, but skip qualifiers and typedef */
Dwarf_Die *die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem);
+/* Get a pointer/array type, following typedefs/qualifiers */
+Dwarf_Die *die_get_pointer_type(Dwarf_Die *type_die, Dwarf_Die *die_mem);
/* Check whether the DIE is signed or not */
bool die_is_signed_type(Dwarf_Die *tp_die);
--
2.53.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v2 02/11] perf dwarf-aux: Preserve typedefs in match_var_offset
2026-03-09 17:55 [PATCH v2 00/11] perf tools: Improvements to data type profiler Zecheng Li
2026-03-09 17:55 ` [PATCH v2 01/11] perf dwarf-aux: Add die_get_pointer_type to get pointer types Zecheng Li
@ 2026-03-09 17:55 ` Zecheng Li
2026-03-09 17:55 ` [PATCH v2 03/11] perf dwarf-aux: Skip check_variable for variable lookup Zecheng Li
` (10 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Zecheng Li @ 2026-03-09 17:55 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Mark Rutland, Alexander Shishkin, Jiri Olsa, Ian Rogers,
Adrian Hunter, James Clark, Zecheng Li, xliuprof,
linux-perf-users, linux-kernel, Zecheng Li
From: Zecheng Li <zecheng@google.com>
Preserve typedefs in match_var_offset to match the results by
__die_get_real_type. Also move the (offset == 0) branch after the
is_pointer check to ensure the correct type is used, fixing cases where
an incorrect pointer type was chosen when the access offset was 0.
Signed-off-by: Zecheng Li <zecheng@google.com>
Signed-off-by: Zecheng Li <zli94@ncsu.edu>
---
tools/perf/util/dwarf-aux.c | 26 +++++++++++++++-----------
1 file changed, 15 insertions(+), 11 deletions(-)
diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
index 38142062d6e5..3b0fc9038f19 100644
--- a/tools/perf/util/dwarf-aux.c
+++ b/tools/perf/util/dwarf-aux.c
@@ -1405,6 +1405,8 @@ struct find_var_data {
Dwarf_Addr addr;
/* Target register */
unsigned reg;
+ /* Access data type */
+ Dwarf_Die type;
/* Access offset, set for global data */
int offset;
/* True if the current register is the frame base */
@@ -1417,29 +1419,31 @@ struct find_var_data {
static bool match_var_offset(Dwarf_Die *die_mem, struct find_var_data *data,
s64 addr_offset, s64 addr_type, bool is_pointer)
{
- Dwarf_Die type_die;
Dwarf_Word size;
+ Dwarf_Die ptr_die;
+ Dwarf_Die *ptr_type;
s64 offset = addr_offset - addr_type;
- if (offset == 0) {
- /* Update offset relative to the start of the variable */
- data->offset = 0;
- return true;
- }
-
if (offset < 0)
return false;
- if (die_get_real_type(die_mem, &type_die) == NULL)
+ if (__die_get_real_type(die_mem, &data->type) == NULL)
return false;
- if (is_pointer && dwarf_tag(&type_die) == DW_TAG_pointer_type) {
+ ptr_type = die_get_pointer_type(&data->type, &ptr_die);
+ if (is_pointer && ptr_type) {
/* Get the target type of the pointer */
- if (die_get_real_type(&type_die, &type_die) == NULL)
+ if (__die_get_real_type(ptr_type, &data->type) == NULL)
return false;
}
- if (dwarf_aggregate_size(&type_die, &size) < 0)
+ if (offset == 0) {
+ /* Update offset relative to the start of the variable */
+ data->offset = 0;
+ return true;
+ }
+
+ if (dwarf_aggregate_size(&data->type, &size) < 0)
return false;
if ((u64)offset >= size)
--
2.53.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v2 03/11] perf dwarf-aux: Skip check_variable for variable lookup
2026-03-09 17:55 [PATCH v2 00/11] perf tools: Improvements to data type profiler Zecheng Li
2026-03-09 17:55 ` [PATCH v2 01/11] perf dwarf-aux: Add die_get_pointer_type to get pointer types Zecheng Li
2026-03-09 17:55 ` [PATCH v2 02/11] perf dwarf-aux: Preserve typedefs in match_var_offset Zecheng Li
@ 2026-03-09 17:55 ` Zecheng Li
2026-03-09 17:55 ` [PATCH v2 04/11] perf annotate-data: Improve type comparison from different scopes Zecheng Li
` (9 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Zecheng Li @ 2026-03-09 17:55 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Mark Rutland, Alexander Shishkin, Jiri Olsa, Ian Rogers,
Adrian Hunter, James Clark, Zecheng Li, xliuprof,
linux-perf-users, linux-kernel, Zecheng Li
From: Zecheng Li <zecheng@google.com>
Both die_find_variable_by_reg and die_find_variable_by_addr call
match_var_offset which already performs sufficient checking and type
matching. The additional check_variable call is redundant, and its
need_pointer logic is only a heuristic. Since DWARF encodes accurate
type information, which match_var_offset verifies, skipping
check_variable improves both coverage and accuracy.
Return the matched type from die_find_variable_by_reg and
die_find_variable_by_addr via the existing `type` field in
find_var_data, removing the need for check_variable in
find_data_type_die.
Signed-off-by: Zecheng Li <zecheng@google.com>
Signed-off-by: Zecheng Li <zli94@ncsu.edu>
---
tools/perf/util/annotate-data.c | 42 ++++++++++++++-------------------
tools/perf/util/dwarf-aux.c | 13 ++++++----
tools/perf/util/dwarf-aux.h | 5 ++--
3 files changed, 30 insertions(+), 30 deletions(-)
diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c
index cda020ea18d5..23a09bf58f86 100644
--- a/tools/perf/util/annotate-data.c
+++ b/tools/perf/util/annotate-data.c
@@ -814,9 +814,8 @@ bool get_global_var_type(Dwarf_Die *cu_die, struct data_loc_info *dloc,
}
/* 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) {
+ if (die_find_variable_by_addr(cu_die, var_addr, &var_die, type_die,
+ &offset)) {
var_name = dwarf_diename(&var_die);
*var_offset = offset;
goto ok;
@@ -1606,12 +1605,13 @@ static int find_data_type_die(struct data_loc_info *dloc, Dwarf_Die *type_die)
if (reg == DWARF_REG_PC) {
if (!die_find_variable_by_addr(&scopes[i], dloc->var_addr,
- &var_die, &type_offset))
+ &var_die, &mem_die,
+ &type_offset))
continue;
} else {
/* Look up variables/parameters in this scope */
if (!die_find_variable_by_reg(&scopes[i], pc, reg,
- &type_offset, is_fbreg, &var_die))
+ &mem_die, &type_offset, is_fbreg, &var_die))
continue;
}
@@ -1619,26 +1619,20 @@ static int find_data_type_die(struct data_loc_info *dloc, Dwarf_Die *type_die)
dwarf_diename(&var_die), (long)dwarf_dieoffset(&var_die),
i+1, nr_scopes, (long)dwarf_dieoffset(&scopes[i]));
- /* Found a variable, see if it's correct */
- result = check_variable(dloc, &var_die, &mem_die, reg, type_offset, is_fbreg);
- if (result == PERF_TMR_OK) {
- if (reg == DWARF_REG_PC) {
- pr_debug_dtp("addr=%#"PRIx64" type_offset=%#x\n",
- dloc->var_addr, type_offset);
- } else if (reg == DWARF_REG_FB || is_fbreg) {
- pr_debug_dtp("stack_offset=%#x type_offset=%#x\n",
- fb_offset, type_offset);
- } else {
- pr_debug_dtp("type_offset=%#x\n", type_offset);
- }
-
- if (!found || is_better_type(type_die, &mem_die)) {
- *type_die = mem_die;
- dloc->type_offset = type_offset;
- found = true;
- }
+ if (reg == DWARF_REG_PC) {
+ pr_debug_dtp("addr=%#"PRIx64" type_offset=%#x\n",
+ dloc->var_addr, type_offset);
+ } else if (reg == DWARF_REG_FB || is_fbreg) {
+ pr_debug_dtp("stack_offset=%#x type_offset=%#x\n",
+ fb_offset, type_offset);
} else {
- pr_debug_dtp("failed: %s\n", match_result_str(result));
+ pr_debug_dtp("type_offset=%#x\n", type_offset);
+ }
+
+ if (!found || is_better_type(type_die, &mem_die)) {
+ *type_die = mem_die;
+ dloc->type_offset = type_offset;
+ found = true;
}
pr_debug_location(&var_die, pc, reg);
diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
index 3b0fc9038f19..1484aa756826 100644
--- a/tools/perf/util/dwarf-aux.c
+++ b/tools/perf/util/dwarf-aux.c
@@ -1560,7 +1560,7 @@ static int __die_find_var_reg_cb(Dwarf_Die *die_mem, void *arg)
* when the variable is in the stack.
*/
Dwarf_Die *die_find_variable_by_reg(Dwarf_Die *sc_die, Dwarf_Addr pc, int reg,
- int *poffset, bool is_fbreg,
+ Dwarf_Die *type_die, int *poffset, bool is_fbreg,
Dwarf_Die *die_mem)
{
struct find_var_data data = {
@@ -1572,8 +1572,10 @@ Dwarf_Die *die_find_variable_by_reg(Dwarf_Die *sc_die, Dwarf_Addr pc, int reg,
Dwarf_Die *result;
result = die_find_child(sc_die, __die_find_var_reg_cb, &data, die_mem);
- if (result)
+ if (result) {
*poffset = data.offset;
+ *type_die = data.type;
+ }
return result;
}
@@ -1617,7 +1619,8 @@ static int __die_find_var_addr_cb(Dwarf_Die *die_mem, void *arg)
* This is usually for global variables.
*/
Dwarf_Die *die_find_variable_by_addr(Dwarf_Die *sc_die, Dwarf_Addr addr,
- Dwarf_Die *die_mem, int *offset)
+ Dwarf_Die *die_mem, Dwarf_Die *type_die,
+ int *offset)
{
struct find_var_data data = {
.addr = addr,
@@ -1625,8 +1628,10 @@ Dwarf_Die *die_find_variable_by_addr(Dwarf_Die *sc_die, Dwarf_Addr addr,
Dwarf_Die *result;
result = die_find_child(sc_die, __die_find_var_addr_cb, &data, die_mem);
- if (result)
+ if (result) {
*offset = data.offset;
+ *type_die = data.type;
+ }
return result;
}
diff --git a/tools/perf/util/dwarf-aux.h b/tools/perf/util/dwarf-aux.h
index 99d2735122d5..939a59c91796 100644
--- a/tools/perf/util/dwarf-aux.h
+++ b/tools/perf/util/dwarf-aux.h
@@ -165,12 +165,13 @@ int die_get_var_range(Dwarf_Die *sp_die, Dwarf_Die *vr_die, struct strbuf *buf);
/* Find a variable saved in the 'reg' at given address */
Dwarf_Die *die_find_variable_by_reg(Dwarf_Die *sc_die, Dwarf_Addr pc, int reg,
- int *poffset, bool is_fbreg,
+ Dwarf_Die *type_die, int *poffset, bool is_fbreg,
Dwarf_Die *die_mem);
/* Find a (global) variable located in the 'addr' */
Dwarf_Die *die_find_variable_by_addr(Dwarf_Die *sc_die, Dwarf_Addr addr,
- Dwarf_Die *die_mem, int *offset);
+ Dwarf_Die *die_mem, Dwarf_Die *type_die,
+ int *offset);
/* Save all variables and parameters in this scope */
void die_collect_vars(Dwarf_Die *sc_die, struct die_var_type **var_types);
--
2.53.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v2 04/11] perf annotate-data: Improve type comparison from different scopes
2026-03-09 17:55 [PATCH v2 00/11] perf tools: Improvements to data type profiler Zecheng Li
` (2 preceding siblings ...)
2026-03-09 17:55 ` [PATCH v2 03/11] perf dwarf-aux: Skip check_variable for variable lookup Zecheng Li
@ 2026-03-09 17:55 ` Zecheng Li
2026-03-09 17:55 ` [PATCH v2 05/11] perf dwarf-aux: Handle array types in die_get_member_type Zecheng Li
` (8 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Zecheng Li @ 2026-03-09 17:55 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Mark Rutland, Alexander Shishkin, Jiri Olsa, Ian Rogers,
Adrian Hunter, James Clark, Zecheng Li, xliuprof,
linux-perf-users, linux-kernel, Zecheng Li
From: Zecheng Li <zecheng@google.com>
When comparing types from different scopes, first compare their type
offsets. A larger offset means the field belongs to an outer (enclosing)
struct. This helps resolve cases where a pointer is found in an inner
scope, but a struct containing that pointer exists in an outer scope.
Previously, is_better_type would prefer the pointer type, but the struct
type is actually more complete and should be chosen.
Prefer types from outer scopes when is_better_type cannot determine a
better type. This is a heuristic for the case `struct A { struct B; }`
where A and B have the same size but I think in most cases A is in the
outer scope and should be preferred.
Signed-off-by: Zecheng Li <zecheng@google.com>
Signed-off-by: Zecheng Li <zli94@ncsu.edu>
---
tools/perf/util/annotate-data.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c
index 23a09bf58f86..6fe2efd48a83 100644
--- a/tools/perf/util/annotate-data.c
+++ b/tools/perf/util/annotate-data.c
@@ -1629,7 +1629,9 @@ static int find_data_type_die(struct data_loc_info *dloc, Dwarf_Die *type_die)
pr_debug_dtp("type_offset=%#x\n", type_offset);
}
- if (!found || is_better_type(type_die, &mem_die)) {
+ if (!found || dloc->type_offset < type_offset ||
+ (dloc->type_offset == type_offset &&
+ !is_better_type(&mem_die, type_die))) {
*type_die = mem_die;
dloc->type_offset = type_offset;
found = true;
--
2.53.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v2 05/11] perf dwarf-aux: Handle array types in die_get_member_type
2026-03-09 17:55 [PATCH v2 00/11] perf tools: Improvements to data type profiler Zecheng Li
` (3 preceding siblings ...)
2026-03-09 17:55 ` [PATCH v2 04/11] perf annotate-data: Improve type comparison from different scopes Zecheng Li
@ 2026-03-09 17:55 ` Zecheng Li
2026-03-09 17:55 ` [PATCH v2 06/11] perf annotate-data: Collect global variables without name Zecheng Li
` (7 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Zecheng Li @ 2026-03-09 17:55 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Mark Rutland, Alexander Shishkin, Jiri Olsa, Ian Rogers,
Adrian Hunter, James Clark, Zecheng Li, xliuprof,
linux-perf-users, linux-kernel
When a struct member is an array type, die_get_member_type() would stop
iterating since array types weren't handled in the loop. This caused
accesses to array elements within structs to not resolve properly.
Add array type handling by resolving the array to its element type and
calculating the offset within an element using modulo arithmetic
This improves type annotation coverage for struct members that are
arrays.
Signed-off-by: Zecheng Li <zli94@ncsu.edu>
---
tools/perf/util/dwarf-aux.c | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
index 1484aa756826..1feefc329154 100644
--- a/tools/perf/util/dwarf-aux.c
+++ b/tools/perf/util/dwarf-aux.c
@@ -2127,13 +2127,28 @@ Dwarf_Die *die_get_member_type(Dwarf_Die *type_die, int offset,
tag = dwarf_tag(&mb_type);
- if (tag == DW_TAG_structure_type || tag == DW_TAG_union_type) {
+ if (tag == DW_TAG_structure_type || tag == DW_TAG_union_type ||
+ tag == DW_TAG_array_type) {
Dwarf_Word loc;
/* Update offset for the start of the member struct */
if (die_get_data_member_location(member, &loc) == 0)
offset -= loc;
}
+
+ /* Handle array types: resolve to the element type by one level */
+ if (tag == DW_TAG_array_type) {
+ Dwarf_Word size;
+
+ if (die_get_real_type(&mb_type, &mb_type) == NULL)
+ return NULL;
+
+ if (dwarf_aggregate_size(&mb_type, &size) < 0)
+ return NULL;
+
+ offset = offset % size;
+ tag = dwarf_tag(&mb_type);
+ }
}
*die_mem = mb_type;
return die_mem;
--
2.53.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v2 06/11] perf annotate-data: Collect global variables without name
2026-03-09 17:55 [PATCH v2 00/11] perf tools: Improvements to data type profiler Zecheng Li
` (4 preceding siblings ...)
2026-03-09 17:55 ` [PATCH v2 05/11] perf dwarf-aux: Handle array types in die_get_member_type Zecheng Li
@ 2026-03-09 17:55 ` Zecheng Li
2026-03-09 17:55 ` [PATCH v2 07/11] perf annotate-data: Handle global variable access with const register Zecheng Li
` (6 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Zecheng Li @ 2026-03-09 17:55 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Mark Rutland, Alexander Shishkin, Jiri Olsa, Ian Rogers,
Adrian Hunter, James Clark, Zecheng Li, xliuprof,
linux-perf-users, linux-kernel
Previously, global_var__collect() required get_global_var_info() to
succeed (i.e., the variable must have a symbol name) before caching a
global variable. This prevented variables that exist in DWARF but lack
symbol table coverage from being cached.
Remove the symbol table requirement since DW_OP_addr already provides
the variable's address directly from DWARF. The symbol table lookup is
now optional to obtain the variable name when available.
Also remove the var_offset != 0 check, which was intended to skip
variables where the access address doesn't match the symbol start. The
symbol table lookup is now optional and I found removing this check has
no effect on the annotation results for both kernel and userspace
programs.
Test results show improved annotation coverage especially for userspace
programs with RIP-relative addressing instructions.
Signed-off-by: Zecheng Li <zli94@ncsu.edu>
---
tools/perf/util/annotate-data.c | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c
index 6fe2efd48a83..301f73ea8275 100644
--- a/tools/perf/util/annotate-data.c
+++ b/tools/perf/util/annotate-data.c
@@ -774,12 +774,7 @@ static void global_var__collect(struct data_loc_info *dloc)
if (!dwarf_offdie(dwarf, pos->die_off, &type_die))
continue;
- if (!get_global_var_info(dloc, pos->addr, &var_name,
- &var_offset))
- continue;
-
- if (var_offset != 0)
- continue;
+ get_global_var_info(dloc, pos->addr, &var_name, &var_offset);
global_var__add(dloc, pos->addr, var_name, &type_die);
}
--
2.53.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v2 07/11] perf annotate-data: Handle global variable access with const register
2026-03-09 17:55 [PATCH v2 00/11] perf tools: Improvements to data type profiler Zecheng Li
` (5 preceding siblings ...)
2026-03-09 17:55 ` [PATCH v2 06/11] perf annotate-data: Collect global variables without name Zecheng Li
@ 2026-03-09 17:55 ` Zecheng Li
2026-03-09 17:55 ` [PATCH v2 08/11] perf annotate-data: Add invalidate_reg_state() helper for x86 Zecheng Li
` (5 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Zecheng Li @ 2026-03-09 17:55 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Mark Rutland, Alexander Shishkin, Jiri Olsa, Ian Rogers,
Adrian Hunter, James Clark, Zecheng Li, xliuprof,
linux-perf-users, linux-kernel
When a register holds a constant value (TSR_KIND_CONST) and is used with
a negative offset, treat it as a potential global variable access
instead of falling through to CFA (frame) handling.
This fixes cases like array indexing with computed offsets:
movzbl -0x7d72725a(%rax), %eax # array[%rax]
Where %rax contains a computed index and the negative offset points to a
global array. Previously this fell through to the CFA path which doesn't
handle global variables, resulting in "no type information".
The fix redirects such accesses to check_kernel which calls
get_global_var_type() to resolve the type from the global variable
cache. This is only done for kernel DSOs since the pattern relies on
kernel-specific global variable resolution. We could also treat
registers with integer types to the global variable path, but this
requires more changes.
Signed-off-by: Zecheng Li <zli94@ncsu.edu>
---
tools/perf/util/annotate-data.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c
index 301f73ea8275..50c82c91f828 100644
--- a/tools/perf/util/annotate-data.c
+++ b/tools/perf/util/annotate-data.c
@@ -1229,6 +1229,11 @@ static enum type_match_result check_matching_type(struct type_state *state,
return PERF_TMR_BAIL_OUT;
}
+ 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)
+ goto check_kernel;
+ }
check_non_register:
if (reg == dloc->fbreg || reg == state->stack_reg) {
struct type_state_stack *stack;
--
2.53.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v2 08/11] perf annotate-data: Add invalidate_reg_state() helper for x86
2026-03-09 17:55 [PATCH v2 00/11] perf tools: Improvements to data type profiler Zecheng Li
` (6 preceding siblings ...)
2026-03-09 17:55 ` [PATCH v2 07/11] perf annotate-data: Handle global variable access with const register Zecheng Li
@ 2026-03-09 17:55 ` Zecheng Li
2026-03-09 17:55 ` [PATCH v2 09/11] perf annotate-data: Invalidate caller-saved regs for all calls Zecheng Li
` (4 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Zecheng Li @ 2026-03-09 17:55 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Mark Rutland, Alexander Shishkin, Jiri Olsa, Ian Rogers,
Adrian Hunter, James Clark, Zecheng Li, xliuprof,
linux-perf-users, linux-kernel
Add a helper function to consistently invalidate register state instead
of field assignments. This ensures kind, ok, and copied_from are all
properly cleared when a register becomes invalid.
The helper sets:
- kind = TSR_KIND_INVALID
- ok = false
- copied_from = -1
Replace all invalidation patterns with calls to this helper. No
functional change and this removes some incorrect annotations that were
caused by incomplete invalidation (e.g. a obsolete copied_from from an
invalidated register).
Signed-off-by: Zecheng Li <zli94@ncsu.edu>
---
tools/perf/util/annotate-arch/annotate-x86.c | 29 ++++++++++++--------
1 file changed, 17 insertions(+), 12 deletions(-)
diff --git a/tools/perf/util/annotate-arch/annotate-x86.c b/tools/perf/util/annotate-arch/annotate-x86.c
index eb9a649ca656..eb390a253d71 100644
--- a/tools/perf/util/annotate-arch/annotate-x86.c
+++ b/tools/perf/util/annotate-arch/annotate-x86.c
@@ -204,6 +204,13 @@ 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->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)
@@ -235,7 +242,7 @@ static void update_insn_state_x86(struct type_state *state,
/* Otherwise invalidate caller-saved registers after call */
for (unsigned i = 0; i < ARRAY_SIZE(state->regs); i++) {
if (state->regs[i].caller_saved)
- state->regs[i].ok = false;
+ invalidate_reg_state(&state->regs[i]);
}
/* Update register with the return type (if any) */
@@ -364,8 +371,7 @@ static void update_insn_state_x86(struct type_state *state,
src_tsr = state->regs[sreg];
tsr = &state->regs[dst->reg1];
- tsr->copied_from = -1;
- tsr->ok = false;
+ invalidate_reg_state(tsr);
/* Case 1: Based on stack pointer or frame pointer */
if (sreg == fbreg || sreg == state->stack_reg) {
@@ -433,8 +439,7 @@ static void update_insn_state_x86(struct type_state *state,
!strncmp(dl->ins.name, "inc", 3) || !strncmp(dl->ins.name, "dec", 3)) {
pr_debug_dtp("%s [%x] invalidate reg%d\n",
dl->ins.name, insn_offset, dst->reg1);
- state->regs[dst->reg1].ok = false;
- state->regs[dst->reg1].copied_from = -1;
+ invalidate_reg_state(&state->regs[dst->reg1]);
return;
}
@@ -496,7 +501,7 @@ static void update_insn_state_x86(struct type_state *state,
if (!get_global_var_type(cu_die, dloc, ip, var_addr,
&offset, &type_die) ||
!die_get_member_type(&type_die, offset, &type_die)) {
- tsr->ok = false;
+ invalidate_reg_state(tsr);
return;
}
@@ -524,7 +529,7 @@ static void update_insn_state_x86(struct type_state *state,
if (!has_reg_type(state, src->reg1) ||
!state->regs[src->reg1].ok) {
- tsr->ok = false;
+ invalidate_reg_state(tsr);
return;
}
@@ -560,7 +565,7 @@ static void update_insn_state_x86(struct type_state *state,
stack = find_stack_state(state, offset);
if (stack == NULL) {
- tsr->ok = false;
+ invalidate_reg_state(tsr);
return;
} else if (!stack->compound) {
tsr->type = stack->type;
@@ -575,7 +580,7 @@ static void update_insn_state_x86(struct type_state *state,
tsr->offset = 0;
tsr->ok = true;
} else {
- tsr->ok = false;
+ invalidate_reg_state(tsr);
return;
}
@@ -628,7 +633,7 @@ static void update_insn_state_x86(struct type_state *state,
if (!get_global_var_type(cu_die, dloc, ip, addr, &offset,
&type_die) ||
!die_get_member_type(&type_die, offset, &type_die)) {
- tsr->ok = false;
+ invalidate_reg_state(tsr);
return;
}
@@ -679,7 +684,7 @@ static void update_insn_state_x86(struct type_state *state,
}
pr_debug_type_name(&tsr->type, tsr->kind);
} else {
- tsr->ok = false;
+ invalidate_reg_state(tsr);
}
}
/* And then dereference the calculated pointer if it has one */
@@ -721,7 +726,7 @@ static void update_insn_state_x86(struct type_state *state,
}
}
- tsr->ok = false;
+ invalidate_reg_state(tsr);
}
}
/* Case 3. register to memory transfers */
--
2.53.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v2 09/11] perf annotate-data: Invalidate caller-saved regs for all calls
2026-03-09 17:55 [PATCH v2 00/11] perf tools: Improvements to data type profiler Zecheng Li
` (7 preceding siblings ...)
2026-03-09 17:55 ` [PATCH v2 08/11] perf annotate-data: Add invalidate_reg_state() helper for x86 Zecheng Li
@ 2026-03-09 17:55 ` Zecheng Li
2026-03-09 17:55 ` [PATCH v2 10/11] perf annotate-data: Use DWARF location ranges to preserve reg state Zecheng Li
` (3 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Zecheng Li @ 2026-03-09 17:55 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Mark Rutland, Alexander Shishkin, Jiri Olsa, Ian Rogers,
Adrian Hunter, James Clark, Zecheng Li, xliuprof,
linux-perf-users, linux-kernel
Previously, the x86 call handler returned early without invalidating
caller-saved registers when the call target symbol could not be resolved
(func == NULL). This violated the ABI which requires caller-saved
registers to be considered clobbered after any call instruction.
Fix this by:
1. Always invalidating caller-saved registers for any call instruction
(except __fentry__ which preserves registers)
2. Using dl->ops.target.name as fallback when func->name is unavailable,
allowing return type lookup for more call targets
This is a conservative change that may reduce type coverage for indirect
calls (e.g., callq *(%rax)) where we cannot determine the return type
but it ensures correctness.
Signed-off-by: Zecheng Li <zli94@ncsu.edu>
---
tools/perf/util/annotate-arch/annotate-x86.c | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/tools/perf/util/annotate-arch/annotate-x86.c b/tools/perf/util/annotate-arch/annotate-x86.c
index eb390a253d71..df9fc0a51b39 100644
--- a/tools/perf/util/annotate-arch/annotate-x86.c
+++ b/tools/perf/util/annotate-arch/annotate-x86.c
@@ -229,24 +229,31 @@ static void update_insn_state_x86(struct type_state *state,
if (ins__is_call(&dl->ins)) {
struct symbol *func = dl->ops.target.sym;
+ const char *call_name;
- if (func == NULL)
- return;
+ /* Try to resolve the call target name */
+ if (func)
+ call_name = func->name;
+ else
+ call_name = dl->ops.target.name;
/* __fentry__ will preserve all registers */
- if (!strcmp(func->name, "__fentry__"))
+ if (call_name && !strcmp(call_name, "__fentry__"))
return;
- pr_debug_dtp("call [%x] %s\n", insn_offset, func->name);
+ if (call_name)
+ pr_debug_dtp("call [%x] %s\n", insn_offset, call_name);
+ else
+ pr_debug_dtp("call [%x] <unknown>\n", insn_offset);
- /* Otherwise invalidate caller-saved registers after call */
+ /* Invalidate caller-saved registers after call (ABI requirement) */
for (unsigned i = 0; i < ARRAY_SIZE(state->regs); i++) {
if (state->regs[i].caller_saved)
invalidate_reg_state(&state->regs[i]);
}
/* Update register with the return type (if any) */
- if (die_find_func_rettype(cu_die, func->name, &type_die)) {
+ if (call_name && die_find_func_rettype(cu_die, call_name, &type_die)) {
tsr = &state->regs[state->ret_reg];
tsr->type = type_die;
tsr->kind = TSR_KIND_TYPE;
--
2.53.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v2 10/11] perf annotate-data: Use DWARF location ranges to preserve reg state
2026-03-09 17:55 [PATCH v2 00/11] perf tools: Improvements to data type profiler Zecheng Li
` (8 preceding siblings ...)
2026-03-09 17:55 ` [PATCH v2 09/11] perf annotate-data: Invalidate caller-saved regs for all calls Zecheng Li
@ 2026-03-09 17:55 ` Zecheng Li
2026-03-09 17:55 ` [PATCH v2 11/11] perf dwarf-aux: Collect all variable locations for insn tracking Zecheng Li
` (2 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Zecheng Li @ 2026-03-09 17:55 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Mark Rutland, Alexander Shishkin, Jiri Olsa, Ian Rogers,
Adrian Hunter, James Clark, Zecheng Li, xliuprof,
linux-perf-users, linux-kernel
When a function call occurs, caller-saved registers are typically
invalidated since the callee may clobber them. However, DWARF debug info
provides location ranges that indicate exactly where a variable is valid
in a register.
Track the DWARF location range end address in type_state_reg and use it
to determine if a caller-saved register should be preserved across a
call. If the current call address is within the DWARF-specified lifetime
of the variable, keep the register state valid instead of invalidating
it.
This improves type annotation for code where the compiler knows a
register value survives across calls (e.g., when the callee is known not
to clobber certain registers or when the value is reloaded after the
call at the same logical location).
Changes:
- Add `end` and `has_range` fields to die_var_type to capture DWARF
location range information
- Add `lifetime_active` and `lifetime_end` fields to type_state_reg
- Check location lifetime before invalidating caller-saved registers
Signed-off-by: Zecheng Li <zli94@ncsu.edu>
---
tools/perf/util/annotate-arch/annotate-x86.c | 25 +++++++++++++++++---
tools/perf/util/annotate-data.c | 24 +++++++++++++++++--
tools/perf/util/annotate-data.h | 3 +++
tools/perf/util/dwarf-aux.c | 6 ++++-
tools/perf/util/dwarf-aux.h | 2 ++
5 files changed, 54 insertions(+), 6 deletions(-)
diff --git a/tools/perf/util/annotate-arch/annotate-x86.c b/tools/perf/util/annotate-arch/annotate-x86.c
index df9fc0a51b39..c77aabd48eba 100644
--- a/tools/perf/util/annotate-arch/annotate-x86.c
+++ b/tools/perf/util/annotate-arch/annotate-x86.c
@@ -208,6 +208,8 @@ 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;
}
@@ -230,6 +232,7 @@ static void update_insn_state_x86(struct type_state *state,
if (ins__is_call(&dl->ins)) {
struct symbol *func = dl->ops.target.sym;
const char *call_name;
+ u64 call_addr;
/* Try to resolve the call target name */
if (func)
@@ -246,10 +249,18 @@ static void update_insn_state_x86(struct type_state *state,
else
pr_debug_dtp("call [%x] <unknown>\n", insn_offset);
- /* Invalidate caller-saved registers after call (ABI requirement) */
+ /* Invalidate caller-saved registers after call */
+ call_addr = map__rip_2objdump(dloc->ms->map,
+ dloc->ms->sym->start + dl->al.offset);
for (unsigned i = 0; i < ARRAY_SIZE(state->regs); i++) {
- if (state->regs[i].caller_saved)
- invalidate_reg_state(&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) */
@@ -279,6 +290,8 @@ static void update_insn_state_x86(struct type_state *state,
tsr = &state->regs[dst->reg1];
tsr->copied_from = -1;
+ tsr->lifetime_active = false;
+ tsr->lifetime_end = 0;
if (src->imm)
imm_value = src->offset;
@@ -344,6 +357,8 @@ static void update_insn_state_x86(struct type_state *state,
tsr = &state->regs[dst->reg1];
tsr->copied_from = -1;
+ tsr->lifetime_active = false;
+ tsr->lifetime_end = 0;
if (src->imm)
imm_value = src->offset;
@@ -458,6 +473,8 @@ static void update_insn_state_x86(struct type_state *state,
state->regs[dst->reg1].kind = TSR_KIND_CONST;
state->regs[dst->reg1].imm_value = 0;
state->regs[dst->reg1].ok = true;
+ state->regs[dst->reg1].lifetime_active = false;
+ state->regs[dst->reg1].lifetime_end = 0;
state->regs[dst->reg1].copied_from = -1;
return;
}
@@ -544,6 +561,8 @@ static void update_insn_state_x86(struct type_state *state,
tsr->kind = state->regs[src->reg1].kind;
tsr->imm_value = state->regs[src->reg1].imm_value;
tsr->offset = state->regs[src->reg1].offset;
+ tsr->lifetime_active = state->regs[src->reg1].lifetime_active;
+ tsr->lifetime_end = state->regs[src->reg1].lifetime_end;
tsr->ok = true;
/* To copy back the variable type later (hopefully) */
diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c
index 50c82c91f828..1eff0a27237d 100644
--- a/tools/perf/util/annotate-data.c
+++ b/tools/perf/util/annotate-data.c
@@ -840,6 +840,18 @@ static bool die_is_same(Dwarf_Die *die_a, Dwarf_Die *die_b)
return (die_a->cu == die_b->cu) && (die_a->addr == die_b->addr);
}
+static void tsr_set_lifetime(struct type_state_reg *tsr,
+ const struct die_var_type *var)
+{
+ if (var && var->has_range && var->end > var->addr) {
+ tsr->lifetime_active = true;
+ tsr->lifetime_end = var->end;
+ } else {
+ tsr->lifetime_active = false;
+ tsr->lifetime_end = 0;
+ }
+}
+
/**
* update_var_state - Update type state using given variables
* @state: type state table
@@ -865,8 +877,14 @@ static void update_var_state(struct type_state *state, struct data_loc_info *dlo
}
for (var = var_types; var != NULL; var = var->next) {
- if (var->addr != addr)
- continue;
+ /* Check if addr falls within the variable's valid range */
+ if (var->has_range) {
+ if (addr < var->addr || (var->end && addr >= var->end))
+ continue;
+ } else {
+ if (addr != var->addr)
+ continue;
+ }
/* Get the type DIE using the offset */
if (!dwarf_offdie(dloc->di->dbg, var->die_off, &mem_die))
continue;
@@ -923,6 +941,7 @@ static void update_var_state(struct type_state *state, struct data_loc_info *dlo
reg->type = mem_die;
reg->kind = TSR_KIND_POINTER;
reg->ok = true;
+ tsr_set_lifetime(reg, var);
pr_debug_dtp("var [%"PRIx64"] reg%d addr offset %x",
insn_offset, var->reg, var->offset);
@@ -939,6 +958,7 @@ static void update_var_state(struct type_state *state, struct data_loc_info *dlo
reg->type = mem_die;
reg->kind = TSR_KIND_TYPE;
reg->ok = true;
+ tsr_set_lifetime(reg, var);
pr_debug_dtp("var [%"PRIx64"] reg%d offset %x",
insn_offset, var->reg, var->offset);
diff --git a/tools/perf/util/annotate-data.h b/tools/perf/util/annotate-data.h
index 9b222869e42d..c26130744260 100644
--- a/tools/perf/util/annotate-data.h
+++ b/tools/perf/util/annotate-data.h
@@ -182,6 +182,9 @@ struct type_state_reg {
s32 offset;
bool ok;
bool caller_saved;
+ /* DWARF location range tracking for register lifetime */
+ bool lifetime_active;
+ u64 lifetime_end;
u8 kind;
u8 copied_from;
};
diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
index 1feefc329154..0710c875416f 100644
--- a/tools/perf/util/dwarf-aux.c
+++ b/tools/perf/util/dwarf-aux.c
@@ -1641,7 +1641,7 @@ static int __die_collect_vars_cb(Dwarf_Die *die_mem, void *arg)
Dwarf_Die type_die;
int tag = dwarf_tag(die_mem);
Dwarf_Attribute attr;
- Dwarf_Addr base, start, end;
+ Dwarf_Addr base, start, end = 0;
Dwarf_Op *ops;
size_t nops;
struct die_var_type *vt;
@@ -1681,6 +1681,8 @@ static int __die_collect_vars_cb(Dwarf_Die *die_mem, void *arg)
vt->die_off = dwarf_dieoffset(&type_die);
vt->addr = start;
+ vt->end = end;
+ vt->has_range = (end != 0 || start != 0);
vt->reg = reg_from_dwarf_op(ops);
vt->offset = offset_from_dwarf_op(ops);
vt->next = *var_types;
@@ -1743,6 +1745,8 @@ static int __die_collect_global_vars_cb(Dwarf_Die *die_mem, void *arg)
vt->die_off = dwarf_dieoffset(&type_die);
vt->addr = ops->number;
+ vt->end = 0;
+ vt->has_range = false;
vt->reg = -1;
vt->offset = 0;
vt->next = *var_types;
diff --git a/tools/perf/util/dwarf-aux.h b/tools/perf/util/dwarf-aux.h
index 939a59c91796..a79968a2e573 100644
--- a/tools/perf/util/dwarf-aux.h
+++ b/tools/perf/util/dwarf-aux.h
@@ -148,10 +148,12 @@ struct die_var_type {
struct die_var_type *next;
u64 die_off;
u64 addr;
+ u64 end; /* end address of location range */
int reg;
int offset;
/* Whether the register holds a address to the type */
bool is_reg_var_addr;
+ bool has_range; /* whether end is valid */
};
/* Return type info of a member at offset */
--
2.53.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v2 11/11] perf dwarf-aux: Collect all variable locations for insn tracking
2026-03-09 17:55 [PATCH v2 00/11] perf tools: Improvements to data type profiler Zecheng Li
` (9 preceding siblings ...)
2026-03-09 17:55 ` [PATCH v2 10/11] perf annotate-data: Use DWARF location ranges to preserve reg state Zecheng Li
@ 2026-03-09 17:55 ` Zecheng Li
2026-03-16 18:17 ` [PATCH v2 00/11] perf tools: Improvements to data type profiler Namhyung Kim
2026-03-20 18:12 ` Namhyung Kim
12 siblings, 0 replies; 14+ messages in thread
From: Zecheng Li @ 2026-03-09 17:55 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Mark Rutland, Alexander Shishkin, Jiri Olsa, Ian Rogers,
Adrian Hunter, James Clark, Zecheng Li, xliuprof,
linux-perf-users, linux-kernel
Previously, only the first DWARF location entry was collected for each
variable. This was based on the assumption that instruction tracking
could reconstruct the remaining state. However, variables may have
different locations across different address ranges, and relying solely
on instruction tracking can miss valid type information.
Change __die_collect_vars_cb() to iterate over all location entries
using dwarf_getlocations() in a loop. This ensures that variables with
multiple location ranges are properly tracked, improving type coverage.
Signed-off-by: Zecheng Li <zli94@ncsu.edu>
---
tools/perf/util/dwarf-aux.c | 60 ++++++++++++++++++-------------------
1 file changed, 30 insertions(+), 30 deletions(-)
diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
index 0710c875416f..92db2fccc788 100644
--- a/tools/perf/util/dwarf-aux.c
+++ b/tools/perf/util/dwarf-aux.c
@@ -1645,6 +1645,7 @@ static int __die_collect_vars_cb(Dwarf_Die *die_mem, void *arg)
Dwarf_Op *ops;
size_t nops;
struct die_var_type *vt;
+ ptrdiff_t off;
if (tag != DW_TAG_variable && tag != DW_TAG_formal_parameter)
return DIE_FIND_CB_SIBLING;
@@ -1652,41 +1653,40 @@ static int __die_collect_vars_cb(Dwarf_Die *die_mem, void *arg)
if (dwarf_attr(die_mem, DW_AT_location, &attr) == NULL)
return DIE_FIND_CB_SIBLING;
- /*
- * Only collect the first location as it can reconstruct the
- * remaining state by following the instructions.
- * start = 0 means it covers the whole range.
- */
- if (dwarf_getlocations(&attr, 0, &base, &start, &end, &ops, &nops) <= 0)
- return DIE_FIND_CB_SIBLING;
-
- if (!check_allowed_ops(ops, nops))
- return DIE_FIND_CB_SIBLING;
-
if (__die_get_real_type(die_mem, &type_die) == NULL)
return DIE_FIND_CB_SIBLING;
- vt = malloc(sizeof(*vt));
- if (vt == NULL)
- return DIE_FIND_CB_END;
-
- /* Usually a register holds the value of a variable */
- vt->is_reg_var_addr = false;
+ /*
+ * Collect all location entries as variables may have different
+ * locations across different address ranges.
+ */
+ off = 0;
+ while ((off = dwarf_getlocations(&attr, off, &base, &start, &end, &ops, &nops)) > 0) {
+ if (!check_allowed_ops(ops, nops))
+ continue;
- if (((ops->atom >= DW_OP_breg0 && ops->atom <= DW_OP_breg31) ||
- ops->atom == DW_OP_bregx || ops->atom == DW_OP_fbreg) &&
- !is_breg_access_indirect(ops, nops))
- /* The register contains an address of the variable. */
- vt->is_reg_var_addr = true;
+ vt = malloc(sizeof(*vt));
+ if (vt == NULL)
+ return DIE_FIND_CB_END;
- vt->die_off = dwarf_dieoffset(&type_die);
- vt->addr = start;
- vt->end = end;
- vt->has_range = (end != 0 || start != 0);
- vt->reg = reg_from_dwarf_op(ops);
- vt->offset = offset_from_dwarf_op(ops);
- vt->next = *var_types;
- *var_types = vt;
+ /* Usually a register holds the value of a variable */
+ vt->is_reg_var_addr = false;
+
+ if (((ops->atom >= DW_OP_breg0 && ops->atom <= DW_OP_breg31) ||
+ ops->atom == DW_OP_bregx || ops->atom == DW_OP_fbreg) &&
+ !is_breg_access_indirect(ops, nops))
+ /* The register contains an address of the variable. */
+ vt->is_reg_var_addr = true;
+
+ vt->die_off = dwarf_dieoffset(&type_die);
+ vt->addr = start;
+ vt->end = end;
+ vt->has_range = (end != 0 || start != 0);
+ vt->reg = reg_from_dwarf_op(ops);
+ vt->offset = offset_from_dwarf_op(ops);
+ vt->next = *var_types;
+ *var_types = vt;
+ }
return DIE_FIND_CB_SIBLING;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH v2 00/11] perf tools: Improvements to data type profiler
2026-03-09 17:55 [PATCH v2 00/11] perf tools: Improvements to data type profiler Zecheng Li
` (10 preceding siblings ...)
2026-03-09 17:55 ` [PATCH v2 11/11] perf dwarf-aux: Collect all variable locations for insn tracking Zecheng Li
@ 2026-03-16 18:17 ` Namhyung Kim
2026-03-20 18:12 ` Namhyung Kim
12 siblings, 0 replies; 14+ messages in thread
From: Namhyung Kim @ 2026-03-16 18:17 UTC (permalink / raw)
To: Zecheng Li
Cc: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Mark Rutland, Alexander Shishkin, Jiri Olsa, Ian Rogers,
Adrian Hunter, James Clark, xliuprof, linux-perf-users,
linux-kernel
Hi Zecheng,
Thanks for working on this continuously!
On Mon, Mar 09, 2026 at 01:55:13PM -0400, Zecheng Li wrote:
> This patch series improves the coverage and correctness of data type
> annotations in the perf tools.
>
> Here's a breakdown of the patches:
>
> Patches 1-4 improve variable type matching:
> - Add die_get_pointer_type() to properly handle typedef'd pointer types
> - Preserve typedefs in match_var_offset for consistent type handling
> - Skip redundant check_variable for die_find_variable_by_reg and
> die_find_variable_by_addr since match_var_offset already performs
> sufficient checking
> - Improve type comparison when variables are found in different scopes
>
> Patch 5 handles array types in die_get_member_type, allowing proper
> resolution of struct members that are arrays.
>
> Patches 6-7 improve global variable handling:
> - Allow collecting global variables without symbol names (DWARF provides
> the address directly via DW_OP_addr)
> - Handle global variable access when a register holds a const value
> with negative offset
>
> Patches 8-9 improve caller-saved register handling:
> - Add invalidate_reg_state() helper for consistent register invalidation
> - Always invalidate caller-saved registers for call instructions per ABI
> requirements, even when the call target is unknown
>
> Patches 10-11 use DWARF location ranges to improve type tracking:
> - Track DWARF location lifetime to preserve register state across calls
> when the debug info indicates the value is still valid
> - Collect all variable location entries instead of just the first one
>
> Tested with the Linux kernel vmlinux with sampled functions and five
> programs from binutils (as, ld, nm, objdump, readelf) with all
> functions. Coverage rate includes all memory access instructions,
> excluding stack memory accesses.
>
> `lost` means coverage loss (only including annotated -> none);
> `chg` means type name change. Net coverage gain is new_rate - prev_rate.
>
> -------- vmlinux -------- ------- binutils -------
> Patch rate lost chg rate lost chg
> ----------------------------------------------------------------
> base 88.87% - - 72.55% - -
> 1 88.87% 0% 0% 74.22% 0% .03%
> 2 88.88% 0% .05% 74.32% 0% 0%
> 3 88.90% 0% .69% 74.38% 0% .31%
> 4 88.90% 0% .83% 74.38% 0% .17%
> 5 89.11% .01% .08% 74.41% 0% .01%
> 6 89.12% 0% .05% 75.33% 0% 1.62%
> 7 89.18% 0% 0% 75.33% 0% 0%
> 8 89.18% 0% 0% 75.30% .02% 0%
> 9 89.09% .09% 0% 75.24% .05% 0%
> 10 89.05% .08% .09% 75.53% .03% .03%
> 11 89.97% 0% .10% 76.63% .26% .07%
It's interesting that the patch 11 lost some coverage as it merely added
more variables (well the same variables with different ranges).
Have you checked the lost cases and what happened there?
Thanks,
Namhyung
>
> Total: vmlinux 88.87% -> 89.97% (+1.10%)
> binutils 72.55% -> 76.63% (+4.08%)
>
> Changes in v2:
> - Reorder patches 1-3 to avoid temporary regression: pointer_type and
> typedef patches now come before skip check_variable (Namhyung)
> - Skip check_variable for both die_find_variable_by_reg and
> die_find_variable_by_addr paths (Namhyung)
> - Add dso__kernel() guard for const register global variable access
> to restrict it to kernel DSOs (Namhyung)
>
> Zecheng Li (11):
> perf dwarf-aux: Add die_get_pointer_type to get pointer types
> perf dwarf-aux: Preserve typedefs in match_var_offset
> perf dwarf-aux: Skip check_variable for variable lookup
> perf annotate-data: Improve type comparison from different scopes
> perf dwarf-aux: Handle array types in die_get_member_type
> perf annotate-data: Collect global variables without name
> perf annotate-data: Handle global variable access with const register
> perf annotate-data: Add invalidate_reg_state() helper for x86
> perf annotate-data: Invalidate caller-saved regs for all calls
> perf annotate-data: Use DWARF location ranges to preserve reg state
> perf dwarf-aux: Collect all variable locations for insn tracking
>
> tools/perf/util/annotate-arch/annotate-x86.c | 69 ++++++---
> tools/perf/util/annotate-data.c | 119 ++++++++-------
> tools/perf/util/annotate-data.h | 3 +
> tools/perf/util/dwarf-aux.c | 145 +++++++++++++------
> tools/perf/util/dwarf-aux.h | 9 +-
> 5 files changed, 230 insertions(+), 115 deletions(-)
>
> --
> 2.53.0
>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2 00/11] perf tools: Improvements to data type profiler
2026-03-09 17:55 [PATCH v2 00/11] perf tools: Improvements to data type profiler Zecheng Li
` (11 preceding siblings ...)
2026-03-16 18:17 ` [PATCH v2 00/11] perf tools: Improvements to data type profiler Namhyung Kim
@ 2026-03-20 18:12 ` Namhyung Kim
12 siblings, 0 replies; 14+ messages in thread
From: Namhyung Kim @ 2026-03-20 18:12 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo, Zecheng Li
Cc: Mark Rutland, Alexander Shishkin, Jiri Olsa, Ian Rogers,
Adrian Hunter, James Clark, xliuprof, linux-perf-users,
linux-kernel
On Mon, 09 Mar 2026 13:55:13 -0400, Zecheng Li wrote:
> This patch series improves the coverage and correctness of data type
> annotations in the perf tools.
>
> Here's a breakdown of the patches:
>
> Patches 1-4 improve variable type matching:
> - Add die_get_pointer_type() to properly handle typedef'd pointer types
> - Preserve typedefs in match_var_offset for consistent type handling
> - Skip redundant check_variable for die_find_variable_by_reg and
> die_find_variable_by_addr since match_var_offset already performs
> sufficient checking
> - Improve type comparison when variables are found in different scopes
>
> [...]
Applied to perf-tools-next, thanks!
Best regards,
Namhyung
^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-03-20 18:12 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-09 17:55 [PATCH v2 00/11] perf tools: Improvements to data type profiler Zecheng Li
2026-03-09 17:55 ` [PATCH v2 01/11] perf dwarf-aux: Add die_get_pointer_type to get pointer types Zecheng Li
2026-03-09 17:55 ` [PATCH v2 02/11] perf dwarf-aux: Preserve typedefs in match_var_offset Zecheng Li
2026-03-09 17:55 ` [PATCH v2 03/11] perf dwarf-aux: Skip check_variable for variable lookup Zecheng Li
2026-03-09 17:55 ` [PATCH v2 04/11] perf annotate-data: Improve type comparison from different scopes Zecheng Li
2026-03-09 17:55 ` [PATCH v2 05/11] perf dwarf-aux: Handle array types in die_get_member_type Zecheng Li
2026-03-09 17:55 ` [PATCH v2 06/11] perf annotate-data: Collect global variables without name Zecheng Li
2026-03-09 17:55 ` [PATCH v2 07/11] perf annotate-data: Handle global variable access with const register Zecheng Li
2026-03-09 17:55 ` [PATCH v2 08/11] perf annotate-data: Add invalidate_reg_state() helper for x86 Zecheng Li
2026-03-09 17:55 ` [PATCH v2 09/11] perf annotate-data: Invalidate caller-saved regs for all calls Zecheng Li
2026-03-09 17:55 ` [PATCH v2 10/11] perf annotate-data: Use DWARF location ranges to preserve reg state Zecheng Li
2026-03-09 17:55 ` [PATCH v2 11/11] perf dwarf-aux: Collect all variable locations for insn tracking Zecheng Li
2026-03-16 18:17 ` [PATCH v2 00/11] perf tools: Improvements to data type profiler Namhyung Kim
2026-03-20 18:12 ` Namhyung Kim
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox