Commit 70ba5e7c authored by Russell King's avatar Russell King

[ARM] switch ptrace to use an undefined instruction

This avoids a problem with the original ptrace code using a system
call (SWI) to implement single stepping; programs such as ltrace
do not expect to receive system call trace traps when breakpoints
are hit.
parent 9a8185da
...@@ -18,10 +18,12 @@ ...@@ -18,10 +18,12 @@
#include <linux/ptrace.h> #include <linux/ptrace.h>
#include <linux/user.h> #include <linux/user.h>
#include <linux/security.h> #include <linux/security.h>
#include <linux/init.h>
#include <asm/uaccess.h> #include <asm/uaccess.h>
#include <asm/pgtable.h> #include <asm/pgtable.h>
#include <asm/system.h> #include <asm/system.h>
#include <asm/traps.h>
#include "ptrace.h" #include "ptrace.h"
...@@ -32,7 +34,7 @@ ...@@ -32,7 +34,7 @@
* in exit.c or in signal.c. * in exit.c or in signal.c.
*/ */
#if 1 #if 0
/* /*
* Breakpoint SWI instruction: SWI &9F0001 * Breakpoint SWI instruction: SWI &9F0001
*/ */
...@@ -479,25 +481,47 @@ void ptrace_break(struct task_struct *tsk, struct pt_regs *regs) ...@@ -479,25 +481,47 @@ void ptrace_break(struct task_struct *tsk, struct pt_regs *regs)
{ {
siginfo_t info; siginfo_t info;
/*
* The PC is always left pointing at the next instruction. Fix this.
*/
regs->ARM_pc -= 4;
if (tsk->thread.debug.nsaved == 0)
printk(KERN_ERR "ptrace: bogus breakpoint trap\n");
ptrace_cancel_bpt(tsk); ptrace_cancel_bpt(tsk);
info.si_signo = SIGTRAP; info.si_signo = SIGTRAP;
info.si_errno = 0; info.si_errno = 0;
info.si_code = TRAP_BRKPT; info.si_code = TRAP_BRKPT;
info.si_addr = (void *)instruction_pointer(regs) - info.si_addr = (void *)instruction_pointer(regs);
(thumb_mode(regs) ? 2 : 4);
force_sig_info(SIGTRAP, &info, tsk); 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 * Read the word at offset "off" into the "struct user". We
* actually access the pt_regs stored on the kernel stack. * actually access the pt_regs stored on the kernel stack.
......
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
#include <asm/system.h> #include <asm/system.h>
#include <asm/uaccess.h> #include <asm/uaccess.h>
#include <asm/unistd.h> #include <asm/unistd.h>
#include <asm/semaphore.h> #include <asm/traps.h>
#include "ptrace.h" #include "ptrace.h"
...@@ -240,17 +240,56 @@ void die_if_kernel(const char *str, struct pt_regs *regs, int err) ...@@ -240,17 +240,56 @@ void die_if_kernel(const char *str, struct pt_regs *regs, int err)
die(str, regs, err); die(str, regs, err);
} }
static LIST_HEAD(undef_hook);
static spinlock_t undef_lock = SPIN_LOCK_UNLOCKED;
void register_undef_hook(struct undef_hook *hook)
{
spin_lock_irq(&undef_lock);
list_add(&hook->node, &undef_hook);
spin_unlock_irq(&undef_lock);
}
void unregister_undef_hook(struct undef_hook *hook)
{
spin_lock_irq(&undef_lock);
list_del(&hook->node);
spin_unlock_irq(&undef_lock);
}
asmlinkage void do_undefinstr(struct pt_regs *regs) asmlinkage void do_undefinstr(struct pt_regs *regs)
{ {
unsigned long *pc; unsigned int correction = thumb_mode(regs) ? 2 : 4;
unsigned int instr;
struct undef_hook *hook;
siginfo_t info; siginfo_t info;
void *pc;
/* /*
* According to the ARM ARM, PC is 2 or 4 bytes ahead, depending * According to the ARM ARM, PC is 2 or 4 bytes ahead,
* whether we're in Thumb mode or not. * depending whether we're in Thumb mode or not.
* Correct this offset.
*/ */
regs->ARM_pc -= thumb_mode(regs) ? 2 : 4; regs->ARM_pc -= correction;
pc = (unsigned long *)instruction_pointer(regs);
pc = (void *)instruction_pointer(regs);
if (thumb_mode(regs)) {
get_user(instr, (u16 *)pc);
} else {
get_user(instr, (u32 *)pc);
}
spin_lock_irq(&undef_lock);
list_for_each_entry(hook, &undef_hook, node) {
if ((instr & hook->instr_mask) == hook->instr_val &&
(regs->ARM_cpsr & hook->cpsr_mask) == hook->cpsr_val) {
if (hook->fn(regs, instr) == 0) {
spin_unlock_irq(&undef_lock);
return;
}
}
}
spin_unlock_irq(&undef_lock);
#ifdef CONFIG_DEBUG_USER #ifdef CONFIG_DEBUG_USER
printk(KERN_INFO "%s (%d): undefined instruction: pc=%p\n", printk(KERN_INFO "%s (%d): undefined instruction: pc=%p\n",
...@@ -377,6 +416,7 @@ asmlinkage int arm_syscall(int no, struct pt_regs *regs) ...@@ -377,6 +416,7 @@ asmlinkage int arm_syscall(int no, struct pt_regs *regs)
return 0; return 0;
case NR(breakpoint): /* SWI BREAK_POINT */ case NR(breakpoint): /* SWI BREAK_POINT */
regs->ARM_pc -= thumb_mode(regs) ? 2 : 4;
ptrace_break(current, regs); ptrace_break(current, regs);
return regs->ARM_r0; return regs->ARM_r0;
......
#ifndef _ASMARM_TRAP_H
#define _ASMARM_TRAP_H
#include <linux/list.h>
struct undef_hook {
struct list_head node;
u32 instr_mask;
u32 instr_val;
u32 cpsr_mask;
u32 cpsr_val;
int (*fn)(struct pt_regs *regs, unsigned int instr);
};
void register_undef_hook(struct undef_hook *hook);
void unregister_undef_hook(struct undef_hook *hook);
#endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment