GDB调试学习总结
命令概览
| 命令名称 | 命令缩写 | 命令说明 | 备注 |
|---|---|---|---|
| run | r | 运行一个程序 | |
| break | b | 添加断点 | -b xx.cpp:line |
| delete | del | 删除断点 | |
| list | l | 显示源代码 | |
| continue | c | 让暂停的程序继续运行 | |
| next | n | 运行到下一行源代码,不进入函数内部 | |
| step | s | 运行到下一行源代码,进入函数内部 | |
| p | 打印变量或者寄存器的值 | ||
| until | u | 运行到指定行停止下来 | |
| jump | j | 将当前程序执行流跳转到指定行或地址 | |
| backtrace | bt | 查看当前线程的调用堆栈 | |
| thread | thread | 切换到指定线程 | |
| frame | f | 切换到当前调用线程的指定堆栈,具体堆栈通过堆栈序号指定 | |
| info | info | 查看断点/线程等信息 | |
| ptype | ptype | 查看变量类型 | |
| disassemble | dis | 查看汇编代码 | |
| watch | watch | 监视某一个变量或内存地址的值是否发生变化 | |
| finish | fin | 结束当前调用函数,返回到上一层函数调用处 | |
| return | return | 结束当前调用函数并返回指定值,到上一层函数调用处 |
调试方法
直接调试
gdb filename
开发过程中,编译生成目标二进制文件后,可以直接gdb filename开始调试,输入run或r运行程序。比如下图所示:

启动后可以通过netstat -nltp | grep 6379来查看程序已经运行起来了

附加进程调试
gdb attach pid
如果当程序已经启动后想调试,但是不想重启程序。或者当程序在测试过程中,不能重启,一旦重启则会丢失当前的状态信息。这种情况下就使用附加进程进行调试。- 获取进程pid

- 生成附加进程

提示 “Attaching to process 8380 时即已成功将 GDB 附加到目标进程,结束调试后输入detach离开且不对当前进程有影响,让程序和gdb调试器分离,如下图:
- 获取进程pid
core文件调试
coredump即核心转储,是进程运行崩溃瞬间的一个内存快照。操作系统在程序发生异常而异常在进程内部又没有被捕获的情况下,会把进程此刻内存、寄存器状态、运行堆栈等信息转储保存到core文件,该文件是个二进制文件,可以使用gdb进行分析。
产生原因
- 内存访问越界
- 多线程程序使用了线程不安全的函数
- 多线程读写数据未加锁保护,对于被多个线程同时访问的全局数据,要加锁保护否则会coredump
- 非法指针
- 堆栈溢出
查看系统产生core文件后存放的位置
1
2
3
4
5
6
7[xw@null coredump]$ cat /proc/sys/kernel/core_pattern
/data/coredump/core_%e_%t
- 表示产生的core文件都以core_%e_%t格式保存到/data/coredump/目录下
-e 程序文件名
-t 时间戳
-p 进程ID
-s 使进程崩溃的信号signal修改core文件的存储位置
1
echo "/data/coredump/core_%e_%t" > /proc/sys/kernel/core_pattern
修改core文件大小
(1) ulimit -c 查看参数core file size的值,如果为0表示不会生成core文件,一般修改为unlimited1
2[xw@null ~]$ ulimit -c
0(2) 修改core文件大小
1
2
3[xw@null ~]$ ulimit -c unlimited
[xw@null ~]$ ulimit -c
unlimitedreadelf -h corefile查看core文件头

gdb调试core文件
1
2gdb 执行文件 core文件
where/bt 查看堆栈信息进一步分析程序参考链接
命令详解
run
1 | [xxxx@gzqc-172_24_21_96-null src]$ gdb redis-server |

此时只是附加了一个调试文件,程序还未启动,输入run命令即可运行程序。
按Ctrl+C可以中断gdb,再次输入r后输入y重启程序
continue
按Ctrl+C可以中断gdb,输入c可以使程序继续运行
1 | ^C |
break
break用于添加断点,添加完成后重启程序即可触发,停在断点处break func在函数名入口处添加断点break line在当前文件行号line处添加断点break filename:line在文件filename行号line处添加断点
redis-server端口
6379,端口号肯定是通过bind()函数创建的,搜索文件位于anet.c 401行。

