2012-03-05 11:49:33 +00:00
/*
* Based on arch / arm / kernel / ptrace . c
*
* By Ross Biro 1 / 23 / 92
* edited by Linus Torvalds
* ARM modifications Copyright ( C ) 2000 Russell King
* Copyright ( C ) 2012 ARM Ltd .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
2014-07-04 08:28:31 +01:00
# include <linux/audit.h>
2014-04-30 10:51:32 +01:00
# include <linux/compat.h>
2012-03-05 11:49:33 +00:00
# include <linux/kernel.h>
# include <linux/sched.h>
# include <linux/mm.h>
# include <linux/smp.h>
# include <linux/ptrace.h>
# include <linux/user.h>
2014-11-28 05:26:39 +00:00
# include <linux/seccomp.h>
2012-03-05 11:49:33 +00:00
# include <linux/security.h>
# include <linux/init.h>
# include <linux/signal.h>
# include <linux/uaccess.h>
# include <linux/perf_event.h>
# include <linux/hw_breakpoint.h>
# include <linux/regset.h>
# include <linux/tracehook.h>
# include <linux/elf.h>
# include <asm/compat.h>
# include <asm/debug-monitors.h>
# include <asm/pgtable.h>
2014-07-04 08:28:31 +01:00
# include <asm/syscall.h>
2012-03-05 11:49:33 +00:00
# include <asm/traps.h>
# include <asm/system_misc.h>
2014-04-30 10:54:36 +01:00
# define CREATE_TRACE_POINTS
# include <trace/events/syscalls.h>
2012-03-05 11:49:33 +00:00
/*
* TODO : does not yet catch signals sent when the child dies .
* in exit . c or in signal . c .
*/
/*
* Called by kernel / ptrace . c when detaching . .
*/
void ptrace_disable ( struct task_struct * child )
{
2015-12-07 11:50:34 +00:00
/*
* This would be better off in core code , but PTRACE_DETACH has
* grown its fair share of arch - specific worts and changing it
* is likely to cause regressions on obscure architectures .
*/
user_disable_single_step ( child ) ;
2012-03-05 11:49:33 +00:00
}
# ifdef CONFIG_HAVE_HW_BREAKPOINT
/*
* Handle hitting a HW - breakpoint .
*/
static void ptrace_hbptriggered ( struct perf_event * bp ,
struct perf_sample_data * data ,
struct pt_regs * regs )
{
struct arch_hw_breakpoint * bkpt = counter_arch_bp ( bp ) ;
siginfo_t info = {
. si_signo = SIGTRAP ,
. si_errno = 0 ,
. si_code = TRAP_HWBKPT ,
. si_addr = ( void __user * ) ( bkpt - > trigger ) ,
} ;
# ifdef CONFIG_COMPAT
int i ;
if ( ! is_compat_task ( ) )
goto send_sig ;
for ( i = 0 ; i < ARM_MAX_BRP ; + + i ) {
if ( current - > thread . debug . hbp_break [ i ] = = bp ) {
info . si_errno = ( i < < 1 ) + 1 ;
break ;
}
}
2014-08-22 14:13:24 +01:00
for ( i = 0 ; i < ARM_MAX_WRP ; + + i ) {
2012-03-05 11:49:33 +00:00
if ( current - > thread . debug . hbp_watch [ i ] = = bp ) {
info . si_errno = - ( ( i < < 1 ) + 1 ) ;
break ;
}
}
send_sig :
# endif
force_sig_info ( SIGTRAP , & info , current ) ;
}
/*
* Unregister breakpoints from this task and reset the pointers in
* the thread_struct .
*/
void flush_ptrace_hw_breakpoint ( struct task_struct * tsk )
{
int i ;
struct thread_struct * t = & tsk - > thread ;
for ( i = 0 ; i < ARM_MAX_BRP ; i + + ) {
if ( t - > debug . hbp_break [ i ] ) {
unregister_hw_breakpoint ( t - > debug . hbp_break [ i ] ) ;
t - > debug . hbp_break [ i ] = NULL ;
}
}
for ( i = 0 ; i < ARM_MAX_WRP ; i + + ) {
if ( t - > debug . hbp_watch [ i ] ) {
unregister_hw_breakpoint ( t - > debug . hbp_watch [ i ] ) ;
t - > debug . hbp_watch [ i ] = NULL ;
}
}
}
void ptrace_hw_copy_thread ( struct task_struct * tsk )
{
memset ( & tsk - > thread . debug , 0 , sizeof ( struct debug_info ) ) ;
}
static struct perf_event * ptrace_hbp_get_event ( unsigned int note_type ,
struct task_struct * tsk ,
unsigned long idx )
{
struct perf_event * bp = ERR_PTR ( - EINVAL ) ;
switch ( note_type ) {
case NT_ARM_HW_BREAK :
if ( idx < ARM_MAX_BRP )
bp = tsk - > thread . debug . hbp_break [ idx ] ;
break ;
case NT_ARM_HW_WATCH :
if ( idx < ARM_MAX_WRP )
bp = tsk - > thread . debug . hbp_watch [ idx ] ;
break ;
}
return bp ;
}
static int ptrace_hbp_set_event ( unsigned int note_type ,
struct task_struct * tsk ,
unsigned long idx ,
struct perf_event * bp )
{
int err = - EINVAL ;
switch ( note_type ) {
case NT_ARM_HW_BREAK :
if ( idx < ARM_MAX_BRP ) {
tsk - > thread . debug . hbp_break [ idx ] = bp ;
err = 0 ;
}
break ;
case NT_ARM_HW_WATCH :
if ( idx < ARM_MAX_WRP ) {
tsk - > thread . debug . hbp_watch [ idx ] = bp ;
err = 0 ;
}
break ;
}
return err ;
}
static struct perf_event * ptrace_hbp_create ( unsigned int note_type ,
struct task_struct * tsk ,
unsigned long idx )
{
struct perf_event * bp ;
struct perf_event_attr attr ;
int err , type ;
switch ( note_type ) {
case NT_ARM_HW_BREAK :
type = HW_BREAKPOINT_X ;
break ;
case NT_ARM_HW_WATCH :
type = HW_BREAKPOINT_RW ;
break ;
default :
return ERR_PTR ( - EINVAL ) ;
}
ptrace_breakpoint_init ( & attr ) ;
/*
* Initialise fields to sane defaults
* ( i . e . values that will pass validation ) .
*/
attr . bp_addr = 0 ;
attr . bp_len = HW_BREAKPOINT_LEN_4 ;
attr . bp_type = type ;
attr . disabled = 1 ;
bp = register_user_hw_breakpoint ( & attr , ptrace_hbptriggered , NULL , tsk ) ;
if ( IS_ERR ( bp ) )
return bp ;
err = ptrace_hbp_set_event ( note_type , tsk , idx , bp ) ;
if ( err )
return ERR_PTR ( err ) ;
return bp ;
}
static int ptrace_hbp_fill_attr_ctrl ( unsigned int note_type ,
struct arch_hw_breakpoint_ctrl ctrl ,
struct perf_event_attr * attr )
{
2012-10-18 15:17:00 +01:00
int err , len , type , disabled = ! ctrl . enabled ;
2013-12-17 17:09:08 +00:00
attr - > disabled = disabled ;
if ( disabled )
return 0 ;
err = arch_bp_generic_fields ( ctrl , & len , & type ) ;
if ( err )
return err ;
switch ( note_type ) {
case NT_ARM_HW_BREAK :
if ( ( type & HW_BREAKPOINT_X ) ! = type )
2012-03-05 11:49:33 +00:00
return - EINVAL ;
2013-12-17 17:09:08 +00:00
break ;
case NT_ARM_HW_WATCH :
if ( ( type & HW_BREAKPOINT_RW ) ! = type )
return - EINVAL ;
break ;
default :
return - EINVAL ;
2012-03-05 11:49:33 +00:00
}
attr - > bp_len = len ;
attr - > bp_type = type ;
return 0 ;
}
static int ptrace_hbp_get_resource_info ( unsigned int note_type , u32 * info )
{
u8 num ;
u32 reg = 0 ;
switch ( note_type ) {
case NT_ARM_HW_BREAK :
num = hw_breakpoint_slots ( TYPE_INST ) ;
break ;
case NT_ARM_HW_WATCH :
num = hw_breakpoint_slots ( TYPE_DATA ) ;
break ;
default :
return - EINVAL ;
}
reg | = debug_monitors_arch ( ) ;
reg < < = 8 ;
reg | = num ;
* info = reg ;
return 0 ;
}
static int ptrace_hbp_get_ctrl ( unsigned int note_type ,
struct task_struct * tsk ,
unsigned long idx ,
u32 * ctrl )
{
struct perf_event * bp = ptrace_hbp_get_event ( note_type , tsk , idx ) ;
if ( IS_ERR ( bp ) )
return PTR_ERR ( bp ) ;
* ctrl = bp ? encode_ctrl_reg ( counter_arch_bp ( bp ) - > ctrl ) : 0 ;
return 0 ;
}
static int ptrace_hbp_get_addr ( unsigned int note_type ,
struct task_struct * tsk ,
unsigned long idx ,
u64 * addr )
{
struct perf_event * bp = ptrace_hbp_get_event ( note_type , tsk , idx ) ;
if ( IS_ERR ( bp ) )
return PTR_ERR ( bp ) ;
* addr = bp ? bp - > attr . bp_addr : 0 ;
return 0 ;
}
static struct perf_event * ptrace_hbp_get_initialised_bp ( unsigned int note_type ,
struct task_struct * tsk ,
unsigned long idx )
{
struct perf_event * bp = ptrace_hbp_get_event ( note_type , tsk , idx ) ;
if ( ! bp )
bp = ptrace_hbp_create ( note_type , tsk , idx ) ;
return bp ;
}
static int ptrace_hbp_set_ctrl ( unsigned int note_type ,
struct task_struct * tsk ,
unsigned long idx ,
u32 uctrl )
{
int err ;
struct perf_event * bp ;
struct perf_event_attr attr ;
struct arch_hw_breakpoint_ctrl ctrl ;
bp = ptrace_hbp_get_initialised_bp ( note_type , tsk , idx ) ;
if ( IS_ERR ( bp ) ) {
err = PTR_ERR ( bp ) ;
return err ;
}
attr = bp - > attr ;
decode_ctrl_reg ( uctrl , & ctrl ) ;
err = ptrace_hbp_fill_attr_ctrl ( note_type , ctrl , & attr ) ;
if ( err )
return err ;
return modify_user_hw_breakpoint ( bp , & attr ) ;
}
static int ptrace_hbp_set_addr ( unsigned int note_type ,
struct task_struct * tsk ,
unsigned long idx ,
u64 addr )
{
int err ;
struct perf_event * bp ;
struct perf_event_attr attr ;
bp = ptrace_hbp_get_initialised_bp ( note_type , tsk , idx ) ;
if ( IS_ERR ( bp ) ) {
err = PTR_ERR ( bp ) ;
return err ;
}
attr = bp - > attr ;
attr . bp_addr = addr ;
err = modify_user_hw_breakpoint ( bp , & attr ) ;
return err ;
}
# define PTRACE_HBP_ADDR_SZ sizeof(u64)
# define PTRACE_HBP_CTRL_SZ sizeof(u32)
2012-10-11 12:10:57 +01:00
# define PTRACE_HBP_PAD_SZ sizeof(u32)
2012-03-05 11:49:33 +00:00
static int hw_break_get ( struct task_struct * target ,
const struct user_regset * regset ,
unsigned int pos , unsigned int count ,
void * kbuf , void __user * ubuf )
{
unsigned int note_type = regset - > core_note_type ;
2012-10-11 12:10:57 +01:00
int ret , idx = 0 , offset , limit ;
2012-03-05 11:49:33 +00:00
u32 info , ctrl ;
u64 addr ;
/* Resource info */
ret = ptrace_hbp_get_resource_info ( note_type , & info ) ;
if ( ret )
return ret ;
2012-10-11 12:10:57 +01:00
ret = user_regset_copyout ( & pos , & count , & kbuf , & ubuf , & info , 0 ,
sizeof ( info ) ) ;
if ( ret )
return ret ;
/* Pad */
offset = offsetof ( struct user_hwdebug_state , pad ) ;
ret = user_regset_copyout_zero ( & pos , & count , & kbuf , & ubuf , offset ,
offset + PTRACE_HBP_PAD_SZ ) ;
2012-03-05 11:49:33 +00:00
if ( ret )
return ret ;
/* (address, ctrl) registers */
2012-10-11 12:10:57 +01:00
offset = offsetof ( struct user_hwdebug_state , dbg_regs ) ;
2012-03-05 11:49:33 +00:00
limit = regset - > n * regset - > size ;
while ( count & & offset < limit ) {
ret = ptrace_hbp_get_addr ( note_type , target , idx , & addr ) ;
if ( ret )
return ret ;
ret = user_regset_copyout ( & pos , & count , & kbuf , & ubuf , & addr ,
offset , offset + PTRACE_HBP_ADDR_SZ ) ;
if ( ret )
return ret ;
offset + = PTRACE_HBP_ADDR_SZ ;
ret = ptrace_hbp_get_ctrl ( note_type , target , idx , & ctrl ) ;
if ( ret )
return ret ;
ret = user_regset_copyout ( & pos , & count , & kbuf , & ubuf , & ctrl ,
offset , offset + PTRACE_HBP_CTRL_SZ ) ;
if ( ret )
return ret ;
offset + = PTRACE_HBP_CTRL_SZ ;
2012-10-11 12:10:57 +01:00
ret = user_regset_copyout_zero ( & pos , & count , & kbuf , & ubuf ,
offset ,
offset + PTRACE_HBP_PAD_SZ ) ;
if ( ret )
return ret ;
offset + = PTRACE_HBP_PAD_SZ ;
2012-03-05 11:49:33 +00:00
idx + + ;
}
return 0 ;
}
static int hw_break_set ( struct task_struct * target ,
const struct user_regset * regset ,
unsigned int pos , unsigned int count ,
const void * kbuf , const void __user * ubuf )
{
unsigned int note_type = regset - > core_note_type ;
2012-10-11 12:10:57 +01:00
int ret , idx = 0 , offset , limit ;
2012-03-05 11:49:33 +00:00
u32 ctrl ;
u64 addr ;
2012-10-11 12:10:57 +01:00
/* Resource info and pad */
offset = offsetof ( struct user_hwdebug_state , dbg_regs ) ;
ret = user_regset_copyin_ignore ( & pos , & count , & kbuf , & ubuf , 0 , offset ) ;
2012-03-05 11:49:33 +00:00
if ( ret )
return ret ;
/* (address, ctrl) registers */
limit = regset - > n * regset - > size ;
while ( count & & offset < limit ) {
ret = user_regset_copyin ( & pos , & count , & kbuf , & ubuf , & addr ,
offset , offset + PTRACE_HBP_ADDR_SZ ) ;
if ( ret )
return ret ;
ret = ptrace_hbp_set_addr ( note_type , target , idx , addr ) ;
if ( ret )
return ret ;
offset + = PTRACE_HBP_ADDR_SZ ;
ret = user_regset_copyin ( & pos , & count , & kbuf , & ubuf , & ctrl ,
offset , offset + PTRACE_HBP_CTRL_SZ ) ;
if ( ret )
return ret ;
ret = ptrace_hbp_set_ctrl ( note_type , target , idx , ctrl ) ;
if ( ret )
return ret ;
offset + = PTRACE_HBP_CTRL_SZ ;
2012-10-11 12:10:57 +01:00
ret = user_regset_copyin_ignore ( & pos , & count , & kbuf , & ubuf ,
offset ,
offset + PTRACE_HBP_PAD_SZ ) ;
if ( ret )
return ret ;
offset + = PTRACE_HBP_PAD_SZ ;
2012-03-05 11:49:33 +00:00
idx + + ;
}
return 0 ;
}
# endif /* CONFIG_HAVE_HW_BREAKPOINT */
static int gpr_get ( struct task_struct * target ,
const struct user_regset * regset ,
unsigned int pos , unsigned int count ,
void * kbuf , void __user * ubuf )
{
struct user_pt_regs * uregs = & task_pt_regs ( target ) - > user_regs ;
return user_regset_copyout ( & pos , & count , & kbuf , & ubuf , uregs , 0 , - 1 ) ;
}
static int gpr_set ( struct task_struct * target , const struct user_regset * regset ,
unsigned int pos , unsigned int count ,
const void * kbuf , const void __user * ubuf )
{
int ret ;
struct user_pt_regs newregs ;
ret = user_regset_copyin ( & pos , & count , & kbuf , & ubuf , & newregs , 0 , - 1 ) ;
if ( ret )
return ret ;
2016-03-01 14:18:50 +00:00
if ( ! valid_user_regs ( & newregs , target ) )
2012-03-05 11:49:33 +00:00
return - EINVAL ;
task_pt_regs ( target ) - > user_regs = newregs ;
return 0 ;
}
/*
* TODO : update fp accessors for lazy context switching ( sync / flush hwstate )
*/
static int fpr_get ( struct task_struct * target , const struct user_regset * regset ,
unsigned int pos , unsigned int count ,
void * kbuf , void __user * ubuf )
{
struct user_fpsimd_state * uregs ;
uregs = & target - > thread . fpsimd_state . user_fpsimd ;
return user_regset_copyout ( & pos , & count , & kbuf , & ubuf , uregs , 0 , - 1 ) ;
}
static int fpr_set ( struct task_struct * target , const struct user_regset * regset ,
unsigned int pos , unsigned int count ,
const void * kbuf , const void __user * ubuf )
{
int ret ;
struct user_fpsimd_state newstate ;
ret = user_regset_copyin ( & pos , & count , & kbuf , & ubuf , & newstate , 0 , - 1 ) ;
if ( ret )
return ret ;
target - > thread . fpsimd_state . user_fpsimd = newstate ;
2014-05-08 11:20:23 +02:00
fpsimd_flush_task_state ( target ) ;
2012-03-05 11:49:33 +00:00
return ret ;
}
static int tls_get ( struct task_struct * target , const struct user_regset * regset ,
unsigned int pos , unsigned int count ,
void * kbuf , void __user * ubuf )
{
unsigned long * tls = & target - > thread . tp_value ;
return user_regset_copyout ( & pos , & count , & kbuf , & ubuf , tls , 0 , - 1 ) ;
}
static int tls_set ( struct task_struct * target , const struct user_regset * regset ,
unsigned int pos , unsigned int count ,
const void * kbuf , const void __user * ubuf )
{
int ret ;
unsigned long tls ;
ret = user_regset_copyin ( & pos , & count , & kbuf , & ubuf , & tls , 0 , - 1 ) ;
if ( ret )
return ret ;
target - > thread . tp_value = tls ;
return ret ;
}
2014-11-28 05:26:34 +00:00
static int system_call_get ( struct task_struct * target ,
const struct user_regset * regset ,
unsigned int pos , unsigned int count ,
void * kbuf , void __user * ubuf )
{
int syscallno = task_pt_regs ( target ) - > syscallno ;
return user_regset_copyout ( & pos , & count , & kbuf , & ubuf ,
& syscallno , 0 , - 1 ) ;
}
static int system_call_set ( struct task_struct * target ,
const struct user_regset * regset ,
unsigned int pos , unsigned int count ,
const void * kbuf , const void __user * ubuf )
{
int syscallno , ret ;
ret = user_regset_copyin ( & pos , & count , & kbuf , & ubuf , & syscallno , 0 , - 1 ) ;
if ( ret )
return ret ;
task_pt_regs ( target ) - > syscallno = syscallno ;
return ret ;
}
2012-03-05 11:49:33 +00:00
enum aarch64_regset {
REGSET_GPR ,
REGSET_FPR ,
REGSET_TLS ,
# ifdef CONFIG_HAVE_HW_BREAKPOINT
REGSET_HW_BREAK ,
REGSET_HW_WATCH ,
# endif
2014-11-28 05:26:34 +00:00
REGSET_SYSTEM_CALL ,
2012-03-05 11:49:33 +00:00
} ;
static const struct user_regset aarch64_regsets [ ] = {
[ REGSET_GPR ] = {
. core_note_type = NT_PRSTATUS ,
. n = sizeof ( struct user_pt_regs ) / sizeof ( u64 ) ,
. size = sizeof ( u64 ) ,
. align = sizeof ( u64 ) ,
. get = gpr_get ,
. set = gpr_set
} ,
[ REGSET_FPR ] = {
. core_note_type = NT_PRFPREG ,
. n = sizeof ( struct user_fpsimd_state ) / sizeof ( u32 ) ,
/*
* We pretend we have 32 - bit registers because the fpsr and
* fpcr are 32 - bits wide .
*/
. size = sizeof ( u32 ) ,
. align = sizeof ( u32 ) ,
. get = fpr_get ,
. set = fpr_set
} ,
[ REGSET_TLS ] = {
. core_note_type = NT_ARM_TLS ,
. n = 1 ,
. size = sizeof ( void * ) ,
. align = sizeof ( void * ) ,
. get = tls_get ,
. set = tls_set ,
} ,
# ifdef CONFIG_HAVE_HW_BREAKPOINT
[ REGSET_HW_BREAK ] = {
. core_note_type = NT_ARM_HW_BREAK ,
. n = sizeof ( struct user_hwdebug_state ) / sizeof ( u32 ) ,
. size = sizeof ( u32 ) ,
. align = sizeof ( u32 ) ,
. get = hw_break_get ,
. set = hw_break_set ,
} ,
[ REGSET_HW_WATCH ] = {
. core_note_type = NT_ARM_HW_WATCH ,
. n = sizeof ( struct user_hwdebug_state ) / sizeof ( u32 ) ,
. size = sizeof ( u32 ) ,
. align = sizeof ( u32 ) ,
. get = hw_break_get ,
. set = hw_break_set ,
} ,
# endif
2014-11-28 05:26:34 +00:00
[ REGSET_SYSTEM_CALL ] = {
. core_note_type = NT_ARM_SYSTEM_CALL ,
. n = 1 ,
. size = sizeof ( int ) ,
. align = sizeof ( int ) ,
. get = system_call_get ,
. set = system_call_set ,
} ,
2012-03-05 11:49:33 +00:00
} ;
static const struct user_regset_view user_aarch64_view = {
. name = " aarch64 " , . e_machine = EM_AARCH64 ,
. regsets = aarch64_regsets , . n = ARRAY_SIZE ( aarch64_regsets )
} ;
# ifdef CONFIG_COMPAT
# include <linux/compat.h>
enum compat_regset {
REGSET_COMPAT_GPR ,
REGSET_COMPAT_VFP ,
} ;
static int compat_gpr_get ( struct task_struct * target ,
const struct user_regset * regset ,
unsigned int pos , unsigned int count ,
void * kbuf , void __user * ubuf )
{
int ret = 0 ;
unsigned int i , start , num_regs ;
/* Calculate the number of AArch32 registers contained in count */
num_regs = count / regset - > size ;
/* Convert pos into an register number */
start = pos / regset - > size ;
if ( start + num_regs > regset - > n )
return - EIO ;
for ( i = 0 ; i < num_regs ; + + i ) {
unsigned int idx = start + i ;
2013-11-28 12:07:22 +00:00
compat_ulong_t reg ;
2012-03-05 11:49:33 +00:00
switch ( idx ) {
case 15 :
2013-11-28 12:07:22 +00:00
reg = task_pt_regs ( target ) - > pc ;
2012-03-05 11:49:33 +00:00
break ;
case 16 :
2013-11-28 12:07:22 +00:00
reg = task_pt_regs ( target ) - > pstate ;
2012-03-05 11:49:33 +00:00
break ;
case 17 :
2013-11-28 12:07:22 +00:00
reg = task_pt_regs ( target ) - > orig_x0 ;
2012-03-05 11:49:33 +00:00
break ;
default :
2013-11-28 12:07:22 +00:00
reg = task_pt_regs ( target ) - > regs [ idx ] ;
2012-03-05 11:49:33 +00:00
}
2014-06-03 19:21:30 +01:00
if ( kbuf ) {
memcpy ( kbuf , & reg , sizeof ( reg ) ) ;
kbuf + = sizeof ( reg ) ;
} else {
ret = copy_to_user ( ubuf , & reg , sizeof ( reg ) ) ;
2014-08-22 14:20:24 +01:00
if ( ret ) {
ret = - EFAULT ;
2014-06-03 19:21:30 +01:00
break ;
2014-08-22 14:20:24 +01:00
}
2014-06-03 19:21:30 +01:00
ubuf + = sizeof ( reg ) ;
}
2012-03-05 11:49:33 +00:00
}
return ret ;
}
static int compat_gpr_set ( struct task_struct * target ,
const struct user_regset * regset ,
unsigned int pos , unsigned int count ,
const void * kbuf , const void __user * ubuf )
{
struct pt_regs newregs ;
int ret = 0 ;
unsigned int i , start , num_regs ;
/* Calculate the number of AArch32 registers contained in count */
num_regs = count / regset - > size ;
/* Convert pos into an register number */
start = pos / regset - > size ;
if ( start + num_regs > regset - > n )
return - EIO ;
newregs = * task_pt_regs ( target ) ;
for ( i = 0 ; i < num_regs ; + + i ) {
unsigned int idx = start + i ;
2013-11-28 12:07:22 +00:00
compat_ulong_t reg ;
2014-06-03 19:21:30 +01:00
if ( kbuf ) {
memcpy ( & reg , kbuf , sizeof ( reg ) ) ;
kbuf + = sizeof ( reg ) ;
} else {
ret = copy_from_user ( & reg , ubuf , sizeof ( reg ) ) ;
2014-08-22 14:20:24 +01:00
if ( ret ) {
ret = - EFAULT ;
break ;
}
2013-11-28 12:07:22 +00:00
2014-06-03 19:21:30 +01:00
ubuf + = sizeof ( reg ) ;
}
2012-03-05 11:49:33 +00:00
switch ( idx ) {
case 15 :
2013-11-28 12:07:22 +00:00
newregs . pc = reg ;
2012-03-05 11:49:33 +00:00
break ;
case 16 :
2013-11-28 12:07:22 +00:00
newregs . pstate = reg ;
2012-03-05 11:49:33 +00:00
break ;
case 17 :
2013-11-28 12:07:22 +00:00
newregs . orig_x0 = reg ;
2012-03-05 11:49:33 +00:00
break ;
default :
2013-11-28 12:07:22 +00:00
newregs . regs [ idx ] = reg ;
2012-03-05 11:49:33 +00:00
}
}
2016-03-01 14:18:50 +00:00
if ( valid_user_regs ( & newregs . user_regs , target ) )
2012-03-05 11:49:33 +00:00
* task_pt_regs ( target ) = newregs ;
else
ret = - EINVAL ;
return ret ;
}
static int compat_vfp_get ( struct task_struct * target ,
const struct user_regset * regset ,
unsigned int pos , unsigned int count ,
void * kbuf , void __user * ubuf )
{
struct user_fpsimd_state * uregs ;
compat_ulong_t fpscr ;
int ret ;
uregs = & target - > thread . fpsimd_state . user_fpsimd ;
/*
* The VFP registers are packed into the fpsimd_state , so they all sit
* nicely together for us . We just need to create the fpscr separately .
*/
ret = user_regset_copyout ( & pos , & count , & kbuf , & ubuf , uregs , 0 ,
VFP_STATE_SIZE - sizeof ( compat_ulong_t ) ) ;
if ( count & & ! ret ) {
fpscr = ( uregs - > fpsr & VFP_FPSCR_STAT_MASK ) |
( uregs - > fpcr & VFP_FPSCR_CTRL_MASK ) ;
ret = put_user ( fpscr , ( compat_ulong_t * ) ubuf ) ;
}
return ret ;
}
static int compat_vfp_set ( struct task_struct * target ,
const struct user_regset * regset ,
unsigned int pos , unsigned int count ,
const void * kbuf , const void __user * ubuf )
{
struct user_fpsimd_state * uregs ;
compat_ulong_t fpscr ;
int ret ;
if ( pos + count > VFP_STATE_SIZE )
return - EIO ;
uregs = & target - > thread . fpsimd_state . user_fpsimd ;
ret = user_regset_copyin ( & pos , & count , & kbuf , & ubuf , uregs , 0 ,
VFP_STATE_SIZE - sizeof ( compat_ulong_t ) ) ;
if ( count & & ! ret ) {
ret = get_user ( fpscr , ( compat_ulong_t * ) ubuf ) ;
uregs - > fpsr = fpscr & VFP_FPSCR_STAT_MASK ;
uregs - > fpcr = fpscr & VFP_FPSCR_CTRL_MASK ;
}
2014-05-08 11:20:23 +02:00
fpsimd_flush_task_state ( target ) ;
2012-03-05 11:49:33 +00:00
return ret ;
}
2015-07-14 16:20:17 +01:00
static int compat_tls_get ( struct task_struct * target ,
const struct user_regset * regset , unsigned int pos ,
unsigned int count , void * kbuf , void __user * ubuf )
{
compat_ulong_t tls = ( compat_ulong_t ) target - > thread . tp_value ;
return user_regset_copyout ( & pos , & count , & kbuf , & ubuf , & tls , 0 , - 1 ) ;
}
static int compat_tls_set ( struct task_struct * target ,
const struct user_regset * regset , unsigned int pos ,
unsigned int count , const void * kbuf ,
const void __user * ubuf )
{
int ret ;
compat_ulong_t tls ;
ret = user_regset_copyin ( & pos , & count , & kbuf , & ubuf , & tls , 0 , - 1 ) ;
if ( ret )
return ret ;
target - > thread . tp_value = tls ;
return ret ;
}
2012-03-05 11:49:33 +00:00
static const struct user_regset aarch32_regsets [ ] = {
[ REGSET_COMPAT_GPR ] = {
. core_note_type = NT_PRSTATUS ,
. n = COMPAT_ELF_NGREG ,
. size = sizeof ( compat_elf_greg_t ) ,
. align = sizeof ( compat_elf_greg_t ) ,
. get = compat_gpr_get ,
. set = compat_gpr_set
} ,
[ REGSET_COMPAT_VFP ] = {
. core_note_type = NT_ARM_VFP ,
. n = VFP_STATE_SIZE / sizeof ( compat_ulong_t ) ,
. size = sizeof ( compat_ulong_t ) ,
. align = sizeof ( compat_ulong_t ) ,
. get = compat_vfp_get ,
. set = compat_vfp_set
} ,
} ;
static const struct user_regset_view user_aarch32_view = {
. name = " aarch32 " , . e_machine = EM_ARM ,
. regsets = aarch32_regsets , . n = ARRAY_SIZE ( aarch32_regsets )
} ;
2015-07-14 16:20:17 +01:00
static const struct user_regset aarch32_ptrace_regsets [ ] = {
[ REGSET_GPR ] = {
. core_note_type = NT_PRSTATUS ,
. n = COMPAT_ELF_NGREG ,
. size = sizeof ( compat_elf_greg_t ) ,
. align = sizeof ( compat_elf_greg_t ) ,
. get = compat_gpr_get ,
. set = compat_gpr_set
} ,
[ REGSET_FPR ] = {
. core_note_type = NT_ARM_VFP ,
. n = VFP_STATE_SIZE / sizeof ( compat_ulong_t ) ,
. size = sizeof ( compat_ulong_t ) ,
. align = sizeof ( compat_ulong_t ) ,
. get = compat_vfp_get ,
. set = compat_vfp_set
} ,
[ REGSET_TLS ] = {
. core_note_type = NT_ARM_TLS ,
. n = 1 ,
. size = sizeof ( compat_ulong_t ) ,
. align = sizeof ( compat_ulong_t ) ,
. get = compat_tls_get ,
. set = compat_tls_set ,
} ,
# ifdef CONFIG_HAVE_HW_BREAKPOINT
[ REGSET_HW_BREAK ] = {
. core_note_type = NT_ARM_HW_BREAK ,
. n = sizeof ( struct user_hwdebug_state ) / sizeof ( u32 ) ,
. size = sizeof ( u32 ) ,
. align = sizeof ( u32 ) ,
. get = hw_break_get ,
. set = hw_break_set ,
} ,
[ REGSET_HW_WATCH ] = {
. core_note_type = NT_ARM_HW_WATCH ,
. n = sizeof ( struct user_hwdebug_state ) / sizeof ( u32 ) ,
. size = sizeof ( u32 ) ,
. align = sizeof ( u32 ) ,
. get = hw_break_get ,
. set = hw_break_set ,
} ,
# endif
[ REGSET_SYSTEM_CALL ] = {
. core_note_type = NT_ARM_SYSTEM_CALL ,
. n = 1 ,
. size = sizeof ( int ) ,
. align = sizeof ( int ) ,
. get = system_call_get ,
. set = system_call_set ,
} ,
} ;
static const struct user_regset_view user_aarch32_ptrace_view = {
. name = " aarch32 " , . e_machine = EM_ARM ,
. regsets = aarch32_ptrace_regsets , . n = ARRAY_SIZE ( aarch32_ptrace_regsets )
} ;
2012-03-05 11:49:33 +00:00
static int compat_ptrace_read_user ( struct task_struct * tsk , compat_ulong_t off ,
compat_ulong_t __user * ret )
{
compat_ulong_t tmp ;
if ( off & 3 )
return - EIO ;
2012-10-10 15:50:03 +01:00
if ( off = = COMPAT_PT_TEXT_ADDR )
2012-03-05 11:49:33 +00:00
tmp = tsk - > mm - > start_code ;
2012-10-10 15:50:03 +01:00
else if ( off = = COMPAT_PT_DATA_ADDR )
2012-03-05 11:49:33 +00:00
tmp = tsk - > mm - > start_data ;
2012-10-10 15:50:03 +01:00
else if ( off = = COMPAT_PT_TEXT_END_ADDR )
2012-03-05 11:49:33 +00:00
tmp = tsk - > mm - > end_code ;
else if ( off < sizeof ( compat_elf_gregset_t ) )
return copy_regset_to_user ( tsk , & user_aarch32_view ,
REGSET_COMPAT_GPR , off ,
sizeof ( compat_ulong_t ) , ret ) ;
else if ( off > = COMPAT_USER_SZ )
return - EIO ;
else
tmp = 0 ;
return put_user ( tmp , ret ) ;
}
static int compat_ptrace_write_user ( struct task_struct * tsk , compat_ulong_t off ,
compat_ulong_t val )
{
int ret ;
2014-06-02 11:47:23 +01:00
mm_segment_t old_fs = get_fs ( ) ;
2012-03-05 11:49:33 +00:00
if ( off & 3 | | off > = COMPAT_USER_SZ )
return - EIO ;
if ( off > = sizeof ( compat_elf_gregset_t ) )
return 0 ;
2014-06-02 11:47:23 +01:00
set_fs ( KERNEL_DS ) ;
2012-03-05 11:49:33 +00:00
ret = copy_regset_from_user ( tsk , & user_aarch32_view ,
REGSET_COMPAT_GPR , off ,
sizeof ( compat_ulong_t ) ,
& val ) ;
2014-06-02 11:47:23 +01:00
set_fs ( old_fs ) ;
2012-03-05 11:49:33 +00:00
return ret ;
}
# ifdef CONFIG_HAVE_HW_BREAKPOINT
/*
* Convert a virtual register number into an index for a thread_info
* breakpoint array . Breakpoints are identified using positive numbers
* whilst watchpoints are negative . The registers are laid out as pairs
* of ( address , control ) , each pair mapping to a unique hw_breakpoint struct .
* Register 0 is reserved for describing resource information .
*/
static int compat_ptrace_hbp_num_to_idx ( compat_long_t num )
{
return ( abs ( num ) - 1 ) > > 1 ;
}
static int compat_ptrace_hbp_get_resource_info ( u32 * kdata )
{
u8 num_brps , num_wrps , debug_arch , wp_len ;
u32 reg = 0 ;
num_brps = hw_breakpoint_slots ( TYPE_INST ) ;
num_wrps = hw_breakpoint_slots ( TYPE_DATA ) ;
debug_arch = debug_monitors_arch ( ) ;
wp_len = 8 ;
reg | = debug_arch ;
reg < < = 8 ;
reg | = wp_len ;
reg < < = 8 ;
reg | = num_wrps ;
reg < < = 8 ;
reg | = num_brps ;
* kdata = reg ;
return 0 ;
}
static int compat_ptrace_hbp_get ( unsigned int note_type ,
struct task_struct * tsk ,
compat_long_t num ,
u32 * kdata )
{
u64 addr = 0 ;
u32 ctrl = 0 ;
int err , idx = compat_ptrace_hbp_num_to_idx ( num ) ; ;
if ( num & 1 ) {
err = ptrace_hbp_get_addr ( note_type , tsk , idx , & addr ) ;
* kdata = ( u32 ) addr ;
} else {
err = ptrace_hbp_get_ctrl ( note_type , tsk , idx , & ctrl ) ;
* kdata = ctrl ;
}
return err ;
}
static int compat_ptrace_hbp_set ( unsigned int note_type ,
struct task_struct * tsk ,
compat_long_t num ,
u32 * kdata )
{
u64 addr ;
u32 ctrl ;
int err , idx = compat_ptrace_hbp_num_to_idx ( num ) ;
if ( num & 1 ) {
addr = * kdata ;
err = ptrace_hbp_set_addr ( note_type , tsk , idx , addr ) ;
} else {
ctrl = * kdata ;
err = ptrace_hbp_set_ctrl ( note_type , tsk , idx , ctrl ) ;
}
return err ;
}
static int compat_ptrace_gethbpregs ( struct task_struct * tsk , compat_long_t num ,
compat_ulong_t __user * data )
{
int ret ;
u32 kdata ;
mm_segment_t old_fs = get_fs ( ) ;
set_fs ( KERNEL_DS ) ;
/* Watchpoint */
if ( num < 0 ) {
ret = compat_ptrace_hbp_get ( NT_ARM_HW_WATCH , tsk , num , & kdata ) ;
/* Resource info */
} else if ( num = = 0 ) {
ret = compat_ptrace_hbp_get_resource_info ( & kdata ) ;
/* Breakpoint */
} else {
ret = compat_ptrace_hbp_get ( NT_ARM_HW_BREAK , tsk , num , & kdata ) ;
}
set_fs ( old_fs ) ;
if ( ! ret )
ret = put_user ( kdata , data ) ;
return ret ;
}
static int compat_ptrace_sethbpregs ( struct task_struct * tsk , compat_long_t num ,
compat_ulong_t __user * data )
{
int ret ;
u32 kdata = 0 ;
mm_segment_t old_fs = get_fs ( ) ;
if ( num = = 0 )
return 0 ;
ret = get_user ( kdata , data ) ;
if ( ret )
return ret ;
set_fs ( KERNEL_DS ) ;
if ( num < 0 )
ret = compat_ptrace_hbp_set ( NT_ARM_HW_WATCH , tsk , num , & kdata ) ;
else
ret = compat_ptrace_hbp_set ( NT_ARM_HW_BREAK , tsk , num , & kdata ) ;
set_fs ( old_fs ) ;
return ret ;
}
# endif /* CONFIG_HAVE_HW_BREAKPOINT */
long compat_arch_ptrace ( struct task_struct * child , compat_long_t request ,
compat_ulong_t caddr , compat_ulong_t cdata )
{
unsigned long addr = caddr ;
unsigned long data = cdata ;
void __user * datap = compat_ptr ( data ) ;
int ret ;
switch ( request ) {
case PTRACE_PEEKUSR :
ret = compat_ptrace_read_user ( child , addr , datap ) ;
break ;
case PTRACE_POKEUSR :
ret = compat_ptrace_write_user ( child , addr , data ) ;
break ;
2012-09-27 11:38:12 +01:00
case COMPAT_PTRACE_GETREGS :
2012-03-05 11:49:33 +00:00
ret = copy_regset_to_user ( child ,
& user_aarch32_view ,
REGSET_COMPAT_GPR ,
0 , sizeof ( compat_elf_gregset_t ) ,
datap ) ;
break ;
2012-09-27 11:38:12 +01:00
case COMPAT_PTRACE_SETREGS :
2012-03-05 11:49:33 +00:00
ret = copy_regset_from_user ( child ,
& user_aarch32_view ,
REGSET_COMPAT_GPR ,
0 , sizeof ( compat_elf_gregset_t ) ,
datap ) ;
break ;
2012-09-27 11:38:12 +01:00
case COMPAT_PTRACE_GET_THREAD_AREA :
2012-03-05 11:49:33 +00:00
ret = put_user ( ( compat_ulong_t ) child - > thread . tp_value ,
( compat_ulong_t __user * ) datap ) ;
break ;
2012-09-27 11:38:12 +01:00
case COMPAT_PTRACE_SET_SYSCALL :
2012-03-05 11:49:33 +00:00
task_pt_regs ( child ) - > syscallno = data ;
ret = 0 ;
break ;
case COMPAT_PTRACE_GETVFPREGS :
ret = copy_regset_to_user ( child ,
& user_aarch32_view ,
REGSET_COMPAT_VFP ,
0 , VFP_STATE_SIZE ,
datap ) ;
break ;
case COMPAT_PTRACE_SETVFPREGS :
ret = copy_regset_from_user ( child ,
& user_aarch32_view ,
REGSET_COMPAT_VFP ,
0 , VFP_STATE_SIZE ,
datap ) ;
break ;
# ifdef CONFIG_HAVE_HW_BREAKPOINT
2012-09-27 11:38:12 +01:00
case COMPAT_PTRACE_GETHBPREGS :
2012-03-05 11:49:33 +00:00
ret = compat_ptrace_gethbpregs ( child , addr , datap ) ;
break ;
2012-09-27 11:38:12 +01:00
case COMPAT_PTRACE_SETHBPREGS :
2012-03-05 11:49:33 +00:00
ret = compat_ptrace_sethbpregs ( child , addr , datap ) ;
break ;
# endif
default :
ret = compat_ptrace_request ( child , request , addr ,
data ) ;
break ;
}
return ret ;
}
# endif /* CONFIG_COMPAT */
const struct user_regset_view * task_user_regset_view ( struct task_struct * task )
{
# ifdef CONFIG_COMPAT
2015-07-14 16:20:17 +01:00
/*
* Core dumping of 32 - bit tasks or compat ptrace requests must use the
* user_aarch32_view compatible with arm32 . Native ptrace requests on
* 32 - bit children use an extended user_aarch32_ptrace_view to allow
* access to the TLS register .
*/
if ( is_compat_task ( ) )
2012-03-05 11:49:33 +00:00
return & user_aarch32_view ;
2015-07-14 16:20:17 +01:00
else if ( is_compat_thread ( task_thread_info ( task ) ) )
return & user_aarch32_ptrace_view ;
2012-03-05 11:49:33 +00:00
# endif
return & user_aarch64_view ;
}
long arch_ptrace ( struct task_struct * child , long request ,
unsigned long addr , unsigned long data )
{
return ptrace_request ( child , request , addr , data ) ;
}
2014-04-30 10:51:30 +01:00
enum ptrace_syscall_dir {
PTRACE_SYSCALL_ENTER = 0 ,
PTRACE_SYSCALL_EXIT ,
} ;
static void tracehook_report_syscall ( struct pt_regs * regs ,
enum ptrace_syscall_dir dir )
2012-03-05 11:49:33 +00:00
{
2014-04-30 10:51:30 +01:00
int regno ;
2012-03-05 11:49:33 +00:00
unsigned long saved_reg ;
2014-04-30 10:51:30 +01:00
/*
* A scratch register ( ip ( r12 ) on AArch32 , x7 on AArch64 ) is
* used to denote syscall entry / exit :
*/
regno = ( is_compat_task ( ) ? 12 : 7 ) ;
saved_reg = regs - > regs [ regno ] ;
regs - > regs [ regno ] = dir ;
2012-03-05 11:49:33 +00:00
2014-04-30 10:51:30 +01:00
if ( dir = = PTRACE_SYSCALL_EXIT )
2012-03-05 11:49:33 +00:00
tracehook_report_syscall_exit ( regs , 0 ) ;
else if ( tracehook_report_syscall_entry ( regs ) )
regs - > syscallno = ~ 0UL ;
2014-04-30 10:51:30 +01:00
regs - > regs [ regno ] = saved_reg ;
}
asmlinkage int syscall_trace_enter ( struct pt_regs * regs )
{
2014-11-28 05:26:39 +00:00
/* Do the secure computing check first; failures should be fast. */
if ( secure_computing ( ) = = - 1 )
return - 1 ;
2014-04-30 10:51:30 +01:00
if ( test_thread_flag ( TIF_SYSCALL_TRACE ) )
tracehook_report_syscall ( regs , PTRACE_SYSCALL_ENTER ) ;
2012-03-05 11:49:33 +00:00
2014-04-30 10:54:36 +01:00
if ( test_thread_flag ( TIF_SYSCALL_TRACEPOINT ) )
trace_sys_enter ( regs , regs - > syscallno ) ;
2014-09-23 16:25:34 -04:00
audit_syscall_entry ( regs - > syscallno , regs - > orig_x0 , regs - > regs [ 1 ] ,
regs - > regs [ 2 ] , regs - > regs [ 3 ] ) ;
2014-07-04 08:28:31 +01:00
2012-03-05 11:49:33 +00:00
return regs - > syscallno ;
}
2014-04-30 10:51:30 +01:00
asmlinkage void syscall_trace_exit ( struct pt_regs * regs )
{
2014-07-04 08:28:31 +01:00
audit_syscall_exit ( regs ) ;
2014-04-30 10:54:36 +01:00
if ( test_thread_flag ( TIF_SYSCALL_TRACEPOINT ) )
trace_sys_exit ( regs , regs_return_value ( regs ) ) ;
2014-04-30 10:51:30 +01:00
if ( test_thread_flag ( TIF_SYSCALL_TRACE ) )
tracehook_report_syscall ( regs , PTRACE_SYSCALL_EXIT ) ;
}
2016-03-01 14:18:50 +00:00
/*
* Bits which are always architecturally RES0 per ARM DDI 04 87 A . h
* Userspace cannot use these until they have an architectural meaning .
* We also reserve IL for the kernel ; SS is handled dynamically .
*/
# define SPSR_EL1_AARCH64_RES0_BITS \
( GENMASK_ULL ( 63 , 32 ) | GENMASK_ULL ( 27 , 22 ) | GENMASK_ULL ( 20 , 10 ) | \
GENMASK_ULL ( 5 , 5 ) )
# define SPSR_EL1_AARCH32_RES0_BITS \
( GENMASK_ULL ( 63 , 32 ) | GENMASK_ULL ( 24 , 22 ) | GENMASK_ULL ( 20 , 20 ) )
static int valid_compat_regs ( struct user_pt_regs * regs )
{
regs - > pstate & = ~ SPSR_EL1_AARCH32_RES0_BITS ;
if ( ! system_supports_mixed_endian_el0 ( ) ) {
if ( IS_ENABLED ( CONFIG_CPU_BIG_ENDIAN ) )
regs - > pstate | = COMPAT_PSR_E_BIT ;
else
regs - > pstate & = ~ COMPAT_PSR_E_BIT ;
}
if ( user_mode ( regs ) & & ( regs - > pstate & PSR_MODE32_BIT ) & &
( regs - > pstate & COMPAT_PSR_A_BIT ) = = 0 & &
( regs - > pstate & COMPAT_PSR_I_BIT ) = = 0 & &
( regs - > pstate & COMPAT_PSR_F_BIT ) = = 0 ) {
return 1 ;
}
/*
* Force PSR to a valid 32 - bit EL0t , preserving the same bits as
* arch / arm .
*/
regs - > pstate & = COMPAT_PSR_N_BIT | COMPAT_PSR_Z_BIT |
COMPAT_PSR_C_BIT | COMPAT_PSR_V_BIT |
COMPAT_PSR_Q_BIT | COMPAT_PSR_IT_MASK |
COMPAT_PSR_GE_MASK | COMPAT_PSR_E_BIT |
COMPAT_PSR_T_BIT ;
regs - > pstate | = PSR_MODE32_BIT ;
return 0 ;
}
static int valid_native_regs ( struct user_pt_regs * regs )
{
regs - > pstate & = ~ SPSR_EL1_AARCH64_RES0_BITS ;
if ( user_mode ( regs ) & & ! ( regs - > pstate & PSR_MODE32_BIT ) & &
( regs - > pstate & PSR_D_BIT ) = = 0 & &
( regs - > pstate & PSR_A_BIT ) = = 0 & &
( regs - > pstate & PSR_I_BIT ) = = 0 & &
( regs - > pstate & PSR_F_BIT ) = = 0 ) {
return 1 ;
}
/* Force PSR to a valid 64-bit EL0t */
regs - > pstate & = PSR_N_BIT | PSR_Z_BIT | PSR_C_BIT | PSR_V_BIT ;
return 0 ;
}
/*
* Are the current registers suitable for user mode ? ( used to maintain
* security in signal handlers )
*/
int valid_user_regs ( struct user_pt_regs * regs , struct task_struct * task )
{
if ( ! test_tsk_thread_flag ( task , TIF_SINGLESTEP ) )
regs - > pstate & = ~ DBG_SPSR_SS ;
if ( is_compat_thread ( task_thread_info ( task ) ) )
return valid_compat_regs ( regs ) ;
else
return valid_native_regs ( regs ) ;
}