2005-04-17 02:20:36 +04:00
/*
* 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 .
*
* Copyright ( C ) 1992 Ross Biro
* Copyright ( C ) Linus Torvalds
* Copyright ( C ) 1994 , 95 , 96 , 97 , 98 , 2000 Ralf Baechle
* Copyright ( C ) 1996 David S . Miller
* Kevin D . Kissell , kevink @ mips . com and Carsten Langgaard , carstenl @ mips . com
* Copyright ( C ) 1999 MIPS Technologies , Inc .
* Copyright ( C ) 2000 Ulf Carlsson
*
* At this time Linux / MIPS64 only supports syscall tracing , even for 32 - bit
* binaries .
*/
# include <linux/compiler.h>
# 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/smp_lock.h>
# include <linux/user.h>
# include <linux/security.h>
# include <asm/cpu.h>
2005-05-31 15:49:19 +04:00
# include <asm/dsp.h>
2005-04-17 02:20:36 +04:00
# include <asm/fpu.h>
# include <asm/mipsregs.h>
2005-10-06 20:39:32 +04:00
# include <asm/mipsmtregs.h>
2005-04-17 02:20:36 +04:00
# include <asm/pgtable.h>
# include <asm/page.h>
# include <asm/system.h>
# include <asm/uaccess.h>
# include <asm/bootinfo.h>
2005-09-29 02:11:15 +04:00
int ptrace_getregs ( struct task_struct * child , __s64 __user * data ) ;
int ptrace_setregs ( struct task_struct * child , __s64 __user * data ) ;
int ptrace_getfpregs ( struct task_struct * child , __u32 __user * data ) ;
int ptrace_setfpregs ( struct task_struct * child , __u32 __user * data ) ;
2005-04-17 02:20:36 +04:00
/*
* Tracing a 32 - bit process with a 64 - bit strace and vice versa will not
* work . I don ' t know how to fix this .
*/
asmlinkage int sys32_ptrace ( int request , int pid , int addr , int data )
{
struct task_struct * child ;
int ret ;
#if 0
printk ( " ptrace(r=%d,pid=%d,addr=%08lx,data=%08lx) \n " ,
( int ) request , ( int ) pid , ( unsigned long ) addr ,
( unsigned long ) data ) ;
# endif
lock_kernel ( ) ;
if ( request = = PTRACE_TRACEME ) {
2006-01-08 12:02:33 +03:00
ret = ptrace_traceme ( ) ;
2005-04-17 02:20:36 +04:00
goto out ;
}
2006-01-08 12:02:33 +03:00
child = ptrace_get_task_struct ( pid ) ;
if ( IS_ERR ( child ) ) {
ret = PTR_ERR ( child ) ;
goto out ;
}
2005-04-17 02:20:36 +04:00
if ( request = = PTRACE_ATTACH ) {
ret = ptrace_attach ( child ) ;
goto out_tsk ;
}
ret = ptrace_check_attach ( child , request = = PTRACE_KILL ) ;
if ( ret < 0 )
goto out_tsk ;
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 : {
unsigned int tmp ;
int copied ;
copied = access_process_vm ( child , addr , & tmp , sizeof ( tmp ) , 0 ) ;
ret = - EIO ;
if ( copied ! = sizeof ( tmp ) )
break ;
2006-01-29 16:34:32 +03:00
ret = put_user ( tmp , ( unsigned int __user * ) ( unsigned long ) data ) ;
2005-04-17 02:20:36 +04:00
break ;
}
2005-09-29 02:11:15 +04:00
/*
* Read 4 bytes of the other process ' storage
* data is a pointer specifying where the user wants the
* 4 bytes copied into
* addr is a pointer in the user ' s storage that contains an 8 byte
* address in the other process of the 4 bytes that is to be read
* ( this is run in a 32 - bit process looking at a 64 - bit process )
* when I and D space are separate , these will need to be fixed .
*/
case PTRACE_PEEKTEXT_3264 :
case PTRACE_PEEKDATA_3264 : {
u32 tmp ;
int copied ;
u32 __user * addrOthers ;
ret = - EIO ;
/* Get the addr in the other process that we want to read */
if ( get_user ( addrOthers , ( u32 __user * __user * ) ( unsigned long ) addr ) ! = 0 )
break ;
copied = access_process_vm ( child , ( u64 ) addrOthers , & tmp ,
sizeof ( tmp ) , 0 ) ;
if ( copied ! = sizeof ( tmp ) )
break ;
ret = put_user ( tmp , ( u32 __user * ) ( unsigned long ) data ) ;
break ;
}
2005-04-17 02:20:36 +04:00
/* Read the word at location addr in the USER area. */
case PTRACE_PEEKUSR : {
struct pt_regs * regs ;
unsigned int tmp ;
2006-01-12 12:06:07 +03:00
regs = task_pt_regs ( child ) ;
2005-04-17 02:20:36 +04:00
ret = 0 ; /* Default return value. */
switch ( addr ) {
case 0 . . . 31 :
tmp = regs - > regs [ addr ] ;
break ;
case FPR_BASE . . . FPR_BASE + 31 :
if ( tsk_used_math ( child ) ) {
fpureg_t * fregs = get_fpu_regs ( child ) ;
/*
* The odd registers are actually the high
* order bits of the values stored in the even
* registers - unless we ' re using r2k_switch . S .
*/
if ( addr & 1 )
tmp = ( unsigned long ) ( fregs [ ( ( addr & ~ 1 ) - 32 ) ] > > 32 ) ;
else
tmp = ( unsigned long ) ( fregs [ ( addr - 32 ) ] & 0xffffffff ) ;
} else {
tmp = - 1 ; /* FP not yet used */
}
break ;
case PC :
tmp = regs - > cp0_epc ;
break ;
case CAUSE :
tmp = regs - > cp0_cause ;
break ;
case BADVADDR :
tmp = regs - > cp0_badvaddr ;
break ;
case MMHI :
tmp = regs - > hi ;
break ;
case MMLO :
tmp = regs - > lo ;
break ;
case FPC_CSR :
2006-05-15 20:26:03 +04:00
tmp = child - > thread . fpu . fcr31 ;
2005-04-17 02:20:36 +04:00
break ;
case FPC_EIR : { /* implementation / version register */
unsigned int flags ;
2006-04-05 12:45:45 +04:00
# ifdef CONFIG_MIPS_MT_SMTC
unsigned int irqflags ;
unsigned int mtflags ;
# endif /* CONFIG_MIPS_MT_SMTC */
2005-04-17 02:20:36 +04:00
2006-01-29 16:34:32 +03:00
if ( ! cpu_has_fpu ) {
tmp = 0 ;
2005-04-17 02:20:36 +04:00
break ;
2006-01-29 16:34:32 +03:00
}
2005-04-17 02:20:36 +04:00
2006-04-05 12:45:45 +04:00
# ifdef CONFIG_MIPS_MT_SMTC
/* Read-modify-write of Status must be atomic */
local_irq_save ( irqflags ) ;
mtflags = dmt ( ) ;
# endif /* CONFIG_MIPS_MT_SMTC */
2005-10-06 20:39:32 +04:00
preempt_disable ( ) ;
if ( cpu_has_mipsmt ) {
unsigned int vpflags = dvpe ( ) ;
flags = read_c0_status ( ) ;
__enable_fpu ( ) ;
__asm__ __volatile__ ( " cfc1 \t %0,$0 " : " =r " ( tmp ) ) ;
write_c0_status ( flags ) ;
evpe ( vpflags ) ;
} else {
flags = read_c0_status ( ) ;
__enable_fpu ( ) ;
__asm__ __volatile__ ( " cfc1 \t %0,$0 " : " =r " ( tmp ) ) ;
write_c0_status ( flags ) ;
}
2006-04-05 12:45:45 +04:00
# ifdef CONFIG_MIPS_MT_SMTC
emt ( mtflags ) ;
local_irq_restore ( irqflags ) ;
# endif /* CONFIG_MIPS_MT_SMTC */
2005-10-06 20:39:32 +04:00
preempt_enable ( ) ;
2005-04-17 02:20:36 +04:00
break ;
}
2006-01-29 16:34:32 +03:00
case DSP_BASE . . . DSP_BASE + 5 : {
dspreg_t * dregs ;
2005-05-31 15:49:19 +04:00
if ( ! cpu_has_dsp ) {
tmp = 0 ;
ret = - EIO ;
goto out_tsk ;
}
2006-01-29 16:34:32 +03:00
dregs = __get_dsp_regs ( child ) ;
2005-12-05 16:47:25 +03:00
tmp = ( unsigned long ) ( dregs [ addr - DSP_BASE ] ) ;
2005-05-31 15:49:19 +04:00
break ;
2006-01-29 16:34:32 +03:00
}
2005-05-31 15:49:19 +04:00
case DSP_CONTROL :
if ( ! cpu_has_dsp ) {
tmp = 0 ;
ret = - EIO ;
goto out_tsk ;
}
tmp = child - > thread . dsp . dspcontrol ;
break ;
2005-04-17 02:20:36 +04:00
default :
tmp = 0 ;
ret = - EIO ;
goto out_tsk ;
}
2006-01-29 16:34:32 +03:00
ret = put_user ( tmp , ( unsigned __user * ) ( unsigned long ) 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 :
ret = 0 ;
if ( access_process_vm ( child , addr , & data , sizeof ( data ) , 1 )
= = sizeof ( data ) )
break ;
ret = - EIO ;
break ;
2005-09-29 02:11:15 +04:00
/*
* Write 4 bytes into the other process ' storage
* data is the 4 bytes that the user wants written
* addr is a pointer in the user ' s storage that contains an
* 8 byte address in the other process where the 4 bytes
* that is to be written
* ( this is run in a 32 - bit process looking at a 64 - bit process )
* when I and D space are separate , these will need to be fixed .
*/
case PTRACE_POKETEXT_3264 :
case PTRACE_POKEDATA_3264 : {
u32 __user * addrOthers ;
/* Get the addr in the other process that we want to write into */
ret = - EIO ;
if ( get_user ( addrOthers , ( u32 __user * __user * ) ( unsigned long ) addr ) ! = 0 )
break ;
ret = 0 ;
if ( access_process_vm ( child , ( u64 ) addrOthers , & data ,
sizeof ( data ) , 1 ) = = sizeof ( data ) )
break ;
ret = - EIO ;
break ;
}
2005-04-17 02:20:36 +04:00
case PTRACE_POKEUSR : {
struct pt_regs * regs ;
ret = 0 ;
2006-01-12 12:06:07 +03:00
regs = task_pt_regs ( child ) ;
2005-04-17 02:20:36 +04:00
switch ( addr ) {
case 0 . . . 31 :
regs - > regs [ addr ] = data ;
break ;
case FPR_BASE . . . FPR_BASE + 31 : {
fpureg_t * fregs = get_fpu_regs ( child ) ;
if ( ! tsk_used_math ( child ) ) {
/* FP not yet used */
2006-05-15 20:26:03 +04:00
memset ( & child - > thread . fpu , ~ 0 ,
sizeof ( child - > thread . fpu ) ) ;
child - > thread . fpu . fcr31 = 0 ;
2005-04-17 02:20:36 +04:00
}
/*
* The odd registers are actually the high order bits
* of the values stored in the even registers - unless
* we ' re using r2k_switch . S .
*/
if ( addr & 1 ) {
fregs [ ( addr & ~ 1 ) - FPR_BASE ] & = 0xffffffff ;
fregs [ ( addr & ~ 1 ) - FPR_BASE ] | = ( ( unsigned long long ) data ) < < 32 ;
} else {
fregs [ addr - FPR_BASE ] & = ~ 0xffffffffLL ;
/* Must cast, lest sign extension fill upper
bits ! */
fregs [ addr - FPR_BASE ] | = ( unsigned int ) data ;
}
break ;
}
case PC :
regs - > cp0_epc = data ;
break ;
case MMHI :
regs - > hi = data ;
break ;
case MMLO :
regs - > lo = data ;
break ;
case FPC_CSR :
2006-05-15 20:26:03 +04:00
child - > thread . fpu . fcr31 = data ;
2005-04-17 02:20:36 +04:00
break ;
2006-01-29 16:34:32 +03:00
case DSP_BASE . . . DSP_BASE + 5 : {
dspreg_t * dregs ;
2005-05-31 15:49:19 +04:00
if ( ! cpu_has_dsp ) {
ret = - EIO ;
break ;
}
2006-01-29 16:34:32 +03:00
dregs = __get_dsp_regs ( child ) ;
2005-05-31 15:49:19 +04:00
dregs [ addr - DSP_BASE ] = data ;
break ;
2006-01-29 16:34:32 +03:00
}
2005-05-31 15:49:19 +04:00
case DSP_CONTROL :
if ( ! cpu_has_dsp ) {
ret = - EIO ;
break ;
}
child - > thread . dsp . dspcontrol = data ;
break ;
2005-04-17 02:20:36 +04:00
default :
/* The rest are not allowed. */
ret = - EIO ;
break ;
}
break ;
}
2005-09-29 02:11:15 +04:00
case PTRACE_GETREGS :
ret = ptrace_getregs ( child , ( __u64 __user * ) ( __u64 ) data ) ;
break ;
case PTRACE_SETREGS :
ret = ptrace_setregs ( child , ( __u64 __user * ) ( __u64 ) data ) ;
break ;
case PTRACE_GETFPREGS :
ret = ptrace_getfpregs ( child , ( __u32 __user * ) ( __u64 ) data ) ;
break ;
case PTRACE_SETFPREGS :
ret = ptrace_setfpregs ( child , ( __u32 __user * ) ( __u64 ) data ) ;
break ;
2005-04-17 02:20:36 +04:00
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 ) ;
}
child - > exit_code = data ;
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 ) /* already dead */
break ;
child - > exit_code = SIGKILL ;
wake_up_process ( child ) ;
break ;
2005-04-13 21:43:59 +04:00
case PTRACE_GET_THREAD_AREA :
2006-01-12 12:06:07 +03:00
ret = put_user ( task_thread_info ( child ) - > tp_value ,
2005-04-13 21:43:59 +04:00
( unsigned int __user * ) ( unsigned long ) data ) ;
break ;
2005-04-17 02:20:36 +04:00
case PTRACE_DETACH : /* detach a process that was attached. */
ret = ptrace_detach ( child , data ) ;
break ;
2005-02-17 00:22:40 +03:00
case PTRACE_GETEVENTMSG :
ret = put_user ( child - > ptrace_message ,
( unsigned int __user * ) ( unsigned long ) data ) ;
break ;
2005-09-29 02:11:15 +04:00
case PTRACE_GET_THREAD_AREA_3264 :
2006-01-12 12:06:07 +03:00
ret = put_user ( task_thread_info ( child ) - > tp_value ,
2005-09-29 02:11:15 +04:00
( unsigned long __user * ) ( unsigned long ) data ) ;
break ;
2005-04-17 02:20:36 +04:00
default :
ret = ptrace_request ( child , request , addr , data ) ;
break ;
}
out_tsk :
put_task_struct ( child ) ;
out :
unlock_kernel ( ) ;
return ret ;
}