gdb高级技巧

Zesty_Fox

2020-03-24 23:05:34

Personal

**注意:** 这里是讲gdb的高级技巧。如果没有接触过gdb,可以看我写的这篇入门教程:[点这里](https://www.cnblogs.com/acceptedzhs/p/13161213.html)。 gdb是一个功能极其强大的命令行调试器。其实,除了我们常用的 `file b s n q disp p` 等命令,也有很多高级技巧。虽然有的功能是为系统级调试提供的,但还是有方便之处。 接下来,我将介绍一些高级技巧,希望可以帮助大家。 (温馨提醒:多用help命令!请提前用 `-g` 参数编译) GDB版本:9.1;系统版本:Arch Linux 245-1 示例代码:(以下示例均以此代码为准) ```cpp #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; } ``` ## 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: /run/media/acceptedzhs/SimpleDisk/编程/洛谷/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: /run/media/acceptedzhs/SimpleDisk/编程/洛谷/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: /run/media/acceptedzhs/SimpleDisk/编程/洛谷/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: /run/media/acceptedzhs/SimpleDisk/编程/洛谷/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命令输出格式(可以用简写) 用法:`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) p/f 100 8 = 1.40129846e-43 //浮点数格式不一样 (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: /run/media/acceptedzhs/SimpleDisk/编程/洛谷/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 ``` ----------- 蒟蒻写博客不易,恳请大佬点个赞!