gdb 技巧
注意:
- 这里是讲 gdb 的技巧。如果没有接触过 gdb,请看这篇:点这里。
- 内容针对算法竞赛而写,因此可能有些不严谨之处。
gdb是一个功能强大的命令行调试器。其实,除了我们常用的 file b s n q disp p
等命令,也有很多高级技巧。虽然有的功能是为系统级调试提供的,但还是有方便之处。
(提醒:请提前使用 -g
参数编译)
GDB版本:9.1。
示例代码:
#include <iostream>
#include <cstdio>
using namespace std;
int f(int n){
if(n==0) return 1;
return f(n-1)*n;
}
int main(){
int n;
scanf("%d",&n);
printf("%d\n",f(n));
return 0;
}
配置
默认的 gdb
对于算法竞赛选手而言,并不算太好用,有诸多限制。
下面是一些有用的选项。可以在调试过程中临时使用,也可以加入 .gdbinit
文件中永久启用。
set max-value-size unlimited
设置打印的变量大小限制为无限制。
set print array on
set print array-indexes on
以更可读的方式打印数组,并加上数组下标。
set confirm off
退出时不显示提示信息。
命令
1. backtrace
backtrace(简写为bt)可以让你查看栈帧信息。这对调试递归的函数很有帮助。
配套命令:
up/down [num]
往栈顶/栈底移动num帧。num默认为1。
frame [num]
切换到第num帧。frame简写为f。
Example:
当抵达某个断点停下后,输入bt,类似于这样:
(gdb) bt
#0 f (n=3) at example.cpp:6
#1 0x000055555555519e in f (n=4) at example.cpp:6
#2 0x000055555555519e in f (n=5) at example.cpp:6
#3 0x000055555555519e in f (n=6) at example.cpp:6
#4 0x000055555555519e in f (n=7) at example.cpp:6
#5 0x000055555555519e in f (n=8) at example.cpp:6
#6 0x000055555555519e in f (n=9) at example.cpp:6
#7 0x000055555555519e in f (n=10) at example.cpp:6
#8 0x00005555555551dd in main () at example.cpp:12
(gdb)
几个示例:
(gdb) up 1
#1 0x000055555555519e in f (n=4) at example.cpp:6
6 return f(n-1)*n;
(gdb) down 1
#0 f (n=3) at example.cpp:6
6 return f(n-1)*n;
(gdb) frame 5 //前面bt输出的结果中,第一项是序号,frame即切换到对应序号的帧
#5 0x000055555555519e in f (n=8) at example.cpp:6
6 return f(n-1)*n;
(gdb)
2. commands
commands(简写为comm)可以在触发某个(或多个)断点的时候运行指定gdb命令。
用法:commands [断点编号1] [断点编号2] ...
之后,它会让你逐行输入要指定的gdb命令。
效果吗...在到你指定的断点时,他都会逐行运行你之前输入的命令。
Example:
(gdb) b 6
Breakpoint 1 at 0x1191: file example.cpp, line 6.
(gdb) comm 1
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>p "Command test" //指定到达1号断点时打印这段字符串
>end //以end结束
(gdb) r
Starting program: /home/xxx/example
10
Breakpoint 1, f (n=10) at example.cpp:6
6 return f(n-1)*n;
$1 = "Command test"
(gdb)
怎么查看断点编号?运行 info b
即可。
输出类似这样:(num是编号)
Num Type Disp Enb Address What
1 breakpoint keep y 0x000000000000129e in pre() at UVA10140 Prime Distance.cpp:12
2 breakpoint keep y 0x00000000000012c7 in pre() at UVA10140 Prime Distance.cpp:13
3 breakpoint keep y 0x00000000000012de in pre() at UVA10140 Prime Distance.cpp:14
3. ignore
用法:ignore [断点编号] [num]
。ignore可缩写为ig。
效果:在前num次触发指定断点时都不停止(即到了第num+1次触发断点才停下)
这在调一些循环结构的代码时比较有用。
Example:
(gdb) b 6
Breakpoint 1 at 0x1191: file example.cpp, line 6.
(gdb) ig 1 4
Will ignore next 4 crossings of breakpoint 1.
(gdb) r
Starting program: /home/xxx/example
10
Breakpoint 1, f (n=6) at example.cpp:6
6 return f(n-1)*n;
(gdb) //前面4次经过断点,分别为f(10)、f(9)、f(8)、f(7),都跳过了
//因此f(6)才触发
4. condition
用法:condition [断点编号] [条件]
。condition可缩写为cond。
效果:触发断点时,只有指定的条件为真时才停下。
Example:
(gdb) b 6
Breakpoint 1 at 0x1191: file example.cpp, line 6.
(gdb) cond 1 n==5 //只有n等于5时才触发断点
(gdb) r
Starting program: /home/xxx/example
10
Breakpoint 1, f (n=5) at example.cpp:6
6 return f(n-1)*n;
(gdb)
5. breakpoint
端点也有类型。
- break(简写b)是我们最熟悉的。
- tbreak(简写tb):临时断点,也就是触发一次后自动消失。与break用法相同。
- hbreak(简写hb):硬件断点。对我们来说没什么用。
- rbreak(简写rb):根据正则表达式设置断点。用法:
rbreak [正则表达式]
。
说明一下rbreak。举个例子,我程序里有两个函数,dfs1与dfs2。如果我运行 rbreak dfs*
,由于dfs1与dfs2均匹配,所以这两个函数均会被加上断点。
Example1:(tbreak)(第一次触发在f函数处的断点,继续运行便不会再触发该断点了)
(gdb) tb f
Temporary breakpoint 1 at 0x1184: file example.cpp, line 5.
(gdb) r
Starting program: /home/xxx//example
10 //这里是输入,10!=3628800
Temporary breakpoint 1, f (n=10) at example.cpp:5
5 if(n==0) return 1; //临时断点,只停了一次
(gdb) c
Continuing. //继续,就不会再停了
3628800
[Inferior 1 (process 31859) exited normally]
(gdb)
Example2:(rbreak)(main符合mai*的条件,因此被加了断点)
(gdb) rb mai*
Breakpoint 1 at 0x11ac: file example.cpp, line 9.
int main();
(gdb)
6. print/display
6.1 输出格式
用法:print/[format] [变量1] [变量2] ...
当然,如果是 display
命令,则要换成 display/[format] [变量1] [变量2] ...
其中,format
是一个小写字母,指定打印变量值的格式。
format字母 | 对应格式 |
---|---|
x | 按十六进制格式显示变量 |
d | 按十进制格式显示变量 |
u | 按十进制格式显示无符号整型 |
o | 按八进制格式显示变量 |
t | 按二进制格式显示变量 |
a | 按十六进制格式显示变量 |
c | 按字符格式显示变量 |
f | 按浮点数格式显示变量 |
比如说,调试状压DP的程序时,就可以 p/t [变量名]
来以二进制形式查看变量了。disp
同理。
Example:
(gdb) p/x 100
$1 = 0x64
(gdb) p/d 100
$2 = 100
(gdb) p/u 100
$3 = 100
(gdb) p/o 100
$4 = 0144
(gdb) p/t 100
$5 = 1100100
(gdb) p/a 100
$6 = 0x64
(gdb) p/c 100
$7 = 100 'd'
(gdb)
6.2 输出指定数量的元素
有时候,我们不想打印整个数组,而只想知道某连续几项的值。这时,可以使用 @
来指定输出元素的个数。
用法:print [数组的第一个元素]@[要输出的元素个数] [变量2] ...
(由于开头的程序中没有数组,因此下面的例子不基于开头的程序)
(gdb) l
1 #include <iostream>
2 #include <cstdio>
3 using namespace std;
4 int f(int n){
5 if(n==0) return 1;
6 return f(n-1)*n;
7 }
8
9 int main(){
10 int n,a[10]={0};
(gdb)
11 scanf("%d",&n);
12 printf("%d\n",f(n));
13 return 0;
14 }
(gdb) b 11
Breakpoint 1 at 0x144c: file /home/xxx/1.cpp, line 11.
(gdb) r
Starting program: /home/xxx/1
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Breakpoint 1, main () at /home/xxx/1.cpp:11
11 scanf("%d",&n);
(gdb) p a[0]@3
$1 = {0, 0, 0}
(gdb) p a[5]@2
$3 = {0, 0}
(gdb)
7. save
其实断点是可以保存的!比如说,我临时要重启一下,又不想丢失当前调试的断点信息。那么,我们可以将当前的断点信息保存到一个文件里,到时候再导入。
用法:save breakpoints [文件名]
效果:将当前所有的断点信息保存到一个指定的文件里。
有人有疑问了,怎么导入断点信息呢?那就是source命令!
用法:source [文件名]
效果:从指定的文件里导入断点信息。
Example:
(gdb) b 5
Breakpoint 1 at 0x1184: file example.cpp, line 5.
(gdb) b 6
Breakpoint 2 at 0x1191: file example.cpp, line 6.
(gdb) b 7
Breakpoint 3 at 0x11a2: file example.cpp, line 7.
(gdb) save breakpoints 123 //保存断点信息
Saved to file '123'.
(gdb) q
---中间退出gdb,再重新进---
(gdb) source 123 //从这个文件中引入断点信息
Breakpoint 1 at 0x1184: file example.cpp, line 5.
Breakpoint 2 at 0x1191: file example.cpp, line 6.
Breakpoint 3 at 0x11a2: file example.cpp, line 7.
(gdb)
8. call
用法:call [调用语句]
效果:调用指定函数。
比如说,我的程序有一个min函数,我就可以通过 call min(a,b)
来获取变量a、b的最小值了。
Example:
(gdb) call f(10) //也就是10!
$1 = 3628800
(gdb)
9. finish(缩写fin)
用法:无参数。
效果:继续运行,直到当前函数返回。
Example:
Breakpoint 1, main() at example.cpp:10
10 int n;
(gdb) fin
Run till exit from #0 main() at example.cpp:9 //main函数执行完毕,返回了0
[Inferior 1 (process 32243) exited normally]
(gdb)
10. watchpoint
其实,断点还有一种特殊的类型——watchpoint。(简写为wa——似乎不太吉祥)
用法:watch/rwatch/awatch [变量名]
作用:监视指定变量。
- watch(简写wa):当指定变量被写时停下。
- rwatch(简写rwa):当指定变量被读时停下。
- awatch(简写awa):当指定变量被读/写时停下。
Example:
(gdb) wa n
Hardware watchpoint 2: n
(gdb) c
Continuing.
10
Hardware watchpoint 2: n
Old value = 32767 //此处由于scanf读入修改了n的值,因此停下
New value = 10
0x00007ffff7ac43a9 in __vfscanf_internal () from /usr/lib/libc.so.6
(gdb)
11. checkpoint
有时候,我们要复现某个bug,这个时候,我们可以创建一个快照,即checkpoint。
命令:checkpoint
(可简写为ch)
用法:无参数。
效果:创建一个快照,包含当前调试的所有信息。同时会输出这个checkpoint的信息,就像这样:
checkpoint 1: fork returned pid 25776.
其中,数字1便是这个checkpoint的编号。
那么,如何回滚到以前的快照呢?那就是restart命令啦!
用法:restart [checkpoint编号]
效果:回退到指定checkpoint的快照。
Example:
Breakpoint 1, f (n=10) at example.cpp:5
5 if(n==0) return 1;
(gdb) ch //创建了编号为1的快照
checkpoint 1: fork returned pid 32213.
(gdb) c
Continuing.
Breakpoint 1, f (n=9) at example.cpp:5
5 if(n==0) return 1;
(gdb) restart 1 //恢复到编号为1的快照
Switching to process 32213
#0 f (n=10) at example.cpp:5
5 if(n==0) return 1;
(gdb)
12. jump
用法:jump [num]
作用:强制使跳转至第num行。(中间的行都跳过了)
注意,这个不能跨函数跳转,否则会出错。
Example:
(gdb) b 10
Breakpoint 1 at 0x11bb: file example.cpp, line 11.
(gdb) r
Starting program: /home/xxx/example
Breakpoint 1, main () at example.cpp:11
11 scanf("%d",&n);
(gdb) jump 13
Continuing at 0x5555555551f0.
[Inferior 1 (process 32243) exited normally] //跳到了第13行,main函数已经结束了,因此直接退出程序
(gdb)
13. return
用法:return [argu]
作用:强制使当前函数退出,并返回argu值。(如果该函数本来就没有返回值,则argu可以省略)
Breakpoint 1, f (n=10) at example.cpp:5
5 if(n==0) return 1;
(gdb) return 15
Make f(int) return now? (y or n) y
#0 0x00005555555551dd in main () at example.cpp:12
12 printf("%d\n",f(n));
(gdb) n
15 //因为前面设定返回15,所以这里输出15