准备工作
开启core, 采集程序崩溃的状态
首先你跟着我做开启core崩溃状态采集. 可以通过ulimit -c查看,如果是0表示没有开启. 开启按照下面操作:
1 | sudo gedit /etc/profile |
在/etc/profile最后一行添加下面几句话设置全局开启 core文件调试,大小不限.
1 | # No core files by default 0, unlimited is oo |
最后立即生效.
1 | source /etc/profile |
再跟着我做, 因为生成的core文件同名会覆盖. 这里为其加上一个core命名规则, 让其变成[core.pid]格式.
1 | sudo gedit /etc/sysctl.conf |
在该文件的最后的加上如下几句话,并保存
1 | # open, add core.pid |
立即启用
1 | sudo sysctl -p /etc/sysctl.conf |
最后是ulimit -c与cat /proc/sys/kernel/core_uses_pid查看,下面状态表示core启用都搞好了.

如果显示没有开启成功,可以试试注销系统或者重启
另外,如果遇到在gdb调试的时候遇到了Program received signal SIGSEGV, Segmentation fault.,则可以在终端中运行man 7 signal | grep SEGV查看错误信息。如下面这个例子,在函数返回的时候没有正确返回,直接报错了。原因是函数传参方式不对,导致一个参数的值没有传进去。
1 | $ man 7 signal | grep SEGV |
简单接触 GDB , 开始调试 r n p
第一个演示代码heoo.c
1 |
|
我们从下图说起,
使用命令
1 | gcc -g -Wall -o heoo.out heoo.c |
gdb heoo.out表示gdb加载heoo.out开始调试. 如果需要使用gdb调试的话编译的时候gcc需要加上-g命令.
其中l命令表示 查看加载源码内容. .

下面将演示如何加断点,使用命令b 函数名或者b 行数,r表示调试的程序开始运行.

p命令表示 打印值. n表示过程调试, 到下一步. 不管子过程如何都不进入. 直接一次跳过.

下面的s 表示单步调试, 遇到子函数,会进入函数内部调试.

总结一下 . l查看源码 ,b加断点, r 开始运行调试, n下一步, s下一步但是会进入子函数. p输出数据. c跳过直到下一个断点处,watch 变量名给变量添加监视点,whatis 变量名打印变量名的类型, finish跳出当前代码(之前跳入调试),q表示程序退出.
终端打印的一行代码是下一行要执行的代码,而不是已经执行过的代码
到这里gdb 基本会用了. 是不是也很容易. 直白. 小代码可以随便调试了.
看到这里基础知识普及完毕了. 后面可以不看了. 有机会再看. 好那我们接着扯.
gdb其它开发中用的命令
开始扯一点, linux总是敲命令操作, 也很不安全. 有时候晕了. 写这样编译命令.
1 | gcc -g -Wall -o heoo.c heoo.out |
非常恐怖, heoo.c代码删除了. heoo.out => heoo.c 先创建后生成失败退出. 原先的内容被抹掉了. 哈哈. 服务器开发, 经验不足, 熟练度不够.自己都怕自己.
gdb 其它常用命令用法 c q b info
首先看 用到的调试文件houge.c
1 |
|
同样需要仔细看下面图中使用的命令. 首先对前言部分加深一些. 看下面

这个图是前言的补充, c跳过直到下一个断点处, q表示程序退出.
在houge.c中我们开始调试. 输入下面指令进行运行:
1 | gcc -g -Wall -o houge.out houge.c |
一运行段错误, 出现了我们的 core.pid 文件

通过gdb houge.out core.27047开始调试. 马上定位出来了错误原因.
循环的调试
例如
1 | while(i<10) |
若想每次while循环均停止,需要在i++行打上断点,而不是在while(i<10)。
调试内存堆栈信息

刚开始print a, 在main中当做数组处理.打印的信息多. 后面在_add函数中, a就是个形参数组地址.
主要看info args查看当前函数参数值
info locals看当前函数栈上值信息,info registers表示查看寄存器值.
后面查看内存信息 需要记得东西多一些. 先看图,x /23dw a 意思是 查看 从a地址开始 23个 4字节 有符号十进制数 输出.

关于x更加详细见下面,这个命令常用于监测内存变化.调试中特别常用.
1 | 用gdb查看内存格式: |
gdb设置条件断点
如下如所示,很简单b 17 if i == 8. 在17行设置一个断点,并且只有i==8的时候才会触发.

gdb删除断点
d后面跟断点索引1,2,3..clear行数或名称. 删除哪一行断点. 看下面演示

