|
@@ -26,8 +26,6 @@
|
|
|
#include <asm/system.h>
|
|
|
#include <asm/traps.h>
|
|
|
|
|
|
-#include "ptrace.h"
|
|
|
-
|
|
|
#define REG_PC 15
|
|
|
#define REG_PSR 16
|
|
|
/*
|
|
@@ -184,389 +182,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 +197,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;
|