GDB 是 GNU 提供的流行Debug 工具,GDB 可以对任何可执行文件进行Debug
使用GDB 启动程序
最简单的方法
gdb ./a.out
如何需要使用命令行参数,则需要使用 --args
参数(下面 someargs 和 arg1 等都只是例子)
gdb –args ./a.out someargs –arg1 value1 –args2 value2
进入GDB后使用 r
开始执行程序
当程序运行遇到关键错误时,GDB会捕获错误并开始调试
当然,最重要的一点,gdb 使用 quit
命令或者 Ctrl + D 退出
使用 Core Dump 调试程序
Linux 支持在程序异常退出时使用 CoreDump
将现场内存保存在磁盘上(文件名默认为core
). GDB 可以利用CoreDump文件恢复现场调试。
如果想使用CoreDump,首先要用ulimt
打开 CoreDump 功能
ulimit -c unlimited
这样程序异常退出时才会保存 core
文件。调试时使用
gdb ./a.out core
来启动调试
编译,加入符号信息
gdb 如果不能在可执行文件中找到符号(symbol)信息,调试时功能会受到极大限制,不能方便的使用断点, 不能查看代码,不能定位到崩溃的具体文件和行数。如果想要使用 GDB 调试,一般需要在编译时加上 -g
参数,加入符号信息。
gdb a.cpp -g
如果调试时发现有些变量不能输出,可能是 编译优化级别太高导致。仅在有必要的情况下,可以加入-O0
(注意是大写英文字母O
和阿拉伯数字0
)选项
g++ a.cpp -g -O0
常用指令
当触发断点,或者异常中断时,GDB会切换到相应的栈帧 (frame) 等待用户调试
常用的调试指令有
- bt (backtrace) 打印当前的调用栈
- p (print) 打印变量内容
- f (frame) 切换栈帧
- l (list) 打印当前代码
- r (run) 从头开始执行
- c (continue) 继续执行
- b (breakpoint) 插入断点
- d (delete) 删除断点
用法在下面介绍
演示
下面给一个含有错误样例代码,演示 GDB 常用的调试命令
1 2 3 4 5 6 7 8 9 |
int divide(int x, int y) { return x / y; } int main() { for (int i = 10; i >= 0; i--) { int j = divide(10, i - 1); } } |
g++ debug.cpp -g -O0
gdb ./a.out
显然这段代码会产生除0错误, 先执行(输入r
)gdb 输出如下
1 2 3 4 5 6 7 8 |
(gdb) r Starting program: /home/comzyh/Projects/tmp/a.out Program received signal SIGFPE, Arithmetic exception. 0x0000555555554608 in divide (x=10, y=0) at debug.cpp:2 2 return x / y; (gdb) |
我们能看到很多信息, 比如错误出现在 debug.cpp 文件的第3行,这行的代码是 return x / y;
, 出现的错误是算数错误(Arithmetic exception)
现在可以使用××打印变量××(p
命令)功能,看看 x 和 y 到底是什么
1 2 3 4 5 |
(gdb) p x $1 = 10 (gdb) p y $2 = 0 |
我们知道了,错误是因为 y = 0,出现了除 0 错误
那么为什么会执行这段代码呢?可以使用 list
命令查看附近的代码. 需要指出的是,list 默认向下浏览代码,你可以使用l -
向上浏览代码
1 2 3 4 5 6 7 8 9 10 |
(gdb) l 1 int divide(int x, int y) { 2 return x / y; 3 } 4 int main() { 5 for (int i = 10; i >= 0; i--) { 6 int j = divide(10, i - 1); 7 } 8 } |
到这里我们知道了,y 是 0 的原因 divide 函数的输入参数 y 是 0,那么是谁调用了这个函数呢?我们可以使用 查看调用栈 功能 (bt命令)
1 2 3 4 |
(gdb) bt #0 0x0000555555554608 in divide (x=10, y=0) at debug.cpp:2 #1 0x0000555555554631 in main () at debug.cpp:6 |
我们可以看出,是debug.cpp 的第 6 行 main 函数调用了 divide 函数,还能看到divide 函数的实参是 x = 10, y = 0.
此时我们应当使用切换栈帧 (frame命令) 回到 main 函数查看调用 divide 的代码. #1
代表main 函数的栈帧编号为 1
1 2 3 4 5 6 7 8 9 10 11 12 13 |
(gdb) f 1 #1 0x0000555555554634 in main () at debug.cpp:6 6 int j = divide(10, i - 1); (gdb) l 1 int divide(int x, int y) { 2 return x / y; 3 } 4 int main() { 5 for (int i = 10; i >= 0; i--) { 6 int j = divide(10, i - 1); 7 } 8 } |
我们通过切换栈帧和打印代码看到,传给 divide 函数的 y 参数是 循环变量 i. 我们可以打印此时 i 的值
1 2 3 4 5 |
(gdb) p i $3 = 1 (gdb) p i - 1 $4 = 0 |
print 指令不仅可以查看变量的值,还能进行简单的运算,例如对vector的元素的访问,可以直接 p vec[10]
这样操作。
最后我们尝试下断点 (breakpoint). 只要gdb等待你的输入,你就可以添加断点,比如我们给 main 函数的 第6行添加断点并重新运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
(gdb) b main.cpp:6 No source file named main.cpp. Make breakpoint pending on future shared library load? (y or [n]) n (gdb) b debug.cpp:6 Breakpoint 1 at 0x555555554622: file debug.cpp, line 6. (gdb) r The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/comzyh/Projects/tmp/a.out Breakpoint 1, main () at debug.cpp:6 6 int j = divide(10, i - 1); (gdb) p i $3 = 10 |
GDB 的确帮我们中断了程序并进入调试,此时我们可以继续(continue)
1 2 3 4 5 6 |
(gdb) c Continuing. Breakpoint 1, main () at debug.cpp:6 6 int j = divide(10, i - 1); |
断点不是一次性的,再次执行代码会再次触发断点。此时我们可以删除断点(d),需要指出断点编号
1 2 3 4 5 6 7 |
(gdb) d 1 (gdb) c Continuing. Breakpoint 2, main () at debug.cpp:6 6 int j = divide(10, i - 1); |
最后,退出GDB (quit)
1 2 3 4 5 6 7 |
(gdb) quit A debugging session is active. Inferior 1 [process 15063] will be killed. Quit anyway? (y or n) y |
原创文章,转载请注明: 转载自Comzyh的博客
本文链接地址: GDB 调试极简入门