[PATCH v7 09/15] perf annotate-x86: Delete stale stack state on store of untracked register

From: Tengda Wu

Date: Tue Oct 13 2026 - 12:16:11 EST


When a register without a tracked type is stored to a stack slot that
previously held a valid type, the code currently performs an early return.
This leaves the old type information intact in the stack state.

As a result, a stale type bug occurs:
1. A tracked register is stored into a stack slot.
2. The register becomes untracked/invalid (e.g., across a function call).
3. The now-untracked register is stored into the same stack slot.
4. Subsequent loads from that slot erroneously pick up the old stale type
because the stack entry was never invalidated.

Fix this by introducing delete_stack_state() to search and free a stack
entry for a given offset. When storing an untracked or invalid source
register into a stack location, call delete_stack_state() to delete the
stale entry before returning.

Reviewed-by: Ian Rogers <irogers@xxxxxxxxxx>
Signed-off-by: Tengda Wu <wutengda@xxxxxxxxxxxxxxx>
---
tools/perf/util/annotate-arch/annotate-x86.c | 17 +++++++++++++----
tools/perf/util/annotate-data.c | 10 ++++++++++
tools/perf/util/annotate-data.h | 1 +
3 files changed, 24 insertions(+), 4 deletions(-)

diff --git a/tools/perf/util/annotate-arch/annotate-x86.c b/tools/perf/util/annotate-arch/annotate-x86.c
index 21729e153fa0..3a6ea7e978e0 100644
--- a/tools/perf/util/annotate-arch/annotate-x86.c
+++ b/tools/perf/util/annotate-arch/annotate-x86.c
@@ -753,15 +753,24 @@ static void update_insn_state_x86(struct type_state *state,
}
/* Case 3. register to memory transfers */
if (!src->mem_ref && dst->mem_ref) {
- if (!has_reg_type(state, src->reg1) ||
- !state->regs[src->reg1].ok)
- return;
-
/* Check stack variables with offset */
if (dst->reg1 == fbreg || dst->reg1 == state->stack_reg) {
struct type_state_stack *stack;
int offset = dst->offset - fboff;

+ if (!has_reg_type(state, src->reg1) ||
+ !state->regs[src->reg1].ok) {
+ stack = find_stack_state(state, offset);
+ /*
+ * Preserve compound states when only a member
+ * is overwritten.
+ */
+ if (stack && !stack->compound)
+ delete_stack_state(state, offset);
+
+ return;
+ }
+
tsr = &state->regs[src->reg1];

stack = find_stack_state(state, offset);
diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c
index 9ed0780d6244..1d47db3a9dd9 100644
--- a/tools/perf/util/annotate-data.c
+++ b/tools/perf/util/annotate-data.c
@@ -761,6 +761,16 @@ struct type_state_stack *findnew_stack_state(struct type_state *state,
return stack;
}

+void delete_stack_state(struct type_state *state, int offset)
+{
+ struct type_state_stack *stack = find_stack_state(state, offset);
+
+ if (stack) {
+ list_del(&stack->list);
+ free(stack);
+ }
+}
+
void invalidate_reg_state(struct type_state_reg *reg)
{
reg->kind = TSR_KIND_INVALID;
diff --git a/tools/perf/util/annotate-data.h b/tools/perf/util/annotate-data.h
index dccd56759cea..425d9ddfad41 100644
--- a/tools/perf/util/annotate-data.h
+++ b/tools/perf/util/annotate-data.h
@@ -268,6 +268,7 @@ void set_stack_state(struct type_state_stack *stack, int offset, u8 kind,
Dwarf_Die *type_die, int ptr_offset, u64 imm_value);
struct type_state_stack *find_stack_state(struct type_state *state,
int offset);
+void delete_stack_state(struct type_state *state, int offset);
void invalidate_reg_state(struct type_state_reg *reg);
bool get_global_var_type(Dwarf_Die *cu_die, struct data_loc_info *dloc,
u64 ip, u64 var_addr, int *var_offset,
--
2.34.1