2005-06-23 22:01:16 -07:00
// TODO some minor issues
/*
* This file is subject to the terms and conditions of the GNU General Public
* License . See the file " COPYING " in the main directory of this archive
* for more details .
*
2008-02-12 13:17:07 -08:00
* Copyright ( C ) 2001 - 2007 Tensilica Inc .
2005-06-23 22:01:16 -07:00
*
* Joe Taylor < joe @ tensilica . com , joetylr @ yahoo . com >
* Chris Zankel < chris @ zankel . net >
* Scott Foehner < sfoehner @ yahoo . com > ,
* Kevin Chea
* Marc Gauthier < marc @ tensilica . com > < marc @ alumni . uwaterloo . ca >
*/
# include <linux/kernel.h>
# include <linux/sched.h>
# include <linux/mm.h>
# include <linux/errno.h>
# include <linux/ptrace.h>
# include <linux/smp.h>
# include <linux/security.h>
2005-06-30 02:58:56 -07:00
# include <linux/signal.h>
2005-06-23 22:01:16 -07:00
# include <asm/pgtable.h>
# include <asm/page.h>
# include <asm/system.h>
# include <asm/uaccess.h>
# include <asm/ptrace.h>
# include <asm/elf.h>
2008-02-12 13:17:07 -08:00
# include <asm/coprocessor.h>
2005-06-23 22:01:16 -07:00
/*
2008-02-12 13:17:07 -08:00
* Called by kernel / ptrace . c when detaching to disable single stepping .
2005-06-23 22:01:16 -07:00
*/
void ptrace_disable ( struct task_struct * child )
{
/* Nothing to do.. */
}
2008-02-12 13:17:07 -08:00
int ptrace_getregs ( struct task_struct * child , void __user * uregs )
2005-06-23 22:01:16 -07:00
{
2008-02-12 13:17:07 -08:00
struct pt_regs * regs = task_pt_regs ( child ) ;
xtensa_gregset_t __user * gregset = uregs ;
unsigned long wm = regs - > wmask ;
2008-01-28 15:55:01 -08:00
unsigned long wb = regs - > windowbase ;
int live , i ;
2008-02-12 13:17:07 -08:00
if ( ! access_ok ( VERIFY_WRITE , uregs , sizeof ( xtensa_gregset_t ) ) )
return - EIO ;
2008-01-28 15:55:01 -08:00
__put_user ( regs - > pc , & gregset - > pc ) ;
__put_user ( regs - > ps & ~ ( 1 < < PS_EXCM_BIT ) , & gregset - > ps ) ;
__put_user ( regs - > lbeg , & gregset - > lbeg ) ;
__put_user ( regs - > lend , & gregset - > lend ) ;
__put_user ( regs - > lcount , & gregset - > lcount ) ;
__put_user ( regs - > windowstart , & gregset - > windowstart ) ;
__put_user ( regs - > windowbase , & gregset - > windowbase ) ;
2008-02-12 13:17:07 -08:00
live = ( wm & 2 ) ? 4 : ( wm & 4 ) ? 8 : ( wm & 8 ) ? 12 : 16 ;
2008-01-28 15:55:01 -08:00
for ( i = 0 ; i < live ; i + + )
__put_user ( regs - > areg [ i ] , gregset - > a + ( ( wb * 4 + i ) % XCHAL_NUM_AREGS ) ) ;
for ( i = XCHAL_NUM_AREGS - ( wm > > 4 ) * 4 ; i < XCHAL_NUM_AREGS ; i + + )
__put_user ( regs - > areg [ i ] , gregset - > a + ( ( wb * 4 + i ) % XCHAL_NUM_AREGS ) ) ;
return 0 ;
2008-02-12 13:17:07 -08:00
}
2005-06-23 22:01:16 -07:00
2008-02-12 13:17:07 -08:00
int ptrace_setregs ( struct task_struct * child , void __user * uregs )
{
struct pt_regs * regs = task_pt_regs ( child ) ;
xtensa_gregset_t * gregset = uregs ;
const unsigned long ps_mask = PS_CALLINC_MASK | PS_OWB_MASK ;
unsigned long ps ;
2008-01-28 15:55:01 -08:00
unsigned long wb ;
2008-02-12 13:17:07 -08:00
if ( ! access_ok ( VERIFY_WRITE , uregs , sizeof ( xtensa_gregset_t ) ) )
return - EIO ;
2008-01-28 15:55:01 -08:00
__get_user ( regs - > pc , & gregset - > pc ) ;
__get_user ( ps , & gregset - > ps ) ;
__get_user ( regs - > lbeg , & gregset - > lbeg ) ;
__get_user ( regs - > lend , & gregset - > lend ) ;
__get_user ( regs - > lcount , & gregset - > lcount ) ;
__get_user ( regs - > windowstart , & gregset - > windowstart ) ;
__get_user ( wb , & gregset - > windowbase ) ;
2008-02-12 13:17:07 -08:00
regs - > ps = ( regs - > ps & ~ ps_mask ) | ( ps & ps_mask ) | ( 1 < < PS_EXCM_BIT ) ;
2008-01-28 15:55:01 -08:00
if ( wb > = XCHAL_NUM_AREGS / 4 )
return - EFAULT ;
2008-02-12 13:17:07 -08:00
2008-01-28 15:55:01 -08:00
regs - > windowbase = wb ;
if ( wb ! = 0 & & __copy_from_user ( regs - > areg + XCHAL_NUM_AREGS - wb * 4 ,
gregset - > a , wb * 16 ) )
return - EFAULT ;
if ( __copy_from_user ( regs - > areg , gregset - > a + wb * 4 , ( WSBITS - wb ) * 16 ) )
return - EFAULT ;
return 0 ;
2008-02-12 13:17:07 -08:00
}
2005-06-23 22:01:16 -07:00
2008-02-12 13:17:07 -08:00
int ptrace_getxregs ( struct task_struct * child , void __user * uregs )
{
struct pt_regs * regs = task_pt_regs ( child ) ;
struct thread_info * ti = task_thread_info ( child ) ;
elf_xtregs_t __user * xtregs = uregs ;
int ret = 0 ;
if ( ! access_ok ( VERIFY_WRITE , uregs , sizeof ( elf_xtregs_t ) ) )
return - EIO ;
# if XTENSA_HAVE_COPROCESSORS
/* Flush all coprocessor registers to memory. */
coprocessor_flush_all ( ti ) ;
ret | = __copy_to_user ( & xtregs - > cp0 , & ti - > xtregs_cp ,
sizeof ( xtregs_coprocessor_t ) ) ;
# endif
ret | = __copy_to_user ( & xtregs - > opt , & regs - > xtregs_opt ,
sizeof ( xtregs - > opt ) ) ;
ret | = __copy_to_user ( & xtregs - > user , & ti - > xtregs_user ,
sizeof ( xtregs - > user ) ) ;
2005-06-23 22:01:16 -07:00
2008-02-12 13:17:07 -08:00
return ret ? - EFAULT : 0 ;
}
int ptrace_setxregs ( struct task_struct * child , void __user * uregs )
{
struct thread_info * ti = task_thread_info ( child ) ;
struct pt_regs * regs = task_pt_regs ( child ) ;
elf_xtregs_t * xtregs = uregs ;
int ret = 0 ;
# if XTENSA_HAVE_COPROCESSORS
/* Flush all coprocessors before we overwrite them. */
coprocessor_flush_all ( ti ) ;
coprocessor_release_all ( ti ) ;
ret | = __copy_from_user ( & ti - > xtregs_cp , & xtregs - > cp0 ,
sizeof ( xtregs_coprocessor_t ) ) ;
# endif
ret | = __copy_from_user ( & regs - > xtregs_opt , & xtregs - > opt ,
sizeof ( xtregs - > opt ) ) ;
ret | = __copy_from_user ( & ti - > xtregs_user , & xtregs - > user ,
sizeof ( xtregs - > user ) ) ;
return ret ? - EFAULT : 0 ;
}
int ptrace_peekusr ( struct task_struct * child , long regno , long __user * ret )
{
struct pt_regs * regs ;
unsigned long tmp ;
regs = task_pt_regs ( child ) ;
tmp = 0 ; /* Default return value. */
2005-06-23 22:01:16 -07:00
2008-02-12 13:17:07 -08:00
switch ( regno ) {
2005-06-23 22:01:16 -07:00
case REG_AR_BASE . . . REG_AR_BASE + XCHAL_NUM_AREGS - 1 :
2008-02-12 13:17:07 -08:00
tmp = regs - > areg [ regno - REG_AR_BASE ] ;
2005-06-23 22:01:16 -07:00
break ;
2008-02-12 13:17:07 -08:00
2005-06-23 22:01:16 -07:00
case REG_A_BASE . . . REG_A_BASE + 15 :
2008-02-12 13:17:07 -08:00
tmp = regs - > areg [ regno - REG_A_BASE ] ;
2005-06-23 22:01:16 -07:00
break ;
2008-02-12 13:17:07 -08:00
2005-06-23 22:01:16 -07:00
case REG_PC :
tmp = regs - > pc ;
break ;
2008-02-12 13:17:07 -08:00
2005-06-23 22:01:16 -07:00
case REG_PS :
/* Note: PS.EXCM is not set while user task is running;
* its being set in regs is for exception handling
* convenience . */
2006-12-10 02:18:48 -08:00
tmp = ( regs - > ps & ~ ( 1 < < PS_EXCM_BIT ) ) ;
2005-06-23 22:01:16 -07:00
break ;
2008-02-12 13:17:07 -08:00
2005-06-23 22:01:16 -07:00
case REG_WB :
2008-02-12 13:17:07 -08:00
break ; /* tmp = 0 */
2005-06-23 22:01:16 -07:00
case REG_WS :
2008-02-12 13:17:07 -08:00
{
unsigned long wb = regs - > windowbase ;
unsigned long ws = regs - > windowstart ;
tmp = ( ( ws > > wb ) | ( ws < < ( WSBITS - wb ) ) ) & ( ( 1 < < WSBITS ) - 1 ) ;
2005-06-23 22:01:16 -07:00
break ;
2008-02-12 13:17:07 -08:00
}
2005-06-23 22:01:16 -07:00
case REG_LBEG :
tmp = regs - > lbeg ;
break ;
2008-02-12 13:17:07 -08:00
2005-06-23 22:01:16 -07:00
case REG_LEND :
tmp = regs - > lend ;
break ;
2008-02-12 13:17:07 -08:00
2005-06-23 22:01:16 -07:00
case REG_LCOUNT :
tmp = regs - > lcount ;
break ;
2008-02-12 13:17:07 -08:00
2005-06-23 22:01:16 -07:00
case REG_SAR :
tmp = regs - > sar ;
break ;
2008-02-12 13:17:07 -08:00
2005-06-23 22:01:16 -07:00
case SYSCALL_NR :
tmp = regs - > syscall ;
break ;
2008-02-12 13:17:07 -08:00
default :
return - EIO ;
}
return put_user ( tmp , ret ) ;
}
2005-06-23 22:01:16 -07:00
2008-02-12 13:17:07 -08:00
int ptrace_pokeusr ( struct task_struct * child , long regno , long val )
{
struct pt_regs * regs ;
regs = task_pt_regs ( child ) ;
2005-06-23 22:01:16 -07:00
2008-02-12 13:17:07 -08:00
switch ( regno ) {
2005-06-23 22:01:16 -07:00
case REG_AR_BASE . . . REG_AR_BASE + XCHAL_NUM_AREGS - 1 :
2008-02-12 13:17:07 -08:00
regs - > areg [ regno - REG_AR_BASE ] = val ;
2005-06-23 22:01:16 -07:00
break ;
2008-02-12 13:17:07 -08:00
2005-06-23 22:01:16 -07:00
case REG_A_BASE . . . REG_A_BASE + 15 :
2008-02-12 13:17:07 -08:00
regs - > areg [ regno - REG_A_BASE ] = val ;
2005-06-23 22:01:16 -07:00
break ;
2008-02-12 13:17:07 -08:00
2005-06-23 22:01:16 -07:00
case REG_PC :
2008-02-12 13:17:07 -08:00
regs - > pc = val ;
2005-06-23 22:01:16 -07:00
break ;
2008-02-12 13:17:07 -08:00
2005-06-23 22:01:16 -07:00
case SYSCALL_NR :
2008-02-12 13:17:07 -08:00
regs - > syscall = val ;
2005-06-23 22:01:16 -07:00
break ;
default :
2008-02-12 13:17:07 -08:00
return - EIO ;
}
return 0 ;
}
long arch_ptrace ( struct task_struct * child , long request , long addr , long data )
{
int ret = - EPERM ;
switch ( request ) {
case PTRACE_PEEKTEXT : /* read word at location addr. */
case PTRACE_PEEKDATA :
ret = generic_ptrace_peekdata ( child , addr , data ) ;
break ;
case PTRACE_PEEKUSR : /* read register specified by addr. */
ret = ptrace_peekusr ( child , addr , ( void __user * ) data ) ;
break ;
case PTRACE_POKETEXT : /* write the word at location addr. */
case PTRACE_POKEDATA :
ret = generic_ptrace_pokedata ( child , addr , data ) ;
break ;
case PTRACE_POKEUSR : /* write register specified by addr. */
ret = ptrace_pokeusr ( child , addr , data ) ;
2005-06-23 22:01:16 -07:00
break ;
/* continue and stop at next (return from) syscall */
2008-02-12 13:17:07 -08:00
2005-06-23 22:01:16 -07:00
case PTRACE_SYSCALL :
case PTRACE_CONT : /* restart after signal. */
{
ret = - EIO ;
2005-06-30 02:58:56 -07:00
if ( ! valid_signal ( data ) )
2005-06-23 22:01:16 -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 the single step bit is not set. */
child - > ptrace & = ~ PT_SINGLESTEP ;
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 ;
2006-08-31 21:27:39 -07:00
if ( child - > exit_state = = EXIT_ZOMBIE ) /* already dead */
2005-06-23 22:01:16 -07:00
break ;
child - > exit_code = SIGKILL ;
child - > ptrace & = ~ PT_SINGLESTEP ;
wake_up_process ( child ) ;
break ;
case PTRACE_SINGLESTEP :
ret = - EIO ;
2005-06-30 02:58:56 -07:00
if ( ! valid_signal ( data ) )
2005-06-23 22:01:16 -07:00
break ;
clear_tsk_thread_flag ( child , TIF_SYSCALL_TRACE ) ;
child - > ptrace | = PT_SINGLESTEP ;
child - > exit_code = data ;
wake_up_process ( child ) ;
ret = 0 ;
break ;
case PTRACE_GETREGS :
2008-02-12 13:17:07 -08:00
ret = ptrace_getregs ( child , ( void __user * ) data ) ;
2005-06-23 22:01:16 -07:00
break ;
case PTRACE_SETREGS :
2008-02-12 13:17:07 -08:00
ret = ptrace_setregs ( child , ( void __user * ) data ) ;
2005-06-23 22:01:16 -07:00
break ;
2008-02-12 13:17:07 -08:00
case PTRACE_GETXTREGS :
ret = ptrace_getxregs ( child , ( void __user * ) data ) ;
2005-06-23 22:01:16 -07:00
break ;
2008-02-12 13:17:07 -08:00
case PTRACE_SETXTREGS :
ret = ptrace_setxregs ( child , ( void __user * ) data ) ;
2005-06-23 22:01:16 -07:00
break ;
default :
ret = ptrace_request ( child , request , addr , data ) ;
2008-02-12 13:17:07 -08:00
break ;
2005-06-23 22:01:16 -07:00
}
2008-02-12 13:17:07 -08:00
2005-06-23 22:01:16 -07:00
return ret ;
}
void do_syscall_trace ( void )
{
/*
* 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 ;
}
}
2006-12-10 02:18:52 -08:00
void do_syscall_trace_enter ( struct pt_regs * regs )
{
if ( test_thread_flag ( TIF_SYSCALL_TRACE )
& & ( current - > ptrace & PT_PTRACED ) )
do_syscall_trace ( ) ;
#if 0
if ( unlikely ( current - > audit_context ) )
audit_syscall_entry ( current , AUDIT_ARCH_XTENSA . . ) ;
# endif
}
void do_syscall_trace_leave ( struct pt_regs * regs )
{
if ( ( test_thread_flag ( TIF_SYSCALL_TRACE ) )
& & ( current - > ptrace & PT_PTRACED ) )
do_syscall_trace ( ) ;
}