diff --git a/Documentation/features/core/tracehook/arch-support.txt b/Documentation/features/core/tracehook/arch-support.txt index 4f36fcbfb6d5..654f38413d16 100644 --- a/Documentation/features/core/tracehook/arch-support.txt +++ b/Documentation/features/core/tracehook/arch-support.txt @@ -6,7 +6,7 @@ ----------------------- | arch |status| ----------------------- - | alpha: | TODO | + | alpha: | ok | | arc: | ok | | arm: | ok | | arm64: | ok | diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index 7b7dafe7d9df..f3b882835617 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -33,6 +33,7 @@ config ALPHA select HAVE_ARCH_AUDITSYSCALL select HAVE_ARCH_SECCOMP select HAVE_ARCH_SECCOMP_FILTER + select HAVE_ARCH_TRACEHOOK select HAVE_MOD_ARCH_SPECIFIC select LOCK_MM_AND_FIND_VMA select MODULES_USE_ELF_RELA diff --git a/arch/alpha/include/asm/elf.h b/arch/alpha/include/asm/elf.h index 50c82187e60e..b15946621d57 100644 --- a/arch/alpha/include/asm/elf.h +++ b/arch/alpha/include/asm/elf.h @@ -53,6 +53,7 @@ #define EF_ALPHA_32BIT 1 /* All addresses are below 2GB */ +#define CORE_DUMP_USE_REGSET 1 /* * ELF register definitions.. */ diff --git a/arch/alpha/include/asm/ptrace.h b/arch/alpha/include/asm/ptrace.h index 3557ce64ed21..8e0a589e2d15 100644 --- a/arch/alpha/include/asm/ptrace.h +++ b/arch/alpha/include/asm/ptrace.h @@ -24,4 +24,11 @@ static inline unsigned long regs_return_value(struct pt_regs *regs) return regs->r0; } +/* Helpers for working with the user stack pointer */ +static inline unsigned long user_stack_pointer(struct pt_regs *regs) +{ + /* Valid for user-mode regs */ + return regs->usp; +} + #endif diff --git a/arch/alpha/include/asm/syscall.h b/arch/alpha/include/asm/syscall.h index 584b1ab2e325..1e78cbd46faf 100644 --- a/arch/alpha/include/asm/syscall.h +++ b/arch/alpha/include/asm/syscall.h @@ -19,6 +19,13 @@ static inline long syscall_get_return_value(struct task_struct *task, return regs->r19 ? -(long)regs->r0 : (long)regs->r0; } +static inline long syscall_get_error(struct task_struct *task, + struct pt_regs *regs) +{ + return regs->r19 ? -(long)regs->r0 : 0; +} + + /* * Alpha syscall ABI / kernel conventions: * - PAL provides syscall number in r0 on entry. diff --git a/arch/alpha/include/asm/thread_info.h b/arch/alpha/include/asm/thread_info.h index 94ef9cfa30f5..1552ecca8520 100644 --- a/arch/alpha/include/asm/thread_info.h +++ b/arch/alpha/include/asm/thread_info.h @@ -66,6 +66,7 @@ register unsigned long *current_stack_pointer __asm__ ("$30"); #define TIF_SYSCALL_AUDIT 4 /* syscall audit active */ #define TIF_NOTIFY_SIGNAL 5 /* signal notifications exist */ #define TIF_SECCOMP 6 /* seccomp syscall filtering active */ +#define TIF_SYSCALL_TRACEPOINT 7 /* syscall tracepoint instrumentation */ #define TIF_DIE_IF_KERNEL 9 /* dik recursion lock */ #define TIF_MEMDIE 13 /* is terminating due to OOM killer */ #define TIF_POLLING_NRFLAG 14 /* idle is polling for TIF_NEED_RESCHED */ @@ -78,6 +79,7 @@ register unsigned long *current_stack_pointer __asm__ ("$30"); #define _TIF_NOTIFY_SIGNAL (1<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 + lda $6, _TIF_SYSCALL_WORK and $3, $6, $3 bne $3, strace -#else - lda $6, _TIF_SYSCALL_TRACE | _TIF_SECCOMP - 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 +#include #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