summaryrefslogtreecommitdiff
path: root/arch/alpha/kernel
diff options
context:
space:
mode:
authorMagnus Lindholm <linmag7@gmail.com>2026-07-06 18:56:42 +0200
committerMagnus Lindholm <linmag7@gmail.com>2026-07-19 13:15:40 +0200
commit8a2857bc2b3039eef6c15900b00424417a2293e8 (patch)
treedd2db09af7d96e1e5ea07b38bfe863f1ed6e0efc /arch/alpha/kernel
parent24d68db713d63dfe3660c56b50e887784844baea (diff)
alpha: enable regset-based ptrace and core dumps
Add a user_regset_view for Alpha and switch ELF core dumping to CORE_DUMP_USE_REGSET. General-purpose registers are exported in ELF gregs layout, including callee-saved registers and a correct user stack pointer. The user stack pointer is not preserved in pt_regs on Alpha, so expose it from the PCB, or via rdusp() for the current task, when building the ELF register image. This makes the user stack pointer consistent for core dumps, ptrace regsets, and PTRACE_GET_SYSCALL_INFO. Implement regset get/set callbacks for both NT_PRSTATUS and NT_PRFPREG. The callbacks translate between Alpha's pt_regs/thread state and the ELF-visible register layouts, while the common ptrace regset code handles PTRACE_GETREGSET and PTRACE_SETREGSET iovec semantics. This avoids duplicating subtle short-buffer and oversized-buffer behavior in arch_ptrace(). With these changes Alpha satisfies the requirements for HAVE_ARCH_TRACEHOOK and selects it, enabling generic tracehook and ptrace syscall-info code paths without changing the existing syscall entry ABI. Reviewed-by: Matt Turner <mattst88@gmail.com> Tested-by: Matt Turner <mattst88@gmail.com> Signed-off-by: Magnus Lindholm <linmag7@gmail.com> Link: https://lore.kernel.org/r/20260706170019.2941459-2-linmag7@gmail.com Signed-off-by: Magnus Lindholm <linmag7@gmail.com>
Diffstat (limited to 'arch/alpha/kernel')
-rw-r--r--arch/alpha/kernel/asm-offsets.c1
-rw-r--r--arch/alpha/kernel/entry.S15
-rw-r--r--arch/alpha/kernel/ptrace.c320
-rw-r--r--arch/alpha/kernel/traps.c8
4 files changed, 289 insertions, 55 deletions
diff --git a/arch/alpha/kernel/asm-offsets.c b/arch/alpha/kernel/asm-offsets.c
index 1ebb05890499..1d3bfca319ae 100644
--- a/arch/alpha/kernel/asm-offsets.c
+++ b/arch/alpha/kernel/asm-offsets.c
@@ -29,4 +29,5 @@ static void __used foo(void)
DEFINE(HAE_CACHE, offsetof(struct alpha_machine_vector, hae_cache));
DEFINE(HAE_REG, offsetof(struct alpha_machine_vector, hae_register));
+ DEFINE(PT_REGS_USP, offsetof(struct pt_regs, usp));
}
diff --git a/arch/alpha/kernel/entry.S b/arch/alpha/kernel/entry.S
index fcfd06529b12..449092a31eef 100644
--- a/arch/alpha/kernel/entry.S
+++ b/arch/alpha/kernel/entry.S
@@ -520,6 +520,12 @@ entSys:
ldq $1, 0($sp) /* syscall nr from saved r0 */
stq $1, 8($sp) /* regs->r1 = shadow syscall nr */
stq $1, 16($sp) /* regs->r2 = restart syscall nr */
+ /* Syscalls always enter from user mode: snapshot USP into pt_regs->usp */
+ mov $0, $8
+ call_pal PAL_rdusp
+ stq $0, PT_REGS_USP($sp)
+ mov $8, $0
+
lda $8, 0x3fff
bic $sp, $8, $8
@@ -535,15 +541,10 @@ entSys:
.cfi_rel_offset $16, SP_OFF+24
.cfi_rel_offset $17, SP_OFF+32
.cfi_rel_offset $18, SP_OFF+40
-#ifdef CONFIG_AUDITSYSCALL
- lda $6, _TIF_SYSCALL_TRACE | _TIF_SYSCALL_AUDIT | _TIF_SECCOMP
- and $3, $6, $3
- bne $3, strace
-#else
- lda $6, _TIF_SYSCALL_TRACE | _TIF_SECCOMP
+ lda $6, _TIF_SYSCALL_WORK
and $3, $6, $3
bne $3, strace
-#endif
+
beq $4, 1f
ldq $27, 0($5)
1: ldq $0, 8($sp) /* syscall nr shadow (regs->r1) */
diff --git a/arch/alpha/kernel/ptrace.c b/arch/alpha/kernel/ptrace.c
index 0687760ea466..69eb337347df 100644
--- a/arch/alpha/kernel/ptrace.c
+++ b/arch/alpha/kernel/ptrace.c
@@ -24,10 +24,15 @@
#include "proto.h"
#include <linux/uio.h>
+#include <linux/regset.h>
#define DEBUG DBG_MEM
#undef DEBUG
+#ifndef NT_FPREGSET
+#define NT_FPREGSET NT_PRFPREG
+#endif
+
#ifdef DEBUG
enum {
DBG_MEM = (1<<0),
@@ -143,19 +148,163 @@ get_reg(struct task_struct * task, unsigned long regno)
return *get_reg_addr(task, regno);
}
+static void alpha_elf_fpregs_get(struct task_struct *target,
+ elf_fpreg_t *fpregs) /* points to ELF_NFPREG entries */
+{
+ memcpy(fpregs, task_thread_info(target)->fp, sizeof(elf_fpregset_t));
+}
+
+static void alpha_elf_fpregs_set(struct task_struct *target,
+ const elf_fpreg_t *fpregs,
+ size_t nwords)
+{
+ size_t n = min_t(size_t, nwords, ELF_NFPREG);
+
+ memcpy(task_thread_info(target)->fp, fpregs, n * sizeof(elf_fpreg_t));
+}
+
+static void alpha_elf_gregs_set(struct task_struct *child,
+ const elf_greg_t *src,
+ size_t nwords)
+{
+ struct pt_regs *pt = task_pt_regs(child);
+ struct thread_info *ti = task_thread_info(child);
+ struct switch_stack *sw = ((struct switch_stack *)pt) - 1;
+
+ /* GPRs r0..r8 live in pt_regs */
+ if (nwords > 0)
+ pt->r0 = src[0];
+ if (nwords > 1)
+ pt->r1 = src[1];
+ if (nwords > 2)
+ pt->r2 = src[2];
+ if (nwords > 3)
+ pt->r3 = src[3];
+ if (nwords > 4)
+ pt->r4 = src[4];
+ if (nwords > 5)
+ pt->r5 = src[5];
+ if (nwords > 6)
+ pt->r6 = src[6];
+ if (nwords > 7)
+ pt->r7 = src[7];
+ if (nwords > 8)
+ pt->r8 = src[8];
+
+ /* r9..r15 live in switch_stack */
+ if (nwords > 9)
+ sw->r9 = src[9];
+ if (nwords > 10)
+ sw->r10 = src[10];
+ if (nwords > 11)
+ sw->r11 = src[11];
+ if (nwords > 12)
+ sw->r12 = src[12];
+ if (nwords > 13)
+ sw->r13 = src[13];
+ if (nwords > 14)
+ sw->r14 = src[14];
+ if (nwords > 15)
+ sw->r15 = src[15];
+
+ /* r16..r28 live in pt_regs */
+ if (nwords > 16)
+ pt->r16 = src[16];
+ if (nwords > 17)
+ pt->r17 = src[17];
+ if (nwords > 18)
+ pt->r18 = src[18];
+ if (nwords > 19)
+ pt->r19 = src[19];
+ if (nwords > 20)
+ pt->r20 = src[20];
+ if (nwords > 21)
+ pt->r21 = src[21];
+ if (nwords > 22)
+ pt->r22 = src[22];
+ if (nwords > 23)
+ pt->r23 = src[23];
+ if (nwords > 24)
+ pt->r24 = src[24];
+ if (nwords > 25)
+ pt->r25 = src[25];
+ if (nwords > 26)
+ pt->r26 = src[26];
+ if (nwords > 27)
+ pt->r27 = src[27];
+ if (nwords > 28)
+ pt->r28 = src[28];
+
+ /* gp, usp, pc, unique */
+ if (nwords > 29)
+ pt->gp = src[29];
+
+ if (nwords > 30) {
+ ti->pcb.usp = src[30];
+ /*
+ * If someone ever does this to current (rare), keep the
+ * hardware usp consistent.
+ */
+ if (child == current)
+ wrusp(src[30]);
+ }
+
+ if (nwords > 31)
+ pt->pc = src[31];
+
+ if (nwords > 32)
+ ti->pcb.unique = src[32];
+
+/*
+ * PTRACE_SETREGSET can be used at a syscall-entry stop to skip the
+ * syscall by setting the syscall number to -1. The seccomp/ptrace
+ * selftests use this to synthesize errno returns.
+ *
+ * Alpha uses r19/a3 as the error flag, so a skipped syscall with a
+ * small positive r0 and a clear r19 must be normalized to an error
+ * return.
+ */
+ if (pt->r1 == (unsigned long)-1 &&
+ pt->r19 == 0 &&
+ pt->r0 > 0 &&
+ pt->r0 < MAX_ERRNO)
+ pt->r19 = 1;
+}
+
+
/*
* Write contents of register REGNO in task TASK.
*/
static int
put_reg(struct task_struct *task, unsigned long regno, unsigned long data)
{
+ struct pt_regs *regs = task_pt_regs(task);
+
if (regno == 63) {
task_thread_info(task)->ieee_state
= ((task_thread_info(task)->ieee_state & ~IEEE_SW_MASK)
| (data & IEEE_SW_MASK));
data = (data & FPCR_DYN_MASK) | ieee_swcr_to_fpcr(data);
}
+
*get_reg_addr(task, regno) = data;
+
+ /*
+ * Alpha historically exposes r0/v0 as the syscall number at a
+ * syscall-entry stop. The generic-entry conversion keeps the
+ * mutable syscall number in regs->r1, so old ptrace users such
+ * as strace that skip a syscall by poking r0 to -1 must also
+ * update the internal shadow syscall number.
+ *
+ * Do not mirror other r0 writes. strace later pokes r0 to the
+ * injected return value, e.g. 42, while r1 must remain -1.
+ */
+
+ if (regno == 0 && data == (unsigned long)-1) {
+ regs->r1 = data;
+ regs->r19 = 0;
+ }
+
return 0;
}
@@ -315,54 +464,6 @@ long arch_ptrace(struct task_struct *child, long request,
DBG(DBG_MEM, ("poke $%lu<-%#lx\n", addr, data));
ret = put_reg(child, addr, data);
break;
- case PTRACE_GETREGSET:
- case PTRACE_SETREGSET: {
- struct iovec __user *uiov = (struct iovec __user *)data;
- struct iovec iov;
- struct pt_regs *regs;
- size_t len;
-
- /* Only support NT_PRSTATUS (general registers) for now. */
- if (addr != NT_PRSTATUS) {
- ret = -EIO;
- break;
- }
-
- if (copy_from_user(&iov, uiov, sizeof(iov))) {
- ret = -EFAULT;
- break;
- }
-
- regs = task_pt_regs(child);
- len = min_t(size_t, iov.iov_len, sizeof(*regs));
-
- if (request == PTRACE_GETREGSET) {
- if (copy_to_user(iov.iov_base, regs, len)) {
- ret = -EFAULT;
- break;
- }
- } else {
- /*
- * Allow writing back regs. This is needed by the TRACE_syscall
- * tests (they change PC/syscall nr/retval).
- */
- if (copy_from_user(regs, iov.iov_base, len)) {
- ret = -EFAULT;
- break;
- }
- }
-
- /* Per API, update iov_len with amount transferred. */
- iov.iov_len = len;
- if (copy_to_user(uiov, &iov, sizeof(iov))) {
- ret = -EFAULT;
- break;
- }
-
- ret = 0;
- break;
- }
-
default:
ret = ptrace_request(child, request, addr, data);
break;
@@ -410,3 +511,126 @@ syscall_trace_leave(void)
if (test_thread_flag(TIF_SYSCALL_TRACE))
ptrace_report_syscall_exit(current_pt_regs(), 0);
}
+
+/*
+ * Minimal regset support for Alpha.
+ *
+ * Alpha-specific notes:
+ * - Do NOT use ELF_CORE_COPY_REGS(): it uses current_thread_info(),
+ * which is wrong for non-current tasks.
+ * - dump_elf_task() returns 1 unconditionally in this tree, while
+ * regset_get should return 0 on success. So call dump_elf_thread()
+ * directly and return membuf_write()'s result.
+ */
+
+static int alpha_regset_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;
+ unsigned int nwords;
+
+ if (pos + count > sizeof(gregs))
+ return -EIO;
+
+ /*
+ * Preserve registers outside the written range.
+ */
+ dump_elf_thread(gregs, task_pt_regs(target),
+ task_thread_info(target));
+
+ if (user_regset_copyin(&pos, &count, &kbuf, &ubuf,
+ gregs, 0, sizeof(gregs)))
+ return -EFAULT;
+
+ nwords = sizeof(gregs) / sizeof(elf_greg_t);
+ alpha_elf_gregs_set(target, gregs, nwords);
+
+ return 0;
+}
+
+static int alpha_fpregset_set(struct task_struct *target,
+ const struct user_regset *regset,
+ unsigned int pos, unsigned int count,
+ const void *kbuf,
+ const void __user *ubuf)
+{
+ elf_fpregset_t fpregs;
+ unsigned int nwords;
+
+ if (pos + count > sizeof(fpregs))
+ return -EIO;
+
+ alpha_elf_fpregs_get(target, fpregs);
+
+ if (user_regset_copyin(&pos, &count, &kbuf, &ubuf,
+ fpregs, 0, sizeof(fpregs)))
+ return -EFAULT;
+
+ nwords = sizeof(fpregs) / sizeof(elf_fpreg_t);
+ alpha_elf_fpregs_set(target, fpregs, nwords);
+
+ return 0;
+}
+
+static int alpha_regset_get(struct task_struct *target,
+ const struct user_regset *regset,
+ struct membuf to)
+{
+ struct pt_regs *pt = task_pt_regs(target);
+ struct thread_info *ti = task_thread_info(target);
+ elf_gregset_t gregs;
+
+ dump_elf_thread(gregs, pt, ti);
+ return membuf_write(&to, gregs, sizeof(gregs));
+}
+
+static int alpha_fpregset_get(struct task_struct *target,
+ const struct user_regset *regset,
+ struct membuf to)
+{
+ elf_fpregset_t fpregs;
+
+ alpha_elf_fpregs_get(target, fpregs);
+ return membuf_write(&to, fpregs, sizeof(fpregs));
+}
+
+enum alpha_regset {
+ REGSET_GPR,
+ REGSET_FPR,
+};
+
+static const struct user_regset alpha_user_regsets[] = {
+ [REGSET_GPR] = {
+ .core_note_type = NT_PRSTATUS,
+ .n = ELF_NGREG,
+ .size = sizeof(elf_greg_t),
+ .align = sizeof(elf_greg_t),
+ .regset_get = alpha_regset_get,
+ .set = alpha_regset_set,
+ },
+ [REGSET_FPR] = {
+ .core_note_type = NT_PRFPREG,
+ .core_note_name = "CORE",
+ .n = ELF_NFPREG,
+ .size = sizeof(elf_fpreg_t),
+ .align = sizeof(elf_fpreg_t),
+ .regset_get = alpha_fpregset_get,
+ .set = alpha_fpregset_set,
+ },
+};
+
+static const struct user_regset_view user_alpha_view = {
+ .name = "alpha",
+ .e_machine = EM_ALPHA,
+ .ei_osabi = ELF_OSABI,
+ .regsets = alpha_user_regsets,
+ .n = ARRAY_SIZE(alpha_user_regsets),
+};
+
+const struct user_regset_view *task_user_regset_view(struct task_struct *task)
+{
+ return &user_alpha_view;
+}
diff --git a/arch/alpha/kernel/traps.c b/arch/alpha/kernel/traps.c
index 7004397937cf..7631129ac914 100644
--- a/arch/alpha/kernel/traps.c
+++ b/arch/alpha/kernel/traps.c
@@ -30,6 +30,12 @@
#include "proto.h"
+static __always_inline void alpha_snapshot_usp(struct pt_regs *regs)
+{
+ if (user_mode(regs))
+ regs->usp = rdusp();
+}
+
void
dik_show_regs(struct pt_regs *regs, unsigned long *r9_15)
{
@@ -180,6 +186,7 @@ do_entArith(unsigned long summary, unsigned long write_mask,
{
long si_code = FPE_FLTINV;
+ alpha_snapshot_usp(regs);
if (summary & 1) {
/* Software-completion summary bit is set, so try to
emulate the instruction. If the processor supports
@@ -201,6 +208,7 @@ do_entIF(unsigned long type, struct pt_regs *regs)
{
int signo, code;
+ alpha_snapshot_usp(regs);
if (type == 3) { /* FEN fault */
/* Irritating users can call PAL_clrfen to disable the
FPU for the process. The kernel will then trap in