2005-04-16 15:20:36 -07:00
/* ptrace.c */
/* By Ross Biro 1/23/92 */
/* edited by Linus Torvalds */
/* mangled further by Bob Manson (manson@santafe.edu) */
/* more mutilation by David Mosberger (davidm@azstarnet.com) */
# include <linux/kernel.h>
# include <linux/sched.h>
# include <linux/mm.h>
# include <linux/smp.h>
# include <linux/smp_lock.h>
# include <linux/errno.h>
# include <linux/ptrace.h>
# include <linux/user.h>
# include <linux/slab.h>
# include <linux/security.h>
2005-05-01 08:59:14 -07:00
# include <linux/signal.h>
2005-04-16 15:20:36 -07:00
# include <asm/uaccess.h>
# include <asm/pgtable.h>
# include <asm/system.h>
# include <asm/fpu.h>
# include "proto.h"
# define DEBUG DBG_MEM
# undef DEBUG
# ifdef DEBUG
enum {
DBG_MEM = ( 1 < < 0 ) ,
DBG_BPT = ( 1 < < 1 ) ,
DBG_MEM_ALL = ( 1 < < 2 )
} ;
# define DBG(fac,args) {if ((fac) & DEBUG) printk args;}
# else
# define DBG(fac,args)
# endif
# define BREAKINST 0x00000080 /* call_pal bpt */
/*
* does not yet catch signals sent when the child dies .
* in exit . c or in signal . c .
*/
/*
* Processes always block with the following stack - layout :
*
* + = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + < - - - - task + 2 * PAGE_SIZE
* | PALcode saved frame ( ps , pc , | ^
* | gp , a0 , a1 , a2 ) | |
* + = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + | struct pt_regs
* | | |
* | frame generated by SAVE_ALL | |
* | | v
* + = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +
* | | ^
* | frame saved by do_switch_stack | | struct switch_stack
* | | v
* + = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +
*/
/*
* The following table maps a register index into the stack offset at
* which the register is saved . Register indices are 0 - 31 for integer
* regs , 32 - 63 for fp regs , and 64 for the pc . Notice that sp and
* zero have no stack - slot and need to be treated specially ( see
* get_reg / put_reg below ) .
*/
enum {
REG_R0 = 0 , REG_F0 = 32 , REG_FPCR = 63 , REG_PC = 64
} ;
static int regoff [ ] = {
PT_REG ( r0 ) , PT_REG ( r1 ) , PT_REG ( r2 ) , PT_REG ( r3 ) ,
PT_REG ( r4 ) , PT_REG ( r5 ) , PT_REG ( r6 ) , PT_REG ( r7 ) ,
PT_REG ( r8 ) , SW_REG ( r9 ) , SW_REG ( r10 ) , SW_REG ( r11 ) ,
SW_REG ( r12 ) , SW_REG ( r13 ) , SW_REG ( r14 ) , SW_REG ( r15 ) ,
PT_REG ( r16 ) , PT_REG ( r17 ) , PT_REG ( r18 ) , PT_REG ( r19 ) ,
PT_REG ( r20 ) , PT_REG ( r21 ) , PT_REG ( r22 ) , PT_REG ( r23 ) ,
PT_REG ( r24 ) , PT_REG ( r25 ) , PT_REG ( r26 ) , PT_REG ( r27 ) ,
PT_REG ( r28 ) , PT_REG ( gp ) , - 1 , - 1 ,
SW_REG ( fp [ 0 ] ) , SW_REG ( fp [ 1 ] ) , SW_REG ( fp [ 2 ] ) , SW_REG ( fp [ 3 ] ) ,
SW_REG ( fp [ 4 ] ) , SW_REG ( fp [ 5 ] ) , SW_REG ( fp [ 6 ] ) , SW_REG ( fp [ 7 ] ) ,
SW_REG ( fp [ 8 ] ) , SW_REG ( fp [ 9 ] ) , SW_REG ( fp [ 10 ] ) , SW_REG ( fp [ 11 ] ) ,
SW_REG ( fp [ 12 ] ) , SW_REG ( fp [ 13 ] ) , SW_REG ( fp [ 14 ] ) , SW_REG ( fp [ 15 ] ) ,
SW_REG ( fp [ 16 ] ) , SW_REG ( fp [ 17 ] ) , SW_REG ( fp [ 18 ] ) , SW_REG ( fp [ 19 ] ) ,
SW_REG ( fp [ 20 ] ) , SW_REG ( fp [ 21 ] ) , SW_REG ( fp [ 22 ] ) , SW_REG ( fp [ 23 ] ) ,
SW_REG ( fp [ 24 ] ) , SW_REG ( fp [ 25 ] ) , SW_REG ( fp [ 26 ] ) , SW_REG ( fp [ 27 ] ) ,
SW_REG ( fp [ 28 ] ) , SW_REG ( fp [ 29 ] ) , SW_REG ( fp [ 30 ] ) , SW_REG ( fp [ 31 ] ) ,
PT_REG ( pc )
} ;
static unsigned long zero ;
/*
* Get address of register REGNO in task TASK .
*/
static unsigned long *
get_reg_addr ( struct task_struct * task , unsigned long regno )
{
unsigned long * addr ;
if ( regno = = 30 ) {
2006-01-12 01:05:36 -08:00
addr = & task_thread_info ( task ) - > pcb . usp ;
2005-04-16 15:20:36 -07:00
} else if ( regno = = 65 ) {
2006-01-12 01:05:36 -08:00
addr = & task_thread_info ( task ) - > pcb . unique ;
2005-04-16 15:20:36 -07:00
} else if ( regno = = 31 | | regno > 65 ) {
zero = 0 ;
addr = & zero ;
} else {
addr = ( void * ) task - > thread_info + regoff [ regno ] ;
}
return addr ;
}
/*
* Get contents of register REGNO in task TASK .
*/
static unsigned long
get_reg ( struct task_struct * task , unsigned long regno )
{
/* Special hack for fpcr -- combine hardware and software bits. */
if ( regno = = 63 ) {
unsigned long fpcr = * get_reg_addr ( task , regno ) ;
unsigned long swcr
2006-01-12 01:05:36 -08:00
= task_thread_info ( task ) - > ieee_state & IEEE_SW_MASK ;
2005-04-16 15:20:36 -07:00
swcr = swcr_update_status ( swcr , fpcr ) ;
return fpcr | swcr ;
}
return * get_reg_addr ( task , regno ) ;
}
/*
* Write contents of register REGNO in task TASK .
*/
static int
put_reg ( struct task_struct * task , unsigned long regno , unsigned long data )
{
if ( regno = = 63 ) {
2006-01-12 01:05:36 -08:00
task_thread_info ( task ) - > ieee_state
= ( ( task_thread_info ( task ) - > ieee_state & ~ IEEE_SW_MASK )
2005-04-16 15:20:36 -07:00
| ( data & IEEE_SW_MASK ) ) ;
data = ( data & FPCR_DYN_MASK ) | ieee_swcr_to_fpcr ( data ) ;
}
* get_reg_addr ( task , regno ) = data ;
return 0 ;
}
static inline int
read_int ( struct task_struct * task , unsigned long addr , int * data )
{
int copied = access_process_vm ( task , addr , data , sizeof ( int ) , 0 ) ;
return ( copied = = sizeof ( int ) ) ? 0 : - EIO ;
}
static inline int
write_int ( struct task_struct * task , unsigned long addr , int data )
{
int copied = access_process_vm ( task , addr , & data , sizeof ( int ) , 1 ) ;
return ( copied = = sizeof ( int ) ) ? 0 : - EIO ;
}
/*
* Set breakpoint .
*/
int
ptrace_set_bpt ( struct task_struct * child )
{
int displ , i , res , reg_b , nsaved = 0 ;
unsigned int insn , op_code ;
unsigned long pc ;
pc = get_reg ( child , REG_PC ) ;
res = read_int ( child , pc , ( int * ) & insn ) ;
if ( res < 0 )
return res ;
op_code = insn > > 26 ;
if ( op_code > = 0x30 ) {
/*
* It ' s a branch : instead of trying to figure out
* whether the branch will be taken or not , we ' ll put
* a breakpoint at either location . This is simpler ,
* more reliable , and probably not a whole lot slower
* than the alternative approach of emulating the
* branch ( emulation can be tricky for fp branches ) .
*/
displ = ( ( s32 ) ( insn < < 11 ) ) > > 9 ;
2006-01-12 01:05:36 -08:00
task_thread_info ( child ) - > bpt_addr [ nsaved + + ] = pc + 4 ;
2005-04-16 15:20:36 -07:00
if ( displ ) /* guard against unoptimized code */
2006-01-12 01:05:36 -08:00
task_thread_info ( child ) - > bpt_addr [ nsaved + + ]
2005-04-16 15:20:36 -07:00
= pc + 4 + displ ;
DBG ( DBG_BPT , ( " execing branch \n " ) ) ;
} else if ( op_code = = 0x1a ) {
reg_b = ( insn > > 16 ) & 0x1f ;
2006-01-12 01:05:36 -08:00
task_thread_info ( child ) - > bpt_addr [ nsaved + + ] = get_reg ( child , reg_b ) ;
2005-04-16 15:20:36 -07:00
DBG ( DBG_BPT , ( " execing jump \n " ) ) ;
} else {
2006-01-12 01:05:36 -08:00
task_thread_info ( child ) - > bpt_addr [ nsaved + + ] = pc + 4 ;
2005-04-16 15:20:36 -07:00
DBG ( DBG_BPT , ( " execing normal insn \n " ) ) ;
}
/* install breakpoints: */
for ( i = 0 ; i < nsaved ; + + i ) {
2006-01-12 01:05:36 -08:00
res = read_int ( child , task_thread_info ( child ) - > bpt_addr [ i ] ,
2005-04-16 15:20:36 -07:00
( int * ) & insn ) ;
if ( res < 0 )
return res ;
2006-01-12 01:05:36 -08:00
task_thread_info ( child ) - > bpt_insn [ i ] = insn ;
2005-04-16 15:20:36 -07:00
DBG ( DBG_BPT , ( " -> next_pc=%lx \n " ,
2006-01-12 01:05:36 -08:00
task_thread_info ( child ) - > bpt_addr [ i ] ) ) ;
res = write_int ( child , task_thread_info ( child ) - > bpt_addr [ i ] ,
2005-04-16 15:20:36 -07:00
BREAKINST ) ;
if ( res < 0 )
return res ;
}
2006-01-12 01:05:36 -08:00
task_thread_info ( child ) - > bpt_nsaved = nsaved ;
2005-04-16 15:20:36 -07:00
return 0 ;
}
/*
* Ensure no single - step breakpoint is pending . Returns non - zero
* value if child was being single - stepped .
*/
int
ptrace_cancel_bpt ( struct task_struct * child )
{
2006-01-12 01:05:36 -08:00
int i , nsaved = task_thread_info ( child ) - > bpt_nsaved ;
2005-04-16 15:20:36 -07:00
2006-01-12 01:05:36 -08:00
task_thread_info ( child ) - > bpt_nsaved = 0 ;
2005-04-16 15:20:36 -07:00
if ( nsaved > 2 ) {
printk ( " ptrace_cancel_bpt: bogus nsaved: %d! \n " , nsaved ) ;
nsaved = 2 ;
}
for ( i = 0 ; i < nsaved ; + + i ) {
2006-01-12 01:05:36 -08:00
write_int ( child , task_thread_info ( child ) - > bpt_addr [ i ] ,
task_thread_info ( child ) - > bpt_insn [ i ] ) ;
2005-04-16 15:20:36 -07:00
}
return ( nsaved ! = 0 ) ;
}
/*
* Called by kernel / ptrace . c when detaching . .
*
* Make sure the single step bit is not set .
*/
void ptrace_disable ( struct task_struct * child )
{
ptrace_cancel_bpt ( child ) ;
}
asmlinkage long
do_sys_ptrace ( long request , long pid , long addr , long data ,
struct pt_regs * regs )
{
struct task_struct * child ;
unsigned long tmp ;
size_t copied ;
long ret ;
lock_kernel ( ) ;
DBG ( DBG_MEM , ( " request=%ld pid=%ld addr=0x%lx data=0x%lx \n " ,
request , pid , addr , data ) ) ;
if ( request = = PTRACE_TRACEME ) {
2006-01-08 01:02:33 -08:00
ret = ptrace_traceme ( ) ;
2005-04-16 15:20:36 -07:00
goto out_notsk ;
}
2006-01-08 01:02:33 -08:00
child = ptrace_get_task_struct ( pid ) ;
if ( IS_ERR ( child ) ) {
ret = PTR_ERR ( child ) ;
2005-04-16 15:20:36 -07:00
goto out_notsk ;
2006-01-08 01:02:33 -08:00
}
2005-04-16 15:20:36 -07:00
if ( request = = PTRACE_ATTACH ) {
ret = ptrace_attach ( child ) ;
goto out ;
}
ret = ptrace_check_attach ( child , request = = PTRACE_KILL ) ;
if ( ret < 0 )
goto out ;
switch ( request ) {
/* When I and D space are separate, these will need to be fixed. */
case PTRACE_PEEKTEXT : /* read word at location addr. */
case PTRACE_PEEKDATA :
copied = access_process_vm ( child , addr , & tmp , sizeof ( tmp ) , 0 ) ;
ret = - EIO ;
if ( copied ! = sizeof ( tmp ) )
break ;
regs - > r0 = 0 ; /* special return: no errors */
ret = tmp ;
break ;
/* Read register number ADDR. */
case PTRACE_PEEKUSR :
regs - > r0 = 0 ; /* special return: no errors */
ret = get_reg ( child , addr ) ;
DBG ( DBG_MEM , ( " peek $%ld->%#lx \n " , addr , ret ) ) ;
break ;
/* When I and D space are separate, this will have to be fixed. */
case PTRACE_POKETEXT : /* write the word at location addr. */
case PTRACE_POKEDATA :
tmp = data ;
copied = access_process_vm ( child , addr , & tmp , sizeof ( tmp ) , 1 ) ;
ret = ( copied = = sizeof ( tmp ) ) ? 0 : - EIO ;
break ;
case PTRACE_POKEUSR : /* write the specified register */
DBG ( DBG_MEM , ( " poke $%ld<-%#lx \n " , addr , data ) ) ;
ret = put_reg ( child , addr , data ) ;
break ;
case PTRACE_SYSCALL :
/* continue and stop at next (return from) syscall */
case PTRACE_CONT : /* restart after signal. */
ret = - EIO ;
2005-05-01 08:59:14 -07:00
if ( ! valid_signal ( data ) )
2005-04-16 15:20:36 -07:00
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 ;
/* make sure single-step breakpoint is gone. */
ptrace_cancel_bpt ( 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 :
ret = 0 ;
if ( child - > exit_state = = EXIT_ZOMBIE )
break ;
child - > exit_code = SIGKILL ;
/* make sure single-step breakpoint is gone. */
ptrace_cancel_bpt ( child ) ;
wake_up_process ( child ) ;
goto out ;
case PTRACE_SINGLESTEP : /* execute single instruction. */
ret = - EIO ;
2005-05-01 08:59:14 -07:00
if ( ! valid_signal ( data ) )
2005-04-16 15:20:36 -07:00
break ;
/* Mark single stepping. */
2006-01-12 01:05:36 -08:00
task_thread_info ( child ) - > bpt_nsaved = - 1 ;
2005-04-16 15:20:36 -07:00
clear_tsk_thread_flag ( child , TIF_SYSCALL_TRACE ) ;
child - > exit_code = data ;
wake_up_process ( child ) ;
/* give it a chance to run. */
ret = 0 ;
goto out ;
case PTRACE_DETACH : /* detach a process that was attached. */
ret = ptrace_detach ( child , data ) ;
goto out ;
default :
ret = ptrace_request ( child , request , addr , data ) ;
goto out ;
}
out :
put_task_struct ( child ) ;
out_notsk :
unlock_kernel ( ) ;
return ret ;
}
asmlinkage void
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 . 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 ;
}
}