为KGDB 增加watchpoint断点支持 on x86

DDD  2010年11月18日 星期四 14:26 | 2757次浏览 | 0条评论

the watchpoint of kgdb 大致流程为:

A:using gdb to set a watchpoint

B:send out a set watchpoint protocol packet to kgdb from gdb
C:kgdb receive/parse the protocol packet

D:kgdb set a watchpoint hardware breakpoint on kernel

E:Once kernel hit a watchpoint breakpoint, kgdb will collect the watchpoint breakpoint
info, fill them to a Stop-Reply-Packets with watchpoint format, and send out to gdb

前言

前面我们在《 gdb 和 watchpoint 》 文章
里讨论了在gdb的watchpoint,这次我们来讨论下如何让kgdb也支持watchpoint特性。

KGDB 相当于一个gdb server,只是这个server是跑在内核里面。所以KGDB支持watchpoint实现和
gdb server的实现如出一辙,即通过 GDB远程串行协议 里的Stop-Reply-Packets来传达
watchpoint信息给gdb,让gdb知道那个watchpoint击中了。

其运行的大致流程为:

      A:using gdb to set a watchpoint
      
 
B:send out a set watchpoint protocol packet to kgdb from gdb
 
C:kgdb receive/parse the protocol packet
 
D:kgdb set a watchpoint hardware breakpoint on kernel
 
E:Once kernel hit a watchpoint breakpoint, kgdb will collect the
watchpoint breakpoint info, fill them to a Stop-Reply-Packets with
watchpoint format, and send out to gdb

1: 设置watchpoint 断点 in kgdb

在gdb敲入watch命令后,会将发送一个设置watchpoint的数据包给kgdb,kgdb收该数据包后就执行设置断点动作.

      (gdb) watch xxx  (假设我们要监视断点地址为 xxx)
      
 
gdb发送数据包"Z2,xxx,4" 给kgdb
 
kgdb 收到协议数据包后,调用gdb_cmd_break()(kernel/debug/gdbstub.c)
来解析设置断点协议数据
 
gdb_cmd_break()
------------------------------------------
parsing "Z2,xxx,4"
--> 'Z' 设置断点
--> '2' 断点类型为write watchpoint
--> 'xxx' 断点地址为 xxx
--> '4' 断点长度为 4
------------------------------------------
 
--> arch_kgdb_ops.set_hw_breakpoint(xxx, 4, BP_WRITE_WATCHPOINT)
--> kgdb_set_hw_break(xxx, 4, BP_WRITE_WATCHPOINT) (arch/x86/kernel/kgdb.c)
这个函数完成了将断点信息加入到breakinfo[HBP_NUM]数组里和设置其enabled标志为1,
真正的断点使能是在kgdb退出,让系统正常运行时,调用kgdb_correct_hw_break()函数
来每个cpu上使能硬件断点.

2: watchpoint触发捕获

watchpoint 断点隶属于硬件断点,所以我们先来谈谈硬件断点的触发的整个流程和细节。

      硬件断点的触发后,会产生一个调试异常
      
       ,
      
      因此会进入do_debug异常的
      
处理程序 ( arch / x86 / kernel / traps. c ) .
 
do_debug ( ) {
 
unsigned long dr6 ;
get_debugreg ( dr6 , 6 ) ; //保持dr6寄存器值到dr6变量
 
/* DR6 may or may not be cleared by the CPU */
set_debugreg ( 0 , 6 ) ; //手动清除dr6寄存器
 
//通过notify_die通知链告诉大家"debug"异常发生了,同时把dr6寄存器值作为参数传递出去。
notify_die ( DIE_DEBUG , "debug" , regs , PTR_ERR ( & dr6 ) , error_code ,
SIGTRAP )
}

如果kgdb处于早期调试模式(那时候Hardware Breakpoint Layer还没初始化),则是由kgdb自己处理
DIE_DEBUG的notify通知,如果是普通模式,即硬件断点管理都是从Hardware Breakpoint Layer获取的,
则是由Hardware Breakpoint Layer处理,然后通过回调函数kgdb_hw_overflow_handler()进入kgdb.

      Hardware Breakpoint Layer处理流程
      
       :
      
      
 
static struct notifier_block hw_breakpoint_exceptions_nb = {
. notifier_call = hw_breakpoint_exceptions_notify ,
/* we need to be notified first */
. priority = 0x7fffffff
} ;
register_die_notifier ( & hw_breakpoint_exceptions_nb ) ;
 
