有错误的代码
要开始使用 GDB,你需要一些代码。这里有一个用 C++ 写的示例应用程序(如果你一般不使用 C++ 编写程序也没关系,在所有语言中原理都是一样的)
-
#include <iostream>
-
#include <stdlib.h> //srand
-
#include <stdio.h> //printf
-
using namespace std;
-
int main () {
-
srand (time(NULL));
-
int alpha = rand() % 8;
-
cout << "Hello world." << endl;
-
int beta = 2;
-
printf("alpha is set to is %s ", alpha);
-
printf("kiwi is set to is %s ", beta);
-
return 0;
-
} // main
这个代码示例中有一个 bug,但它确实可以编译(至少在 GCC 5 的时候)。如果你熟悉 C++,你可能已经看到了,但这是一个简单的问题,可以帮助新的 GDB 用户了解调试过程。编译并运行它就可以看到错误:
-
$ g++ -o buggy example.cpp
-
$ ./buggy
-
Hello world.
-
Segmentation fault
排除段故障
从这个输出中,你可以推测变量 alpha
的设置是正确的,因为否则的话,你就不会看到它后面的那行代码执行。当然,这并不总是正确的,但这是一个很好的工作理论,如果你使用 printf
作为日志和调试器,基本上也会得出同样的结论。从这里,你可以假设 bug 在于成功打印的那一行之后的某行。然而,不清楚错误是在下一行还是在几行之后。
GNU 调试器是一个交互式的故障排除工具,所以你可以使用 gdb
命令来运行错误的代码。为了得到更好的结果,你应该从包含有调试符号的源代码中重新编译你的错误应用程序。首先,看看 GDB 在不重新编译的情况下能提供哪些信息:
-
$ gdb ./buggy
-
Reading symbols from ./buggy...done.
-
(gdb) start
-
Temporary breakpoint 1 at 0x400a44
-
Starting program: /home/seth/demo/buggy
-
Temporary breakpoint 1, 0x0000000000400a44 in main ()
-
(gdb)
当你以一个二进制可执行文件作为参数启动 GDB 时,GDB 会加载该应用程序,然后等待你的指令。因为这是你第一次在这个可执行文件上运行 GDB,所以尝试重复这个错误是有意义的,希望 GDB 能够提供进一步的见解。很直观,GDB 用来启动它所加载的应用程序的命令就是 start
。默认情况下,GDB 内置了一个断点,所以当它遇到你的应用程序的 main
函数时,它会暂停执行。要让 GDB 继续执行,使用命令 continue
:
-
(gdb) continue
-
Continuing.
-
Hello world.
-
Program received signal SIGSEGV, Segmentation fault.
-
0x00007ffff71c0c0b in vfprintf () from /lib64/libc.so.6
-
(gdb)
毫不意外:应用程序在打印 “Hello world” 后不久就崩溃了,但 GDB 可以提供崩溃发生时正在发生的函数调用。这有可能就足够你找到导致崩溃的 bug,但为了更好地了解 GDB 的功能和一般的调试过程,想象一下,如果问题还没有变得清晰,你想更深入地挖掘这段代码发生了什么。