自己最近在Linux下编译C/C++,但是没有加断点调试过,以前用过gdb调试C,只是简单的加断点、核心转储抗段错误;然后想想调试C++应该也是一样的道理吧,果真如此,见下文:
先总结下当时自己用GDB时的情况:
1.确保编译时已加入 -g
2.gdb test.exe 进入gdb调试模式
3.list列显源程序,默认显示10行,回车/list再显示下10行;list 1 表示从第一行开始显示:
4.break 13 在第13行设置断点,断点不为空;
5.info break 查看设置的断点情况,列出断点的详情,del num删除特定号码num的断点;
6.run 开始执行程序,执行到断点的前一行;当执行完所有程序后,再次run,即可再次进入GDB模式;
7.continue,继续执行,直至下一个断点;
8.step 单步, 步入子函数内部;
9.print value1, 查看某个变量的值;
10.quit / ctrl+D 退出GDB调试模式;
下文是转载IBM的, 感觉挺有用;http://www.ibm.com/developerworks/cn/linux/sdk/gdb/
1.编译:
开始调试之前,必须用程序中的调试信息编译要调试的程序。这样,gdb 才能够调试所使用的变量、代码行和函数。如果要进行编译,请在 gcc(或 g++)下使用额外的'-g' 选项来编译程序:
gcc -g eg.c -o eg 或者:g++ -g eg.c -o eg
2.运行gdb:
在 shell 中,可以使用 'gdb' 命令并指定程序名作为参数来运行 gdb,例如 'gdb eg';或者在 gdb 中,可以使用 file 命令来装入要调试的程序,例如 'file eg'。这两种方式都假设您是在包含程序的目录中执行命令。装入程序之后,可以用 gdb 命令 'run' 来启动程序。
3.调试会话示例:
如果一切正常,程序将执行到结束,此时 gdb 将重新获得控制。但如果有错误将会怎么样?这种情况下,gdb 会获得控制并中断程序,从而可以让您检查所有事物的状态,如果运气好的话,可以找出原因。为了引发这种情况,我们将使用一个示例程序:
#include<stdio.h> int wib(int no1, int no2) { int result, diff; diff = no1 - no2; result = no1 / diff; return result; } int main(int argc, char *argv[]) { int value, div, result, i, total; value = 10; div = 6; total = 0; for(i = 0; i < 10; i++) { result = wib(value, div); total += result; div++; value--; } printf("%d wibed by %d equals %d\n", value, div, total); return 0; }
这个程序将运行 10 次 for 循环,使用 'wib()" 函数计算出累积值,最后打印出结果。
在您喜欢的文本编辑器中输入这个程序(要保持相同的行距),保存为 'eg1.c',使用 'gcc -g eg1.c -o eg1' 进行编译,并用 'gdb eg1' 启动 gdb。使用'run' 运行程序可能会产生以下消息:
Program received signal SIGFPE, Arithmetic exception. 0x80483ea in wib (no1=8, no2=8) at eg1.c:7 7 result = no1 / diff; (gdb) |
要查看变量的值,使用 gdb 'print' 命令并指定变量名。输入 'print no1' 和 'print diff',可以相应看到 "no1" 和 "diff" 的值,结果如下:
(gdb) print no1 $5 = 8 (gdb) print diff $2 = 0 |
(gdb) continue Continuing. Program terminated with signal SIGFPE, Arithmetic exception. The program no longer exists. |
为了查看在 main() 中发生了什么情况,可以在程序代码中的某一特定行或函数中设置断点,这样 gdb 会在遇到断点时中断执行。可以使用命令 'break main' 在进入 main() 函数时设置断点,或者可以指定其它任何感兴趣的函数名来设置断点。然而,我们只希望在调用 wib() 函数之前中断执行。输入 'list main' 将打印从 main() 函数开始的源码清单,再次按回车将显示第 21 行上的 wib() 函数调用。要在那一行上设置断点,只需输入 'break 21'。gdb 将发出以下响应:
(gdb) break 21
Breakpoint 1 at 0x8048428: file eg1.c, line 21.
以显示它已在我们请求的行上设置了 1 号断点。'run' 命令将从头重新运行程序,直到 gdb 中断为止。发生这种情况时,gdb 会生成一条消息,指出它在哪个断点上中断,以及程序运行到何处:
Breakpoint 1, main (argc=1, argv=0xbffff954) at eg1.c:21 21 result = wib(value, div); |
从以上的调查中可以看出,当 "value" 和 "div" 相等时就会出现问题,因此输入 'continue' 继续执行,直到下一次遇到 1 号断点。对于这次迭代,'info locals' 显示了 value=9 和 div=7。
与其再次继续,还不如使用 'next' 命令单步调试程序,以查看 "value" 和 "div" 是如何改变的。gdb 将响应:
(gdb) next
22 total += result;
|
再按两次回车将显示加法和减法表达式:
(gdb) 23 div++; (gdb) 24 value--; |
再按两次回车将显示第 21 行,wib() 调用。'info locals' 将显示目前 "div" 等于 "value",这就意味着将发生问题。如果有兴趣,可以使用 'step' 命令(与 'next' 形成对比,'next' 将跳过函数调用)来继续执行 wib() 函数,以再次查看除法错误,然后使用 'next' 来计算 "result"。
现在已完成了调试,可以使用 'quit' 命令退出 gdb, 或ctrl+x或者其他。由于程序仍在运行,这个操作会终止它,gdb 将提示您确认。
5.更多断点和观察点:
由于我们想要知道在调用 wib() 函数之前 "value" 什么时候等于 "div",因此在上一示例中我们在第 21 行中设置断点。我们必须继续执行两次程序才会发生这种情况,但是只要在断点上设置一个条件就可以使 gdb 只在 "value" 与 "div" 真正相等时暂停。要设置条件,可以在定义断点时指定 "break <line number> if <conditional expression>"。将 eg1 再次装入 gdb,并输入:
(gdb) break 21 if value==div
Breakpoint 1 at 0x8048428: file eg1.c, line 21.
|
(gdb) condition 1 value==div |
使用 'condition' 命令时,如果指定断点编号但又不指定表达式,可以将断点设置成无条件断点,例如,'condition 1' 就将 1 号断点设置成无条件断点。
要查看当前定义了什么断点及其条件,请发出命令 'info break':
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x08048428 in main at eg1.c:21
stop only if value == div
breakpoint already hit 1 time
|
如果我们对 "value" 什么时候变得与 "div" 相等更感兴趣,那么可以使用另一种断点,称作监视。当指定表达式的值改变时,监视点将中断程序执行,但必须在表达式中所使用的变量在作用域中时设置监视点。要获取作用域中的 "value" 和 "div",可以在 main 函数上设置断点,然后运行程序,当遇到 main() 断点时设置监视点。重新启动 gdb,并装入 eg1,然后输入:
(gdb) break main
Breakpoint 1 at 0x8048402: file eg1.c, line 15.
(gdb) run
...
Breakpoint 1, main (argc=1, argv=0xbffff954) at eg1.c:15
15 value = 10;
|
(gdb) watch div==value Hardware watchpoint 2: div == value |
(gdb) continue
Continuing.
Hardware watchpoint 2: div == value
Old value = 0
New value = 1
main (argc=1, argv=0xbffff954) at eg1.c:19
19 for(i = 0; i < 10; i++)
|
'info watch' 命令将列出已定义的监视点和断点(此命令等价于 'info break'),而且可以使用与断点相同的语法来启用、禁用和删除监视点。
6.core文件:
在 gdb 下运行程序可以使俘获错误变得更容易,但在调试器外运行的程序通常会中止而只留下一个 core 文件。gdb 可以装入 core 文件,并让您检查程序中止之前的状态。
在 gdb 外运行示例程序 eg1 将会导致核心信息转储:
$ ./eg1 Floating point exception (core dumped) |
... Core was generated by `./eg1'. Program terminated with signal 8, Floating point exception. ... #0 0x80483ea in wib (no1=8, no2=8) at eg1.c:7 7 result = no1 / diff; |
7.堆栈跟踪:
程序“调用堆栈”是当前函数之前的所有已调用函数的列表(包括当前函数)。每个函数及其变量都被分配了一个“帧”,最近调用的函数在 0 号帧中(“底部”帧)。要打印堆栈,发出命令 'bt'('backtrace' [回溯] 的缩写):
(gdb) bt #0 0x80483ea in wib (no1=8, no2=8) at eg1.c:7 #1 0x8048435 in main (argc=1, argv=0xbffff9c4) at eg1.c:21 |
实际上,发出 'info locals' 命令时,gdb 会打印出当前帧中的局部变量,缺省情况下,这个帧中的函数就是被中断的函数(0 号帧)。可以使用命令 'frame' 打印当前帧。要查看 main 函数(在 1 号帧中)中的变量,可以发出 'frame 1' 切换到 1 号帧,然后发出 'info locals' 命令:
(gdb) frame 1 #1 0x8048435 in main (argc=1, argv=0xbffff9c4) at eg1.c:21 21 result = wib(value, div); (gdb) info locals value = 8 div = 8 result = 4 i = 2 total = 6 |
此信息显示了在第三次执行 "for" 循环时(i 等于 2)发生了错误,此时 "value" 等于 "div"。
可以通过如上所示在 'frame' 命令中明确指定号码,或者使用 'up' 命令在堆栈中上移以及 'down' 命令在堆栈中下移来切换帧。要获取有关帧的进一步信息,如它的地址和程序语言,可以使用命令 'info frame'。
gdb 堆栈命令可以在程序执行期间使用,也可以在 core 文件中使用,因此对于复杂的程序,可以在程序运行时跟踪它是如何转到函数的。
8.连接到其他进程:
除了调试 core 文件或程序之外,gdb 还可以连接到已经运行的进程(它的程序已经过编译,并加入了调试信息),并中断该进程。只需用希望 gdb 连接的进程标识替换 core 文件名就可以执行此操作。以下是一个执行循环并睡眠的示例程序:
eg2 示例代码
#include int main(int argc, char *argv[]) { int i; for(i = 0; i < 60; i++) { sleep(1); } return 0; } |
./eg2 & [3] 1283 |
... /home/seager/gdb/1283: No such file or directory. Attaching to program: /home/seager/gdb/eg2, Pid 1283 ... 0x400a87f1 in __libc_nanosleep () from /lib/libc.so.6 (gdb) |
(gdb) backtrace #0 0x400a87f1 in __libc_nanosleep () from /lib/libc.so.6 #1 0x400a877d in __sleep (seconds=1) at ../sysdeps/unix/sysv/linux/sleep.c:78 #2 0x80483ef in main (argc=1, argv=0xbffff9c4) at eg2.c:7 (gdb) frame 2 #2 0x80483ef in main (argc=1, argv=0xbffff9c4) at eg2.c:7 7 sleep(1); (gdb) print i $1 = 50 |
9.其他技巧:
gdb 可以让您通过使用 shell 命令在不退出调试环境的情况下运行 shell 命令,调用形式是 'shell [commandline]',这有助于在调试时更改源代码。
最后,在程序运行时,可以使用 'set ' 命令修改变量的值。在 gdb 下再次运行 eg1,使用命令 'break 7 if diff==0' 在第 7 行(将在此处计算结果)设置条件断点,然后运行程序。当 gdb 中断执行时,可以将 "diff" 设置成非零值,使程序继续运行直至结束:
Breakpoint 1, wib (no1=8, no2=8) at eg1.c:7 7 result = no1 / diff; (gdb) print diff $1 = 0 (gdb) set diff=1 (gdb) continue Continuing. 0 wibed by 16 equals 10 Program exited normally. |
GNU 调试器是所有程序员工具库中的一个功能非常强大的工具。在本文中,我只介绍了 gdb 的一小部分功能。要了解更多知识,建议您阅读 GNU 调试器手册。