到这里 介绍的gdb调试技巧基本都够用了. 感觉用图形ide,例如vs调试也就用到这些了.
估计gdb调试突破20min过去了.够用了. 后面可以不用看了.
gdb调试回退
加入你正在使用GDB7.0以上版本的调试器并且运行在支持反向调试的平台,你就可以用以下几条命令来调试程序:
首先,需要输入
r开始执行程序,然后输入record指令记录,否则的话,不能回退。
反向运行程序知道遇到一个能使程序中断的事件(比如断点,观察点,异常)。
1 | reverse-continue |
反向运行程序到上一次被执行的源代码行。
1 | reverse-step |
反向运行程序到上一条机器指令
1 | reverse-stepi |
反向运行到上一次被执行的源代码行,但是不进入函数。
1 | reverse-next |
反向运行到上一条机器指令,除非这条指令用来返回一个函数调用、整个函数将会被反向执行。
1 | reverse-nexti |
反向运行程序回到调用当前函数的地方。
1 | reverse-finish |
设置程序运行方向,可以用平常的命令step和continue等来执行反向的调试命令。
1 | set exec-direction [forward | reverse] |
上面的反向运行也可以理解为撤销后面运行的语句所产生的效果,回到以前的状态。
好的,接下来我们来试试看如何反向调试。
首先确认自己的平台支持进程记录回放(Process Record and Replay),当在调试器启用进程记录回放功能时,调试器会记录下子进程,也就是被调试进程的每一步的运行状态与上一步运行状态的差异,需要撤销的时候就可以很方便回到上一步。
假设我们有以下C程序:
1 | int main(int argc, const char *argv[]) |
将它编译并加上调试符号:
1 | gcc -Wall -g a.c |
开始调试
1 | gdb a.out |
接下来设置一个断点在第三行:
1 | (gdb) b 3 |
运行,程序会在第三行的地方停下来:
1 | (gdb) r |
给变量a设置监视点方便我们观察:
1 | (gdb) watch a |
启动进程记录回放:
1 | (gdb) record |
现在每运行一步调试器都会记录下变化,以便回溯。我们连续执行3条语句。
1 | (gdb) n |
可以看到,a的值先是从0变为了1,然后变为2,如果想让程序倒退回到以前的状态怎么办?可以用reverse-next命令:
1 | (gdb) reverse-next |
这样程序就倒退到了我们启动进程记录回放的地方,a的值经过两步回到了最初的状态。
若需要关闭进程记录回放,可以使用record stop:
1 | (gdb) record stop |
带参数的调参
如正常运行为./main file1.pgm file2.pgm,则使用gdb调试的时候为下面语句,加上了--args参数
1 | gdb --args ./main file1.pgm file2.pgm |
设置命中断点次数
使用ignore命令,ignore bnum count。例如ignore 2 10含义为2号断点命中10次时停止。
gdb 格式化结构体输出
set print address on
打开地址输出,当程序显示函数信息时,GDB会显出函数的参数地址。系统默认为打开的,
show print address
查看当前地址显示选项是否打开。
set print array on
打开数组显示,打开后当数组显示时,每个元素占一行,如果不打开的话,每个元素则以逗号分隔。这个选项默认是关闭的。与之相关的两个命令如下,我就不再多说了。
set print array off
show print array
set print elements
这个选项主要是设置数组的,如果你的数组太大了,那么就可以指定一个来指定数据显示的最大长度,当到达这个长度时,GDB就不再往下显示了。如果设置为0,则表示不限制。
show print elements
查看print elements的选项信息。
set print null-stop
如果打开了这个选项,那么当显示字符串时,遇到结束符则停止显示。这个选项默认为off。
set print pretty on
如果打开printf pretty这个选项,那么当GDB显示结构体时会比较漂亮。
set print pretty off
show print pretty
set print union on
set print union off
show print union
打印 C 中的联合体。默认是 on 。
set pagination off
使用该命令可以禁止gdb打印的信息翻页
gdb 打印数组
可以用下面的方法来显示数组
1 | p *array@len |
其中p相当于print,array就是数组首地址,也可以是数组名,len是想要显示的数组的长度。
比如我有一个数组的定义
1 | int a[] = {1, 2, 3, 4, 5}; |
那么想要显示的时候就可以写:
1 | p *a@5 |
这样就会显示数组a中的所有元素。
也可以使用display在每一步调试的时候都显示:
1 | display *a@5 |
取消显示就用undisplay,不过这时候要写显示的号码。
gdb输出重定向
方法一:适合临时向文件输出些信息的情况。
比如要用info functions输出所有函数,结果往往有一大坨,所以可以将之输出到文件。
1 | (gdb) set logging file <file name> |
方法二:适合整个gdb会话期间都重定向输出的情况。
1 | gdb |tee newfile |
gdb 调试darknet实际工程
darknet源代码是makefile管理的,之前不会在Linux调试大型项目,今天探索了一下,这里介绍一下。
准备工作
从这里下载源代码
修改makefile文件中DEBUG=0改为DEBUG=1进行调试。其中编译选项-O0,意思是不进行编译优化,gdb在默认情况下会使用-O2,会出现print变量中出现<optimized out>。
接着编译源代码:
1 | make clean |
根目录会出现darknet可执行文件。
在工程根目录运行如下命令下载权重:
1 | wget https://pjreddie.com/media/files/yolov3-tiny.weights |
开始调试
终端输入如下语句,开始调试
1 | gdb ./darknet |
在gdb命令中输入运行程序需要的参数类型
1 | set args detect cfg/yolov3-tiny.cfg yolov3-tiny.weights data/dog.jpg |
为了对整个工程进行调试,这里需要将src目录添加进来,在gdb命令中输入如下指令:
1 | DIR ./src |
在gdb命令中为main函数设置断点
1 | b main |
开始调试,在gdb命令中输入r,回车,发现程序停留在第一行。
接着可以在第435行,即char *outfile = find_char_arg(argc, argv, "-out", 0);,打上断点b 435;
在gdb命令中输入b parser.c:761在子函数parser.c的761行打上断点;
输入c,回车,程序跳到下一个断点,即停留下一个断点所在行;
输入n单步执行,不跳入子函数。
输入s命令单步执行并跳入此处调用的子函数;
输入print 变量名或者p 变量名即可查看该变量值;输入finish跳出子函数;
输入q结束调试。
gdb 多线程多进程调试
到这里实战中用的机会少了, 也就老鸟会用上些. 这部分可以调试,不好调试. 一般一调估计小半天就走了. 好,那我们处理最后10min.
gdb调试宏

