[PATCH v4 4/7] alpha/perf: do not report user registers from the entry code

From: Matt Turner

Date: Mon Nov 02 2026 - 22:03:14 EST


perf_get_regs_user() returns task_pt_regs() whenever a sample of a user
task lands in the kernel. But exception entry runs with interrupts
enabled, so the sample can land before the entry code has saved the user
registers. Only the part PALcode pushed is valid then. An interrupt
taken on the first instruction of entUna, say, pushes its own frame on
top of PALcode's, which overlaps the rest of the user frame, so r7 and r8
read as the interrupt's r23 and r24.

Report no user registers while the interrupted PC is in the entry code.
Such a sample also gets no user stack dump. The range is all of the entry
code from entInt to the end of the save in entSys, so it also drops the
registers of samples that land after they were saved there.

An interrupt that nests on one taken in the entry code still reads the
incomplete frame, as its own PC has left the entry code.

Fixes: 9280e99d87dd ("alpha: add HAVE_PERF_REGS and HAVE_PERF_USER_STACK_DUMP support")
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Matt Turner <mattst88@xxxxxxxxx>
---
arch/alpha/include/asm/ptrace.h | 12 ++++++++++++
arch/alpha/kernel/entry.S | 9 +++++++++
arch/alpha/kernel/perf_regs.c | 13 +++++++++++++
3 files changed, 34 insertions(+)

diff --git a/arch/alpha/include/asm/ptrace.h b/arch/alpha/include/asm/ptrace.h
index 45d1229344b6..1400ba3e7d43 100644
--- a/arch/alpha/include/asm/ptrace.h
+++ b/arch/alpha/include/asm/ptrace.h
@@ -14,6 +14,18 @@
#define task_pt_regs(task) \
((struct pt_regs *) (task_stack_page(task) + 2*PAGE_SIZE) - 1)

+/*
+ * Whether pc is in entry code that may not have completed the frame at
+ * task_pt_regs(). See entry.S.
+ */
+static inline bool in_entry_save_code(unsigned long pc)
+{
+ extern char __entry_save_start[], __entry_save_end[];
+
+ return pc >= (unsigned long)__entry_save_start &&
+ pc < (unsigned long)__entry_save_end;
+}
+
#define current_pt_regs() \
((struct pt_regs *) ((char *)current_thread_info() + 2*PAGE_SIZE) - 1)

diff --git a/arch/alpha/kernel/entry.S b/arch/alpha/kernel/entry.S
index 790389a15c3c..b8d7804e5aa3 100644
--- a/arch/alpha/kernel/entry.S
+++ b/arch/alpha/kernel/entry.S
@@ -186,8 +186,14 @@

/*
* Non-syscall kernel entry points.
+ *
+ * Between __entry_save_start and __entry_save_end the frame at task_pt_regs()
+ * may not be complete. perf uses these to avoid reading it.
*/

+ .globl __entry_save_start
+__entry_save_start:
+
CFI_START_OSF_FRAME entInt
SAVE_ALL
lda $8, 0x3fff
@@ -479,9 +485,12 @@ entSys:
lda $8, 0x3fff
bic $sp, $8, $8

+ /* PAL_callsys does not save these, unlike the other entries. */
stq $16, SP_OFF+24($sp)
stq $17, SP_OFF+32($sp)
stq $18, SP_OFF+40($sp)
+ .globl __entry_save_end
+__entry_save_end:

mov $0, $1
lda $16, 7
diff --git a/arch/alpha/kernel/perf_regs.c b/arch/alpha/kernel/perf_regs.c
index 33ea2e831ba0..894683851b30 100644
--- a/arch/alpha/kernel/perf_regs.c
+++ b/arch/alpha/kernel/perf_regs.c
@@ -59,6 +59,19 @@ u64 perf_reg_abi(struct task_struct *task)
void perf_get_regs_user(struct perf_regs *regs_user,
struct pt_regs *regs)
{
+ /*
+ * Exception entry runs with interrupts enabled, so a sample can land
+ * before the entry code has saved the user registers. PALcode has
+ * pushed only its part of the frame, and the rest may hold an
+ * interrupt's frame. An interrupt that nests on top of one taken here
+ * is not caught, as its PC has left the entry code.
+ */
+ if (in_entry_save_code(regs->pc)) {
+ regs_user->regs = NULL;
+ regs_user->abi = PERF_SAMPLE_REGS_ABI_NONE;
+ return;
+ }
+
regs_user->regs = task_pt_regs(current);
regs_user->abi = perf_reg_abi(current);
}

--
2.55.0