diff options
Diffstat (limited to 'arch/xtensa/kernel/ptrace.c')
-rw-r--r-- | arch/xtensa/kernel/ptrace.c | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/arch/xtensa/kernel/ptrace.c b/arch/xtensa/kernel/ptrace.c new file mode 100644 index 000000000..4d54b4811 --- /dev/null +++ b/arch/xtensa/kernel/ptrace.c @@ -0,0 +1,354 @@ +// TODO some minor issues +/* + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2001 - 2007 Tensilica Inc. + * + * Joe Taylor <joe@tensilica.com, joetylr@yahoo.com> + * Chris Zankel <chris@zankel.net> + * Scott Foehner<sfoehner@yahoo.com>, + * Kevin Chea + * Marc Gauthier<marc@tensilica.com> <marc@alumni.uwaterloo.ca> + */ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/errno.h> +#include <linux/ptrace.h> +#include <linux/smp.h> +#include <linux/security.h> +#include <linux/signal.h> + +#include <asm/pgtable.h> +#include <asm/page.h> +#include <asm/uaccess.h> +#include <asm/ptrace.h> +#include <asm/elf.h> +#include <asm/coprocessor.h> + + +void user_enable_single_step(struct task_struct *child) +{ + child->ptrace |= PT_SINGLESTEP; +} + +void user_disable_single_step(struct task_struct *child) +{ + child->ptrace &= ~PT_SINGLESTEP; +} + +/* + * Called by kernel/ptrace.c when detaching to disable single stepping. + */ + +void ptrace_disable(struct task_struct *child) +{ + /* Nothing to do.. */ +} + +int ptrace_getregs(struct task_struct *child, void __user *uregs) +{ + struct pt_regs *regs = task_pt_regs(child); + xtensa_gregset_t __user *gregset = uregs; + unsigned long wb = regs->windowbase; + int i; + + if (!access_ok(VERIFY_WRITE, uregs, sizeof(xtensa_gregset_t))) + return -EIO; + + __put_user(regs->pc, &gregset->pc); + __put_user(regs->ps & ~(1 << PS_EXCM_BIT), &gregset->ps); + __put_user(regs->lbeg, &gregset->lbeg); + __put_user(regs->lend, &gregset->lend); + __put_user(regs->lcount, &gregset->lcount); + __put_user(regs->windowstart, &gregset->windowstart); + __put_user(regs->windowbase, &gregset->windowbase); + __put_user(regs->threadptr, &gregset->threadptr); + + for (i = 0; i < XCHAL_NUM_AREGS; i++) + __put_user(regs->areg[i], + gregset->a + ((wb * 4 + i) % XCHAL_NUM_AREGS)); + + return 0; +} + +int ptrace_setregs(struct task_struct *child, void __user *uregs) +{ + struct pt_regs *regs = task_pt_regs(child); + xtensa_gregset_t *gregset = uregs; + const unsigned long ps_mask = PS_CALLINC_MASK | PS_OWB_MASK; + unsigned long ps; + unsigned long wb, ws; + + if (!access_ok(VERIFY_WRITE, uregs, sizeof(xtensa_gregset_t))) + return -EIO; + + __get_user(regs->pc, &gregset->pc); + __get_user(ps, &gregset->ps); + __get_user(regs->lbeg, &gregset->lbeg); + __get_user(regs->lend, &gregset->lend); + __get_user(regs->lcount, &gregset->lcount); + __get_user(ws, &gregset->windowstart); + __get_user(wb, &gregset->windowbase); + __get_user(regs->threadptr, &gregset->threadptr); + + regs->ps = (regs->ps & ~ps_mask) | (ps & ps_mask) | (1 << PS_EXCM_BIT); + + if (wb >= XCHAL_NUM_AREGS / 4) + return -EFAULT; + + if (wb != regs->windowbase || ws != regs->windowstart) { + unsigned long rotws, wmask; + + rotws = (((ws | (ws << WSBITS)) >> wb) & + ((1 << WSBITS) - 1)) & ~1; + wmask = ((rotws ? WSBITS + 1 - ffs(rotws) : 0) << 4) | + (rotws & 0xF) | 1; + regs->windowbase = wb; + regs->windowstart = ws; + regs->wmask = wmask; + } + + if (wb != 0 && __copy_from_user(regs->areg + XCHAL_NUM_AREGS - wb * 4, + gregset->a, wb * 16)) + return -EFAULT; + + if (__copy_from_user(regs->areg, gregset->a + wb * 4, + (WSBITS - wb) * 16)) + return -EFAULT; + + return 0; +} + + +int ptrace_getxregs(struct task_struct *child, void __user *uregs) +{ + struct pt_regs *regs = task_pt_regs(child); + struct thread_info *ti = task_thread_info(child); + elf_xtregs_t __user *xtregs = uregs; + int ret = 0; + + if (!access_ok(VERIFY_WRITE, uregs, sizeof(elf_xtregs_t))) + return -EIO; + +#if XTENSA_HAVE_COPROCESSORS + /* Flush all coprocessor registers to memory. */ + coprocessor_flush_all(ti); + ret |= __copy_to_user(&xtregs->cp0, &ti->xtregs_cp, + sizeof(xtregs_coprocessor_t)); +#endif + ret |= __copy_to_user(&xtregs->opt, ®s->xtregs_opt, + sizeof(xtregs->opt)); + ret |= __copy_to_user(&xtregs->user,&ti->xtregs_user, + sizeof(xtregs->user)); + + return ret ? -EFAULT : 0; +} + +int ptrace_setxregs(struct task_struct *child, void __user *uregs) +{ + struct thread_info *ti = task_thread_info(child); + struct pt_regs *regs = task_pt_regs(child); + elf_xtregs_t *xtregs = uregs; + int ret = 0; + + if (!access_ok(VERIFY_READ, uregs, sizeof(elf_xtregs_t))) + return -EFAULT; + +#if XTENSA_HAVE_COPROCESSORS + /* Flush all coprocessors before we overwrite them. */ + coprocessor_flush_all(ti); + coprocessor_release_all(ti); + + ret |= __copy_from_user(&ti->xtregs_cp, &xtregs->cp0, + sizeof(xtregs_coprocessor_t)); +#endif + ret |= __copy_from_user(®s->xtregs_opt, &xtregs->opt, + sizeof(xtregs->opt)); + ret |= __copy_from_user(&ti->xtregs_user, &xtregs->user, + sizeof(xtregs->user)); + + return ret ? -EFAULT : 0; +} + +int ptrace_peekusr(struct task_struct *child, long regno, long __user *ret) +{ + struct pt_regs *regs; + unsigned long tmp; + + regs = task_pt_regs(child); + tmp = 0; /* Default return value. */ + + switch(regno) { + + case REG_AR_BASE ... REG_AR_BASE + XCHAL_NUM_AREGS - 1: + tmp = regs->areg[regno - REG_AR_BASE]; + break; + + case REG_A_BASE ... REG_A_BASE + 15: + tmp = regs->areg[regno - REG_A_BASE]; + break; + + case REG_PC: + tmp = regs->pc; + break; + + case REG_PS: + /* Note: PS.EXCM is not set while user task is running; + * its being set in regs is for exception handling + * convenience. */ + tmp = (regs->ps & ~(1 << PS_EXCM_BIT)); + break; + + case REG_WB: + break; /* tmp = 0 */ + + case REG_WS: + { + unsigned long wb = regs->windowbase; + unsigned long ws = regs->windowstart; + tmp = ((ws>>wb) | (ws<<(WSBITS-wb))) & ((1<<WSBITS)-1); + break; + } + case REG_LBEG: + tmp = regs->lbeg; + break; + + case REG_LEND: + tmp = regs->lend; + break; + + case REG_LCOUNT: + tmp = regs->lcount; + break; + + case REG_SAR: + tmp = regs->sar; + break; + + case SYSCALL_NR: + tmp = regs->syscall; + break; + + default: + return -EIO; + } + return put_user(tmp, ret); +} + +int ptrace_pokeusr(struct task_struct *child, long regno, long val) +{ + struct pt_regs *regs; + regs = task_pt_regs(child); + + switch (regno) { + case REG_AR_BASE ... REG_AR_BASE + XCHAL_NUM_AREGS - 1: + regs->areg[regno - REG_AR_BASE] = val; + break; + + case REG_A_BASE ... REG_A_BASE + 15: + regs->areg[regno - REG_A_BASE] = val; + break; + + case REG_PC: + regs->pc = val; + break; + + case SYSCALL_NR: + regs->syscall = val; + break; + + default: + return -EIO; + } + return 0; +} + +long arch_ptrace(struct task_struct *child, long request, + unsigned long addr, unsigned long data) +{ + int ret = -EPERM; + void __user *datap = (void __user *) data; + + switch (request) { + case PTRACE_PEEKTEXT: /* read word at location addr. */ + case PTRACE_PEEKDATA: + ret = generic_ptrace_peekdata(child, addr, data); + break; + + case PTRACE_PEEKUSR: /* read register specified by addr. */ + ret = ptrace_peekusr(child, addr, datap); + break; + + case PTRACE_POKETEXT: /* write the word at location addr. */ + case PTRACE_POKEDATA: + ret = generic_ptrace_pokedata(child, addr, data); + break; + + case PTRACE_POKEUSR: /* write register specified by addr. */ + ret = ptrace_pokeusr(child, addr, data); + break; + + case PTRACE_GETREGS: + ret = ptrace_getregs(child, datap); + break; + + case PTRACE_SETREGS: + ret = ptrace_setregs(child, datap); + break; + + case PTRACE_GETXTREGS: + ret = ptrace_getxregs(child, datap); + break; + + case PTRACE_SETXTREGS: + ret = ptrace_setxregs(child, datap); + break; + + default: + ret = ptrace_request(child, request, addr, data); + break; + } + + return ret; +} + +void do_syscall_trace(void) +{ + /* + * The 0x80 provides a way for the tracing parent to distinguish + * between a syscall stop and SIGTRAP delivery + */ + ptrace_notify(SIGTRAP|((current->ptrace & PT_TRACESYSGOOD) ? 0x80 : 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 (current->exit_code) { + send_sig(current->exit_code, current, 1); + current->exit_code = 0; + } +} + +void do_syscall_trace_enter(struct pt_regs *regs) +{ + if (test_thread_flag(TIF_SYSCALL_TRACE) + && (current->ptrace & PT_PTRACED)) + do_syscall_trace(); + +#if 0 + audit_syscall_entry(...); +#endif +} + +void do_syscall_trace_leave(struct pt_regs *regs) +{ + if ((test_thread_flag(TIF_SYSCALL_TRACE)) + && (current->ptrace & PT_PTRACED)) + do_syscall_trace(); +} |