2009-06-01 22:13:57 +04:00
/*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* 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 , write to the Free Software
* Foundation , Inc . , 59 Temple Place - Suite 330 , Boston , MA 02111 - 1307 , USA .
*
* Copyright ( C ) 2007 Alan Stern
* Copyright ( C ) 2009 IBM Corporation
2009-09-09 21:22:48 +04:00
* Copyright ( C ) 2009 Frederic Weisbecker < fweisbec @ gmail . com >
2009-11-23 18:47:13 +03:00
*
* Authors : Alan Stern < stern @ rowland . harvard . edu >
* K . Prasad < prasad @ linux . vnet . ibm . com >
* Frederic Weisbecker < fweisbec @ gmail . com >
2009-06-01 22:13:57 +04:00
*/
/*
* HW_breakpoint : a unified kernel / user - space hardware breakpoint facility ,
* using the CPU ' s debug registers .
*/
2009-09-09 21:22:48 +04:00
# include <linux/perf_event.h>
# include <linux/hw_breakpoint.h>
2009-06-01 22:13:57 +04:00
# include <linux/irqflags.h>
# include <linux/notifier.h>
# include <linux/kallsyms.h>
# include <linux/kprobes.h>
# include <linux/percpu.h>
# include <linux/kdebug.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/sched.h>
# include <linux/init.h>
# include <linux/smp.h>
# include <asm/hw_breakpoint.h>
# include <asm/processor.h>
# include <asm/debugreg.h>
2009-09-09 21:22:48 +04:00
/* Per cpu debug control register value */
2009-11-25 16:24:44 +03:00
DEFINE_PER_CPU ( unsigned long , cpu_dr7 ) ;
EXPORT_PER_CPU_SYMBOL ( cpu_dr7 ) ;
2009-09-09 21:22:48 +04:00
/* Per cpu debug address registers values */
static DEFINE_PER_CPU ( unsigned long , cpu_debugreg [ HBP_NUM ] ) ;
2009-06-01 22:13:57 +04:00
/*
2009-09-09 21:22:48 +04:00
* Stores the breakpoints currently in use on each breakpoint address
* register for each cpus
2009-06-01 22:13:57 +04:00
*/
2009-09-09 21:22:48 +04:00
static DEFINE_PER_CPU ( struct perf_event * , bp_per_reg [ HBP_NUM ] ) ;
2009-06-01 22:13:57 +04:00
2009-11-26 08:04:38 +03:00
static inline unsigned long
__encode_dr7 ( int drnum , unsigned int len , unsigned int type )
2009-06-01 22:13:57 +04:00
{
unsigned long bp_info ;
bp_info = ( len | type ) & 0xf ;
bp_info < < = ( DR_CONTROL_SHIFT + drnum * DR_CONTROL_SIZE ) ;
2009-11-26 08:04:38 +03:00
bp_info | = ( DR_GLOBAL_ENABLE < < ( drnum * DR_ENABLE_SIZE ) ) ;
2009-06-01 22:13:57 +04:00
return bp_info ;
}
2009-11-26 08:04:38 +03:00
/*
* Encode the length , type , Exact , and Enable bits for a particular breakpoint
* as stored in debug register 7.
*/
unsigned long encode_dr7 ( int drnum , unsigned int len , unsigned int type )
{
return __encode_dr7 ( drnum , len , type ) | DR_GLOBAL_SLOWDOWN ;
}
2009-09-09 21:22:48 +04:00
/*
* Decode the length and type bits for a particular breakpoint as
* stored in debug register 7. Return the " enabled " status .
*/
int decode_dr7 ( unsigned long dr7 , int bpnum , unsigned * len , unsigned * type )
2009-06-01 22:13:57 +04:00
{
2009-09-09 21:22:48 +04:00
int bp_info = dr7 > > ( DR_CONTROL_SHIFT + bpnum * DR_CONTROL_SIZE ) ;
2009-06-01 22:13:57 +04:00
2009-09-09 21:22:48 +04:00
* len = ( bp_info & 0xc ) | 0x40 ;
* type = ( bp_info & 0x3 ) | 0x80 ;
2009-06-01 22:13:57 +04:00
2009-09-09 21:22:48 +04:00
return ( dr7 > > ( bpnum * DR_ENABLE_SIZE ) ) & 0x3 ;
2009-06-01 22:13:57 +04:00
}
/*
2009-09-09 21:22:48 +04:00
* Install a perf counter breakpoint .
*
* We seek a free debug address register and use it for this
* breakpoint . Eventually we enable it in the debug control register .
*
* Atomic : we hold the counter - > ctx - > lock and we only handle variables
* and registers local to this cpu .
2009-06-01 22:13:57 +04:00
*/
2009-09-09 21:22:48 +04:00
int arch_install_hw_breakpoint ( struct perf_event * bp )
2009-06-01 22:13:57 +04:00
{
2009-09-09 21:22:48 +04:00
struct arch_hw_breakpoint * info = counter_arch_bp ( bp ) ;
unsigned long * dr7 ;
int i ;
for ( i = 0 ; i < HBP_NUM ; i + + ) {
struct perf_event * * slot = & __get_cpu_var ( bp_per_reg [ i ] ) ;
if ( ! * slot ) {
* slot = bp ;
break ;
}
2009-06-01 22:13:57 +04:00
}
2009-09-09 21:22:48 +04:00
if ( WARN_ONCE ( i = = HBP_NUM , " Can't find any breakpoint slot " ) )
return - EBUSY ;
set_debugreg ( info - > address , i ) ;
__get_cpu_var ( cpu_debugreg [ i ] ) = info - > address ;
2009-11-25 16:24:44 +03:00
dr7 = & __get_cpu_var ( cpu_dr7 ) ;
2009-09-09 21:22:48 +04:00
* dr7 | = encode_dr7 ( i , info - > len , info - > type ) ;
set_debugreg ( * dr7 , 7 ) ;
return 0 ;
2009-06-01 22:13:57 +04:00
}
/*
2009-09-09 21:22:48 +04:00
* Uninstall the breakpoint contained in the given counter .
*
* First we search the debug address register it uses and then we disable
* it .
*
* Atomic : we hold the counter - > ctx - > lock and we only handle variables
* and registers local to this cpu .
2009-06-01 22:13:57 +04:00
*/
2009-09-09 21:22:48 +04:00
void arch_uninstall_hw_breakpoint ( struct perf_event * bp )
2009-06-01 22:13:57 +04:00
{
2009-09-09 21:22:48 +04:00
struct arch_hw_breakpoint * info = counter_arch_bp ( bp ) ;
unsigned long * dr7 ;
int i ;
for ( i = 0 ; i < HBP_NUM ; i + + ) {
struct perf_event * * slot = & __get_cpu_var ( bp_per_reg [ i ] ) ;
if ( * slot = = bp ) {
* slot = NULL ;
break ;
}
}
if ( WARN_ONCE ( i = = HBP_NUM , " Can't find any breakpoint slot " ) )
return ;
2009-06-01 22:13:57 +04:00
2009-11-25 16:24:44 +03:00
dr7 = & __get_cpu_var ( cpu_dr7 ) ;
2009-11-26 08:04:38 +03:00
* dr7 & = ~ __encode_dr7 ( i , info - > len , info - > type ) ;
2009-09-09 21:22:48 +04:00
set_debugreg ( * dr7 , 7 ) ;
2009-06-01 22:13:57 +04:00
}
static int get_hbp_len ( u8 hbp_len )
{
unsigned int len_in_bytes = 0 ;
switch ( hbp_len ) {
2009-09-09 21:22:48 +04:00
case X86_BREAKPOINT_LEN_1 :
2009-06-01 22:13:57 +04:00
len_in_bytes = 1 ;
break ;
2009-09-09 21:22:48 +04:00
case X86_BREAKPOINT_LEN_2 :
2009-06-01 22:13:57 +04:00
len_in_bytes = 2 ;
break ;
2009-09-09 21:22:48 +04:00
case X86_BREAKPOINT_LEN_4 :
2009-06-01 22:13:57 +04:00
len_in_bytes = 4 ;
break ;
# ifdef CONFIG_X86_64
2009-09-09 21:22:48 +04:00
case X86_BREAKPOINT_LEN_8 :
2009-06-01 22:13:57 +04:00
len_in_bytes = 8 ;
break ;
# endif
}
return len_in_bytes ;
}
/*
* Check for virtual address in user space .
*/
int arch_check_va_in_userspace ( unsigned long va , u8 hbp_len )
{
unsigned int len ;
len = get_hbp_len ( hbp_len ) ;
return ( va < = TASK_SIZE - len ) ;
}
/*
* Check for virtual address in kernel space .
*/
2009-06-17 13:14:19 +04:00
static int arch_check_va_in_kernelspace ( unsigned long va , u8 hbp_len )
2009-06-01 22:13:57 +04:00
{
unsigned int len ;
len = get_hbp_len ( hbp_len ) ;
return ( va > = TASK_SIZE ) & & ( ( va + len - 1 ) > = TASK_SIZE ) ;
}
/*
* Store a breakpoint ' s encoded address , length , and type .
*/
2009-09-09 21:22:48 +04:00
static int arch_store_info ( struct perf_event * bp )
2009-06-01 22:13:57 +04:00
{
2009-09-09 21:22:48 +04:00
struct arch_hw_breakpoint * info = counter_arch_bp ( bp ) ;
2009-06-01 22:13:57 +04:00
/*
* For kernel - addresses , either the address or symbol name can be
* specified .
*/
2009-09-09 21:22:48 +04:00
if ( info - > name )
info - > address = ( unsigned long )
kallsyms_lookup_name ( info - > name ) ;
if ( info - > address )
2009-06-01 22:13:57 +04:00
return 0 ;
2009-09-09 21:22:48 +04:00
2009-06-01 22:13:57 +04:00
return - EINVAL ;
}
2009-09-09 21:22:48 +04:00
int arch_bp_generic_fields ( int x86_len , int x86_type ,
int * gen_len , int * gen_type )
2009-06-01 22:13:57 +04:00
{
2009-09-09 21:22:48 +04:00
/* Len */
switch ( x86_len ) {
case X86_BREAKPOINT_LEN_1 :
* gen_len = HW_BREAKPOINT_LEN_1 ;
break ;
case X86_BREAKPOINT_LEN_2 :
* gen_len = HW_BREAKPOINT_LEN_2 ;
break ;
case X86_BREAKPOINT_LEN_4 :
* gen_len = HW_BREAKPOINT_LEN_4 ;
break ;
# ifdef CONFIG_X86_64
case X86_BREAKPOINT_LEN_8 :
* gen_len = HW_BREAKPOINT_LEN_8 ;
break ;
# endif
default :
return - EINVAL ;
}
2009-06-01 22:13:57 +04:00
2009-09-09 21:22:48 +04:00
/* Type */
switch ( x86_type ) {
case X86_BREAKPOINT_EXECUTE :
* gen_type = HW_BREAKPOINT_X ;
2009-06-01 22:13:57 +04:00
break ;
2009-09-09 21:22:48 +04:00
case X86_BREAKPOINT_WRITE :
* gen_type = HW_BREAKPOINT_W ;
2009-06-01 22:13:57 +04:00
break ;
2009-09-09 21:22:48 +04:00
case X86_BREAKPOINT_RW :
* gen_type = HW_BREAKPOINT_W | HW_BREAKPOINT_R ;
2009-06-01 22:13:57 +04:00
break ;
default :
2009-09-09 21:22:48 +04:00
return - EINVAL ;
2009-06-01 22:13:57 +04:00
}
2009-09-09 21:22:48 +04:00
return 0 ;
}
static int arch_build_bp_info ( struct perf_event * bp )
{
struct arch_hw_breakpoint * info = counter_arch_bp ( bp ) ;
info - > address = bp - > attr . bp_addr ;
/* Len */
switch ( bp - > attr . bp_len ) {
2009-06-01 22:13:57 +04:00
case HW_BREAKPOINT_LEN_1 :
2009-09-09 21:22:48 +04:00
info - > len = X86_BREAKPOINT_LEN_1 ;
2009-06-01 22:13:57 +04:00
break ;
case HW_BREAKPOINT_LEN_2 :
2009-09-09 21:22:48 +04:00
info - > len = X86_BREAKPOINT_LEN_2 ;
2009-06-01 22:13:57 +04:00
break ;
case HW_BREAKPOINT_LEN_4 :
2009-09-09 21:22:48 +04:00
info - > len = X86_BREAKPOINT_LEN_4 ;
2009-06-01 22:13:57 +04:00
break ;
# ifdef CONFIG_X86_64
case HW_BREAKPOINT_LEN_8 :
2009-09-09 21:22:48 +04:00
info - > len = X86_BREAKPOINT_LEN_8 ;
break ;
# endif
default :
return - EINVAL ;
}
/* Type */
switch ( bp - > attr . bp_type ) {
case HW_BREAKPOINT_W :
info - > type = X86_BREAKPOINT_WRITE ;
break ;
case HW_BREAKPOINT_W | HW_BREAKPOINT_R :
info - > type = X86_BREAKPOINT_RW ;
break ;
case HW_BREAKPOINT_X :
info - > type = X86_BREAKPOINT_EXECUTE ;
break ;
default :
return - EINVAL ;
}
return 0 ;
}
/*
* Validate the arch - specific HW Breakpoint register settings
*/
int arch_validate_hwbkpt_settings ( struct perf_event * bp ,
struct task_struct * tsk )
{
struct arch_hw_breakpoint * info = counter_arch_bp ( bp ) ;
unsigned int align ;
int ret ;
ret = arch_build_bp_info ( bp ) ;
if ( ret )
return ret ;
ret = - EINVAL ;
if ( info - > type = = X86_BREAKPOINT_EXECUTE )
/*
* Ptrace - refactoring code
* For now , we ' ll allow instruction breakpoint only for user - space
* addresses
*/
if ( ( ! arch_check_va_in_userspace ( info - > address , info - > len ) ) & &
info - > len ! = X86_BREAKPOINT_EXECUTE )
return ret ;
switch ( info - > len ) {
case X86_BREAKPOINT_LEN_1 :
align = 0 ;
break ;
case X86_BREAKPOINT_LEN_2 :
align = 1 ;
break ;
case X86_BREAKPOINT_LEN_4 :
align = 3 ;
break ;
# ifdef CONFIG_X86_64
case X86_BREAKPOINT_LEN_8 :
2009-06-01 22:13:57 +04:00
align = 7 ;
break ;
# endif
default :
return ret ;
}
2009-12-05 11:44:31 +03:00
ret = arch_store_info ( bp ) ;
2009-06-01 22:13:57 +04:00
if ( ret < 0 )
return ret ;
/*
* Check that the low - order bits of the address are appropriate
* for the alignment implied by len .
*/
2009-09-09 21:22:48 +04:00
if ( info - > address & align )
2009-06-01 22:13:57 +04:00
return - EINVAL ;
/* Check that the virtual address is in the proper range */
if ( tsk ) {
2009-09-09 21:22:48 +04:00
if ( ! arch_check_va_in_userspace ( info - > address , info - > len ) )
2009-06-01 22:13:57 +04:00
return - EFAULT ;
} else {
2009-09-09 21:22:48 +04:00
if ( ! arch_check_va_in_kernelspace ( info - > address , info - > len ) )
2009-06-01 22:13:57 +04:00
return - EFAULT ;
}
2009-09-09 21:22:48 +04:00
2009-06-01 22:13:57 +04:00
return 0 ;
}
2009-11-09 23:03:43 +03:00
/*
* Dump the debug register contents to the user .
* We can ' t dump our per cpu values because it
* may contain cpu wide breakpoint , something that
* doesn ' t belong to the current task .
*
* TODO : include non - ptrace user breakpoints ( perf )
*/
void aout_dump_debugregs ( struct user * dump )
{
int i ;
int dr7 = 0 ;
struct perf_event * bp ;
struct arch_hw_breakpoint * info ;
struct thread_struct * thread = & current - > thread ;
for ( i = 0 ; i < HBP_NUM ; i + + ) {
bp = thread - > ptrace_bps [ i ] ;
if ( bp & & ! bp - > attr . disabled ) {
dump - > u_debugreg [ i ] = bp - > attr . bp_addr ;
info = counter_arch_bp ( bp ) ;
dr7 | = encode_dr7 ( i , info - > len , info - > type ) ;
} else {
dump - > u_debugreg [ i ] = 0 ;
}
}
dump - > u_debugreg [ 4 ] = 0 ;
dump - > u_debugreg [ 5 ] = 0 ;
dump - > u_debugreg [ 6 ] = current - > thread . debugreg6 ;
dump - > u_debugreg [ 7 ] = dr7 ;
}
2009-11-14 03:35:29 +03:00
EXPORT_SYMBOL_GPL ( aout_dump_debugregs ) ;
2009-11-09 23:03:43 +03:00
2009-09-09 21:22:48 +04:00
/*
* Release the user breakpoints used by ptrace
*/
void flush_ptrace_hw_breakpoint ( struct task_struct * tsk )
2009-06-01 22:13:57 +04:00
{
2009-09-09 21:22:48 +04:00
int i ;
struct thread_struct * t = & tsk - > thread ;
for ( i = 0 ; i < HBP_NUM ; i + + ) {
unregister_hw_breakpoint ( t - > ptrace_bps [ i ] ) ;
t - > ptrace_bps [ i ] = NULL ;
}
2009-06-01 22:13:57 +04:00
}
2009-09-09 21:22:48 +04:00
void hw_breakpoint_restore ( void )
2009-06-01 22:13:57 +04:00
{
2009-09-09 21:22:48 +04:00
set_debugreg ( __get_cpu_var ( cpu_debugreg [ 0 ] ) , 0 ) ;
set_debugreg ( __get_cpu_var ( cpu_debugreg [ 1 ] ) , 1 ) ;
set_debugreg ( __get_cpu_var ( cpu_debugreg [ 2 ] ) , 2 ) ;
set_debugreg ( __get_cpu_var ( cpu_debugreg [ 3 ] ) , 3 ) ;
set_debugreg ( current - > thread . debugreg6 , 6 ) ;
2009-11-25 16:24:44 +03:00
set_debugreg ( __get_cpu_var ( cpu_dr7 ) , 7 ) ;
2009-06-01 22:13:57 +04:00
}
2009-09-09 21:22:48 +04:00
EXPORT_SYMBOL_GPL ( hw_breakpoint_restore ) ;
2009-06-01 22:13:57 +04:00
/*
* Handle debug exception notifications .
*
* Return value is either NOTIFY_STOP or NOTIFY_DONE as explained below .
*
* NOTIFY_DONE returned if one of the following conditions is true .
* i ) When the causative address is from user - space and the exception
* is a valid one , i . e . not triggered as a result of lazy debug register
* switching
* ii ) When there are more bits than trap < n > set in DR6 register ( such
* as BD , BS or BT ) indicating that more than one debug condition is
* met and requires some more action in do_debug ( ) .
*
* NOTIFY_STOP returned for all other cases
*
*/
2009-06-17 13:14:19 +04:00
static int __kprobes hw_breakpoint_handler ( struct die_args * args )
2009-06-01 22:13:57 +04:00
{
int i , cpu , rc = NOTIFY_STOP ;
2009-09-09 21:22:48 +04:00
struct perf_event * bp ;
2009-06-01 22:17:06 +04:00
unsigned long dr7 , dr6 ;
unsigned long * dr6_p ;
/* The DR6 value is pointed by args->err */
dr6_p = ( unsigned long * ) ERR_PTR ( args - > err ) ;
dr6 = * dr6_p ;
2009-06-01 22:13:57 +04:00
/* Do an early return if no trap bits are set in DR6 */
if ( ( dr6 & DR_TRAP_BITS ) = = 0 )
return NOTIFY_DONE ;
get_debugreg ( dr7 , 7 ) ;
/* Disable breakpoints during exception handling */
set_debugreg ( 0UL , 7 ) ;
/*
* Assert that local interrupts are disabled
* Reset the DRn bits in the virtualized register value .
* The ptrace trigger routine will add in whatever is needed .
*/
current - > thread . debugreg6 & = ~ DR_TRAP_BITS ;
cpu = get_cpu ( ) ;
/* Handle all the breakpoints that were triggered */
for ( i = 0 ; i < HBP_NUM ; + + i ) {
if ( likely ( ! ( dr6 & ( DR_TRAP0 < < i ) ) ) )
continue ;
2009-09-09 21:22:48 +04:00
2009-06-01 22:13:57 +04:00
/*
2009-09-09 21:22:48 +04:00
* The counter may be concurrently released but that can only
* occur from a call_rcu ( ) path . We can then safely fetch
* the breakpoint , use its callback , touch its counter
* while we are in an rcu_read_lock ( ) path .
2009-06-01 22:13:57 +04:00
*/
2009-09-09 21:22:48 +04:00
rcu_read_lock ( ) ;
bp = per_cpu ( bp_per_reg [ i ] , cpu ) ;
if ( bp )
rc = NOTIFY_DONE ;
2009-06-01 22:17:06 +04:00
/*
* Reset the ' i ' th TRAP bit in dr6 to denote completion of
* exception handling
*/
( * dr6_p ) & = ~ ( DR_TRAP0 < < i ) ;
2009-06-01 22:13:57 +04:00
/*
* bp can be NULL due to lazy debug register switching
2009-09-09 21:22:48 +04:00
* or due to concurrent perf counter removing .
2009-06-01 22:13:57 +04:00
*/
2009-09-09 21:22:48 +04:00
if ( ! bp ) {
rcu_read_unlock ( ) ;
break ;
}
2009-12-05 11:44:31 +03:00
perf_bp_event ( bp , args - > regs ) ;
2009-06-01 22:13:57 +04:00
2009-09-09 21:22:48 +04:00
rcu_read_unlock ( ) ;
2009-06-01 22:13:57 +04:00
}
if ( dr6 & ( ~ DR_TRAP_BITS ) )
rc = NOTIFY_DONE ;
set_debugreg ( dr7 , 7 ) ;
2009-06-17 14:52:15 +04:00
put_cpu ( ) ;
2009-09-09 21:22:48 +04:00
2009-06-01 22:13:57 +04:00
return rc ;
}
/*
* Handle debug exception notifications .
*/
int __kprobes hw_breakpoint_exceptions_notify (
struct notifier_block * unused , unsigned long val , void * data )
{
if ( val ! = DIE_DEBUG )
return NOTIFY_DONE ;
return hw_breakpoint_handler ( data ) ;
}
2009-09-09 21:22:48 +04:00
void hw_breakpoint_pmu_read ( struct perf_event * bp )
{
/* TODO */
}
void hw_breakpoint_pmu_unthrottle ( struct perf_event * bp )
{
/* TODO */
}