* [PATCH v1 00/11] perf tools: Improvements to data type profiler
@ 2026-01-27 2:04 Zecheng Li
2026-01-27 2:04 ` [PATCH v1 01/11] perf dwarf-aux: Skip check_variable for die_find_variable_by_reg Zecheng Li
` (10 more replies)
0 siblings, 11 replies; 19+ messages in thread
From: Zecheng Li @ 2026-01-27 2:04 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:
- Skip redundant check_variable for die_find_variable_by_reg since
match_var_offset already performs sufficient checking
- Add die_get_pointer_type() to properly handle typedef'd pointer types
- Preserve typedefs in match_var_offset for consistent type handling
- 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 assess 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.78% - - 72.55% - -
1 72.95% 15.63% .71% 65.01% 8.59% 22.89%
2 72.95% 0% 0% 65.16% 0% 0%
3 88.88% 0% .73% 74.73% 0% 23.72%
4 88.88% 0% .83% 74.73% 0% .17%
5 89.09% .01% .08% 74.75% 0% .01%
6 89.10% 0% .02% 75.67% 0% 1.62%
7 89.16% 0% 0% 75.67% 0% 0%
8 89.16% 0% 0% 75.64% .02% 0%
9 89.07% .08% 0% 75.59% .05% 0%
10 89.02% .08% .09% 75.85% .03% .03%
11 89.95% 0% .10% 76.94% .26% .07%
Total: vmlinux 88.78% -> 89.95% (+1.17%)
binutils 72.55% -> 76.94% (+4.39%)
Note: Patches 1-3 are designed to work together. Patch 1 causes a
temporary regression that patch 3 resolves while adding typedef support.
Zecheng Li (11):
perf dwarf-aux: Skip check_variable for die_find_variable_by_reg
perf dwarf-aux: Add die_get_pointer_type to get pointer types
perf dwarf-aux: Preserve typedefs in match_var_offset
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/arch/x86/annotate/instructions.c | 69 +++++++---
tools/perf/util/annotate-data.c | 86 ++++++++----
tools/perf/util/annotate-data.h | 3 +
tools/perf/util/dwarf-aux.c | 139 ++++++++++++++------
tools/perf/util/dwarf-aux.h | 6 +-
5 files changed, 211 insertions(+), 92 deletions(-)
--
2.52.0
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH v1 01/11] perf dwarf-aux: Skip check_variable for die_find_variable_by_reg
2026-01-27 2:04 [PATCH v1 00/11] perf tools: Improvements to data type profiler Zecheng Li
@ 2026-01-27 2:04 ` Zecheng Li
2026-02-11 2:08 ` Namhyung Kim
2026-01-27 2:04 ` [PATCH v1 02/11] perf dwarf-aux: Add die_get_pointer_type to get pointer types Zecheng Li
` (9 subsequent siblings)
10 siblings, 1 reply; 19+ messages in thread
From: Zecheng Li @ 2026-01-27 2:04 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>
In die_find_variable_by_reg, match_var_offset 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 type from die_find_variable_by_reg via a new `type` field in
find_var_data.
Signed-off-by: Zecheng Li <zecheng@google.com>
Signed-off-by: Zecheng Li <zli94@ncsu.edu>
---
tools/perf/util/annotate-data.c | 8 +++++---
tools/perf/util/dwarf-aux.c | 18 +++++++++++-------
tools/perf/util/dwarf-aux.h | 2 +-
3 files changed, 17 insertions(+), 11 deletions(-)
diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c
index 07cf9c334be0..99ffc6d70565 100644
--- a/tools/perf/util/annotate-data.c
+++ b/tools/perf/util/annotate-data.c
@@ -1603,19 +1603,21 @@ static int find_data_type_die(struct data_loc_info *dloc, Dwarf_Die *type_die)
if (!die_find_variable_by_addr(&scopes[i], dloc->var_addr,
&var_die, &type_offset))
continue;
+ /* Found a variable, see if it's correct */
+ result = check_variable(dloc, &var_die, &mem_die, reg,
+ type_offset, is_fbreg);
} 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;
+ result = PERF_TMR_OK;
}
pr_debug_dtp("found \"%s\" (die: %#lx) in scope=%d/%d (die: %#lx) ",
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",
diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
index 9267af204c7d..b57cdc8860f0 100644
--- a/tools/perf/util/dwarf-aux.c
+++ b/tools/perf/util/dwarf-aux.c
@@ -1378,6 +1378,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 */
@@ -1390,7 +1392,6 @@ 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;
s64 offset = addr_offset - addr_type;
@@ -1403,16 +1404,16 @@ static bool match_var_offset(Dwarf_Die *die_mem, struct find_var_data *data,
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) {
+ if (is_pointer && dwarf_tag(&data->type) == DW_TAG_pointer_type) {
/* Get the target type of the pointer */
- if (die_get_real_type(&type_die, &type_die) == NULL)
+ if (die_get_real_type(&data->type, &data->type) == NULL)
return false;
}
- if (dwarf_aggregate_size(&type_die, &size) < 0)
+ if (dwarf_aggregate_size(&data->type, &size) < 0)
return false;
if ((u64)offset >= size)
@@ -1529,7 +1530,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 = {
@@ -1541,8 +1542,11 @@ 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;
}
diff --git a/tools/perf/util/dwarf-aux.h b/tools/perf/util/dwarf-aux.h
index cd481ec9c5a1..b3ee5df0b6be 100644
--- a/tools/perf/util/dwarf-aux.h
+++ b/tools/perf/util/dwarf-aux.h
@@ -163,7 +163,7 @@ 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' */
--
2.52.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v1 02/11] perf dwarf-aux: Add die_get_pointer_type to get pointer types
2026-01-27 2:04 [PATCH v1 00/11] perf tools: Improvements to data type profiler Zecheng Li
2026-01-27 2:04 ` [PATCH v1 01/11] perf dwarf-aux: Skip check_variable for die_find_variable_by_reg Zecheng Li
@ 2026-01-27 2:04 ` Zecheng Li
2026-02-11 2:17 ` Namhyung Kim
2026-01-27 2:04 ` [PATCH v1 03/11] perf dwarf-aux: Preserve typedefs in match_var_offset Zecheng Li
` (8 subsequent siblings)
10 siblings, 1 reply; 19+ messages in thread
From: Zecheng Li @ 2026-01-27 2:04 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 99ffc6d70565..8ff1972981b7 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 b57cdc8860f0..96cfcbd40c45 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 b3ee5df0b6be..8045281f219c 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.52.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v1 03/11] perf dwarf-aux: Preserve typedefs in match_var_offset
2026-01-27 2:04 [PATCH v1 00/11] perf tools: Improvements to data type profiler Zecheng Li
2026-01-27 2:04 ` [PATCH v1 01/11] perf dwarf-aux: Skip check_variable for die_find_variable_by_reg Zecheng Li
2026-01-27 2:04 ` [PATCH v1 02/11] perf dwarf-aux: Add die_get_pointer_type to get pointer types Zecheng Li
@ 2026-01-27 2:04 ` Zecheng Li
2026-02-11 2:23 ` Namhyung Kim
2026-01-27 2:04 ` [PATCH v1 04/11] perf annotate-data: Improve type comparison from different scopes Zecheng Li
` (7 subsequent siblings)
10 siblings, 1 reply; 19+ messages in thread
From: Zecheng Li @ 2026-01-27 2:04 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>
Since we are skipping the check_variable, we need to 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 | 21 ++++++++++++---------
1 file changed, 12 insertions(+), 9 deletions(-)
diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
index 96cfcbd40c45..ce816aefa95b 100644
--- a/tools/perf/util/dwarf-aux.c
+++ b/tools/perf/util/dwarf-aux.c
@@ -1420,26 +1420,29 @@ static bool match_var_offset(Dwarf_Die *die_mem, struct find_var_data *data,
s64 addr_offset, s64 addr_type, bool is_pointer)
{
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, &data->type) == NULL)
+ if (__die_get_real_type(die_mem, &data->type) == NULL)
return false;
- if (is_pointer && dwarf_tag(&data->type) == 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(&data->type, &data->type) == NULL)
+ if (__die_get_real_type(ptr_type, &data->type) == NULL)
return false;
}
+ 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;
--
2.52.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v1 04/11] perf annotate-data: Improve type comparison from different scopes
2026-01-27 2:04 [PATCH v1 00/11] perf tools: Improvements to data type profiler Zecheng Li
` (2 preceding siblings ...)
2026-01-27 2:04 ` [PATCH v1 03/11] perf dwarf-aux: Preserve typedefs in match_var_offset Zecheng Li
@ 2026-01-27 2:04 ` Zecheng Li
2026-02-11 2:25 ` Namhyung Kim
2026-01-27 2:04 ` [PATCH v1 05/11] perf dwarf-aux: Handle array types in die_get_member_type Zecheng Li
` (6 subsequent siblings)
10 siblings, 1 reply; 19+ messages in thread
From: Zecheng Li @ 2026-01-27 2:04 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 8ff1972981b7..8ea3e8b024e8 100644
--- a/tools/perf/util/annotate-data.c
+++ b/tools/perf/util/annotate-data.c
@@ -1634,7 +1634,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.52.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v1 05/11] perf dwarf-aux: Handle array types in die_get_member_type
2026-01-27 2:04 [PATCH v1 00/11] perf tools: Improvements to data type profiler Zecheng Li
` (3 preceding siblings ...)
2026-01-27 2:04 ` [PATCH v1 04/11] perf annotate-data: Improve type comparison from different scopes Zecheng Li
@ 2026-01-27 2:04 ` Zecheng Li
2026-01-27 2:04 ` [PATCH v1 06/11] perf annotate-data: Collect global variables without name Zecheng Li
` (5 subsequent siblings)
10 siblings, 0 replies; 19+ messages in thread
From: Zecheng Li @ 2026-01-27 2:04 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 ce816aefa95b..ee72b7a4a65d 100644
--- a/tools/perf/util/dwarf-aux.c
+++ b/tools/perf/util/dwarf-aux.c
@@ -2125,13 +2125,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.52.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v1 06/11] perf annotate-data: Collect global variables without name
2026-01-27 2:04 [PATCH v1 00/11] perf tools: Improvements to data type profiler Zecheng Li
` (4 preceding siblings ...)
2026-01-27 2:04 ` [PATCH v1 05/11] perf dwarf-aux: Handle array types in die_get_member_type Zecheng Li
@ 2026-01-27 2:04 ` Zecheng Li
2026-02-11 2:29 ` Namhyung Kim
2026-01-27 2:05 ` [PATCH v1 07/11] perf annotate-data: Handle global variable access with const register Zecheng Li
` (4 subsequent siblings)
10 siblings, 1 reply; 19+ messages in thread
From: Zecheng Li @ 2026-01-27 2:04 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 8ea3e8b024e8..970238bc81b7 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.52.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v1 07/11] perf annotate-data: Handle global variable access with const register
2026-01-27 2:04 [PATCH v1 00/11] perf tools: Improvements to data type profiler Zecheng Li
` (5 preceding siblings ...)
2026-01-27 2:04 ` [PATCH v1 06/11] perf annotate-data: Collect global variables without name Zecheng Li
@ 2026-01-27 2:05 ` Zecheng Li
2026-02-11 2:37 ` Namhyung Kim
2026-01-27 2:05 ` [PATCH v1 08/11] perf annotate-data: Add invalidate_reg_state() helper for x86 Zecheng Li
` (3 subsequent siblings)
10 siblings, 1 reply; 19+ messages in thread
From: Zecheng Li @ 2026-01-27 2:05 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. 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 | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c
index 970238bc81b7..177aa6634504 100644
--- a/tools/perf/util/annotate-data.c
+++ b/tools/perf/util/annotate-data.c
@@ -1230,6 +1230,10 @@ 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) {
+ 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.52.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v1 08/11] perf annotate-data: Add invalidate_reg_state() helper for x86
2026-01-27 2:04 [PATCH v1 00/11] perf tools: Improvements to data type profiler Zecheng Li
` (6 preceding siblings ...)
2026-01-27 2:05 ` [PATCH v1 07/11] perf annotate-data: Handle global variable access with const register Zecheng Li
@ 2026-01-27 2:05 ` Zecheng Li
2026-02-11 2:38 ` Namhyung Kim
2026-01-27 2:05 ` [PATCH v1 09/11] perf annotate-data: Invalidate caller-saved regs for all calls Zecheng Li
` (2 subsequent siblings)
10 siblings, 1 reply; 19+ messages in thread
From: Zecheng Li @ 2026-01-27 2:05 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/arch/x86/annotate/instructions.c | 29 ++++++++++++---------
1 file changed, 17 insertions(+), 12 deletions(-)
diff --git a/tools/perf/arch/x86/annotate/instructions.c b/tools/perf/arch/x86/annotate/instructions.c
index 803f9351a3fb..e033abb0667b 100644
--- a/tools/perf/arch/x86/annotate/instructions.c
+++ b/tools/perf/arch/x86/annotate/instructions.c
@@ -209,6 +209,13 @@ static int x86__annotate_init(struct arch *arch, 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)
@@ -240,7 +247,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) */
@@ -369,8 +376,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) {
@@ -438,8 +444,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;
}
@@ -501,7 +506,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;
}
@@ -529,7 +534,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;
}
@@ -565,7 +570,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;
@@ -580,7 +585,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;
}
@@ -633,7 +638,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;
}
@@ -684,7 +689,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 */
@@ -726,7 +731,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.52.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v1 09/11] perf annotate-data: Invalidate caller-saved regs for all calls
2026-01-27 2:04 [PATCH v1 00/11] perf tools: Improvements to data type profiler Zecheng Li
` (7 preceding siblings ...)
2026-01-27 2:05 ` [PATCH v1 08/11] perf annotate-data: Add invalidate_reg_state() helper for x86 Zecheng Li
@ 2026-01-27 2:05 ` Zecheng Li
2026-01-27 2:05 ` [PATCH v1 10/11] perf annotate-data: Use DWARF location ranges to preserve reg state Zecheng Li
2026-01-27 2:05 ` [PATCH v1 11/11] perf dwarf-aux: Collect all variable locations for insn tracking Zecheng Li
10 siblings, 0 replies; 19+ messages in thread
From: Zecheng Li @ 2026-01-27 2:05 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/arch/x86/annotate/instructions.c | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/tools/perf/arch/x86/annotate/instructions.c b/tools/perf/arch/x86/annotate/instructions.c
index e033abb0667b..aba356754520 100644
--- a/tools/perf/arch/x86/annotate/instructions.c
+++ b/tools/perf/arch/x86/annotate/instructions.c
@@ -234,24 +234,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.52.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v1 10/11] perf annotate-data: Use DWARF location ranges to preserve reg state
2026-01-27 2:04 [PATCH v1 00/11] perf tools: Improvements to data type profiler Zecheng Li
` (8 preceding siblings ...)
2026-01-27 2:05 ` [PATCH v1 09/11] perf annotate-data: Invalidate caller-saved regs for all calls Zecheng Li
@ 2026-01-27 2:05 ` Zecheng Li
2026-01-27 2:05 ` [PATCH v1 11/11] perf dwarf-aux: Collect all variable locations for insn tracking Zecheng Li
10 siblings, 0 replies; 19+ messages in thread
From: Zecheng Li @ 2026-01-27 2:05 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/arch/x86/annotate/instructions.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/arch/x86/annotate/instructions.c b/tools/perf/arch/x86/annotate/instructions.c
index aba356754520..e45041855e17 100644
--- a/tools/perf/arch/x86/annotate/instructions.c
+++ b/tools/perf/arch/x86/annotate/instructions.c
@@ -213,6 +213,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;
}
@@ -235,6 +237,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)
@@ -251,10 +254,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) */
@@ -284,6 +295,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;
@@ -349,6 +362,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;
@@ -463,6 +478,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;
}
@@ -549,6 +566,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 177aa6634504..b42b17ba0d10 100644
--- a/tools/perf/util/annotate-data.c
+++ b/tools/perf/util/annotate-data.c
@@ -841,6 +841,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
@@ -866,8 +878,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;
@@ -924,6 +942,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);
@@ -940,6 +959,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 869307c7f130..46bc770e150e 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 ee72b7a4a65d..a05d73d6e9e7 100644
--- a/tools/perf/util/dwarf-aux.c
+++ b/tools/perf/util/dwarf-aux.c
@@ -1639,7 +1639,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;
@@ -1679,6 +1679,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;
@@ -1741,6 +1743,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 8045281f219c..6d418acfff14 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.52.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v1 11/11] perf dwarf-aux: Collect all variable locations for insn tracking
2026-01-27 2:04 [PATCH v1 00/11] perf tools: Improvements to data type profiler Zecheng Li
` (9 preceding siblings ...)
2026-01-27 2:05 ` [PATCH v1 10/11] perf annotate-data: Use DWARF location ranges to preserve reg state Zecheng Li
@ 2026-01-27 2:05 ` Zecheng Li
10 siblings, 0 replies; 19+ messages in thread
From: Zecheng Li @ 2026-01-27 2:05 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 a05d73d6e9e7..3a028d4acb78 100644
--- a/tools/perf/util/dwarf-aux.c
+++ b/tools/perf/util/dwarf-aux.c
@@ -1643,6 +1643,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;
@@ -1650,41 +1651,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.52.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* Re: [PATCH v1 01/11] perf dwarf-aux: Skip check_variable for die_find_variable_by_reg
2026-01-27 2:04 ` [PATCH v1 01/11] perf dwarf-aux: Skip check_variable for die_find_variable_by_reg Zecheng Li
@ 2026-02-11 2:08 ` Namhyung Kim
0 siblings, 0 replies; 19+ messages in thread
From: Namhyung Kim @ 2026-02-11 2:08 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, Zecheng Li
Hello,
On Mon, Jan 26, 2026 at 09:04:54PM -0500, Zecheng Li wrote:
> From: Zecheng Li <zecheng@google.com>
>
> In die_find_variable_by_reg, match_var_offset 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.
In that case, I think you also skip it for die_find_variable_by_addr
as it calls match_var_offset() too.
>
> Return type from die_find_variable_by_reg via a new `type` field in
> find_var_data.
>
> Signed-off-by: Zecheng Li <zecheng@google.com>
> Signed-off-by: Zecheng Li <zli94@ncsu.edu>
> ---
> tools/perf/util/annotate-data.c | 8 +++++---
> tools/perf/util/dwarf-aux.c | 18 +++++++++++-------
> tools/perf/util/dwarf-aux.h | 2 +-
> 3 files changed, 17 insertions(+), 11 deletions(-)
>
> diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c
> index 07cf9c334be0..99ffc6d70565 100644
> --- a/tools/perf/util/annotate-data.c
> +++ b/tools/perf/util/annotate-data.c
> @@ -1603,19 +1603,21 @@ static int find_data_type_die(struct data_loc_info *dloc, Dwarf_Die *type_die)
> if (!die_find_variable_by_addr(&scopes[i], dloc->var_addr,
> &var_die, &type_offset))
> continue;
> + /* Found a variable, see if it's correct */
> + result = check_variable(dloc, &var_die, &mem_die, reg,
> + type_offset, is_fbreg);
> } 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;
> + result = PERF_TMR_OK;
> }
>
> pr_debug_dtp("found \"%s\" (die: %#lx) in scope=%d/%d (die: %#lx) ",
> 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) {
Then maybe we can just get rid of this check.
Thanks,
Namhyung
> if (reg == DWARF_REG_PC) {
> pr_debug_dtp("addr=%#"PRIx64" type_offset=%#x\n",
> diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
> index 9267af204c7d..b57cdc8860f0 100644
> --- a/tools/perf/util/dwarf-aux.c
> +++ b/tools/perf/util/dwarf-aux.c
> @@ -1378,6 +1378,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 */
> @@ -1390,7 +1392,6 @@ 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;
> s64 offset = addr_offset - addr_type;
>
> @@ -1403,16 +1404,16 @@ static bool match_var_offset(Dwarf_Die *die_mem, struct find_var_data *data,
> 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) {
> + if (is_pointer && dwarf_tag(&data->type) == DW_TAG_pointer_type) {
> /* Get the target type of the pointer */
> - if (die_get_real_type(&type_die, &type_die) == NULL)
> + if (die_get_real_type(&data->type, &data->type) == NULL)
> return false;
> }
>
> - if (dwarf_aggregate_size(&type_die, &size) < 0)
> + if (dwarf_aggregate_size(&data->type, &size) < 0)
> return false;
>
> if ((u64)offset >= size)
> @@ -1529,7 +1530,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 = {
> @@ -1541,8 +1542,11 @@ 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;
> }
>
> diff --git a/tools/perf/util/dwarf-aux.h b/tools/perf/util/dwarf-aux.h
> index cd481ec9c5a1..b3ee5df0b6be 100644
> --- a/tools/perf/util/dwarf-aux.h
> +++ b/tools/perf/util/dwarf-aux.h
> @@ -163,7 +163,7 @@ 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' */
> --
> 2.52.0
>
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v1 02/11] perf dwarf-aux: Add die_get_pointer_type to get pointer types
2026-01-27 2:04 ` [PATCH v1 02/11] perf dwarf-aux: Add die_get_pointer_type to get pointer types Zecheng Li
@ 2026-02-11 2:17 ` Namhyung Kim
0 siblings, 0 replies; 19+ messages in thread
From: Namhyung Kim @ 2026-02-11 2: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
On Mon, Jan 26, 2026 at 09:04:55PM -0500, Zecheng Li wrote:
> 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>
Reviewed-by: Namhyung Kim <namhyung@kernel.org>
Thanks,
Namhyung
> ---
> 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 99ffc6d70565..8ff1972981b7 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 b57cdc8860f0..96cfcbd40c45 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 b3ee5df0b6be..8045281f219c 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.52.0
>
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v1 03/11] perf dwarf-aux: Preserve typedefs in match_var_offset
2026-01-27 2:04 ` [PATCH v1 03/11] perf dwarf-aux: Preserve typedefs in match_var_offset Zecheng Li
@ 2026-02-11 2:23 ` Namhyung Kim
0 siblings, 0 replies; 19+ messages in thread
From: Namhyung Kim @ 2026-02-11 2:23 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, Zecheng Li
On Mon, Jan 26, 2026 at 09:04:56PM -0500, Zecheng Li wrote:
> From: Zecheng Li <zecheng@google.com>
>
> Since we are skipping the check_variable, we need to 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.
Can you move the patch 1 after this so that it doesn't get the temporary
regression? Maybe it doesn't matter much whether it sees a typedef or
an underlying type, but it'd be better to keep it consistent if it's not
too hard. :)
Thanks,
Namhyung
>
> Signed-off-by: Zecheng Li <zecheng@google.com>
> Signed-off-by: Zecheng Li <zli94@ncsu.edu>
> ---
> tools/perf/util/dwarf-aux.c | 21 ++++++++++++---------
> 1 file changed, 12 insertions(+), 9 deletions(-)
>
> diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
> index 96cfcbd40c45..ce816aefa95b 100644
> --- a/tools/perf/util/dwarf-aux.c
> +++ b/tools/perf/util/dwarf-aux.c
> @@ -1420,26 +1420,29 @@ static bool match_var_offset(Dwarf_Die *die_mem, struct find_var_data *data,
> s64 addr_offset, s64 addr_type, bool is_pointer)
> {
> 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, &data->type) == NULL)
> + if (__die_get_real_type(die_mem, &data->type) == NULL)
> return false;
>
> - if (is_pointer && dwarf_tag(&data->type) == 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(&data->type, &data->type) == NULL)
> + if (__die_get_real_type(ptr_type, &data->type) == NULL)
> return false;
> }
>
> + 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;
>
> --
> 2.52.0
>
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v1 04/11] perf annotate-data: Improve type comparison from different scopes
2026-01-27 2:04 ` [PATCH v1 04/11] perf annotate-data: Improve type comparison from different scopes Zecheng Li
@ 2026-02-11 2:25 ` Namhyung Kim
0 siblings, 0 replies; 19+ messages in thread
From: Namhyung Kim @ 2026-02-11 2:25 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, Zecheng Li
On Mon, Jan 26, 2026 at 09:04:57PM -0500, Zecheng Li wrote:
> 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>
Reviewed-by: Namhyung Kim <namhyung@kernel.org>
Thanks,
Namhyung
> ---
> 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 8ff1972981b7..8ea3e8b024e8 100644
> --- a/tools/perf/util/annotate-data.c
> +++ b/tools/perf/util/annotate-data.c
> @@ -1634,7 +1634,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.52.0
>
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v1 06/11] perf annotate-data: Collect global variables without name
2026-01-27 2:04 ` [PATCH v1 06/11] perf annotate-data: Collect global variables without name Zecheng Li
@ 2026-02-11 2:29 ` Namhyung Kim
0 siblings, 0 replies; 19+ messages in thread
From: Namhyung Kim @ 2026-02-11 2:29 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
On Mon, Jan 26, 2026 at 09:04:59PM -0500, Zecheng Li wrote:
> 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.
Do you have any example? It's not clear to me why global variables
don't have name.
Thanks,
Namhyung
>
> 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 8ea3e8b024e8..970238bc81b7 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.52.0
>
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v1 07/11] perf annotate-data: Handle global variable access with const register
2026-01-27 2:05 ` [PATCH v1 07/11] perf annotate-data: Handle global variable access with const register Zecheng Li
@ 2026-02-11 2:37 ` Namhyung Kim
0 siblings, 0 replies; 19+ messages in thread
From: Namhyung Kim @ 2026-02-11 2:37 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
On Mon, Jan 26, 2026 at 09:05:00PM -0500, Zecheng Li wrote:
> 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. We could also treat registers with integer types to the global
> variable path, but this requires more changes.
I'm thinking of how we can support user binaries with this code pattern.
For now, I think it's ok for the kernel, but at least we need to check
if dso__kernel(map__dso(dloc->ms->map)) is not zero (DSO_SPACE__USER).
Thanks,
Namhyung
>
> Signed-off-by: Zecheng Li <zli94@ncsu.edu>
> ---
> tools/perf/util/annotate-data.c | 4 ++++
> 1 file changed, 4 insertions(+)
>
> diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c
> index 970238bc81b7..177aa6634504 100644
> --- a/tools/perf/util/annotate-data.c
> +++ b/tools/perf/util/annotate-data.c
> @@ -1230,6 +1230,10 @@ 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) {
> + 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.52.0
>
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v1 08/11] perf annotate-data: Add invalidate_reg_state() helper for x86
2026-01-27 2:05 ` [PATCH v1 08/11] perf annotate-data: Add invalidate_reg_state() helper for x86 Zecheng Li
@ 2026-02-11 2:38 ` Namhyung Kim
0 siblings, 0 replies; 19+ messages in thread
From: Namhyung Kim @ 2026-02-11 2:38 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
On Mon, Jan 26, 2026 at 09:05:01PM -0500, Zecheng Li wrote:
> 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).
It doesn't apply cleanly from here. Please rebase onto the current
perf-tools-next. Maybe we want to wait for v7.0-rc1.
Thanks,
Namhyung
>
> Signed-off-by: Zecheng Li <zli94@ncsu.edu>
> ---
> tools/perf/arch/x86/annotate/instructions.c | 29 ++++++++++++---------
> 1 file changed, 17 insertions(+), 12 deletions(-)
>
> diff --git a/tools/perf/arch/x86/annotate/instructions.c b/tools/perf/arch/x86/annotate/instructions.c
> index 803f9351a3fb..e033abb0667b 100644
> --- a/tools/perf/arch/x86/annotate/instructions.c
> +++ b/tools/perf/arch/x86/annotate/instructions.c
> @@ -209,6 +209,13 @@ static int x86__annotate_init(struct arch *arch, 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)
> @@ -240,7 +247,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) */
> @@ -369,8 +376,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) {
> @@ -438,8 +444,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;
> }
>
> @@ -501,7 +506,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;
> }
>
> @@ -529,7 +534,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;
> }
>
> @@ -565,7 +570,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;
> @@ -580,7 +585,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;
> }
>
> @@ -633,7 +638,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;
> }
>
> @@ -684,7 +689,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 */
> @@ -726,7 +731,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.52.0
>
^ permalink raw reply [flat|nested] 19+ messages in thread
end of thread, other threads:[~2026-02-11 2:38 UTC | newest]
Thread overview: 19+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-27 2:04 [PATCH v1 00/11] perf tools: Improvements to data type profiler Zecheng Li
2026-01-27 2:04 ` [PATCH v1 01/11] perf dwarf-aux: Skip check_variable for die_find_variable_by_reg Zecheng Li
2026-02-11 2:08 ` Namhyung Kim
2026-01-27 2:04 ` [PATCH v1 02/11] perf dwarf-aux: Add die_get_pointer_type to get pointer types Zecheng Li
2026-02-11 2:17 ` Namhyung Kim
2026-01-27 2:04 ` [PATCH v1 03/11] perf dwarf-aux: Preserve typedefs in match_var_offset Zecheng Li
2026-02-11 2:23 ` Namhyung Kim
2026-01-27 2:04 ` [PATCH v1 04/11] perf annotate-data: Improve type comparison from different scopes Zecheng Li
2026-02-11 2:25 ` Namhyung Kim
2026-01-27 2:04 ` [PATCH v1 05/11] perf dwarf-aux: Handle array types in die_get_member_type Zecheng Li
2026-01-27 2:04 ` [PATCH v1 06/11] perf annotate-data: Collect global variables without name Zecheng Li
2026-02-11 2:29 ` Namhyung Kim
2026-01-27 2:05 ` [PATCH v1 07/11] perf annotate-data: Handle global variable access with const register Zecheng Li
2026-02-11 2:37 ` Namhyung Kim
2026-01-27 2:05 ` [PATCH v1 08/11] perf annotate-data: Add invalidate_reg_state() helper for x86 Zecheng Li
2026-02-11 2:38 ` Namhyung Kim
2026-01-27 2:05 ` [PATCH v1 09/11] perf annotate-data: Invalidate caller-saved regs for all calls Zecheng Li
2026-01-27 2:05 ` [PATCH v1 10/11] perf annotate-data: Use DWARF location ranges to preserve reg state Zecheng Li
2026-01-27 2:05 ` [PATCH v1 11/11] perf dwarf-aux: Collect all variable locations for insn tracking Zecheng Li
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox