2005-04-17 02:20:36 +04:00
/*
* linux / arch / sh / kernel / ptrace . c
*
* Original x86 implementation :
* By Ross Biro 1 / 23 / 92
* edited by Linus Torvalds
*
* SuperH version : Copyright ( C ) 1999 , 2000 Kaz Kojima & Niibe Yutaka
2007-11-10 13:21:34 +03:00
* Audit support : Yuichi Nakamura < ynakam @ hitachisoft . jp >
2005-04-17 02:20:36 +04:00
*/
# include <linux/kernel.h>
# include <linux/sched.h>
# include <linux/mm.h>
# include <linux/smp.h>
# include <linux/errno.h>
# include <linux/ptrace.h>
# include <linux/user.h>
# include <linux/slab.h>
# include <linux/security.h>
2005-05-01 19:59:14 +04:00
# include <linux/signal.h>
2007-02-23 07:22:17 +03:00
# include <linux/io.h>
2007-11-10 13:21:34 +03:00
# include <linux/audit.h>
2005-04-17 02:20:36 +04:00
# include <asm/uaccess.h>
# include <asm/pgtable.h>
# include <asm/system.h>
# include <asm/processor.h>
# include <asm/mmu_context.h>
/*
* does not yet catch signals sent when the child dies .
* in exit . c or in signal . c .
*/
/*
* This routine will get a word off of the process kernel stack .
*/
static inline int get_stack_long ( struct task_struct * task , int offset )
{
unsigned char * stack ;
2006-01-12 12:05:44 +03:00
stack = ( unsigned char * ) task_pt_regs ( task ) ;
2005-04-17 02:20:36 +04:00
stack + = offset ;
return ( * ( ( int * ) stack ) ) ;
}
/*
* This routine will put a word on the process kernel stack .
*/
static inline int put_stack_long ( struct task_struct * task , int offset ,
unsigned long data )
{
unsigned char * stack ;
2006-01-12 12:05:44 +03:00
stack = ( unsigned char * ) task_pt_regs ( task ) ;
2005-04-17 02:20:36 +04:00
stack + = offset ;
* ( unsigned long * ) stack = data ;
return 0 ;
}
2007-02-23 07:22:17 +03:00
static void ptrace_disable_singlestep ( struct task_struct * child )
{
clear_tsk_thread_flag ( child , TIF_SINGLESTEP ) ;
/*
* Ensure the UBC is not programmed at the next context switch .
*
* Normally this is not needed but there are sequences such as
* singlestep , signal delivery , and continue that leave the
* ubc_pc non - zero leading to spurious SIGTRAPs .
*/
if ( child - > thread . ubc_pc ! = 0 ) {
ubc_usercnt - = 1 ;
child - > thread . ubc_pc = 0 ;
}
}
2005-04-17 02:20:36 +04:00
/*
* Called by kernel / ptrace . c when detaching . .
*
* Make sure single step bits etc are not set .
*/
void ptrace_disable ( struct task_struct * child )
{
2007-02-23 07:22:17 +03:00
ptrace_disable_singlestep ( child ) ;
2005-04-17 02:20:36 +04:00
}
2005-11-07 11:59:47 +03:00
long arch_ptrace ( struct task_struct * child , long request , long addr , long data )
2005-04-17 02:20:36 +04:00
{
struct user * dummy = NULL ;
int ret ;
switch ( request ) {
/* when I and D space are separate, these will need to be fixed. */
2007-02-23 07:22:17 +03:00
case PTRACE_PEEKTEXT : /* read word at location addr. */
2007-07-17 15:03:43 +04:00
case PTRACE_PEEKDATA :
ret = generic_ptrace_peekdata ( child , addr , data ) ;
2007-08-04 08:39:21 +04:00
break ;
2005-04-17 02:20:36 +04:00
/* read the word at location addr in the USER area. */
case PTRACE_PEEKUSR : {
unsigned long tmp ;
ret = - EIO ;
2007-02-23 07:22:17 +03:00
if ( ( addr & 3 ) | | addr < 0 | |
2005-04-17 02:20:36 +04:00
addr > sizeof ( struct user ) - 3 )
break ;
if ( addr < sizeof ( struct pt_regs ) )
tmp = get_stack_long ( child , addr ) ;
else if ( addr > = ( long ) & dummy - > fpu & &
addr < ( long ) & dummy - > u_fpvalid ) {
if ( ! tsk_used_math ( child ) ) {
if ( addr = = ( long ) & dummy - > fpu . fpscr )
tmp = FPSCR_INIT ;
else
tmp = 0 ;
} else
tmp = ( ( long * ) & child - > thread . fpu )
[ ( addr - ( long ) & dummy - > fpu ) > > 2 ] ;
} else if ( addr = = ( long ) & dummy - > u_fpvalid )
tmp = ! ! tsk_used_math ( child ) ;
else
tmp = 0 ;
2007-05-14 07:52:56 +04:00
ret = put_user ( tmp , ( unsigned long __user * ) data ) ;
2005-04-17 02:20:36 +04:00
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 :
2007-07-17 15:03:44 +04:00
ret = generic_ptrace_pokedata ( child , addr , data ) ;
2005-04-17 02:20:36 +04:00
break ;
case PTRACE_POKEUSR : /* write the word at location addr in the USER area */
ret = - EIO ;
2007-02-23 07:22:17 +03:00
if ( ( addr & 3 ) | | addr < 0 | |
2005-04-17 02:20:36 +04:00
addr > sizeof ( struct user ) - 3 )
break ;
if ( addr < sizeof ( struct pt_regs ) )
ret = put_stack_long ( child , addr , data ) ;
else if ( addr > = ( long ) & dummy - > fpu & &
addr < ( long ) & dummy - > u_fpvalid ) {
set_stopped_child_used_math ( child ) ;
( ( long * ) & child - > thread . fpu )
[ ( addr - ( long ) & dummy - > fpu ) > > 2 ] = data ;
ret = 0 ;
} else if ( addr = = ( long ) & dummy - > u_fpvalid ) {
conditional_stopped_child_used_math ( data , child ) ;
ret = 0 ;
}
break ;
case PTRACE_SYSCALL : /* continue and stop at next (return from) syscall */
case PTRACE_CONT : { /* restart after signal. */
ret = - EIO ;
2005-05-01 19:59:14 +04:00
if ( ! valid_signal ( data ) )
2005-04-17 02:20:36 +04:00
break ;
if ( request = = PTRACE_SYSCALL )
set_tsk_thread_flag ( child , TIF_SYSCALL_TRACE ) ;
else
clear_tsk_thread_flag ( child , TIF_SYSCALL_TRACE ) ;
2007-02-23 07:22:17 +03:00
ptrace_disable_singlestep ( child ) ;
2005-04-17 02:20:36 +04:00
child - > exit_code = data ;
wake_up_process ( child ) ;
ret = 0 ;
break ;
}
/*
2007-02-23 07:22:17 +03:00
* 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
2005-04-17 02:20:36 +04:00
* exit .
*/
case PTRACE_KILL : {
ret = 0 ;
if ( child - > exit_state = = EXIT_ZOMBIE ) /* already dead */
break ;
2007-02-23 07:22:17 +03:00
ptrace_disable_singlestep ( child ) ;
2005-04-17 02:20:36 +04:00
child - > exit_code = SIGKILL ;
wake_up_process ( child ) ;
break ;
}
case PTRACE_SINGLESTEP : { /* set the trap flag. */
long pc ;
2007-05-14 07:52:56 +04:00
struct pt_regs * regs = NULL ;
2005-04-17 02:20:36 +04:00
ret = - EIO ;
2005-05-01 19:59:14 +04:00
if ( ! valid_signal ( data ) )
2005-04-17 02:20:36 +04:00
break ;
clear_tsk_thread_flag ( child , TIF_SYSCALL_TRACE ) ;
if ( ( child - > ptrace & PT_DTRACE ) = = 0 ) {
/* Spurious delayed TF traps may occur */
child - > ptrace | = PT_DTRACE ;
}
2007-05-14 07:52:56 +04:00
pc = get_stack_long ( child , ( long ) & regs - > pc ) ;
2005-04-17 02:20:36 +04:00
/* Next scheduling will set up UBC */
if ( child - > thread . ubc_pc = = 0 )
ubc_usercnt + = 1 ;
child - > thread . ubc_pc = pc ;
2007-02-23 07:22:17 +03:00
set_tsk_thread_flag ( child , TIF_SINGLESTEP ) ;
2005-04-17 02:20:36 +04:00
child - > exit_code = data ;
/* give it a chance to run. */
wake_up_process ( child ) ;
ret = 0 ;
break ;
}
# ifdef CONFIG_SH_DSP
case PTRACE_GETDSPREGS : {
unsigned long dp ;
ret = - EIO ;
dp = ( ( unsigned long ) child ) + THREAD_SIZE -
sizeof ( struct pt_dspregs ) ;
if ( * ( ( int * ) ( dp - 4 ) ) = = SR_FD ) {
copy_to_user ( addr , ( void * ) dp ,
sizeof ( struct pt_dspregs ) ) ;
ret = 0 ;
}
break ;
}
case PTRACE_SETDSPREGS : {
unsigned long dp ;
ret = - EIO ;
dp = ( ( unsigned long ) child ) + THREAD_SIZE -
sizeof ( struct pt_dspregs ) ;
if ( * ( ( int * ) ( dp - 4 ) ) = = SR_FD ) {
copy_from_user ( ( void * ) dp , addr ,
sizeof ( struct pt_dspregs ) ) ;
ret = 0 ;
}
break ;
}
# endif
default :
ret = ptrace_request ( child , request , addr , data ) ;
break ;
}
2005-11-07 11:59:47 +03:00
2005-04-17 02:20:36 +04:00
return ret ;
}
2007-11-10 13:21:34 +03:00
asmlinkage void do_syscall_trace ( struct pt_regs * regs , int entryexit )
2005-04-17 02:20:36 +04:00
{
struct task_struct * tsk = current ;
2007-11-10 13:21:34 +03:00
if ( unlikely ( current - > audit_context ) & & entryexit )
audit_syscall_exit ( AUDITSC_RESULT ( regs - > regs [ 0 ] ) ,
regs - > regs [ 0 ] ) ;
2007-02-23 07:22:17 +03:00
if ( ! test_thread_flag ( TIF_SYSCALL_TRACE ) & &
! test_thread_flag ( TIF_SINGLESTEP ) )
2007-11-10 13:21:34 +03:00
goto out ;
2005-04-17 02:20:36 +04:00
if ( ! ( tsk - > ptrace & PT_PTRACED ) )
2007-11-10 13:21:34 +03:00
goto out ;
2005-04-17 02:20:36 +04:00
/* the 0x80 provides a way for the tracing parent to distinguish
between a syscall stop and SIGTRAP delivery */
2007-02-23 07:22:17 +03:00
ptrace_notify ( SIGTRAP | ( ( current - > ptrace & PT_TRACESYSGOOD ) & &
! test_thread_flag ( TIF_SINGLESTEP ) ? 0x80 : 0 ) ) ;
2005-04-17 02:20:36 +04:00
/*
* 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 ( tsk - > exit_code ) {
send_sig ( tsk - > exit_code , tsk , 1 ) ;
tsk - > exit_code = 0 ;
}
2007-11-10 13:21:34 +03:00
out :
if ( unlikely ( current - > audit_context ) & & ! entryexit )
audit_syscall_entry ( AUDIT_ARCH_SH , regs - > regs [ 3 ] ,
regs - > regs [ 4 ] , regs - > regs [ 5 ] ,
regs - > regs [ 6 ] , regs - > regs [ 7 ] ) ;
2005-04-17 02:20:36 +04:00
}