1、简介
GDB(GNU Debugger)是GCC的调试工具,其功能强大,主要帮你完成以下4个方面的功能:
1.启动你的程序,可以按照你的自定义的要求随心所欲的运行程序。
2.可让被调试的程序在你所指定的调置的断点处停住。(断点可以是条件表达式)
3.当程序被停住时,可以检查此时你的程序中所发生的事。
4.动态的改变你程序的执行环境。
2、生成调试信息
一般来说GDB主要调试的是C/C++的程序。要调试C/C++的程序,首先在编译时,我们必须要把调试信息加到可执行文件中。使用编译器(cc/gcc/g++)的 -g 参数可以做到这一点。如:
g++ -g HelloWorld.cpp -o HelloWorld
查看ELF信息(顺带说一下,ELF文件是release还是debug也可通过该命令查看):
可见加了-g参数,可执行文件中包含了调试信息,如果我们不加-g参数,可执行文件中就不会包含这些调试信息,大家可自行验证一下。
3、启动GDB
shell环境下输入命令“gdb”,启动gdb:
输入gdb命令“file HelloWorld”,装载想要调试的可执行程序,也可以执行命令“gdb HelloWorld”在启动gdb时就装载。
4、查看源码
help list //查看list命令帮助
list //查看当前行附近10行,再次执行查看后10行
list - //查看当前行的前10行
list linenum //查看第linenum行附近10行
list numbegin,numend //查看第numbegin行到第numend行
list function //查看function函数附近10行
list file:linenum //查看file文件第linenum行附近10行
list file:function //查看file文件function函数附近10行
list *address //查看地址为address的符号附近的10行
5、断点操作
break linenum //在当前源文件的第linenum行设置断点
break function //在function函数入口处设置断点
break file:linenum //在file文件的第linenum行设置断点
break file:function //在file文件的function函数入口处设置断点
break class::function //在类class的function函数入口处设置断点
break namespace::class::function //在命名空间为namespace的类class的function函数的入口处设置断点
info break //查看断点信息
6、调试代码
run 运行程序,可简写为r
next 单步跟踪,函数调用当作一条简单语句执行,可简写为n
step 单步跟踪,函数调进入被调用函数体内,可简写为s
finish 退出函数
until 在一个循环体内单步跟踪时,这个命令可以运行程序直到退出循环体,可简写为u
continue 继续运行程序,可简写为c
stepi或si, nexti或ni 单步跟踪一条机器指令,一条程序代码有可能由数条机器指令完成,stepi和nexti可以单步执行机器指令
info program 来查看程序的是否在运行,进程号,被暂停的原因
7、查看运行时数据
print 打印变量、字符串、表达式等的值,可简写为p
p count 打印count的值
p cou1+cou2+cou3 打印表达式值
print接受一个表达式,GDB会根据当前的程序运行的数据来计算这个表达式,表达式可以是当前程序运行中的const常量、变量、函数等内容。但是GDB不能使用程序中定义的宏。
以上是GDB的常用操作,请参考:http://www.cnblogs.com/ggjucheng/archive/2011/12/14/2288004.html
8、多进程调试
8.1 attach子进程
最近正在调试PostgreSQL数据库,就以此为例。
查看PostgreSQL进程:
终端连接PostgreSQL数据库后,服务端创建了一个子进程来处理,该子进程id为3438。
gdb ./bin/postgres 启动gdb加载可执行程序后执行gdb命令 attach 3438 附加到进程,如下图:
为了调试数据库对SQL的处理流程,我们在函数exec_simple_query入口处设置断点,命令如下:
break exec_simple_query
在终端执行了一个SQL语句后,数据库服务程序停在了断点处,此时我们可以查看源码,执行单步调试,查看变量值等操作,具体参见前面章节。
问题:子进程一直在运行,attach上去后,不知道运行到哪里了。比如上面的例子,子进程阻塞在等待客户端的请求,我们只能调试从这以后的代码的执行,而子进程启动和初始化过程我们已经错过,无法调试了。
解决的办法:在子进程初始化代码中,比如main函数开始处,加入一段特殊代码,使子进程在某个条件成立时循环睡眠等待,attach到该子进程后在该段代码后设置断点,再把成立的条件取消,让代码继续执行下去。
至于这段代码的执行条件,看个人偏好了。比如我们可以检查一个指定的环境变量的值,或者检查一个特定文件是否存在。以文件为例,其代码如下:
void debug_wait(const char* trigger_file)
{
while (1)
{
if (trigger_file 不存在)
{
睡眠一段时间;
}
else
{
break;
}
}
}
当attach到该子进程后,在该段代码后设置断点,再创建trigger_file让进程在断点处暂停,便可以调试了。
8.1 catch fork
默认设置下,在调试多进程程序时GDB只会调试主进程。但是GDB(>V7.0)支持多进程的
分别以及同时
调试,换句话说,GDB可以同时调试多个程序。只需要设置follow-fork-mode(默认值:parent)和detach-on-fork(默认值:on)即可。catch fork命令可以捕获进程的创建,inferior命令可以切换进程。这些让GDB的多进程调试变得非常方便。下面分享一篇这方面的博客,对我的工作帮助很大。
http://blog.csdn.net/pbymw8iwm/article/details/7876797
9、多线程调试
GDB多线程调试一个重要的设置参数就是scheduler-locking,取值on、off、step,默认为step。
show scheduler-locking //显示当前scheduler-locking
set scheduler-locking [on/off/step] //设置scheduler-locking
注意:set scheduler-locking要处于线程运行环境下才能生效,也就是程序已经运行并且暂停在某个断点处,否则会出现“Target 'exec' cannot support this command.”这样的错误;而且经过测试,设置后的scheduler-locking值在整个进程内有效,不属于某个线程。
下面根据实际测试结果对scheduler-locking的3种取值进行说明:
on:只有当前调试线程运行,其他线程处于暂停状态。
off:当前调试线程外的其他线程一直在正常运行。
step:其他线程跟随当前调试线程运行,但具体怎么协同运行,测试中无法体现。
10、core文件调试