diff options
Diffstat (limited to 'arch/sh/kernel/ptrace_64.c')
| -rw-r--r-- | arch/sh/kernel/ptrace_64.c | 475 |
1 files changed, 363 insertions, 112 deletions
diff --git a/arch/sh/kernel/ptrace_64.c b/arch/sh/kernel/ptrace_64.c index d453c47dc52..af90339dadc 100644 --- a/arch/sh/kernel/ptrace_64.c +++ b/arch/sh/kernel/ptrace_64.c @@ -2,7 +2,7 @@ * arch/sh/kernel/ptrace_64.c * * Copyright (C) 2000, 2001 Paolo Alberelli - * Copyright (C) 2003 - 2007 Paul Mundt + * Copyright (C) 2003 - 2008 Paul Mundt * * Started from SH3/4 version: * SuperH version: Copyright (C) 1999, 2000 Kaz Kojima & Niibe Yutaka @@ -20,20 +20,28 @@ #include <linux/sched.h> #include <linux/mm.h> #include <linux/smp.h> -#include <linux/smp_lock.h> +#include <linux/bitops.h> #include <linux/errno.h> #include <linux/ptrace.h> #include <linux/user.h> #include <linux/signal.h> #include <linux/syscalls.h> #include <linux/audit.h> +#include <linux/seccomp.h> +#include <linux/tracehook.h> +#include <linux/elf.h> +#include <linux/regset.h> #include <asm/io.h> #include <asm/uaccess.h> #include <asm/pgtable.h> -#include <asm/system.h> #include <asm/processor.h> #include <asm/mmu_context.h> +#include <asm/syscalls.h> #include <asm/fpu.h> +#include <asm/traps.h> + +#define CREATE_TRACE_POINTS +#include <trace/events/syscalls.h> /* This mask defines the bits of the SR which the user is not allowed to change, which are everything except S, Q, M, PR, SZ, FR. */ @@ -74,13 +82,13 @@ get_fpu_long(struct task_struct *task, unsigned long addr) if (last_task_used_math == task) { enable_fpu(); - save_fpu(task, regs); + save_fpu(task); disable_fpu(); last_task_used_math = 0; regs->sr |= SR_FD; } - tmp = ((long *)&task->thread.fpu)[addr / sizeof(unsigned long)]; + tmp = ((long *)task->thread.xstate)[addr / sizeof(unsigned long)]; return tmp; } @@ -106,32 +114,282 @@ put_fpu_long(struct task_struct *task, unsigned long addr, unsigned long data) regs = (struct pt_regs*)((unsigned char *)task + THREAD_SIZE) - 1; if (!tsk_used_math(task)) { - fpinit(&task->thread.fpu.hard); - set_stopped_child_used_math(task); + init_fpu(task); } else if (last_task_used_math == task) { enable_fpu(); - save_fpu(task, regs); + save_fpu(task); disable_fpu(); last_task_used_math = 0; regs->sr |= SR_FD; } - ((long *)&task->thread.fpu)[addr / sizeof(unsigned long)] = data; + ((long *)task->thread.xstate)[addr / sizeof(unsigned long)] = data; return 0; } +void user_enable_single_step(struct task_struct *child) +{ + struct pt_regs *regs = child->thread.uregs; + + regs->sr |= SR_SSTEP; /* auto-resetting upon exception */ + + set_tsk_thread_flag(child, TIF_SINGLESTEP); +} + +void user_disable_single_step(struct task_struct *child) +{ + struct pt_regs *regs = child->thread.uregs; + + regs->sr &= ~SR_SSTEP; -long arch_ptrace(struct task_struct *child, long request, long addr, long data) + clear_tsk_thread_flag(child, TIF_SINGLESTEP); +} + +static int genregs_get(struct task_struct *target, + const struct user_regset *regset, + unsigned int pos, unsigned int count, + void *kbuf, void __user *ubuf) { + const struct pt_regs *regs = task_pt_regs(target); int ret; - switch (request) { - /* when I and D space are separate, these will need to be fixed. */ - case PTRACE_PEEKTEXT: /* read word at location addr. */ - case PTRACE_PEEKDATA: - ret = generic_ptrace_peekdata(child, addr, data); - break; + /* PC, SR, SYSCALL */ + ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, + ®s->pc, + 0, 3 * sizeof(unsigned long long)); + + /* R1 -> R63 */ + if (!ret) + ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, + regs->regs, + offsetof(struct pt_regs, regs[0]), + 63 * sizeof(unsigned long long)); + /* TR0 -> TR7 */ + if (!ret) + ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, + regs->tregs, + offsetof(struct pt_regs, tregs[0]), + 8 * sizeof(unsigned long long)); + + if (!ret) + ret = user_regset_copyout_zero(&pos, &count, &kbuf, &ubuf, + sizeof(struct pt_regs), -1); + + return ret; +} + +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) +{ + struct pt_regs *regs = task_pt_regs(target); + int ret; + + /* PC, SR, SYSCALL */ + ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, + ®s->pc, + 0, 3 * sizeof(unsigned long long)); + + /* R1 -> R63 */ + if (!ret && count > 0) + ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, + regs->regs, + offsetof(struct pt_regs, regs[0]), + 63 * sizeof(unsigned long long)); + + /* TR0 -> TR7 */ + if (!ret && count > 0) + ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, + regs->tregs, + offsetof(struct pt_regs, tregs[0]), + 8 * sizeof(unsigned long long)); + + if (!ret) + ret = user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf, + sizeof(struct pt_regs), -1); + + return ret; +} + +#ifdef CONFIG_SH_FPU +int fpregs_get(struct task_struct *target, + const struct user_regset *regset, + unsigned int pos, unsigned int count, + void *kbuf, void __user *ubuf) +{ + int ret; + + ret = init_fpu(target); + if (ret) + return ret; + + return user_regset_copyout(&pos, &count, &kbuf, &ubuf, + &target->thread.xstate->hardfpu, 0, -1); +} + +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) +{ + int ret; + + ret = init_fpu(target); + if (ret) + return ret; + + set_stopped_child_used_math(target); + + return user_regset_copyin(&pos, &count, &kbuf, &ubuf, + &target->thread.xstate->hardfpu, 0, -1); +} + +static int fpregs_active(struct task_struct *target, + const struct user_regset *regset) +{ + return tsk_used_math(target) ? regset->n : 0; +} +#endif + +const struct pt_regs_offset regoffset_table[] = { + REG_OFFSET_NAME(pc), + REG_OFFSET_NAME(sr), + REG_OFFSET_NAME(syscall_nr), + REGS_OFFSET_NAME(0), + REGS_OFFSET_NAME(1), + REGS_OFFSET_NAME(2), + REGS_OFFSET_NAME(3), + REGS_OFFSET_NAME(4), + REGS_OFFSET_NAME(5), + REGS_OFFSET_NAME(6), + REGS_OFFSET_NAME(7), + REGS_OFFSET_NAME(8), + REGS_OFFSET_NAME(9), + REGS_OFFSET_NAME(10), + REGS_OFFSET_NAME(11), + REGS_OFFSET_NAME(12), + REGS_OFFSET_NAME(13), + REGS_OFFSET_NAME(14), + REGS_OFFSET_NAME(15), + REGS_OFFSET_NAME(16), + REGS_OFFSET_NAME(17), + REGS_OFFSET_NAME(18), + REGS_OFFSET_NAME(19), + REGS_OFFSET_NAME(20), + REGS_OFFSET_NAME(21), + REGS_OFFSET_NAME(22), + REGS_OFFSET_NAME(23), + REGS_OFFSET_NAME(24), + REGS_OFFSET_NAME(25), + REGS_OFFSET_NAME(26), + REGS_OFFSET_NAME(27), + REGS_OFFSET_NAME(28), + REGS_OFFSET_NAME(29), + REGS_OFFSET_NAME(30), + REGS_OFFSET_NAME(31), + REGS_OFFSET_NAME(32), + REGS_OFFSET_NAME(33), + REGS_OFFSET_NAME(34), + REGS_OFFSET_NAME(35), + REGS_OFFSET_NAME(36), + REGS_OFFSET_NAME(37), + REGS_OFFSET_NAME(38), + REGS_OFFSET_NAME(39), + REGS_OFFSET_NAME(40), + REGS_OFFSET_NAME(41), + REGS_OFFSET_NAME(42), + REGS_OFFSET_NAME(43), + REGS_OFFSET_NAME(44), + REGS_OFFSET_NAME(45), + REGS_OFFSET_NAME(46), + REGS_OFFSET_NAME(47), + REGS_OFFSET_NAME(48), + REGS_OFFSET_NAME(49), + REGS_OFFSET_NAME(50), + REGS_OFFSET_NAME(51), + REGS_OFFSET_NAME(52), + REGS_OFFSET_NAME(53), + REGS_OFFSET_NAME(54), + REGS_OFFSET_NAME(55), + REGS_OFFSET_NAME(56), + REGS_OFFSET_NAME(57), + REGS_OFFSET_NAME(58), + REGS_OFFSET_NAME(59), + REGS_OFFSET_NAME(60), + REGS_OFFSET_NAME(61), + REGS_OFFSET_NAME(62), + REGS_OFFSET_NAME(63), + TREGS_OFFSET_NAME(0), + TREGS_OFFSET_NAME(1), + TREGS_OFFSET_NAME(2), + TREGS_OFFSET_NAME(3), + TREGS_OFFSET_NAME(4), + TREGS_OFFSET_NAME(5), + TREGS_OFFSET_NAME(6), + TREGS_OFFSET_NAME(7), + REG_OFFSET_END, +}; + +/* + * These are our native regset flavours. + */ +enum sh_regset { + REGSET_GENERAL, +#ifdef CONFIG_SH_FPU + REGSET_FPU, +#endif +}; + +static const struct user_regset sh_regsets[] = { + /* + * Format is: + * PC, SR, SYSCALL, + * R1 --> R63, + * TR0 --> TR7, + */ + [REGSET_GENERAL] = { + .core_note_type = NT_PRSTATUS, + .n = ELF_NGREG, + .size = sizeof(long long), + .align = sizeof(long long), + .get = genregs_get, + .set = genregs_set, + }, + +#ifdef CONFIG_SH_FPU + [REGSET_FPU] = { + .core_note_type = NT_PRFPREG, + .n = sizeof(struct user_fpu_struct) / + sizeof(long long), + .size = sizeof(long long), + .align = sizeof(long long), + .get = fpregs_get, + .set = fpregs_set, + .active = fpregs_active, + }, +#endif +}; + +static const struct user_regset_view user_sh64_native_view = { + .name = "sh64", + .e_machine = EM_SH, + .regsets = sh_regsets, + .n = ARRAY_SIZE(sh_regsets), +}; + +const struct user_regset_view *task_user_regset_view(struct task_struct *task) +{ + return &user_sh64_native_view; +} +long arch_ptrace(struct task_struct *child, long request, + unsigned long addr, unsigned long data) +{ + int ret; + unsigned long __user *datap = (unsigned long __user *) data; + + switch (request) { /* read the word at location addr in the USER area. */ case PTRACE_PEEKUSR: { unsigned long tmp; @@ -144,22 +402,21 @@ long arch_ptrace(struct task_struct *child, long request, long addr, long data) tmp = get_stack_long(child, addr); else if ((addr >= offsetof(struct user, fpu)) && (addr < offsetof(struct user, u_fpvalid))) { - tmp = get_fpu_long(child, addr - offsetof(struct user, fpu)); + unsigned long index; + ret = init_fpu(child); + if (ret) + break; + index = addr - offsetof(struct user, fpu); + tmp = get_fpu_long(child, index); } else if (addr == offsetof(struct user, u_fpvalid)) { tmp = !!tsk_used_math(child); } else { break; } - ret = put_user(tmp, (unsigned long *)data); + ret = put_user(tmp, datap); break; } - /* when I and D space are separate, this will have to be fixed. */ - case PTRACE_POKETEXT: /* write the word at location addr. */ - case PTRACE_POKEDATA: - ret = generic_ptrace_pokedata(child, addr, data); - break; - case PTRACE_POKEUSR: /* write the word at location addr in the USER area. We must disallow any changes to certain SR bits or u_fpvalid, since @@ -187,76 +444,52 @@ long arch_ptrace(struct task_struct *child, long request, long addr, long data) } else if ((addr >= offsetof(struct user, fpu)) && (addr < offsetof(struct user, u_fpvalid))) { - ret = put_fpu_long(child, addr - offsetof(struct user, fpu), data); - } - break; - - case PTRACE_SYSCALL: /* continue and stop at next (return from) syscall */ - case PTRACE_CONT: { /* restart after signal. */ - ret = -EIO; - if (!valid_signal(data)) - break; - if (request == PTRACE_SYSCALL) - set_tsk_thread_flag(child, TIF_SYSCALL_TRACE); - else - clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); - child->exit_code = data; - wake_up_process(child); - ret = 0; - break; - } - -/* - * make the child exit. Best I can do is send it a sigkill. - * perhaps it should be put in the status that it wants to - * exit. - */ - case PTRACE_KILL: { - ret = 0; - if (child->exit_state == EXIT_ZOMBIE) /* already dead */ - break; - child->exit_code = SIGKILL; - wake_up_process(child); - break; - } - - case PTRACE_SINGLESTEP: { /* set the trap flag. */ - struct pt_regs *regs; - - ret = -EIO; - if (!valid_signal(data)) - break; - clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); - if ((child->ptrace & PT_DTRACE) == 0) { - /* Spurious delayed TF traps may occur */ - child->ptrace |= PT_DTRACE; + unsigned long index; + ret = init_fpu(child); + if (ret) + break; + index = addr - offsetof(struct user, fpu); + ret = put_fpu_long(child, index, data); } - - regs = child->thread.uregs; - - regs->sr |= SR_SSTEP; /* auto-resetting upon exception */ - - child->exit_code = data; - /* give it a chance to run. */ - wake_up_process(child); - ret = 0; break; - } + case PTRACE_GETREGS: + return copy_regset_to_user(child, &user_sh64_native_view, + REGSET_GENERAL, + 0, sizeof(struct pt_regs), + datap); + case PTRACE_SETREGS: + return copy_regset_from_user(child, &user_sh64_native_view, + REGSET_GENERAL, + 0, sizeof(struct pt_regs), + datap); +#ifdef CONFIG_SH_FPU + case PTRACE_GETFPREGS: + return copy_regset_to_user(child, &user_sh64_native_view, + REGSET_FPU, + 0, sizeof(struct user_fpu_struct), + datap); + case PTRACE_SETFPREGS: + return copy_regset_from_user(child, &user_sh64_native_view, + REGSET_FPU, + 0, sizeof(struct user_fpu_struct), + datap); +#endif default: ret = ptrace_request(child, request, addr, data); break; } + return ret; } -asmlinkage int sh64_ptrace(long request, long pid, long addr, long data) +asmlinkage int sh64_ptrace(long request, long pid, + unsigned long addr, unsigned long data) { #define WPC_DBRMODE 0x0d104008 - static int first_call = 1; + static unsigned long first_call; - lock_kernel(); - if (first_call) { + if (!test_and_set_bit(0, &first_call)) { /* Set WPC.DBRMODE to 0. This makes all debug events get * delivered through RESVEC, i.e. into the handlers in entry.S. * (If the kernel was downloaded using a remote gdb, WPC.DBRMODE @@ -266,45 +499,62 @@ asmlinkage int sh64_ptrace(long request, long pid, long addr, long data) * the remote gdb.) */ printk("DBRMODE set to 0 to permit native debugging\n"); poke_real_address_q(WPC_DBRMODE, 0); - first_call = 0; } - unlock_kernel(); return sys_ptrace(request, pid, addr, data); } -asmlinkage void syscall_trace(struct pt_regs *regs, int entryexit) +static inline int audit_arch(void) { - struct task_struct *tsk = current; + int arch = EM_SH; - if (unlikely(current->audit_context) && entryexit) - audit_syscall_exit(AUDITSC_RESULT(regs->regs[9]), - regs->regs[9]); +#ifdef CONFIG_64BIT + arch |= __AUDIT_ARCH_64BIT; +#endif +#ifdef CONFIG_CPU_LITTLE_ENDIAN + arch |= __AUDIT_ARCH_LE; +#endif - if (!test_thread_flag(TIF_SYSCALL_TRACE) && - !test_thread_flag(TIF_SINGLESTEP)) - goto out; - if (!(tsk->ptrace & PT_PTRACED)) - goto out; + return arch; +} - ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD) && - !test_thread_flag(TIF_SINGLESTEP) ? 0x80 : 0)); +asmlinkage long long do_syscall_trace_enter(struct pt_regs *regs) +{ + long long ret = 0; - /* - * this isn't the same as continuing with a signal, but it will do - * for normal use. strace only continues with a signal if the - * stopping signal is not SIGTRAP. -brl - */ - if (tsk->exit_code) { - send_sig(tsk->exit_code, tsk, 1); - tsk->exit_code = 0; - } + secure_computing_strict(regs->regs[9]); + + if (test_thread_flag(TIF_SYSCALL_TRACE) && + tracehook_report_syscall_entry(regs)) + /* + * Tracing decided this syscall should not happen. + * We'll return a bogus call number to get an ENOSYS + * error, but leave the original number in regs->regs[0]. + */ + ret = -1LL; + + if (unlikely(test_thread_flag(TIF_SYSCALL_TRACEPOINT))) + trace_sys_enter(regs, regs->regs[9]); -out: - if (unlikely(current->audit_context) && !entryexit) - audit_syscall_entry(AUDIT_ARCH_SH, regs->regs[1], - regs->regs[2], regs->regs[3], - regs->regs[4], regs->regs[5]); + audit_syscall_entry(audit_arch(), regs->regs[1], + regs->regs[2], regs->regs[3], + regs->regs[4], regs->regs[5]); + + return ret ?: regs->regs[9]; +} + +asmlinkage void do_syscall_trace_leave(struct pt_regs *regs) +{ + int step; + + audit_syscall_exit(regs); + + if (unlikely(test_thread_flag(TIF_SYSCALL_TRACEPOINT))) + trace_sys_exit(regs, regs->regs[9]); + + step = test_thread_flag(TIF_SINGLESTEP); + if (step || test_thread_flag(TIF_SYSCALL_TRACE)) + tracehook_report_syscall_exit(regs, step); } /* Called with interrupts disabled */ @@ -321,9 +571,10 @@ asmlinkage void do_single_step(unsigned long long vec, struct pt_regs *regs) } /* Called with interrupts disabled */ -asmlinkage void do_software_break_point(unsigned long long vec, - struct pt_regs *regs) +BUILD_TRAP_HANDLER(breakpoint) { + TRAP_HANDLER_DECL; + /* We need to forward step the PC, to counteract the backstep done in signal.c. */ local_irq_enable(); @@ -338,5 +589,5 @@ asmlinkage void do_software_break_point(unsigned long long vec, */ void ptrace_disable(struct task_struct *child) { - /* nothing to do.. */ + user_disable_single_step(child); } |
