GDB(GNU Debugger)是在Unix以及类Unix系统下的调试工具。功能极其强大,几乎涵盖了你所需要的全部功能。
GDB主要帮忙你完成下面四个方面的功能:
1.启动你的程序,可以按照你的定制要求随心所欲的运行程序。
2.可让被调试的程序在你所指定的调置的断点处停住。
3.当程序被停住时,可以检查此时你的程序中所发生的事,以及内存状态等。
4.动态的改变你程序的执行环境。
gdb使用总旨:help指令很强大!多用help!help里面总会有你需要的信息。如果你不知道如何使用help,请在gdb里面输入:help all
一、gdb使用前置条件:编译时加入debug信息。
gcc/g++是在编译时加入-g,其他语言请自行百度。值得注意的是,-g分4个等级:
- -g0等于不加-g。即不包含任何信息
- -g1只包含最小信息,一般来说只有你不需要debug,只需要backtrace信息,并且真的很在意程序大小,或者有其他保密/特殊需求时才会使用-g1。
- –g2为gdb默认等级,包含绝大多数你需要的信息。
- –g3包含一些额外信息,例如包含宏定义信息。当你需要调试宏定义时,请使用-g3
二、gdb最常见的几个用法:
1. 调试程序。有几种方法可以在gdb下运行你的程序:
1) gdb ${你的程序} 进入gdb后,输入run(简写r) ${arg1} ${arg2} … ${argN}
2) gdb --args ${你的程序} ${arg1} ${arg2} … ${argN} 进入gdb后,运行run。
3) gdb进入gdb后,输入file ${你的程序}。然后使用set args ${arg1} ${arg2} … ${argN} 设定好你的程序参数,再运行run。
2. 调试正在运行的程序:
gdb ${你的程序} ${程序pid}
3. 查core:
gdb ${你的程序} ${core文件}
三、gdb常用命令:
1. backtrace:显示栈信息。简写为bt。
2. frame x 切换到第x帧。其中x会在bt命令中显示,从0开始。0表示栈顶。简写为f。
3. up/down x 往栈顶/栈底移动x帧。当不输入x时,默认为1。
4. print x打印x的信息,x可以是变量,也可以是对象或者数组。简写为p。
5. print */&x 打印x的内容/地址。
6. call 调用函数。注意此命令需要一个正在运行的程序。
7. set substitute-path from_path to_path,替换源码文件路径。当编译机与运行程序的机器代码路径不同时,需要使用该指令替换代码路径,否则你无法在gdb中看到源码。
8. break x.cpp:n 在x.cpp的第n行设置断点,然后gdb会给出断点编号m。命令可简写为b。后面会对break命令进行更详细的解释。
9. command m 设置程序执行到断点m时要看的内容,例如:
command n
>printf "x is %d\n",x
>c
>end
如果command后面没有参数n,则命令被赋给最后一个breakpoint,这其实是说break和command连在一起用,在脚本里用就非常方便了。gdb脚本会在后面详细介绍
10. x /nfu ${addr} 打印addr的内容。addr可以是任何合法的地址表达式,例如0x562fb3d,一个当前有效的指针变量p,或者一个当前有效的变量var的地址&var。nfu是格式,n表示查看的长度,F表示格式(例如16进制或10进制),U表示单位(例如单字节b,双字h,四字w等)。举个栗子:
(gdb) x /3xw 0x562fb3d //这个指令的意思为:以16进制格式显示地址0x562fb3d处3个单位,每个单位四字节的内容。你将得到下列数值:
0x562fb3d: 0x00282ff4 0x080484e0 0x00000000
11. continue 继续运行程序。进入调试模式后,若你已经获取了你需要的信息或者需要程序继续运行时使用。可简写为c
12. until 执行到当前循环完成。可简写为u
13. step 单步调试,步入当前函数。可简写为s
14. next 单步调试,步过当前函数。可简写为n
15. finish 执行到当前函数返回
16. set var x=10 改变当前变量x的值。也可以这样用:set {int}0x83040 = 10把内存地址0x83040的值强制转换为int并赋值为10
17. info locals 打印当前栈帧的本地变量
18. jump使当前执行的程序跳转到某一行,或者跳转到某个地址。由于只会使程序跳转而不会改变栈值,因此若跳出函数到另外的地方 会导致return出错。另外,熟悉汇编的人都知道,程序运行时,有一个寄存器用于保存当前代码所在的内存地址。所以,jump命令也就是改变了这个寄存器中的值。于是,你可以使用“set $pc”来更改跳转执行的地址。如: set $pc = 0x485
19. return: 强制函数返回。可以指定返回值
四、程序中断机制:监视点(watchpoint)、断点(breakpoint)和捕捉点(catchpoint):
1. 监视点。监视点是监视内存中某个地址,当该地址的数据被改变(或者被读取)时,程序交出控制权进入调试器。注意监视点分为软件模式和硬件模式:GDB 使用软件监视点的方式是在单步执行你的程序的同时测试变量的值,所以执行程序的速度会变慢。同时,软件监视点仅在当前线程有效。幸运的是,32 位的 Intel x86 处理器提供了 4 个特殊的调试寄存器用来方便调试程序,GDB 可以使用这些寄存器建立硬件监视点。GDB 总是会优先使用硬件监视点,因为这样不会减慢程序的执行速度。然而,可用的(enable的)硬件监视点的个数是有限的。如果你设置了过多的硬件监视点,当程序从中断的状态变为执行的状态(例如continue,until或者finish)时,GDB 可能无法把它们全部激活。另外,活动的硬件监视点的数量只有在试图继续执行程序时才能知道,也就是说,即使你设置了过多的硬件监视点,gdb在你运行程序之前也不会警告你。
设置监视点的命令有3个,watch(写监视),rwatch(读监视)以及awatch(读写监视)。他们的使用方法一样,皆为以下几种:
1) (r/a)watch x。x是一个变量名。当x的值改变/被读取时,程序交出控制权进入调试器。
2) (r/a)watch 0xN。N为一个有效地址。当该地址的内容变化/被读取时,程序交出控制权进入调试器。
3) (r/a)watch *(int *)0xN。N为一个有效地址。当该地址的中的int指针指向的内容变化/被读取时,程序交出控制权进入调试器。
4) (r/a)watch -l *(int *)0xN。N为一个有效地址。当该地址的中的int指针指向的内容变化/被读取,或者该地址的内容变化/被读取时,程序交出控制权进入调试器。
注意3)和4)的区别在于,当加入-l选项后,会同时监视表达式本身以及表达式指向的内容。
2. 断点是指当执行到程序某一步时,程序交出控制权进入调试器。值得注意的是,break会有一些变体:tbreak,hbreak,thbreak与rbreak。tbreak与break功能相同,只是所设置的断点在触发一次后自动删除。hbreak是一个硬件断点。thbreak则既是一个临时的硬件断点。注意硬件断点需要硬件支持,某些硬件可能不支持这种类型的断点。rbreak稍微特殊一些,它会在匹配正则表达式的全部位置加上断点,后面会有详细讲解。除去rbreak,其他break家族的使用方法如下:
1) (t/h)break x.cpp:y 。在代码x.cpp的第y行加入断点。x.cpp若不指定,则会以当前执行的文件作为断点文件。若程序未执行,则以包含main函数的源代码文件作为断点文件。若x.cpp和y都不指定,则以当前debugger的点作为断点处。
2) (t/h)break 0xN。在地址N处加入断点。N必须为一个有效的代码段(code segment)地址。
3) (t/h)break x.cpp:func。在x.cpp的func函数入口处加入断点。x.cpp可以不提供直接使用break func。注意由于重载(overload)的存在,因此gdb可能会询问你希望在哪个函数加上断点。你也可以通过指定参数类型来避免该问题,例如break func(int ,char *)
4) (t/h)break +/-N。在当前运行处的第N行后/前加入断点。
5) rbreak REGEXP。 在所有符合正则表达式REGEXP的函数入口加入断点。例如rbeak EX_* 表示在所有符合以EX_开头的函数入口处加入断点。
注意break后面还有2个可选参数,线程id和条件。线程id指在info threads中的线程序号,而非系统提供的tid。例如break x.cpp:y 2 if (a==24),表示在2号线程的x.cpp的第y行加入断点,并且只有当a的值为24时,程序才会交出控制权进入调试器。
另外,breakpoints可以通过save命令保存,以方便使用者下次再次进入程序调试时不需要重设断点。
3. 捕捉点是当某些事件发生时,程序交出控制权进入调试器。例如catch一个exception,assert,signal,fork甚至syscall。tcatch与catch功能一样,只是所设置的捕捉点在触发一次后自动删除。以后会详细介绍catchpoint。
五、跟踪点(tracepoint):
跟踪点与上面三个断点不同之处在于,它只是跟踪记录信息而不会中断程序的运行。当你的程序是realtime程序,或者与其他的程序有交互时,你可能会希望使用跟踪点达到监视程序而又不破坏程序自身行为的目的。与断点相同的是,跟踪点会保存下在跟踪点时的一些内存信息供使用者查阅,例如数组或者对象。
另外,tracepoints可以通过save命令保存,以方便使用者下次再次进入程序调试时不需要重设这些跟踪点。
六、检查点(checkpoint):
gdb可以保存某一个时间点的程序状态或者说是程序映像,并且稍后又可以返回到这个状态。这个称之为checkpoint。
每个检查点是进程的一个拷贝。这样当一个bug很难重现,而又担心调试过头了又要从头开始重现时,可以在估计要重现这个bug之前,做一个checkpoint,这样即使debug过头了,也可以从这个checkpoint开始,而不用重启整个程序并且期待它重现这个bug(也许需要很久!!)。
但是每个checkpoint有一个唯一的进程id,这个pid与原始程序的pid不同,因此如果程序需要使用pid的信息时,需要慎重考虑。