diff options
Diffstat (limited to 'arch/cris/arch-v10/kernel/ptrace.c')
-rw-r--r-- | arch/cris/arch-v10/kernel/ptrace.c | 202 |
1 files changed, 202 insertions, 0 deletions
diff --git a/arch/cris/arch-v10/kernel/ptrace.c b/arch/cris/arch-v10/kernel/ptrace.c new file mode 100644 index 000000000..bfddfb994 --- /dev/null +++ b/arch/cris/arch-v10/kernel/ptrace.c @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2000-2003, Axis Communications AB. + */ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/smp.h> +#include <linux/errno.h> +#include <linux/ptrace.h> +#include <linux/user.h> +#include <linux/signal.h> +#include <linux/security.h> + +#include <asm/uaccess.h> +#include <asm/page.h> +#include <asm/pgtable.h> +#include <asm/processor.h> + +/* + * Determines which bits in DCCR the user has access to. + * 1 = access, 0 = no access. + */ +#define DCCR_MASK 0x0000001f /* XNZVC */ + +/* + * Get contents of register REGNO in task TASK. + */ +inline long get_reg(struct task_struct *task, unsigned int regno) +{ + /* USP is a special case, it's not in the pt_regs struct but + * in the tasks thread struct + */ + + if (regno == PT_USP) + return task->thread.usp; + else if (regno < PT_MAX) + return ((unsigned long *)task_pt_regs(task))[regno]; + else + return 0; +} + +/* + * Write contents of register REGNO in task TASK. + */ +inline int put_reg(struct task_struct *task, unsigned int regno, + unsigned long data) +{ + if (regno == PT_USP) + task->thread.usp = data; + else if (regno < PT_MAX) + ((unsigned long *)task_pt_regs(task))[regno] = data; + else + return -1; + return 0; +} + +/* + * Called by kernel/ptrace.c when detaching. + * + * Make sure the single step bit is not set. + */ +void +ptrace_disable(struct task_struct *child) +{ + /* Todo - pending singlesteps? */ + clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); +} + +/* + * Note that this implementation of ptrace behaves differently from vanilla + * ptrace. Contrary to what the man page says, in the PTRACE_PEEKTEXT, + * PTRACE_PEEKDATA, and PTRACE_PEEKUSER requests the data variable is not + * ignored. Instead, the data variable is expected to point at a location + * (in user space) where the result of the ptrace call is written (instead of + * being returned). + */ +long arch_ptrace(struct task_struct *child, long request, + unsigned long addr, unsigned long data) +{ + int ret; + unsigned int regno = addr >> 2; + unsigned long __user *datap = (unsigned long __user *)data; + + switch (request) { + /* Read word at location address. */ + case PTRACE_PEEKTEXT: + case PTRACE_PEEKDATA: + ret = generic_ptrace_peekdata(child, addr, data); + break; + + /* Read the word at location address in the USER area. */ + case PTRACE_PEEKUSR: { + unsigned long tmp; + + ret = -EIO; + if ((addr & 3) || regno > PT_MAX) + break; + + tmp = get_reg(child, regno); + ret = put_user(tmp, datap); + break; + } + + /* Write the word at location address. */ + case PTRACE_POKETEXT: + case PTRACE_POKEDATA: + ret = generic_ptrace_pokedata(child, addr, data); + break; + + /* Write the word at location address in the USER area. */ + case PTRACE_POKEUSR: + ret = -EIO; + if ((addr & 3) || regno > PT_MAX) + break; + + if (regno == PT_DCCR) { + /* don't allow the tracing process to change stuff like + * interrupt enable, kernel/user bit, dma enables etc. + */ + data &= DCCR_MASK; + data |= get_reg(child, PT_DCCR) & ~DCCR_MASK; + } + if (put_reg(child, regno, data)) + break; + ret = 0; + break; + + /* Get all GP registers from the child. */ + case PTRACE_GETREGS: { + int i; + unsigned long tmp; + + ret = 0; + for (i = 0; i <= PT_MAX; i++) { + tmp = get_reg(child, i); + + if (put_user(tmp, datap)) { + ret = -EFAULT; + break; + } + + datap++; + } + + break; + } + + /* Set all GP registers in the child. */ + case PTRACE_SETREGS: { + int i; + unsigned long tmp; + + ret = 0; + for (i = 0; i <= PT_MAX; i++) { + if (get_user(tmp, datap)) { + ret = -EFAULT; + break; + } + + if (i == PT_DCCR) { + tmp &= DCCR_MASK; + tmp |= get_reg(child, PT_DCCR) & ~DCCR_MASK; + } + + put_reg(child, i, tmp); + datap++; + } + + break; + } + + default: + ret = ptrace_request(child, request, addr, data); + break; + } + + return ret; +} + +void do_syscall_trace(void) +{ + if (!test_thread_flag(TIF_SYSCALL_TRACE)) + return; + + if (!(current->ptrace & PT_PTRACED)) + return; + + /* 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. + */ + if (current->exit_code) { + send_sig(current->exit_code, current, 1); + current->exit_code = 0; + } +} |