diff options
Diffstat (limited to 'arch/arm/kernel/ptrace.c')
-rw-r--r-- | arch/arm/kernel/ptrace.c | 739 |
1 files changed, 248 insertions, 491 deletions
diff --git a/arch/arm/kernel/ptrace.c b/arch/arm/kernel/ptrace.c index b13e70f63d71..97260060bf26 100644 --- a/arch/arm/kernel/ptrace.c +++ b/arch/arm/kernel/ptrace.c @@ -21,13 +21,12 @@ #include <linux/uaccess.h> #include <linux/perf_event.h> #include <linux/hw_breakpoint.h> +#include <linux/regset.h> #include <asm/pgtable.h> #include <asm/system.h> #include <asm/traps.h> -#include "ptrace.h" - #define REG_PC 15 #define REG_PSR 16 /* @@ -184,389 +183,12 @@ put_user_reg(struct task_struct *task, int offset, long data) return ret; } -static inline int -read_u32(struct task_struct *task, unsigned long addr, u32 *res) -{ - int ret; - - ret = access_process_vm(task, addr, res, sizeof(*res), 0); - - return ret == sizeof(*res) ? 0 : -EIO; -} - -static inline int -read_instr(struct task_struct *task, unsigned long addr, u32 *res) -{ - int ret; - - if (addr & 1) { - u16 val; - ret = access_process_vm(task, addr & ~1, &val, sizeof(val), 0); - ret = ret == sizeof(val) ? 0 : -EIO; - *res = val; - } else { - u32 val; - ret = access_process_vm(task, addr & ~3, &val, sizeof(val), 0); - ret = ret == sizeof(val) ? 0 : -EIO; - *res = val; - } - return ret; -} - -/* - * Get value of register `rn' (in the instruction) - */ -static unsigned long -ptrace_getrn(struct task_struct *child, unsigned long insn) -{ - unsigned int reg = (insn >> 16) & 15; - unsigned long val; - - val = get_user_reg(child, reg); - if (reg == 15) - val += 8; - - return val; -} - -/* - * Get value of operand 2 (in an ALU instruction) - */ -static unsigned long -ptrace_getaluop2(struct task_struct *child, unsigned long insn) -{ - unsigned long val; - int shift; - int type; - - if (insn & 1 << 25) { - val = insn & 255; - shift = (insn >> 8) & 15; - type = 3; - } else { - val = get_user_reg (child, insn & 15); - - if (insn & (1 << 4)) - shift = (int)get_user_reg (child, (insn >> 8) & 15); - else - shift = (insn >> 7) & 31; - - type = (insn >> 5) & 3; - } - - switch (type) { - case 0: val <<= shift; break; - case 1: val >>= shift; break; - case 2: - val = (((signed long)val) >> shift); - break; - case 3: - val = (val >> shift) | (val << (32 - shift)); - break; - } - return val; -} - -/* - * Get value of operand 2 (in a LDR instruction) - */ -static unsigned long -ptrace_getldrop2(struct task_struct *child, unsigned long insn) -{ - unsigned long val; - int shift; - int type; - - val = get_user_reg(child, insn & 15); - shift = (insn >> 7) & 31; - type = (insn >> 5) & 3; - - switch (type) { - case 0: val <<= shift; break; - case 1: val >>= shift; break; - case 2: - val = (((signed long)val) >> shift); - break; - case 3: - val = (val >> shift) | (val << (32 - shift)); - break; - } - return val; -} - -#define OP_MASK 0x01e00000 -#define OP_AND 0x00000000 -#define OP_EOR 0x00200000 -#define OP_SUB 0x00400000 -#define OP_RSB 0x00600000 -#define OP_ADD 0x00800000 -#define OP_ADC 0x00a00000 -#define OP_SBC 0x00c00000 -#define OP_RSC 0x00e00000 -#define OP_ORR 0x01800000 -#define OP_MOV 0x01a00000 -#define OP_BIC 0x01c00000 -#define OP_MVN 0x01e00000 - -static unsigned long -get_branch_address(struct task_struct *child, unsigned long pc, unsigned long insn) -{ - u32 alt = 0; - - switch (insn & 0x0e000000) { - case 0x00000000: - case 0x02000000: { - /* - * data processing - */ - long aluop1, aluop2, ccbit; - - if ((insn & 0x0fffffd0) == 0x012fff10) { - /* - * bx or blx - */ - alt = get_user_reg(child, insn & 15); - break; - } - - - if ((insn & 0xf000) != 0xf000) - break; - - aluop1 = ptrace_getrn(child, insn); - aluop2 = ptrace_getaluop2(child, insn); - ccbit = get_user_reg(child, REG_PSR) & PSR_C_BIT ? 1 : 0; - - switch (insn & OP_MASK) { - case OP_AND: alt = aluop1 & aluop2; break; - case OP_EOR: alt = aluop1 ^ aluop2; break; - case OP_SUB: alt = aluop1 - aluop2; break; - case OP_RSB: alt = aluop2 - aluop1; break; - case OP_ADD: alt = aluop1 + aluop2; break; - case OP_ADC: alt = aluop1 + aluop2 + ccbit; break; - case OP_SBC: alt = aluop1 - aluop2 + ccbit; break; - case OP_RSC: alt = aluop2 - aluop1 + ccbit; break; - case OP_ORR: alt = aluop1 | aluop2; break; - case OP_MOV: alt = aluop2; break; - case OP_BIC: alt = aluop1 & ~aluop2; break; - case OP_MVN: alt = ~aluop2; break; - } - break; - } - - case 0x04000000: - case 0x06000000: - /* - * ldr - */ - if ((insn & 0x0010f000) == 0x0010f000) { - unsigned long base; - - base = ptrace_getrn(child, insn); - if (insn & 1 << 24) { - long aluop2; - - if (insn & 0x02000000) - aluop2 = ptrace_getldrop2(child, insn); - else - aluop2 = insn & 0xfff; - - if (insn & 1 << 23) - base += aluop2; - else - base -= aluop2; - } - read_u32(child, base, &alt); - } - break; - - case 0x08000000: - /* - * ldm - */ - if ((insn & 0x00108000) == 0x00108000) { - unsigned long base; - unsigned int nr_regs; - - if (insn & (1 << 23)) { - nr_regs = hweight16(insn & 65535) << 2; - - if (!(insn & (1 << 24))) - nr_regs -= 4; - } else { - if (insn & (1 << 24)) - nr_regs = -4; - else - nr_regs = 0; - } - - base = ptrace_getrn(child, insn); - - read_u32(child, base + nr_regs, &alt); - break; - } - break; - - case 0x0a000000: { - /* - * bl or b - */ - signed long displ; - /* It's a branch/branch link: instead of trying to - * figure out whether the branch will be taken or not, - * we'll put a breakpoint at both locations. This is - * simpler, more reliable, and probably not a whole lot - * slower than the alternative approach of emulating the - * branch. - */ - displ = (insn & 0x00ffffff) << 8; - displ = (displ >> 6) + 8; - if (displ != 0 && displ != 4) - alt = pc + displ; - } - break; - } - - return alt; -} - -static int -swap_insn(struct task_struct *task, unsigned long addr, - void *old_insn, void *new_insn, int size) -{ - int ret; - - ret = access_process_vm(task, addr, old_insn, size, 0); - if (ret == size) - ret = access_process_vm(task, addr, new_insn, size, 1); - return ret; -} - -static void -add_breakpoint(struct task_struct *task, struct debug_info *dbg, unsigned long addr) -{ - int nr = dbg->nsaved; - - if (nr < 2) { - u32 new_insn = BREAKINST_ARM; - int res; - - res = swap_insn(task, addr, &dbg->bp[nr].insn, &new_insn, 4); - - if (res == 4) { - dbg->bp[nr].address = addr; - dbg->nsaved += 1; - } - } else - printk(KERN_ERR "ptrace: too many breakpoints\n"); -} - -/* - * Clear one breakpoint in the user program. We copy what the hardware - * does and use bit 0 of the address to indicate whether this is a Thumb - * breakpoint or an ARM breakpoint. - */ -static void clear_breakpoint(struct task_struct *task, struct debug_entry *bp) -{ - unsigned long addr = bp->address; - union debug_insn old_insn; - int ret; - - if (addr & 1) { - ret = swap_insn(task, addr & ~1, &old_insn.thumb, - &bp->insn.thumb, 2); - - if (ret != 2 || old_insn.thumb != BREAKINST_THUMB) - printk(KERN_ERR "%s:%d: corrupted Thumb breakpoint at " - "0x%08lx (0x%04x)\n", task->comm, - task_pid_nr(task), addr, old_insn.thumb); - } else { - ret = swap_insn(task, addr & ~3, &old_insn.arm, - &bp->insn.arm, 4); - - if (ret != 4 || old_insn.arm != BREAKINST_ARM) - printk(KERN_ERR "%s:%d: corrupted ARM breakpoint at " - "0x%08lx (0x%08x)\n", task->comm, - task_pid_nr(task), addr, old_insn.arm); - } -} - -void ptrace_set_bpt(struct task_struct *child) -{ - struct pt_regs *regs; - unsigned long pc; - u32 insn; - int res; - - regs = task_pt_regs(child); - pc = instruction_pointer(regs); - - if (thumb_mode(regs)) { - printk(KERN_WARNING "ptrace: can't handle thumb mode\n"); - return; - } - - res = read_instr(child, pc, &insn); - if (!res) { - struct debug_info *dbg = &child->thread.debug; - unsigned long alt; - - dbg->nsaved = 0; - - alt = get_branch_address(child, pc, insn); - if (alt) - add_breakpoint(child, dbg, alt); - - /* - * Note that we ignore the result of setting the above - * breakpoint since it may fail. When it does, this is - * not so much an error, but a forewarning that we may - * be receiving a prefetch abort shortly. - * - * If we don't set this breakpoint here, then we can - * lose control of the thread during single stepping. - */ - if (!alt || predicate(insn) != PREDICATE_ALWAYS) - add_breakpoint(child, dbg, pc + 4); - } -} - -/* - * Ensure no single-step breakpoint is pending. Returns non-zero - * value if child was being single-stepped. - */ -void ptrace_cancel_bpt(struct task_struct *child) -{ - int i, nsaved = child->thread.debug.nsaved; - - child->thread.debug.nsaved = 0; - - if (nsaved > 2) { - printk("ptrace_cancel_bpt: bogus nsaved: %d!\n", nsaved); - nsaved = 2; - } - - for (i = 0; i < nsaved; i++) - clear_breakpoint(child, &child->thread.debug.bp[i]); -} - -void user_disable_single_step(struct task_struct *task) -{ - task->ptrace &= ~PT_SINGLESTEP; - ptrace_cancel_bpt(task); -} - -void user_enable_single_step(struct task_struct *task) -{ - task->ptrace |= PT_SINGLESTEP; -} - /* * Called by kernel/ptrace.c when detaching.. */ void ptrace_disable(struct task_struct *child) { - user_disable_single_step(child); + /* Nothing to do. */ } /* @@ -576,8 +198,6 @@ void ptrace_break(struct task_struct *tsk, struct pt_regs *regs) { siginfo_t info; - ptrace_cancel_bpt(tsk); - info.si_signo = SIGTRAP; info.si_errno = 0; info.si_code = TRAP_BRKPT; @@ -689,58 +309,6 @@ static int ptrace_write_user(struct task_struct *tsk, unsigned long off, return put_user_reg(tsk, off >> 2, val); } -/* - * Get all user integer registers. - */ -static int ptrace_getregs(struct task_struct *tsk, void __user *uregs) -{ - struct pt_regs *regs = task_pt_regs(tsk); - - return copy_to_user(uregs, regs, sizeof(struct pt_regs)) ? -EFAULT : 0; -} - -/* - * Set all user integer registers. - */ -static int ptrace_setregs(struct task_struct *tsk, void __user *uregs) -{ - struct pt_regs newregs; - int ret; - - ret = -EFAULT; - if (copy_from_user(&newregs, uregs, sizeof(struct pt_regs)) == 0) { - struct pt_regs *regs = task_pt_regs(tsk); - - ret = -EINVAL; - if (valid_user_regs(&newregs)) { - *regs = newregs; - ret = 0; - } - } - - return ret; -} - -/* - * Get the child FPU state. - */ -static int ptrace_getfpregs(struct task_struct *tsk, void __user *ufp) -{ - return copy_to_user(ufp, &task_thread_info(tsk)->fpstate, - sizeof(struct user_fp)) ? -EFAULT : 0; -} - -/* - * Set the child FPU state. - */ -static int ptrace_setfpregs(struct task_struct *tsk, void __user *ufp) -{ - struct thread_info *thread = task_thread_info(tsk); - thread->used_cp[1] = thread->used_cp[2] = 1; - return copy_from_user(&thread->fpstate, ufp, - sizeof(struct user_fp)) ? -EFAULT : 0; -} - #ifdef CONFIG_IWMMXT /* @@ -799,56 +367,6 @@ static int ptrace_setcrunchregs(struct task_struct *tsk, void __user *ufp) } #endif -#ifdef CONFIG_VFP -/* - * Get the child VFP state. - */ -static int ptrace_getvfpregs(struct task_struct *tsk, void __user *data) -{ - struct thread_info *thread = task_thread_info(tsk); - union vfp_state *vfp = &thread->vfpstate; - struct user_vfp __user *ufp = data; - - vfp_sync_hwstate(thread); - - /* copy the floating point registers */ - if (copy_to_user(&ufp->fpregs, &vfp->hard.fpregs, - sizeof(vfp->hard.fpregs))) - return -EFAULT; - - /* copy the status and control register */ - if (put_user(vfp->hard.fpscr, &ufp->fpscr)) - return -EFAULT; - - return 0; -} - -/* - * Set the child VFP state. - */ -static int ptrace_setvfpregs(struct task_struct *tsk, void __user *data) -{ - struct thread_info *thread = task_thread_info(tsk); - union vfp_state *vfp = &thread->vfpstate; - struct user_vfp __user *ufp = data; - - vfp_sync_hwstate(thread); - - /* copy the floating point registers */ - if (copy_from_user(&vfp->hard.fpregs, &ufp->fpregs, - sizeof(vfp->hard.fpregs))) - return -EFAULT; - - /* copy the status and control register */ - if (get_user(vfp->hard.fpscr, &ufp->fpscr)) - return -EFAULT; - - vfp_flush_hwstate(thread); - - return 0; -} -#endif - #ifdef CONFIG_HAVE_HW_BREAKPOINT /* * Convert a virtual register number into an index for a thread_info @@ -1075,6 +593,219 @@ out: } #endif +/* regset get/set implementations */ + +static int gpr_get(struct task_struct *target, + const struct user_regset *regset, + unsigned int pos, unsigned int count, + void *kbuf, void __user *ubuf) +{ + struct pt_regs *regs = task_pt_regs(target); + + return user_regset_copyout(&pos, &count, &kbuf, &ubuf, + regs, + 0, sizeof(*regs)); +} + +static int gpr_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; + struct pt_regs newregs; + + ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, + &newregs, + 0, sizeof(newregs)); + if (ret) + return ret; + + if (!valid_user_regs(&newregs)) + return -EINVAL; + + *task_pt_regs(target) = newregs; + return 0; +} + +static int fpa_get(struct task_struct *target, + const struct user_regset *regset, + unsigned int pos, unsigned int count, + void *kbuf, void __user *ubuf) +{ + return user_regset_copyout(&pos, &count, &kbuf, &ubuf, + &task_thread_info(target)->fpstate, + 0, sizeof(struct user_fp)); +} + +static int fpa_set(struct task_struct *target, + const struct user_regset *regset, + unsigned int pos, unsigned int count, + const void *kbuf, const void __user *ubuf) +{ + struct thread_info *thread = task_thread_info(target); + + thread->used_cp[1] = thread->used_cp[2] = 1; + + return user_regset_copyin(&pos, &count, &kbuf, &ubuf, + &thread->fpstate, + 0, sizeof(struct user_fp)); +} + +#ifdef CONFIG_VFP +/* + * VFP register get/set implementations. + * + * With respect to the kernel, struct user_fp is divided into three chunks: + * 16 or 32 real VFP registers (d0-d15 or d0-31) + * These are transferred to/from the real registers in the task's + * vfp_hard_struct. The number of registers depends on the kernel + * configuration. + * + * 16 or 0 fake VFP registers (d16-d31 or empty) + * i.e., the user_vfp structure has space for 32 registers even if + * the kernel doesn't have them all. + * + * vfp_get() reads this chunk as zero where applicable + * vfp_set() ignores this chunk + * + * 1 word for the FPSCR + * + * The bounds-checking logic built into user_regset_copyout and friends + * means that we can make a simple sequence of calls to map the relevant data + * to/from the specified slice of the user regset structure. + */ +static int vfp_get(struct task_struct *target, + const struct user_regset *regset, + unsigned int pos, unsigned int count, + void *kbuf, void __user *ubuf) +{ + int ret; + struct thread_info *thread = task_thread_info(target); + struct vfp_hard_struct const *vfp = &thread->vfpstate.hard; + const size_t user_fpregs_offset = offsetof(struct user_vfp, fpregs); + const size_t user_fpscr_offset = offsetof(struct user_vfp, fpscr); + + vfp_sync_hwstate(thread); + + ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, + &vfp->fpregs, + user_fpregs_offset, + user_fpregs_offset + sizeof(vfp->fpregs)); + if (ret) + return ret; + + ret = user_regset_copyout_zero(&pos, &count, &kbuf, &ubuf, + user_fpregs_offset + sizeof(vfp->fpregs), + user_fpscr_offset); + if (ret) + return ret; + + return user_regset_copyout(&pos, &count, &kbuf, &ubuf, + &vfp->fpscr, + user_fpscr_offset, + user_fpscr_offset + sizeof(vfp->fpscr)); +} + +/* + * For vfp_set() a read-modify-write is done on the VFP registers, + * in order to avoid writing back a half-modified set of registers on + * failure. + */ +static int vfp_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; + struct thread_info *thread = task_thread_info(target); + struct vfp_hard_struct new_vfp = thread->vfpstate.hard; + const size_t user_fpregs_offset = offsetof(struct user_vfp, fpregs); + const size_t user_fpscr_offset = offsetof(struct user_vfp, fpscr); + + ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, + &new_vfp.fpregs, + user_fpregs_offset, + user_fpregs_offset + sizeof(new_vfp.fpregs)); + if (ret) + return ret; + + ret = user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf, + user_fpregs_offset + sizeof(new_vfp.fpregs), + user_fpscr_offset); + if (ret) + return ret; + + ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, + &new_vfp.fpscr, + user_fpscr_offset, + user_fpscr_offset + sizeof(new_vfp.fpscr)); + if (ret) + return ret; + + vfp_sync_hwstate(thread); + thread->vfpstate.hard = new_vfp; + vfp_flush_hwstate(thread); + + return 0; +} +#endif /* CONFIG_VFP */ + +enum arm_regset { + REGSET_GPR, + REGSET_FPR, +#ifdef CONFIG_VFP + REGSET_VFP, +#endif +}; + +static const struct user_regset arm_regsets[] = { + [REGSET_GPR] = { + .core_note_type = NT_PRSTATUS, + .n = ELF_NGREG, + .size = sizeof(u32), + .align = sizeof(u32), + .get = gpr_get, + .set = gpr_set + }, + [REGSET_FPR] = { + /* + * For the FPA regs in fpstate, the real fields are a mixture + * of sizes, so pretend that the registers are word-sized: + */ + .core_note_type = NT_PRFPREG, + .n = sizeof(struct user_fp) / sizeof(u32), + .size = sizeof(u32), + .align = sizeof(u32), + .get = fpa_get, + .set = fpa_set + }, +#ifdef CONFIG_VFP + [REGSET_VFP] = { + /* + * Pretend that the VFP regs are word-sized, since the FPSCR is + * a single word dangling at the end of struct user_vfp: + */ + .core_note_type = NT_ARM_VFP, + .n = ARM_VFPREGS_SIZE / sizeof(u32), + .size = sizeof(u32), + .align = sizeof(u32), + .get = vfp_get, + .set = vfp_set + }, +#endif /* CONFIG_VFP */ +}; + +static const struct user_regset_view user_arm_view = { + .name = "arm", .e_machine = ELF_ARCH, .ei_osabi = ELF_OSABI, + .regsets = arm_regsets, .n = ARRAY_SIZE(arm_regsets) +}; + +const struct user_regset_view *task_user_regset_view(struct task_struct *task) +{ + return &user_arm_view; +} + long arch_ptrace(struct task_struct *child, long request, unsigned long addr, unsigned long data) { @@ -1091,19 +822,31 @@ long arch_ptrace(struct task_struct *child, long request, break; case PTRACE_GETREGS: - ret = ptrace_getregs(child, datap); + ret = copy_regset_to_user(child, + &user_arm_view, REGSET_GPR, + 0, sizeof(struct pt_regs), + datap); break; case PTRACE_SETREGS: - ret = ptrace_setregs(child, datap); + ret = copy_regset_from_user(child, + &user_arm_view, REGSET_GPR, + 0, sizeof(struct pt_regs), + datap); break; case PTRACE_GETFPREGS: - ret = ptrace_getfpregs(child, datap); + ret = copy_regset_to_user(child, + &user_arm_view, REGSET_FPR, + 0, sizeof(union fp_state), + datap); break; - + case PTRACE_SETFPREGS: - ret = ptrace_setfpregs(child, datap); + ret = copy_regset_from_user(child, + &user_arm_view, REGSET_FPR, + 0, sizeof(union fp_state), + datap); break; #ifdef CONFIG_IWMMXT @@ -1138,22 +881,36 @@ long arch_ptrace(struct task_struct *child, long request, #ifdef CONFIG_VFP case PTRACE_GETVFPREGS: - ret = ptrace_getvfpregs(child, datap); + ret = copy_regset_to_user(child, + &user_arm_view, REGSET_VFP, + 0, ARM_VFPREGS_SIZE, + datap); break; case PTRACE_SETVFPREGS: - ret = ptrace_setvfpregs(child, datap); + ret = copy_regset_from_user(child, + &user_arm_view, REGSET_VFP, + 0, ARM_VFPREGS_SIZE, + datap); break; #endif #ifdef CONFIG_HAVE_HW_BREAKPOINT case PTRACE_GETHBPREGS: + if (ptrace_get_breakpoints(child) < 0) + return -ESRCH; + ret = ptrace_gethbpregs(child, addr, (unsigned long __user *)data); + ptrace_put_breakpoints(child); break; case PTRACE_SETHBPREGS: + if (ptrace_get_breakpoints(child) < 0) + return -ESRCH; + ret = ptrace_sethbpregs(child, addr, (unsigned long __user *)data); + ptrace_put_breakpoints(child); break; #endif |