[PATCH] alpha: Add PTRACE_GETREGSET/PTRACE_SETREGSET support
From: Matt Turner
Date: Fri Apr 03 2026 - 11:07:48 EST
Enable HAVE_ARCH_TRACEHOOK and implement task_user_regset_view() to
provide regset-based register access on Alpha. This adds support for
PTRACE_GETREGSET and PTRACE_SETREGSET, which are handled by the
generic ptrace_request() path.
Two regsets are defined:
- REGSET_GENERAL (NT_PRSTATUS): 33 registers (32 GPRs + unique),
matching the existing elf_gregset_t layout used by dump_elf_thread()
- REGSET_FPU (NT_PRFPREG): 32 floating-point registers from
thread_info->fp[]
Also implement the full set of syscall accessor functions in
asm/syscall.h and user_stack_pointer() in asm/ptrace.h, which are
required by the generic PTRACE_GET_SYSCALL_INFO code that
HAVE_ARCH_TRACEHOOK enables.
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Matt Turner <mattst88@xxxxxxxxx>
---
This is an implementation of PTRACE_{G,S}ETREGSET using the standard
infrastructure.
arch/alpha/Kconfig | 1 +
arch/alpha/include/asm/ptrace.h | 12 +++
arch/alpha/include/asm/syscall.h | 67 ++++++++++++++-
arch/alpha/kernel/ptrace.c | 137 +++++++++++++++++++++++++++++++
4 files changed, 215 insertions(+), 2 deletions(-)
diff --git ./arch/alpha/Kconfig ./arch/alpha/Kconfig
index 6c7dbf0adad6..3e61dfaa2ba9 100644
--- ./arch/alpha/Kconfig
+++ ./arch/alpha/Kconfig
@@ -14,6 +14,7 @@ config ALPHA
select FORCE_PCI
select PCI_DOMAINS if PCI
select PCI_SYSCALL if PCI
+ select HAVE_ARCH_TRACEHOOK
select HAVE_ASM_MODVERSIONS
select HAVE_PAGE_SIZE_8KB
select HAVE_PCSPKR_PLATFORM
diff --git ./arch/alpha/include/asm/ptrace.h ./arch/alpha/include/asm/ptrace.h
index 3557ce64ed21..af97a8d3ed5b 100644
--- ./arch/alpha/include/asm/ptrace.h
+++ ./arch/alpha/include/asm/ptrace.h
@@ -4,6 +4,7 @@
#include <uapi/asm/ptrace.h>
+#include <asm/thread_info.h>
#define arch_has_single_step() (1)
#define user_mode(regs) (((regs)->ps & 8) != 0)
@@ -24,4 +25,15 @@ static inline unsigned long regs_return_value(struct pt_regs *regs)
return regs->r0;
}
+/*
+ * The user stack pointer is not stored in pt_regs. It lives in the
+ * PCB, which sits at the base of the kernel stack (i.e. in thread_info).
+ */
+static inline unsigned long user_stack_pointer(struct pt_regs *regs)
+{
+ struct thread_info *ti =
+ (struct thread_info *)((char *)(regs + 1) - 2 * PAGE_SIZE);
+ return ti->pcb.usp;
+}
+
#endif
diff --git ./arch/alpha/include/asm/syscall.h ./arch/alpha/include/asm/syscall.h
index f21babaeed85..dab35748ef74 100644
--- ./arch/alpha/include/asm/syscall.h
+++ ./arch/alpha/include/asm/syscall.h
@@ -3,10 +3,31 @@
#define _ASM_ALPHA_SYSCALL_H
#include <uapi/linux/audit.h>
+#include <linux/sched.h>
+#include <asm/ptrace.h>
-static inline int syscall_get_arch(struct task_struct *task)
+static inline int syscall_get_nr(struct task_struct *task,
+ struct pt_regs *regs)
{
- return AUDIT_ARCH_ALPHA;
+ return regs->r0;
+}
+
+static inline void syscall_set_nr(struct task_struct *task,
+ struct pt_regs *regs, int nr)
+{
+ regs->r0 = nr;
+}
+
+static inline void syscall_rollback(struct task_struct *task,
+ struct pt_regs *regs)
+{
+ /* Alpha does not save the original syscall number separately. */
+}
+
+static inline long syscall_get_error(struct task_struct *task,
+ struct pt_regs *regs)
+{
+ return regs->r19 ? regs->r0 : 0;
}
static inline long syscall_get_return_value(struct task_struct *task,
@@ -15,4 +36,46 @@ static inline long syscall_get_return_value(struct task_struct *task,
return regs->r0;
}
+static inline void syscall_set_return_value(struct task_struct *task,
+ struct pt_regs *regs,
+ int error, long val)
+{
+ if (error) {
+ regs->r0 = error;
+ regs->r19 = 1; /* a3: signal error */
+ } else {
+ regs->r0 = val;
+ regs->r19 = 0; /* a3: no error */
+ }
+}
+
+static inline void syscall_get_arguments(struct task_struct *task,
+ struct pt_regs *regs,
+ unsigned long *args)
+{
+ args[0] = regs->r16; /* a0 */
+ args[1] = regs->r17; /* a1 */
+ args[2] = regs->r18; /* a2 */
+ args[3] = regs->r19; /* a3 */
+ args[4] = regs->r20; /* a4 */
+ args[5] = regs->r21; /* a5 */
+}
+
+static inline void syscall_set_arguments(struct task_struct *task,
+ struct pt_regs *regs,
+ const unsigned long *args)
+{
+ regs->r16 = args[0]; /* a0 */
+ regs->r17 = args[1]; /* a1 */
+ regs->r18 = args[2]; /* a2 */
+ regs->r19 = args[3]; /* a3 */
+ regs->r20 = args[4]; /* a4 */
+ regs->r21 = args[5]; /* a5 */
+}
+
+static inline int syscall_get_arch(struct task_struct *task)
+{
+ return AUDIT_ARCH_ALPHA;
+}
+
#endif /* _ASM_ALPHA_SYSCALL_H */
diff --git ./arch/alpha/kernel/ptrace.c ./arch/alpha/kernel/ptrace.c
index fde4c68e7a0b..f1a10c4112e7 100644
--- ./arch/alpha/kernel/ptrace.c
+++ ./arch/alpha/kernel/ptrace.c
@@ -12,10 +12,12 @@
#include <linux/smp.h>
#include <linux/errno.h>
#include <linux/ptrace.h>
+#include <linux/regset.h>
#include <linux/user.h>
#include <linux/security.h>
#include <linux/signal.h>
#include <linux/audit.h>
+#include <linux/elf.h>
#include <linux/uaccess.h>
#include <asm/fpu.h>
@@ -274,6 +276,141 @@ void ptrace_disable(struct task_struct *child)
user_disable_single_step(child);
}
+/*
+ * Get the general registers from a task.
+ * Follows the elf_gregset_t layout: 32 GPRs + unique (replacing PS).
+ */
+static int genregs_get(struct task_struct *target,
+ const struct user_regset *regset,
+ struct membuf to)
+{
+ elf_gregset_t gregs;
+
+ dump_elf_thread(gregs, task_pt_regs(target),
+ task_thread_info(target));
+ return membuf_write(&to, &gregs, sizeof(gregs));
+}
+
+/*
+ * Set the general registers for a task.
+ */
+static int genregs_set(struct task_struct *target,
+ const struct user_regset *regset,
+ unsigned int pos, unsigned int count,
+ const void *kbuf, const void __user *ubuf)
+{
+ elf_gregset_t gregs;
+ struct pt_regs *regs = task_pt_regs(target);
+ struct switch_stack *sw = ((struct switch_stack *)regs) - 1;
+ struct thread_info *ti = task_thread_info(target);
+ int ret;
+
+ /* Start with the current register values. */
+ dump_elf_thread(gregs, regs, ti);
+
+ ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf,
+ &gregs, 0, sizeof(gregs));
+ if (ret)
+ return ret;
+
+ /* Apply the register values back. */
+ regs->r0 = gregs[0];
+ regs->r1 = gregs[1];
+ regs->r2 = gregs[2];
+ regs->r3 = gregs[3];
+ regs->r4 = gregs[4];
+ regs->r5 = gregs[5];
+ regs->r6 = gregs[6];
+ regs->r7 = gregs[7];
+ regs->r8 = gregs[8];
+ sw->r9 = gregs[9];
+ sw->r10 = gregs[10];
+ sw->r11 = gregs[11];
+ sw->r12 = gregs[12];
+ sw->r13 = gregs[13];
+ sw->r14 = gregs[14];
+ sw->r15 = gregs[15];
+ regs->r16 = gregs[16];
+ regs->r17 = gregs[17];
+ regs->r18 = gregs[18];
+ regs->r19 = gregs[19];
+ regs->r20 = gregs[20];
+ regs->r21 = gregs[21];
+ regs->r22 = gregs[22];
+ regs->r23 = gregs[23];
+ regs->r24 = gregs[24];
+ regs->r25 = gregs[25];
+ regs->r26 = gregs[26];
+ regs->r27 = gregs[27];
+ regs->r28 = gregs[28];
+ regs->gp = gregs[29];
+ ti->pcb.usp = gregs[30];
+ regs->pc = gregs[31];
+ ti->pcb.unique = gregs[32];
+
+ return 0;
+}
+
+/*
+ * Get the floating-point registers from a task.
+ */
+static int fpregs_get(struct task_struct *target,
+ const struct user_regset *regset,
+ struct membuf to)
+{
+ return membuf_write(&to, task_thread_info(target)->fp,
+ ELF_NFPREG * sizeof(elf_fpreg_t));
+}
+
+/*
+ * Set the floating-point registers for a task.
+ */
+static int fpregs_set(struct task_struct *target,
+ const struct user_regset *regset,
+ unsigned int pos, unsigned int count,
+ const void *kbuf, const void __user *ubuf)
+{
+ return user_regset_copyin(&pos, &count, &kbuf, &ubuf,
+ task_thread_info(target)->fp,
+ 0, ELF_NFPREG * sizeof(elf_fpreg_t));
+}
+
+enum alpha_regset {
+ REGSET_GENERAL,
+ REGSET_FPU,
+};
+
+static const struct user_regset alpha_regsets[] = {
+ [REGSET_GENERAL] = {
+ USER_REGSET_NOTE_TYPE(PRSTATUS),
+ .n = ELF_NGREG,
+ .size = sizeof(elf_greg_t),
+ .align = sizeof(elf_greg_t),
+ .regset_get = genregs_get,
+ .set = genregs_set,
+ },
+ [REGSET_FPU] = {
+ USER_REGSET_NOTE_TYPE(PRFPREG),
+ .n = ELF_NFPREG,
+ .size = sizeof(elf_fpreg_t),
+ .align = sizeof(elf_fpreg_t),
+ .regset_get = fpregs_get,
+ .set = fpregs_set,
+ },
+};
+
+static const struct user_regset_view user_alpha_view = {
+ .name = "alpha",
+ .e_machine = ELF_ARCH,
+ .regsets = alpha_regsets,
+ .n = ARRAY_SIZE(alpha_regsets),
+};
+
+const struct user_regset_view *task_user_regset_view(struct task_struct *task)
+{
+ return &user_alpha_view;
+}
+
long arch_ptrace(struct task_struct *child, long request,
unsigned long addr, unsigned long data)
{
--
2.52.0