2005-04-17 02:20:36 +04:00
/* ptrace.c: Sparc process tracing support.
*
* Copyright ( C ) 1996 David S . Miller ( davem @ caipfs . rutgers . edu )
*
* Based upon code written by Ross Biro , Linus Torvalds , Bob Manson ,
* and David Mosberger .
*
* Added Linux support - miguel ( weird , eh ? , the orignal code was meant
* to emulate SunOS ) .
*/
# include <linux/kernel.h>
# include <linux/sched.h>
# include <linux/mm.h>
# include <linux/errno.h>
# include <linux/ptrace.h>
# include <linux/user.h>
# include <linux/smp.h>
# include <linux/smp_lock.h>
# include <linux/security.h>
2005-05-01 19:59:14 +04:00
# include <linux/signal.h>
2005-04-17 02:20:36 +04:00
# include <asm/pgtable.h>
# include <asm/system.h>
# include <asm/uaccess.h>
# define MAGIC_CONSTANT 0x80000000
/* Returning from ptrace is a bit tricky because the syscall return
* low level code assumes any value returned which is negative and
* is a valid errno will mean setting the condition codes to indicate
* an error return . This doesn ' t work , so we have this hook .
*/
static inline void pt_error_return ( struct pt_regs * regs , unsigned long error )
{
regs - > u_regs [ UREG_I0 ] = error ;
regs - > psr | = PSR_C ;
regs - > pc = regs - > npc ;
regs - > npc + = 4 ;
}
static inline void pt_succ_return ( struct pt_regs * regs , unsigned long value )
{
regs - > u_regs [ UREG_I0 ] = value ;
regs - > psr & = ~ PSR_C ;
regs - > pc = regs - > npc ;
regs - > npc + = 4 ;
}
static void
pt_succ_return_linux ( struct pt_regs * regs , unsigned long value , long __user * addr )
{
if ( put_user ( value , addr ) ) {
pt_error_return ( regs , EFAULT ) ;
return ;
}
regs - > u_regs [ UREG_I0 ] = 0 ;
regs - > psr & = ~ PSR_C ;
regs - > pc = regs - > npc ;
regs - > npc + = 4 ;
}
static void
pt_os_succ_return ( struct pt_regs * regs , unsigned long val , long __user * addr )
{
if ( current - > personality = = PER_SUNOS )
pt_succ_return ( regs , val ) ;
else
pt_succ_return_linux ( regs , val , addr ) ;
}
/* Fuck me gently with a chainsaw... */
static inline void read_sunos_user ( struct pt_regs * regs , unsigned long offset ,
struct task_struct * tsk , long __user * addr )
{
struct pt_regs * cregs = tsk - > thread . kregs ;
2006-01-12 12:05:46 +03:00
struct thread_info * t = task_thread_info ( tsk ) ;
2005-04-17 02:20:36 +04:00
int v ;
if ( offset > = 1024 )
offset - = 1024 ; /* whee... */
if ( offset & ( ( sizeof ( unsigned long ) - 1 ) ) ) {
pt_error_return ( regs , EIO ) ;
return ;
}
if ( offset > = 16 & & offset < 784 ) {
offset - = 16 ; offset > > = 2 ;
pt_os_succ_return ( regs , * ( ( ( unsigned long * ) ( & t - > reg_window [ 0 ] ) ) + offset ) , addr ) ;
return ;
}
if ( offset > = 784 & & offset < 832 ) {
offset - = 784 ; offset > > = 2 ;
pt_os_succ_return ( regs , * ( ( ( unsigned long * ) ( & t - > rwbuf_stkptrs [ 0 ] ) ) + offset ) , addr ) ;
return ;
}
switch ( offset ) {
case 0 :
v = t - > ksp ;
break ;
case 4 :
v = t - > kpc ;
break ;
case 8 :
v = t - > kpsr ;
break ;
case 12 :
v = t - > uwinmask ;
break ;
case 832 :
v = t - > w_saved ;
break ;
case 896 :
v = cregs - > u_regs [ UREG_I0 ] ;
break ;
case 900 :
v = cregs - > u_regs [ UREG_I1 ] ;
break ;
case 904 :
v = cregs - > u_regs [ UREG_I2 ] ;
break ;
case 908 :
v = cregs - > u_regs [ UREG_I3 ] ;
break ;
case 912 :
v = cregs - > u_regs [ UREG_I4 ] ;
break ;
case 916 :
v = cregs - > u_regs [ UREG_I5 ] ;
break ;
case 920 :
v = cregs - > u_regs [ UREG_I6 ] ;
break ;
case 924 :
if ( tsk - > thread . flags & MAGIC_CONSTANT )
v = cregs - > u_regs [ UREG_G1 ] ;
else
v = 0 ;
break ;
case 940 :
v = cregs - > u_regs [ UREG_I0 ] ;
break ;
case 944 :
v = cregs - > u_regs [ UREG_I1 ] ;
break ;
case 948 :
/* Isn't binary compatibility _fun_??? */
if ( cregs - > psr & PSR_C )
v = cregs - > u_regs [ UREG_I0 ] < < 24 ;
else
v = 0 ;
break ;
/* Rest of them are completely unsupported. */
default :
printk ( " %s [%d]: Wants to read user offset %ld \n " ,
current - > comm , current - > pid , offset ) ;
pt_error_return ( regs , EIO ) ;
return ;
}
if ( current - > personality = = PER_SUNOS )
pt_succ_return ( regs , v ) ;
else
pt_succ_return_linux ( regs , v , addr ) ;
return ;
}
static inline void write_sunos_user ( struct pt_regs * regs , unsigned long offset ,
struct task_struct * tsk )
{
struct pt_regs * cregs = tsk - > thread . kregs ;
2006-01-12 12:05:46 +03:00
struct thread_info * t = task_thread_info ( tsk ) ;
2005-04-17 02:20:36 +04:00
unsigned long value = regs - > u_regs [ UREG_I3 ] ;
if ( offset > = 1024 )
offset - = 1024 ; /* whee... */
if ( offset & ( ( sizeof ( unsigned long ) - 1 ) ) )
goto failure ;
if ( offset > = 16 & & offset < 784 ) {
offset - = 16 ; offset > > = 2 ;
* ( ( ( unsigned long * ) ( & t - > reg_window [ 0 ] ) ) + offset ) = value ;
goto success ;
}
if ( offset > = 784 & & offset < 832 ) {
offset - = 784 ; offset > > = 2 ;
* ( ( ( unsigned long * ) ( & t - > rwbuf_stkptrs [ 0 ] ) ) + offset ) = value ;
goto success ;
}
switch ( offset ) {
case 896 :
cregs - > u_regs [ UREG_I0 ] = value ;
break ;
case 900 :
cregs - > u_regs [ UREG_I1 ] = value ;
break ;
case 904 :
cregs - > u_regs [ UREG_I2 ] = value ;
break ;
case 908 :
cregs - > u_regs [ UREG_I3 ] = value ;
break ;
case 912 :
cregs - > u_regs [ UREG_I4 ] = value ;
break ;
case 916 :
cregs - > u_regs [ UREG_I5 ] = value ;
break ;
case 920 :
cregs - > u_regs [ UREG_I6 ] = value ;
break ;
case 924 :
cregs - > u_regs [ UREG_I7 ] = value ;
break ;
case 940 :
cregs - > u_regs [ UREG_I0 ] = value ;
break ;
case 944 :
cregs - > u_regs [ UREG_I1 ] = value ;
break ;
/* Rest of them are completely unsupported or "no-touch". */
default :
printk ( " %s [%d]: Wants to write user offset %ld \n " ,
current - > comm , current - > pid , offset ) ;
goto failure ;
}
success :
pt_succ_return ( regs , 0 ) ;
return ;
failure :
pt_error_return ( regs , EIO ) ;
return ;
}
/* #define ALLOW_INIT_TRACING */
/* #define DEBUG_PTRACE */
# ifdef DEBUG_PTRACE
char * pt_rq [ ] = {
/* 0 */ " TRACEME " , " PEEKTEXT " , " PEEKDATA " , " PEEKUSR " ,
/* 4 */ " POKETEXT " , " POKEDATA " , " POKEUSR " , " CONT " ,
/* 8 */ " KILL " , " SINGLESTEP " , " SUNATTACH " , " SUNDETACH " ,
/* 12 */ " GETREGS " , " SETREGS " , " GETFPREGS " , " SETFPREGS " ,
/* 16 */ " READDATA " , " WRITEDATA " , " READTEXT " , " WRITETEXT " ,
/* 20 */ " GETFPAREGS " , " SETFPAREGS " , " unknown " , " unknown " ,
/* 24 */ " SYSCALL " , " "
} ;
# endif
/*
* Called by kernel / ptrace . c when detaching . .
*
* Make sure single step bits etc are not set .
*/
void ptrace_disable ( struct task_struct * child )
{
/* nothing to do */
}
asmlinkage void do_ptrace ( struct pt_regs * regs )
{
unsigned long request = regs - > u_regs [ UREG_I0 ] ;
unsigned long pid = regs - > u_regs [ UREG_I1 ] ;
unsigned long addr = regs - > u_regs [ UREG_I2 ] ;
unsigned long data = regs - > u_regs [ UREG_I3 ] ;
unsigned long addr2 = regs - > u_regs [ UREG_I4 ] ;
struct task_struct * child ;
int ret ;
lock_kernel ( ) ;
# ifdef DEBUG_PTRACE
{
char * s ;
if ( ( request > = 0 ) & & ( request < = 24 ) )
s = pt_rq [ request ] ;
else
s = " unknown " ;
if ( request = = PTRACE_POKEDATA & & data = = 0x91d02001 ) {
printk ( " do_ptrace: breakpoint pid=%d, addr=%08lx addr2=%08lx \n " ,
pid , addr , addr2 ) ;
} else
printk ( " do_ptrace: rq=%s(%d) pid=%d addr=%08lx data=%08lx addr2=%08lx \n " ,
s , ( int ) request , ( int ) pid , addr , data , addr2 ) ;
}
# endif
2006-01-08 12:02:33 +03:00
if ( request = = PTRACE_TRACEME ) {
ret = ptrace_traceme ( ) ;
2005-04-17 02:20:36 +04:00
pt_succ_return ( regs , 0 ) ;
goto out ;
}
2006-01-08 12:02:33 +03:00
child = ptrace_get_task_struct ( pid ) ;
if ( IS_ERR ( child ) ) {
ret = PTR_ERR ( child ) ;
pt_error_return ( regs , - ret ) ;
2005-04-17 02:20:36 +04:00
goto out ;
}
if ( ( current - > personality = = PER_SUNOS & & request = = PTRACE_SUNATTACH )
| | ( current - > personality ! = PER_SUNOS & & request = = PTRACE_ATTACH ) ) {
if ( ptrace_attach ( child ) ) {
pt_error_return ( regs , EPERM ) ;
goto out_tsk ;
}
pt_succ_return ( regs , 0 ) ;
goto out_tsk ;
}
ret = ptrace_check_attach ( child , request = = PTRACE_KILL ) ;
if ( ret < 0 ) {
pt_error_return ( regs , - ret ) ;
goto out_tsk ;
}
switch ( request ) {
case PTRACE_PEEKTEXT : /* read word at location addr. */
case PTRACE_PEEKDATA : {
unsigned long tmp ;
if ( access_process_vm ( child , addr ,
& tmp , sizeof ( tmp ) , 0 ) = = sizeof ( tmp ) )
pt_os_succ_return ( regs , tmp , ( long __user * ) data ) ;
else
pt_error_return ( regs , EIO ) ;
goto out_tsk ;
}
case PTRACE_PEEKUSR :
read_sunos_user ( regs , addr , child , ( long __user * ) data ) ;
goto out_tsk ;
case PTRACE_POKEUSR :
write_sunos_user ( regs , addr , child ) ;
goto out_tsk ;
case PTRACE_POKETEXT : /* write the word at location addr. */
case PTRACE_POKEDATA : {
if ( access_process_vm ( child , addr ,
& data , sizeof ( data ) , 1 ) = = sizeof ( data ) )
pt_succ_return ( regs , 0 ) ;
else
pt_error_return ( regs , EIO ) ;
goto out_tsk ;
}
case PTRACE_GETREGS : {
struct pt_regs __user * pregs = ( struct pt_regs __user * ) addr ;
struct pt_regs * cregs = child - > thread . kregs ;
int rval ;
if ( ! access_ok ( VERIFY_WRITE , pregs , sizeof ( struct pt_regs ) ) ) {
rval = - EFAULT ;
pt_error_return ( regs , - rval ) ;
goto out_tsk ;
}
__put_user ( cregs - > psr , ( & pregs - > psr ) ) ;
__put_user ( cregs - > pc , ( & pregs - > pc ) ) ;
__put_user ( cregs - > npc , ( & pregs - > npc ) ) ;
__put_user ( cregs - > y , ( & pregs - > y ) ) ;
for ( rval = 1 ; rval < 16 ; rval + + )
__put_user ( cregs - > u_regs [ rval ] , ( & pregs - > u_regs [ rval - 1 ] ) ) ;
pt_succ_return ( regs , 0 ) ;
# ifdef DEBUG_PTRACE
printk ( " PC=%x nPC=%x o7=%x \n " , cregs - > pc , cregs - > npc , cregs - > u_regs [ 15 ] ) ;
# endif
goto out_tsk ;
}
case PTRACE_SETREGS : {
struct pt_regs __user * pregs = ( struct pt_regs __user * ) addr ;
struct pt_regs * cregs = child - > thread . kregs ;
unsigned long psr , pc , npc , y ;
int i ;
/* Must be careful, tracing process can only set certain
* bits in the psr .
*/
if ( ! access_ok ( VERIFY_READ , pregs , sizeof ( struct pt_regs ) ) ) {
pt_error_return ( regs , EFAULT ) ;
goto out_tsk ;
}
__get_user ( psr , ( & pregs - > psr ) ) ;
__get_user ( pc , ( & pregs - > pc ) ) ;
__get_user ( npc , ( & pregs - > npc ) ) ;
__get_user ( y , ( & pregs - > y ) ) ;
psr & = PSR_ICC ;
cregs - > psr & = ~ PSR_ICC ;
cregs - > psr | = psr ;
if ( ! ( ( pc | npc ) & 3 ) ) {
cregs - > pc = pc ;
cregs - > npc = npc ;
}
cregs - > y = y ;
for ( i = 1 ; i < 16 ; i + + )
__get_user ( cregs - > u_regs [ i ] , ( & pregs - > u_regs [ i - 1 ] ) ) ;
pt_succ_return ( regs , 0 ) ;
goto out_tsk ;
}
case PTRACE_GETFPREGS : {
struct fps {
unsigned long regs [ 32 ] ;
unsigned long fsr ;
unsigned long flags ;
unsigned long extra ;
unsigned long fpqd ;
struct fq {
unsigned long * insnaddr ;
unsigned long insn ;
} fpq [ 16 ] ;
} ;
struct fps __user * fps = ( struct fps __user * ) addr ;
int i ;
if ( ! access_ok ( VERIFY_WRITE , fps , sizeof ( struct fps ) ) ) {
i = - EFAULT ;
pt_error_return ( regs , - i ) ;
goto out_tsk ;
}
for ( i = 0 ; i < 32 ; i + + )
__put_user ( child - > thread . float_regs [ i ] , ( & fps - > regs [ i ] ) ) ;
__put_user ( child - > thread . fsr , ( & fps - > fsr ) ) ;
__put_user ( child - > thread . fpqdepth , ( & fps - > fpqd ) ) ;
__put_user ( 0 , ( & fps - > flags ) ) ;
__put_user ( 0 , ( & fps - > extra ) ) ;
for ( i = 0 ; i < 16 ; i + + ) {
__put_user ( child - > thread . fpqueue [ i ] . insn_addr ,
( & fps - > fpq [ i ] . insnaddr ) ) ;
__put_user ( child - > thread . fpqueue [ i ] . insn , ( & fps - > fpq [ i ] . insn ) ) ;
}
pt_succ_return ( regs , 0 ) ;
goto out_tsk ;
}
case PTRACE_SETFPREGS : {
struct fps {
unsigned long regs [ 32 ] ;
unsigned long fsr ;
unsigned long flags ;
unsigned long extra ;
unsigned long fpqd ;
struct fq {
unsigned long * insnaddr ;
unsigned long insn ;
} fpq [ 16 ] ;
} ;
struct fps __user * fps = ( struct fps __user * ) addr ;
int i ;
if ( ! access_ok ( VERIFY_READ , fps , sizeof ( struct fps ) ) ) {
i = - EFAULT ;
pt_error_return ( regs , - i ) ;
goto out_tsk ;
}
copy_from_user ( & child - > thread . float_regs [ 0 ] , & fps - > regs [ 0 ] , ( 32 * sizeof ( unsigned long ) ) ) ;
__get_user ( child - > thread . fsr , ( & fps - > fsr ) ) ;
__get_user ( child - > thread . fpqdepth , ( & fps - > fpqd ) ) ;
for ( i = 0 ; i < 16 ; i + + ) {
__get_user ( child - > thread . fpqueue [ i ] . insn_addr ,
( & fps - > fpq [ i ] . insnaddr ) ) ;
__get_user ( child - > thread . fpqueue [ i ] . insn , ( & fps - > fpq [ i ] . insn ) ) ;
}
pt_succ_return ( regs , 0 ) ;
goto out_tsk ;
}
case PTRACE_READTEXT :
case PTRACE_READDATA : {
int res = ptrace_readdata ( child , addr ,
( void __user * ) addr2 , data ) ;
if ( res = = data ) {
pt_succ_return ( regs , 0 ) ;
goto out_tsk ;
}
/* Partial read is an IO failure */
if ( res > = 0 )
res = - EIO ;
pt_error_return ( regs , - res ) ;
goto out_tsk ;
}
case PTRACE_WRITETEXT :
case PTRACE_WRITEDATA : {
int res = ptrace_writedata ( child , ( void __user * ) addr2 ,
addr , data ) ;
if ( res = = data ) {
pt_succ_return ( regs , 0 ) ;
goto out_tsk ;
}
/* Partial write is an IO failure */
if ( res > = 0 )
res = - EIO ;
pt_error_return ( regs , - res ) ;
goto out_tsk ;
}
case PTRACE_SYSCALL : /* continue and stop at (return from) syscall */
addr = 1 ;
case PTRACE_CONT : { /* restart after signal. */
2005-05-01 19:59:14 +04:00
if ( ! valid_signal ( data ) ) {
2005-04-17 02:20:36 +04:00
pt_error_return ( regs , EIO ) ;
goto out_tsk ;
}
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 ;
# ifdef DEBUG_PTRACE
printk ( " CONT: %s [%d]: set exit_code = %x %lx %lx \n " ,
child - > comm , child - > pid , child - > exit_code ,
child - > thread . kregs - > pc ,
child - > thread . kregs - > npc ) ;
# endif
wake_up_process ( child ) ;
pt_succ_return ( regs , 0 ) ;
goto out_tsk ;
}
/*
* 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 : {
if ( child - > exit_state = = EXIT_ZOMBIE ) { /* already dead */
pt_succ_return ( regs , 0 ) ;
goto out_tsk ;
}
wake_up_process ( child ) ;
child - > exit_code = SIGKILL ;
pt_succ_return ( regs , 0 ) ;
goto out_tsk ;
}
case PTRACE_SUNDETACH : { /* detach a process that was attached. */
int err = ptrace_detach ( child , data ) ;
if ( err ) {
pt_error_return ( regs , EIO ) ;
goto out_tsk ;
}
pt_succ_return ( regs , 0 ) ;
goto out_tsk ;
}
/* PTRACE_DUMPCORE unsupported... */
default : {
int err = ptrace_request ( child , request , addr , data ) ;
if ( err )
pt_error_return ( regs , - err ) ;
else
pt_succ_return ( regs , 0 ) ;
goto out_tsk ;
}
}
out_tsk :
if ( child )
put_task_struct ( child ) ;
out :
unlock_kernel ( ) ;
}
asmlinkage void syscall_trace ( void )
{
# ifdef DEBUG_PTRACE
printk ( " %s [%d]: syscall_trace \n " , current - > comm , current - > pid ) ;
# endif
if ( ! test_thread_flag ( TIF_SYSCALL_TRACE ) )
return ;
if ( ! ( current - > ptrace & PT_PTRACED ) )
return ;
current - > thread . flags ^ = MAGIC_CONSTANT ;
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
*/
# ifdef DEBUG_PTRACE
printk ( " %s [%d]: syscall_trace exit= %x \n " , current - > comm ,
current - > pid , current - > exit_code ) ;
# endif
if ( current - > exit_code ) {
send_sig ( current - > exit_code , current , 1 ) ;
current - > exit_code = 0 ;
}
}