2005-04-17 02:20:36 +04:00
/*
* Kernel support for the ptrace ( ) and syscall tracing interfaces .
*
* Copyright ( C ) 2000 Hewlett - Packard Co , Linuxcare Inc .
* Copyright ( C ) 2000 Matthew Wilcox < matthew @ wil . cx >
* Copyright ( C ) 2000 David Huggins - Daines < dhd @ debian . org >
2008-10-17 22:48:36 +04:00
* Copyright ( C ) 2008 Helge Deller < deller @ gmx . de >
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/personality.h>
# include <linux/security.h>
# include <linux/compat.h>
2005-05-01 19:59:14 +04:00
# include <linux/signal.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>
2005-09-09 22:57:26 +04:00
# include <asm/asm-offsets.h>
2005-04-17 02:20:36 +04:00
/* PSW bits we allow the debugger to modify */
# define USER_PSW_BITS (PSW_N | PSW_V | PSW_CB)
2008-10-17 22:48: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 * task )
{
task - > ptrace & = ~ ( PT_SINGLESTEP | PT_BLOCKSTEP ) ;
2005-04-17 02:20:36 +04:00
2008-10-17 22:48:36 +04:00
/* make sure the trap bits are not set */
pa_psw ( task ) - > r = 0 ;
pa_psw ( task ) - > t = 0 ;
pa_psw ( task ) - > h = 0 ;
pa_psw ( task ) - > l = 0 ;
}
/*
* The following functions are called by ptrace_resume ( ) when
* enabling or disabling single / block tracing .
*/
void user_disable_single_step ( struct task_struct * task )
{
ptrace_disable ( task ) ;
}
void user_enable_single_step ( struct task_struct * task )
{
task - > ptrace & = ~ PT_BLOCKSTEP ;
task - > ptrace | = PT_SINGLESTEP ;
if ( pa_psw ( task ) - > n ) {
struct siginfo si ;
/* Nullified, just crank over the queue. */
task_regs ( task ) - > iaoq [ 0 ] = task_regs ( task ) - > iaoq [ 1 ] ;
task_regs ( task ) - > iasq [ 0 ] = task_regs ( task ) - > iasq [ 1 ] ;
task_regs ( task ) - > iaoq [ 1 ] = task_regs ( task ) - > iaoq [ 0 ] + 4 ;
pa_psw ( task ) - > n = 0 ;
pa_psw ( task ) - > x = 0 ;
pa_psw ( task ) - > y = 0 ;
pa_psw ( task ) - > z = 0 ;
pa_psw ( task ) - > b = 0 ;
ptrace_disable ( task ) ;
/* Don't wake up the task, but let the
parent know something happened . */
si . si_code = TRAP_TRACE ;
si . si_addr = ( void __user * ) ( task_regs ( task ) - > iaoq [ 0 ] & ~ 3 ) ;
si . si_signo = SIGTRAP ;
si . si_errno = 0 ;
force_sig_info ( SIGTRAP , & si , task ) ;
/* notify_parent(task, SIGCHLD); */
return ;
}
/* Enable recovery counter traps. The recovery counter
* itself will be set to zero on a task switch . If the
* task is suspended on a syscall then the syscall return
* path will overwrite the recovery counter with a suitable
* value such that it traps once back in user space . We
* disable interrupts in the tasks PSW here also , to avoid
* interrupts while the recovery counter is decrementing .
*/
pa_psw ( task ) - > r = 1 ;
pa_psw ( task ) - > t = 0 ;
pa_psw ( task ) - > h = 0 ;
pa_psw ( task ) - > l = 0 ;
}
void user_enable_block_step ( struct task_struct * task )
{
task - > ptrace & = ~ PT_SINGLESTEP ;
task - > ptrace | = PT_BLOCKSTEP ;
/* Enable taken branch trap. */
pa_psw ( task ) - > r = 0 ;
pa_psw ( task ) - > t = 1 ;
pa_psw ( task ) - > h = 0 ;
pa_psw ( task ) - > l = 0 ;
}
long arch_ptrace ( struct task_struct * child , long request , long addr , long data )
{
unsigned long tmp ;
long ret = - EIO ;
2005-04-17 02:20:36 +04:00
2008-10-17 22:48:36 +04:00
switch ( request ) {
/* Read the word at location addr in the USER area. For ptraced
processes , the kernel saves all regs on a syscall . */
case PTRACE_PEEKUSR :
if ( ( addr & ( sizeof ( long ) - 1 ) ) | |
( unsigned long ) addr > = sizeof ( struct pt_regs ) )
break ;
tmp = * ( unsigned long * ) ( ( char * ) task_regs ( child ) + addr ) ;
ret = put_user ( tmp , ( unsigned long * ) data ) ;
break ;
/* Write the word at location addr in the USER area. This will need
to change when the kernel no longer saves all regs on a syscall .
FIXME . There is a problem at the moment in that r3 - r18 are only
saved if the process is ptraced on syscall entry , and even then
those values are overwritten by actual register values on syscall
exit . */
case PTRACE_POKEUSR :
/* Some register values written here may be ignored in
* entry . S : syscall_restore_rfi ; e . g . iaoq is written with
* r31 / r31 + 4 , and not with the values in pt_regs .
*/
if ( addr = = PT_PSW ) {
/* Allow writing to Nullify, Divide-step-correction,
* and carry / borrow bits .
* BEWARE , if you set N , and then single step , it won ' t
* stop on the nullified instruction .
*/
data & = USER_PSW_BITS ;
task_regs ( child ) - > gr [ 0 ] & = ~ USER_PSW_BITS ;
task_regs ( child ) - > gr [ 0 ] | = data ;
ret = 0 ;
break ;
}
if ( ( addr & ( sizeof ( long ) - 1 ) ) | |
( unsigned long ) addr > = sizeof ( struct pt_regs ) )
break ;
if ( ( addr > = PT_GR1 & & addr < = PT_GR31 ) | |
addr = = PT_IAOQ0 | | addr = = PT_IAOQ1 | |
( addr > = PT_FR0 & & addr < = PT_FR31 + 4 ) | |
addr = = PT_SAR ) {
* ( unsigned long * ) ( ( char * ) task_regs ( child ) + addr ) = data ;
ret = 0 ;
}
break ;
default :
ret = ptrace_request ( child , request , addr , data ) ;
break ;
}
return ret ;
}
# ifdef CONFIG_COMPAT
2005-04-17 02:20:36 +04:00
/* This function is needed to translate 32 bit pt_regs offsets in to
* 64 bit pt_regs offsets . For example , a 32 bit gdb under a 64 bit kernel
* will request offset 12 if it wants gr3 , but the lower 32 bits of
* the 64 bit kernels view of gr3 will be at offset 28 ( 3 * 8 + 4 ) .
* This code relies on a 32 bit pt_regs being comprised of 32 bit values
* except for the fp registers which ( a ) are 64 bits , and ( b ) follow
* the gr registers at the start of pt_regs . The 32 bit pt_regs should
* be half the size of the 64 bit pt_regs , plus 32 * 4 to allow for fr [ ]
* being 64 bit in both cases .
*/
2008-11-20 12:54:09 +03:00
static compat_ulong_t translate_usr_offset ( compat_ulong_t offset )
2005-04-17 02:20:36 +04:00
{
if ( offset < 0 )
2008-11-20 12:54:09 +03:00
return sizeof ( struct pt_regs ) ;
2005-04-17 02:20:36 +04:00
else if ( offset < = 32 * 4 ) /* gr[0..31] */
return offset * 2 + 4 ;
else if ( offset < = 32 * 4 + 32 * 8 ) /* gr[0..31] + fr[0..31] */
return offset + 32 * 4 ;
else if ( offset < sizeof ( struct pt_regs ) / 2 + 32 * 4 )
return offset * 2 + 4 - 32 * 8 ;
else
2008-11-20 12:54:09 +03:00
return sizeof ( struct pt_regs ) ;
2005-04-17 02:20:36 +04:00
}
2008-10-17 22:48:36 +04:00
long compat_arch_ptrace ( struct task_struct * child , compat_long_t request ,
compat_ulong_t addr , compat_ulong_t data )
2005-04-17 02:20:36 +04:00
{
2008-10-17 22:48:36 +04:00
compat_uint_t tmp ;
long ret = - EIO ;
2005-04-17 02:20:36 +04:00
switch ( request ) {
2008-10-17 22:48:36 +04:00
case PTRACE_PEEKUSR :
if ( addr & ( sizeof ( compat_uint_t ) - 1 ) )
break ;
addr = translate_usr_offset ( addr ) ;
2008-11-20 12:54:09 +03:00
if ( addr > = sizeof ( struct pt_regs ) )
2008-10-17 22:48:36 +04:00
break ;
2005-04-17 02:20:36 +04:00
2008-10-17 22:48:36 +04:00
tmp = * ( compat_uint_t * ) ( ( char * ) task_regs ( child ) + addr ) ;
ret = put_user ( tmp , ( compat_uint_t * ) ( unsigned long ) data ) ;
break ;
2005-04-17 02:20:36 +04:00
/* Write the word at location addr in the USER area. This will need
to change when the kernel no longer saves all regs on a syscall .
FIXME . There is a problem at the moment in that r3 - r18 are only
saved if the process is ptraced on syscall entry , and even then
those values are overwritten by actual register values on syscall
exit . */
case PTRACE_POKEUSR :
/* Some register values written here may be ignored in
* entry . S : syscall_restore_rfi ; e . g . iaoq is written with
* r31 / r31 + 4 , and not with the values in pt_regs .
*/
if ( addr = = PT_PSW ) {
2008-10-17 22:48:36 +04:00
/* Since PT_PSW==0, it is valid for 32 bit processes
* under 64 bit kernels as well .
2005-04-17 02:20:36 +04:00
*/
2008-10-17 22:48:36 +04:00
ret = arch_ptrace ( child , request , addr , data ) ;
} else {
if ( addr & ( sizeof ( compat_uint_t ) - 1 ) )
break ;
addr = translate_usr_offset ( addr ) ;
2008-11-20 12:54:09 +03:00
if ( addr > = sizeof ( struct pt_regs ) )
2008-10-17 22:48:36 +04:00
break ;
2005-04-17 02:20:36 +04:00
if ( addr > = PT_FR0 & & addr < = PT_FR31 + 4 ) {
/* Special case, fp regs are 64 bits anyway */
2008-10-17 22:48:36 +04:00
* ( __u64 * ) ( ( char * ) task_regs ( child ) + addr ) = data ;
2005-04-17 02:20:36 +04:00
ret = 0 ;
}
else if ( ( addr > = PT_GR1 + 4 & & addr < = PT_GR31 + 4 ) | |
addr = = PT_IAOQ0 + 4 | | addr = = PT_IAOQ1 + 4 | |
addr = = PT_SAR + 4 ) {
/* Zero the top 32 bits */
2008-10-17 22:48:36 +04:00
* ( __u32 * ) ( ( char * ) task_regs ( child ) + addr - 4 ) = 0 ;
* ( __u32 * ) ( ( char * ) task_regs ( child ) + addr ) = data ;
2005-04-17 02:20:36 +04:00
ret = 0 ;
}
}
2008-10-17 22:48:36 +04:00
break ;
2005-04-17 02:20:36 +04:00
default :
2008-10-17 22:48:36 +04:00
ret = compat_ptrace_request ( child , request , addr , data ) ;
break ;
2005-04-17 02:20:36 +04:00
}
return ret ;
}
2008-10-17 22:48:36 +04:00
# endif
2005-04-17 02:20:36 +04:00
void syscall_trace ( void )
{
if ( ! test_thread_flag ( TIF_SYSCALL_TRACE ) )
return ;
if ( ! ( current - > ptrace & PT_PTRACED ) )
return ;
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 ;
}
}