触发硬件断点 :
do_debug ( ) -- notify_die ( DIE_DEBUG ) ->
arch / x86 / kernel / hw_breakpoint. c
hw_breakpoint_exceptions_notify ( ) ->
hw_breakpoint_handler ( ) {
for ( i = 0 ; i < HBP_NUM ; ++ i ) {
if ( likely ( ! ( dr6 & ( DR_TRAP0 << i ) ) ) )
continue ;
perf_bp_event ( bp , args -> regs ) ; -->
perf_swevent_overflow ( event ) ->
event -> overflow_handler ( event , nmi , data , regs ) ;-->
kgdb_hw_overflow_handler ( ) . 这里最终跑到kgdb的代码里了.
} /* end of for()*/
}

3: watchpoint 断点信息获取

kgdb只需要知道是哪个watchpoint断点被踩中了,并把它的地址传递给gdb就可以了。

如果kgdb处于早期调试模式,则可以挨个判断dr6寄存器值的bit位来看哪个watchpoint击中了。
但是普通模式的Hardware Breakpoint Layer回调函数并没有传递dr6值给我们,不过还好,它传
递struct perf_event的event对象给我们,通过这个对象,我们可以得到发生硬断点的地址,然后
根据地址和kgdb里保存的硬断点数组对比,相同的,则是踩中的断点..

      
       static
      
      
       void
      
      kgdb_hw_overflow_handler
      
       (
      
      
       struct
      
      perf_event
      
       *
      
      event
      
       ,
      
      
       int
      
      nmi
      
       ,
      
      
struct perf_sample_data * data , struct pt_regs * regs ) {
for ( i = 0 ; i < HBP_NUM ; i ++ ) {
if ( ! breakinfo [ i ] . enabled )
continue ;
if ( breakinfo [ i ] . addr == event -> attr. bp_addr ) //查找和判断踩中断点信息
break ;
}
}

在得到是哪个breakinfo[i]被触发后,在查看下breakinfo[i].type,如果是
X86_BREAKPOINT_WRITE/X86_BREAKPOINT_RW 就可以认定是watchpoint断点了,
而断点地址则保存在breakinfo[i].addr.

4: 填充Stop-Reply-Packets来通知gdb踩中watchpoint断点了
根据watchpoint断点的类型,返回给gdb的Stop-Reply-Packets可以有如下格式:

      type
      
       =
      
      breakinfo
      
       [
      
      i
      
       ]
      
      .
      
       type
      
      
       ;
      
      
addr = breakinfo [ i ] . addr ;
switch ( type ) {
case KST_READ_WATCHPOINT :
ptr += strlen ( strcpy ( ptr , "rwatch:" ) ) ;
break ;
case KST_ACCESS_WATCHPOINT :
ptr += strlen ( strcpy ( ptr , "awatch:" ) ) ;
break ;
case KST_WRITE_WATCHPOINT :
ptr += strlen ( strcpy ( ptr , "watch:" ) ) ;
break ;
}
ptr += kgdb_long2hex ( addr , ptr ) ;
* ptr ++ = ';' ;

具体packgets包格式,可以阅读 Stop-Reply-Packets

5: test watchpoint support on x86

      After gdb connect successly to kgdb
      
       ,
      
      setting a watchpoint
      
at softlockup_thresh address.
 
( gdb ) watch softlockup_thresh
 
Then change the watchdog_thresh value to 24 on target :
# echo "24" > /proc/sys/kernel/watchdog_thresh
 
Then the gdb will stop and show :
----------------------------------------------------------
Old value = 60
New value = 24
0xffffffff810481f2 in do_proc_dointvec_minmax_conv ( negp = 0xffff88066cbb9e17 ,
lvalp = 0xffffffff8190e718 ,
valp = 0xffffffff8190e718 , write =< value optimized out >,
data = 0xffff88066cbb9e68 ) at kernel / sysctl. c : 2408
2408 * valp = val ;
( gdb )
----------------------------------------------------------
 
Change the watchdog_thresh value to 24 on target again :
# echo "24" > /proc/sys/kernel/watchdog_thresh
 
As the previous watchdog_thresh value is 24 , thus gdb will not stop..

6: 扩展阅读

更多有关kgdb和Hardware Breakpoint Layer的内容,可阅读:《 抓虫日记之 kgdb 和 删除硬断点
更多有关x86硬件调试寄存器信息,可阅读:《 x86 调试寄存器

本文转载自:
http://www.kgdb.info/kgdb/kgdb_watchpoint_support_on_x86/

评论

我的评论:

发表评论

请 登录 后发表评论。还没有在Zeuux哲思注册吗?现在 注册 !

暂时没有评论

Zeuux © 2024

京ICP备05028076号