- 在
anet.c:401处添加断点后重启触发断点1,continue后触发断点2。如下所示:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15(gdb) b anet.c:401
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000442962 in main at server.c:6151
2 breakpoint keep y 0x00000000004340e2 in anetListen at anet.c:401
(gdb) r
Starting program: /data/home/xxxx/redis-6.2.3/src/redis-server
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Breakpoint 1, main (argc=1, argv=0x7fffffffdf58) at server.c:6151
6151 char config_from_stdin = 0;
(gdb) c
Breakpoint 2, anetListen (err=0xbcee50 <server+752> "", s=10, sa=0xdf79a0, len=16, backlog=511) at anet.c:401
401 if (bind(s,sa,len) == -1) { - 分别在
anet.c 404 410 412处添加断点,用于判断从该函数从何处返回。如下图所示:
continue可以看到触发断点5。如下图所示:
- 在
backtrace 和 frame
backtrace用于查看堆栈信息。frame用于切换到某个堆栈处。上面的gdb程序中断在anet.c:412处bt查看此时的堆栈信息共有6层堆栈#0 ~ #5,如下图所示:
frame 堆栈编号切换堆栈信息,如下图所示:
可以得到调用关系main()函数在6322行调用initServer()函数initServer()函数在3195行调用listenToPort()函数listenToPort()函数在3052行调用anetTcpServer()函数anetTcpServer()函数在468行调用_anetTcpServer()函数_anetTcpServer()函数在450行调用anetListen()函数- 此时断点正停在
anetListen()函数中
info break、enable、disable、delete
info b查看断点个数

enable num启用编号为num的断点,不加参数表示全部启用disable num禁用编号为num的断点,不加参数表示全部禁用delete num删除编号为num的断点,不加参数表示全部删除1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000442962 in main at server.c:6151
breakpoint already hit 1 time
2 breakpoint keep y 0x00000000004340e2 in anetListen at anet.c:401
breakpoint already hit 2 times
3 breakpoint keep y 0x000000000043412c in anetListen at anet.c:404
4 breakpoint keep y 0x0000000000434178 in anetListen at anet.c:410
5 breakpoint keep y 0x000000000043417f in anetListen at anet.c:412
breakpoint already hit 1 time
(gdb) disable 4
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000442962 in main at server.c:6151
breakpoint already hit 1 time
2 breakpoint keep y 0x00000000004340e2 in anetListen at anet.c:401
breakpoint already hit 2 times
3 breakpoint keep y 0x000000000043412c in anetListen at anet.c:404
4 breakpoint keep n 0x0000000000434178 in anetListen at anet.c:410
5 breakpoint keep y 0x000000000043417f in anetListen at anet.c:412
breakpoint already hit 1 time
(gdb) delete 3
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000442962 in main at server.c:6151
breakpoint already hit 1 time
2 breakpoint keep y 0x00000000004340e2 in anetListen at anet.c:401
breakpoint already hit 2 times
4 breakpoint keep n 0x0000000000434178 in anetListen at anet.c:410
5 breakpoint keep y 0x000000000043417f in anetListen at anet.c:412
breakpoint already hit 1 time
(gdb) disable
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep n 0x0000000000442962 in main at server.c:6151
breakpoint already hit 1 time
2 breakpoint keep n 0x00000000004340e2 in anetListen at anet.c:401
breakpoint already hit 2 times
4 breakpoint keep n 0x0000000000434178 in anetListen at anet.c:410
5 breakpoint keep n 0x000000000043417f in anetListen at anet.c:412
breakpoint already hit 1 time
(gdb) enable
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000442962 in main at server.c:6151
breakpoint already hit 1 time
2 breakpoint keep y 0x00000000004340e2 in anetListen at anet.c:401
breakpoint already hit 2 times
4 breakpoint keep y 0x0000000000434178 in anetListen at anet.c:410
5 breakpoint keep y 0x000000000043417f in anetListen at anet.c:412
breakpoint already hit 1 time
(gdb) delete
Delete all breakpoints? (y or n) y
(gdb) info b
No breakpoints or watchpoints.
list
list用于显示源码,默认显示10行。如图所示:

print、ptype
print用于查看变量的值,ptype用于查看变量类型。如下所示:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18(gdb) f 4
#4 0x000000000043b304 in initServer () at server.c:3195
3195 listenToPort(server.port,&server.ipfd) == C_ERR) {
(gdb) p server.port
$1 = 6379
(gdb) ptype server
type = struct redisServer {
pid_t pid;
pthread_t main_thread_id;
char *configfile;
char *executable;
char **exec_argv;
int dynamic_hz;
int config_hz;
mode_t umask;
....}
(gdb) ptype server.port
type = int
info 和 thread
info thread查看redis-server启动后一共产生5个线程,1个主线程和4个工作线程。主线程阻塞在epoll_wait()处,其他工作线程阻塞在pthread_cond_wait()处。*表示的是当前gdb作用于哪个线程上,而不是*就代表指向主线程。

bt查看当前线程的调用堆栈,如下图所示:

堆栈#4上显示的调用main()函数表示当前线程即主线程。
thread num切换到其他线程

next 和 step
next单步步过(step over),遇到函数直接跳过不会进入函数内部。(回车键默认是将最近的一条命令执行)step单步步入(step into),遇到函数进入函数内部。
until 和 jump
until可以指定程序运行到某一行停下来。jump可以指定程序执行流跳转到指定位置执行,若跳转的地方没有断点则会继续执行,可以用于测试异常分支代码。
finish 和 return
finish命令会执行函数到正常退出该函数。return命令是立即结束执行当前函数并返回,即使当前函数还有剩余的代码未执行完毕,也不会执行。
disassemble
disassemble命令查看汇编代码,一般用于高级调试选哟查看某段代码的汇编指令,或调试没有调式信息的发版程序。

set args 和 show args
set args用于传递命令行参数。做法:gdb附加程序,在run之前,使用set args 参数内容来传递命令行参数。show args用于查看命令行参数是否设置成功。

watch
用于监视一个变量或者一段内存,当其值发生变化时,gdb会中断下来。被监视的变量或内存会产生一个watch point(观察点)。
watch命令时通过添加硬件断点来实现监视数据变化,格式:
watch 变量名/内存地址。整形变量
1
2int i;
watch i指针类型
1
2
3char *p;
watch p
watch *p数组/内存区间(这里是对buf的128个数据监视,不是采用硬件中断,是用软中断实现)
1
2char buf[10];
watch buf程序例子,可见
watch的变量a的值在发生变化时会中断1
2
3
4
5
6
7
8
9
10
11
12#include <iostream>
using namespace std;
int main() {
int a = 1;
while (a <= 5)
{
a = a*2;
}
cout << a << endl;
return 0;
}
多进程/线程调试
调试父子进程
gdb调试父进程,等子进程fork完成后,使用gdb attach pid进入子进程,需要重新开启一个窗口用于调试。gdb调试器提供选项follow-fork,可以使用show follow-fork mode查看当前值,也可以通过set follow-fork mode来设置是当一个进程fork出新的子进程时,gdb时继续调试父进程还是子进程(取值child),默认是父进程(取值parent)
1
2
3
4
5
6(gdb) show follow-fork mode
Debugger response to a program call of fork or vfork is "parent".
(gdb) set follow-fork child
(gdb) show follow-fork mode
Debugger response to a program call of fork or vfork is "child".
(gdb)spp 线程
- 在
tool目录下将服务停止 - 进入
bin目录1
2
3
4
5./spp_xxx_proxy ../etc/spp_proxy.xml
gdb --args ./spp_xxx_worker ../etc/spp_worker1.xml
set follow-fork-mode child
b 设置断点
r 运行程序
- 在