[PATCH] alpha: Add PTRACE_GETREGSET/PTRACE_SETREGSET support

Matt Turner posted 1 patch 3 days, 19 hours ago
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(-)
[PATCH] alpha: Add PTRACE_GETREGSET/PTRACE_SETREGSET support
Posted by Matt Turner 3 days, 19 hours ago
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@gmail.com>
---
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