gdb 和 watchpoint

/ 9评 / 1

1: 什么是watchpoint

watchpoint,顾名思义,其一般用来观察某个变量/内存地址的状态(也可以是表达式),如可以监控该变量/内存值是否被程序读/写情况。

在gdb中可通过下面的几种方法来设置watchpoint:

    (gdb) watch 
        在指定变量/内存地址(表达式)expr设置一个watchpoint。
        一但expr值有变化时,将停住程序。
    (gdb) rwatch 
       当expr被读时,停住程序。
    (gdb) awatch 
       当expr被读或被写时,停住程序。
    (gdb) info watchpoints 
        列出当前所设置了的所有观察点。(info break也可查看)

gdb watchpoint实践:

GDB十分钟快速入门教程的gdb-sample.c为例,
在gdb-sample.c中, 变量n总共被改变了3次,如果我们下个watchpoint在n变量处,因为n变量改变了3次而响应3次watchpoint,因而程序会将被调试器暂停运行3次:

编译gdb-sample.c,并使用gdb 加载gdb-sample:


$ gcc gdb-sample.c -o gdb-sample -g
$ gdb ./gdb-sample
GNU gdb (GDB) 7.0.50.20090928-cvs
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu".
For bug reporting instructions, please see:
...
Reading symbols from /home/ddd/gdb-sample...done.
(gdb)

watchpoint只能在程序启动后设置,先在main那下个断点,让程序启动后暂停在main函数处:


(gdb) b main
Breakpoint 1 at 0x80483ad: file gdb-sample.c, line 19.
(gdb) r
Starting program: /home/ddd/gdb-sample

Breakpoint 1, main () at gdb-sample.c:19
19 n = 1;
(gdb)

给n变量下个watchpoint:


(gdb) watch n
Hardware watchpoint 2: n
(gdb)

敲入"c"命令让程序恢复运行,这时候程序会停止在第一次n变量改变处


20 n++;

并提示即将运行的下一条的语句:


23 n--;

(gdb) c
Continuing.
Hardware watchpoint 2: n

Old value = -1208017424
New value = 2
main () at gdb-sample.c:23
23 n--;
(gdb)

重复如上操作,程序还会停止两次,所有gdb输出如下:


(gdb) c
Continuing.
Hardware watchpoint 2: n

Old value = 2
New value = 1
main () at gdb-sample.c:25
25 nGlobalVar += 100;
(gdb) ----> 这次停止是由 ”23 n--; “改变变量n的值引起的

(gdb) c
Continuing.
n = 1, nGlobalVar = 88
tempFunction is called, a = 1, b = 2
Hardware watchpoint 2: n

Old value = 1
New value = 3
main () at gdb-sample.c:31
31 printf("n = %d", n);
(gdb) ----> 这次停止是由 ” 30 n = tempFunction(1, 2); “改变变量n的值引起的

(gdb) c
Continuing.

Watchpoint 2 deleted because the program has left the block in
which its expression is valid.
0xb7e91450 in __libc_start_main () from /lib/tls/i686/cmov/libc.so.6
(gdb)

2: watchpoint在gdb中的实现原理

watchpoint可以看成是一种特殊的"断点", 其实现一般需要CPU支持硬件断点,如果纯软件实现watchpoint,那好像会很耗CPU.(我没
去看gdb的软0watchpoint的实现,有时间得去研究下,不过如果让我来实现这个功能(和同事讨论过),应该是设置watchpoint所在的
那个页表为不可读/访问,然后在缺页处理那检测当前的页和地址是否是软设置watchpoint所在的页和watchpoint的地址,如果是,则
说明可以假设该watchpoint发生了)

目前支持watchpoint硬件断点的arch有x86,ppc和mips。

如果支持硬件断点,那么可以将监控的操作交给硬件来完成,而gdb这边只要做个简单的逻辑处理就行.
还是以上面的gdb-sample.c为例:

当gdb执行watch n命令后,gdb会在n变量所在的内存地址上下个硬件写断点


(gdb) watch n
Hardware watchpoint 2: n

(如果是 rwatch n命令,gdb会在n变量所在的内存地址上下个硬件读断点)
(tips: gdb 通过系统调用ptrace()去修改调试寄存器值,从而达到实现硬件断点的目的)