首先看上面命令
- macro expand 宏(参数) => 得到宏导出内容.
- info macro 宏名 => 宏定义内容
如果你需要用到上面gdb功能, 查看和导出宏的话.还需要gcc 支持,生成的时候加上 -ggdb3如下
1 | gcc -Wall -ggdb3 -o houge.out houge.c |
就可以使用了. 扩展一下 对于 gcc 编译的有个过程叫做 预编译gcc -E -o *.i *.c.
这时候处理多数宏,直接展开, 也可以查看最后结果. 也算也是一个黑科技.
开始多线程调试
首先看测试用例dasheng.c
1 |
|
编译命令
1 | gcc -Wall -g -o dasheng.out dasheng.c -lpthread |
那先看下面测试图

上面info threads查看所有运行的线程信息. *表示当前调试的线程.
后面l _run表示查看 _run附近代码. 当然还有l 16 查看16行附近文件内容.
gdb多线程切换 测试如下

thread 3表示切换到第三个线程, info threads 第一列id 就是 thread 切换的id.
上面测试线程 就算你切换到 thread 3. 其它线程还是在跑的. 我们用下面命令 只让待调试的线程跑. 其它线程阻塞.

set scheduler-locking on开始多线程单独调试. 不用了 设置set scheduler-locking off关闭. 又会回到你调试这个, 其它线程不阻塞.

总结 多线程调试常用就这三个实用命令
- info threads
- thread id
- set scheduler-locking on/off
分别是查看,切换,设置同步调试.到这里多线程调试基本完毕了.
开始gdb多进行调试
首先看liaobude.c测试代码
1 |
|
编译命令
1 | gcc -Wall -g -o liaobude.out liaobude.c |
其实对多进程调试, 先介绍一个 常用的, 调试正在运行的程序. 首先让./liaobude.out跑起来.

再通过ps -ef找到需要调试的进程. 复制进程文件描述符pid.
这时候启动gdb.
attach pid
gdb就把pid那个进程加载进来了. 加载的进程会阻塞到当前正在运行的地方. 直到使用命令控制. 这个功能还是非常猛的.
最后介绍 进程调试的有关命令(需要最新的gdb才会支持). 多进程的调试思路和多线程调试流程很相似.
1 | GDB可以同时调试多个程序。 |
具体的意思有
1 | set follow-fork-mode [parent|child] set detach-on-fork [on|off] |
更加详细的 gdb 多进程调试demo 可以参照 http://blog.csdn.net/pbymw8iwm/article/details/7876797
使用方式和线程调试思路是一样的. 就是gdb 的命令换了字符. 工作中多进程调试遇到少.
遇到了很少用gdb调试. 会用下面2种调试好办法
2) 写单元测试
3) 打日志检测日志,分析
到这里 gdb30分钟内容讲解完毕. 多试试写写练一练, gdb基本突破没有问题.
经验
如果遇到子函数在函数返回的时候,也就是执行到最后一个)的时候,忽然报了Program received signal SIGSEGV, Segmentation fault.错误,那么可以使用s先进入该子函数,然后运行finish跳出子函数,若此时正常跳出了,那么就不是该子函数的问题,此时再使用s进入下一个调用的子函数,依次排查。此外,还可以先输入set pagination off禁止gdb打印消息分页,然后使用bt查看调用的堆栈。
参考链接
Linux基础 30分钟GDB调试快速突破
gdb调试4—回退
gdb debug with more than one argument
C/C++中的段错误(Segmentation fault)[转]
关于type return to continue,or q
各种奇特的事情:内存错误,无常的段错误,堆栈消失
将GDB中的输出定向到文件
gdb断点(六)condition 与ignore

