[PATCH v1 09/11] perf annotate-data: Invalidate caller-saved regs for all calls

From: Zecheng Li

Date: Mon Jan 26 2026 - 21:10:57 EST


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@xxxxxxxx>
---
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