这样只要系统操作了n变量(内存地址),就会触发一个硬件断点中断。
gdb捕获到这个断点中断后,就会将新的n变量值和改变前的值做比较,
1)如果n变量的值改变了,则将程序停止。
2)如果n变量的值没有改变了,则程序继续运行。

关于硬件断点,可以参考x86 调试寄存器 一文。

3: 远程gdb server的watchpoint 实现

如果调试本地应用程序,gdb可以直接通过ptrace发出的信号得到watchpoint信息。

如果远程调试程序,gdb怎么从远程gdb server那得到watchpoint信息呢?

说到这里,又不得不搬出 GDB远程串行协议了..
在GDB远程串行协议里定义了gdb server和gdb所有的通信规则,所以要告诉gdb,远程gdb server那边踩中watchpoint了,还得通过那个协议来传达。

在GDB远程串行协议的Stop-Reply-Packets里定义了如何传达watchpoint信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
`T AA n1:r1;n2:r2;...'
    The program received signal number AA (a two-digit hexadecimal number).
    This is equivalent to an `S' response, except that the `n:r' pairs can
    carry values of important registers and other information directly in
    the stop reply packet, reducing round-trip latency. Single-step and
    breakpoint traps are reported this way. Each `n:r' pair is interpreted
    as follows:
        * If n is a recognized stop reason, it describes a more specific
    event that stopped the target. The currently defined stop reasons are
    listed below. aa should be `05', the trap signal. At most one stop 
    reason should be present.
 
    The currently defined stop reasons are:
    `watch'
    `rwatch'
    `awatch'
     The packet indicates a watchpoint hit, and r is the data address, in hex.

所以只要在Stop-Reply-Packets里添加 watch+断点地址格式的数据,gdb就知道那边踩中watchpoint了.

9条回应:“gdb 和 watchpoint”

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

  2. yorua007说道:

    博主,从你的博客上我学到了很多有关GDB的知识,收获很大。提一个问题,有什么方法可以让GDB显示调试寄存器DR0-DR7的内容么?maint set show-debug-regs on以后,倒是可以让执行过程中显示调试寄存器的值,我想要的是在某处stop以后显示当前调试寄存器的值。谢谢!

    [回复]

    DDD 回复:

    @yorua007 这个目前还没有. :-)

    [回复]

  3. pc8504说道:

    博主,您好,我想请问一下gdb中观察点的最小观察范围能精确到字节吗?假设如下:
    char buf[4];
    buf[0]=1;
    buf[1]=2;
    buf[2]=3;
    buf[3]=4;
    watch buf[0]
    能实现只在buf[0]=1处停止吗? 我现在观察到的现象是每句都停。所在判断最小观察颗粒度为4字节,能实现只观察一个字节吗? 我用的环境 gdb-7.4 + sparc v8 +grmon

    [回复]

    DDD 回复:

    @pc8504 貌似不能的,监控地址是4字节的,也就是你说的判断最小观察颗粒,所以不能观察一个字节。

    x86的硬件调试寄存器信息,其中Dr0~3 用于设置硬件断点,都是32bit的。
    http://www.kgdb.info/kgdb/understand_kgdb/x86_debug_registers/

    [回复]

  4. pc8504说道:

    花了三天时间,终于搞定了,呵呵,通过gdb和调试代理的配合,还是能实现的。

    [回复]

    DDD 回复:

    @pc8504 怎么搞定的?共享下?

    [回复]

  5. pc8504说道:

    首先如果调试代理如果是自己实现的,有源码的话,很容易。在新版本的gdb中支持观察点包,在调试代理中只要将此时停下来的观察点地址传送给gdb,gdb根据传回来的地址,读取该地址中的内容,和刚设置观察点的时候此地址中的值进行比较,如果值相同,则继续给调试代理发送继续运行包,否则调用stop_stepping。这样就可实现观察单个字节。
    其次,如果调试代理没有源码,并且可能由于调试代理比较old,不支持观察点包,此时就要修改gdb源代码,通过在gdbarch中增加新函数,手动分析。
    目前这两种方式,都可以实现。

    [回复]

  6. pc8504说道:

    最近又遇到一个问题,自己实现调试代理中,在CPU指令cache,和数据cache全部开启的情况下,调试代理让CPU全速运行一个死循环程序,竟然能从死循环里跳出来。真真切切的出现了,数据cache和内存不一致的情况。但是只要将 Icache或者Dcache任意之一关闭,就一切正常,不知大侠有没有遇到过这种情况?太郁闷了….

    [回复]

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据