![Paul Brook](/assets/img/avatar_default.png)
The patch below adds ARM ptrace functions to get the process load address. This is required for useful userspace debugging on mmuless systems. These values are obtained by reading magic offsets with PTRACE_PEEKUSR, as on other nommu targets. I picked arbitrary large values for the offsets. Signed-off-by: Paul Brook <paul@codesourcery.com>
885 lines
19 KiB
C
885 lines
19 KiB
C
/*
|
|
* linux/arch/arm/kernel/ptrace.c
|
|
*
|
|
* By Ross Biro 1/23/92
|
|
* edited by Linus Torvalds
|
|
* ARM modifications Copyright (C) 2000 Russell King
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
#include <linux/kernel.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/smp.h>
|
|
#include <linux/ptrace.h>
|
|
#include <linux/user.h>
|
|
#include <linux/security.h>
|
|
#include <linux/init.h>
|
|
#include <linux/signal.h>
|
|
#include <linux/uaccess.h>
|
|
|
|
#include <asm/pgtable.h>
|
|
#include <asm/system.h>
|
|
#include <asm/traps.h>
|
|
|
|
#include "ptrace.h"
|
|
|
|
#define REG_PC 15
|
|
#define REG_PSR 16
|
|
/*
|
|
* does not yet catch signals sent when the child dies.
|
|
* in exit.c or in signal.c.
|
|
*/
|
|
|
|
#if 0
|
|
/*
|
|
* Breakpoint SWI instruction: SWI &9F0001
|
|
*/
|
|
#define BREAKINST_ARM 0xef9f0001
|
|
#define BREAKINST_THUMB 0xdf00 /* fill this in later */
|
|
#else
|
|
/*
|
|
* New breakpoints - use an undefined instruction. The ARM architecture
|
|
* reference manual guarantees that the following instruction space
|
|
* will produce an undefined instruction exception on all CPUs:
|
|
*
|
|
* ARM: xxxx 0111 1111 xxxx xxxx xxxx 1111 xxxx
|
|
* Thumb: 1101 1110 xxxx xxxx
|
|
*/
|
|
#define BREAKINST_ARM 0xe7f001f0
|
|
#define BREAKINST_THUMB 0xde01
|
|
#endif
|
|
|
|
/*
|
|
* this routine will get a word off of the processes privileged stack.
|
|
* the offset is how far from the base addr as stored in the THREAD.
|
|
* this routine assumes that all the privileged stacks are in our
|
|
* data space.
|
|
*/
|
|
static inline long get_user_reg(struct task_struct *task, int offset)
|
|
{
|
|
return task_pt_regs(task)->uregs[offset];
|
|
}
|
|
|
|
/*
|
|
* this routine will put a word on the processes privileged stack.
|
|
* the offset is how far from the base addr as stored in the THREAD.
|
|
* this routine assumes that all the privileged stacks are in our
|
|
* data space.
|
|
*/
|
|
static inline int
|
|
put_user_reg(struct task_struct *task, int offset, long data)
|
|
{
|
|
struct pt_regs newregs, *regs = task_pt_regs(task);
|
|
int ret = -EINVAL;
|
|
|
|
newregs = *regs;
|
|
newregs.uregs[offset] = data;
|
|
|
|
if (valid_user_regs(&newregs)) {
|
|
regs->uregs[offset] = data;
|
|
ret = 0;
|
|
}
|
|
|
|
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]);
|
|
}
|
|
|
|
/*
|
|
* Called by kernel/ptrace.c when detaching..
|
|
*/
|
|
void ptrace_disable(struct task_struct *child)
|
|
{
|
|
single_step_disable(child);
|
|
}
|
|
|
|
/*
|
|
* Handle hitting a breakpoint.
|
|
*/
|
|
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;
|
|
info.si_addr = (void __user *)instruction_pointer(regs);
|
|
|
|
force_sig_info(SIGTRAP, &info, tsk);
|
|
}
|
|
|
|
static int break_trap(struct pt_regs *regs, unsigned int instr)
|
|
{
|
|
ptrace_break(current, regs);
|
|
return 0;
|
|
}
|
|
|
|
static struct undef_hook arm_break_hook = {
|
|
.instr_mask = 0x0fffffff,
|
|
.instr_val = 0x07f001f0,
|
|
.cpsr_mask = PSR_T_BIT,
|
|
.cpsr_val = 0,
|
|
.fn = break_trap,
|
|
};
|
|
|
|
static struct undef_hook thumb_break_hook = {
|
|
.instr_mask = 0xffff,
|
|
.instr_val = 0xde01,
|
|
.cpsr_mask = PSR_T_BIT,
|
|
.cpsr_val = PSR_T_BIT,
|
|
.fn = break_trap,
|
|
};
|
|
|
|
static int __init ptrace_break_init(void)
|
|
{
|
|
register_undef_hook(&arm_break_hook);
|
|
register_undef_hook(&thumb_break_hook);
|
|
return 0;
|
|
}
|
|
|
|
core_initcall(ptrace_break_init);
|
|
|
|
/*
|
|
* Read the word at offset "off" into the "struct user". We
|
|
* actually access the pt_regs stored on the kernel stack.
|
|
*/
|
|
static int ptrace_read_user(struct task_struct *tsk, unsigned long off,
|
|
unsigned long __user *ret)
|
|
{
|
|
unsigned long tmp;
|
|
|
|
if (off & 3 || off >= sizeof(struct user))
|
|
return -EIO;
|
|
|
|
tmp = 0;
|
|
if (off == PT_TEXT_ADDR)
|
|
tmp = tsk->mm->start_code;
|
|
else if (off == PT_DATA_ADDR)
|
|
tmp = tsk->mm->start_data;
|
|
else if (off == PT_TEXT_END_ADDR)
|
|
tmp = tsk->mm->end_code;
|
|
else if (off < sizeof(struct pt_regs))
|
|
tmp = get_user_reg(tsk, off >> 2);
|
|
|
|
return put_user(tmp, ret);
|
|
}
|
|
|
|
/*
|
|
* Write the word at offset "off" into "struct user". We
|
|
* actually access the pt_regs stored on the kernel stack.
|
|
*/
|
|
static int ptrace_write_user(struct task_struct *tsk, unsigned long off,
|
|
unsigned long val)
|
|
{
|
|
if (off & 3 || off >= sizeof(struct user))
|
|
return -EIO;
|
|
|
|
if (off >= sizeof(struct pt_regs))
|
|
return 0;
|
|
|
|
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
|
|
|
|
/*
|
|
* Get the child iWMMXt state.
|
|
*/
|
|
static int ptrace_getwmmxregs(struct task_struct *tsk, void __user *ufp)
|
|
{
|
|
struct thread_info *thread = task_thread_info(tsk);
|
|
|
|
if (!test_ti_thread_flag(thread, TIF_USING_IWMMXT))
|
|
return -ENODATA;
|
|
iwmmxt_task_disable(thread); /* force it to ram */
|
|
return copy_to_user(ufp, &thread->fpstate.iwmmxt, IWMMXT_SIZE)
|
|
? -EFAULT : 0;
|
|
}
|
|
|
|
/*
|
|
* Set the child iWMMXt state.
|
|
*/
|
|
static int ptrace_setwmmxregs(struct task_struct *tsk, void __user *ufp)
|
|
{
|
|
struct thread_info *thread = task_thread_info(tsk);
|
|
|
|
if (!test_ti_thread_flag(thread, TIF_USING_IWMMXT))
|
|
return -EACCES;
|
|
iwmmxt_task_release(thread); /* force a reload */
|
|
return copy_from_user(&thread->fpstate.iwmmxt, ufp, IWMMXT_SIZE)
|
|
? -EFAULT : 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef CONFIG_CRUNCH
|
|
/*
|
|
* Get the child Crunch state.
|
|
*/
|
|
static int ptrace_getcrunchregs(struct task_struct *tsk, void __user *ufp)
|
|
{
|
|
struct thread_info *thread = task_thread_info(tsk);
|
|
|
|
crunch_task_disable(thread); /* force it to ram */
|
|
return copy_to_user(ufp, &thread->crunchstate, CRUNCH_SIZE)
|
|
? -EFAULT : 0;
|
|
}
|
|
|
|
/*
|
|
* Set the child Crunch state.
|
|
*/
|
|
static int ptrace_setcrunchregs(struct task_struct *tsk, void __user *ufp)
|
|
{
|
|
struct thread_info *thread = task_thread_info(tsk);
|
|
|
|
crunch_task_release(thread); /* force a reload */
|
|
return copy_from_user(&thread->crunchstate, ufp, CRUNCH_SIZE)
|
|
? -EFAULT : 0;
|
|
}
|
|
#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_state(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_state(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;
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
long arch_ptrace(struct task_struct *child, long request, long addr, long data)
|
|
{
|
|
int ret;
|
|
|
|
switch (request) {
|
|
/*
|
|
* read word at location "addr" in the child process.
|
|
*/
|
|
case PTRACE_PEEKTEXT:
|
|
case PTRACE_PEEKDATA:
|
|
ret = generic_ptrace_peekdata(child, addr, data);
|
|
break;
|
|
|
|
case PTRACE_PEEKUSR:
|
|
ret = ptrace_read_user(child, addr, (unsigned long __user *)data);
|
|
break;
|
|
|
|
/*
|
|
* write the word at location addr.
|
|
*/
|
|
case PTRACE_POKETEXT:
|
|
case PTRACE_POKEDATA:
|
|
ret = generic_ptrace_pokedata(child, addr, data);
|
|
break;
|
|
|
|
case PTRACE_POKEUSR:
|
|
ret = ptrace_write_user(child, addr, data);
|
|
break;
|
|
|
|
/*
|
|
* continue/restart and stop at next (return from) syscall
|
|
*/
|
|
case PTRACE_SYSCALL:
|
|
case PTRACE_CONT:
|
|
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;
|
|
single_step_disable(child);
|
|
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:
|
|
single_step_disable(child);
|
|
if (child->exit_state != EXIT_ZOMBIE) {
|
|
child->exit_code = SIGKILL;
|
|
wake_up_process(child);
|
|
}
|
|
ret = 0;
|
|
break;
|
|
|
|
/*
|
|
* execute single instruction.
|
|
*/
|
|
case PTRACE_SINGLESTEP:
|
|
ret = -EIO;
|
|
if (!valid_signal(data))
|
|
break;
|
|
single_step_enable(child);
|
|
clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE);
|
|
child->exit_code = data;
|
|
/* give it a chance to run. */
|
|
wake_up_process(child);
|
|
ret = 0;
|
|
break;
|
|
|
|
case PTRACE_GETREGS:
|
|
ret = ptrace_getregs(child, (void __user *)data);
|
|
break;
|
|
|
|
case PTRACE_SETREGS:
|
|
ret = ptrace_setregs(child, (void __user *)data);
|
|
break;
|
|
|
|
case PTRACE_GETFPREGS:
|
|
ret = ptrace_getfpregs(child, (void __user *)data);
|
|
break;
|
|
|
|
case PTRACE_SETFPREGS:
|
|
ret = ptrace_setfpregs(child, (void __user *)data);
|
|
break;
|
|
|
|
#ifdef CONFIG_IWMMXT
|
|
case PTRACE_GETWMMXREGS:
|
|
ret = ptrace_getwmmxregs(child, (void __user *)data);
|
|
break;
|
|
|
|
case PTRACE_SETWMMXREGS:
|
|
ret = ptrace_setwmmxregs(child, (void __user *)data);
|
|
break;
|
|
#endif
|
|
|
|
case PTRACE_GET_THREAD_AREA:
|
|
ret = put_user(task_thread_info(child)->tp_value,
|
|
(unsigned long __user *) data);
|
|
break;
|
|
|
|
case PTRACE_SET_SYSCALL:
|
|
task_thread_info(child)->syscall = data;
|
|
ret = 0;
|
|
break;
|
|
|
|
#ifdef CONFIG_CRUNCH
|
|
case PTRACE_GETCRUNCHREGS:
|
|
ret = ptrace_getcrunchregs(child, (void __user *)data);
|
|
break;
|
|
|
|
case PTRACE_SETCRUNCHREGS:
|
|
ret = ptrace_setcrunchregs(child, (void __user *)data);
|
|
break;
|
|
#endif
|
|
|
|
#ifdef CONFIG_VFP
|
|
case PTRACE_GETVFPREGS:
|
|
ret = ptrace_getvfpregs(child, (void __user *)data);
|
|
break;
|
|
|
|
case PTRACE_SETVFPREGS:
|
|
ret = ptrace_setvfpregs(child, (void __user *)data);
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
ret = ptrace_request(child, request, addr, data);
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
asmlinkage int syscall_trace(int why, struct pt_regs *regs, int scno)
|
|
{
|
|
unsigned long ip;
|
|
|
|
if (!test_thread_flag(TIF_SYSCALL_TRACE))
|
|
return scno;
|
|
if (!(current->ptrace & PT_PTRACED))
|
|
return scno;
|
|
|
|
/*
|
|
* Save IP. IP is used to denote syscall entry/exit:
|
|
* IP = 0 -> entry, = 1 -> exit
|
|
*/
|
|
ip = regs->ARM_ip;
|
|
regs->ARM_ip = why;
|
|
|
|
current_thread_info()->syscall = scno;
|
|
|
|
/* 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;
|
|
}
|
|
regs->ARM_ip = ip;
|
|
|
|
return current_thread_info()->syscall;
|
|
}
|