开始调试以前,必须用程序中的调试信息编译要调试的程序。这样,gdb 才可以调试所使用的变量、代码行和函数。若是要进行编译,请在 gcc(或 g++)下使用额外的 '-g' 选项来编译程序:html
1
|
gcc -g eg.c -o eg
|
在 shell 中,可使用 'gdb' 命令并指定程序名做为参数来运行 gdb,例如 'gdb eg';或者在 gdb 中,可使用 file 命令来装入要调试的程序,例如 'file eg'。这两种方式都假设您是在包含程序的目录中执行命令。装入程序以后,能够用 gdb 命令 'run' 来启动程序。linux
若是一切正常,程序将执行到结束,此时 gdb 将从新得到控制。但若是有错误将会怎么样?这种状况下,gdb 会得到控制并中断程序,从而可让您检查全部事物的状态,若是运气好的话,能够找出缘由。为了引起这种状况,咱们将使用一个 示例程序:程序员
1 #include <stdio.h> 2 3 int wib(int no1, int no2) 4 { 5 int result, diff; 6 diff = no1 - no2; 7 result = no1 / diff; 8 return result; 9 } 10 11 int main(int argc, char *argv[]) 12 { 13 int value, div, result, i, total; 14 15 value = 10; 16 div = 6; 17 total = 0; 18 19 for(i = 0; i < 10; i++) 20 { 21 result = wib(value, div); 22 total += result; 23 div++; 24 value--; 25 } 26 printf("%d wibed by %d equals %d\n", value, div, total); 27 return 0; 28 }
这个程序将运行 10 次 for 循环,使用 'wib()" 函数计算出累积值,最后打印出结果。shell
在您喜欢的文本编辑器中输入这个程序(要保持相同的行距),保存为 'eg1.c',使用 'gcc -g eg1.c -o eg1' 进行编译,并用 'gdb eg1' 启动 gdb。使用 'run' 运行程序可能会产生如下消息:express
1
2
3
4
|
Program received signal SIGFPE, Arithmetic exception.
0x80483ea in wib (no1=8, no2=8) at eg1.c:7
7 result = no1 / diff;
(gdb)
|
gdb 指出在程序第 7 行发生一个算术异常,一般它会打印这一行以及 wib() 函数的自变量值。要查看第 7 行先后的源代码,请使用 'list' 命令,它一般会打印 10 行。再次输入 'list'(或者按回车重复上一条命令)将列出程序的下 10 行。从 gdb 消息中能够看出,第 7 行中的除法运算出了错,程序在这一行中将变量 "no1" 除以 "diff"。编辑器
要查看变量的值,使用 gdb 'print' 命令并指定变量名。输入 'print no1' 和 'print diff',能够相应看到 "no1" 和 "diff" 的值,结果以下:函数
1
2
3
4
|
(gdb) print no1
$5 = 8
(gdb) print diff
$2 = 0
|
gdb 指出 "no1" 等于 8,"diff" 等于 0。根据这些值和第 7 行中的语句,咱们能够推断出算术异常是由除数为 0 的除法运算形成的。清单显示了第 6 行计算的变量 "diff",咱们能够打印 "diff" 表达式(使用 'print no1 - no2' 命令),来从新估计这个变量。gdb 告诉咱们 wib 函数的这两个自变量都等于 8,因而咱们要检查调用 wib() 函数的 main() 函数,以查看这是在何时发生的。在容许程序天然终止的同时,咱们使用 'continue' 命令告诉 gdb 继续执行。工具
1
2
3
4
|
(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 将发出如下响应:ui
1
2
|
(gdb) break 21
Breakpoint 1 at 0x8048428: file eg1.c, line 21.
|
以显示它已在咱们请求的行上设置了 1 号断点。'run' 命令将从头从新运行程序,直到 gdb 中断为止。发生这种状况时,gdb 会生成一条消息,指出它在哪一个断点上中断,以及程序运行到何处:this
1
2
|
Breakpoint 1, main (argc=1, argv=0xbffff954) at eg1.c:21
21 result = wib(value, div);
|
发出 'print value' 和 'print div' 将会显示在第一次调用 wib() 时,变量分别等于 10 和 6,而 'print i' 将会显示 0。幸亏,gdb 将显示全部局部变量的值,并使用 'info locals' 命令保存大量输入信息。
从以上的调查中能够看出,当 "value" 和 "div" 相等时就会出现问题,所以输入 'continue' 继续执行,直到下一次遇到 1 号断点。对于此次迭代,'info locals' 显示了 value=9 和 div=7。
与其再次继续,还不如使用 'next' 命令单步调试程序,以查看 "value" 和 "div" 是如何改变的。gdb 将响应:
1
2
|
(gdb) next
22 total += result;
|
再按两次回车将显示加法和减法表达式:
1
2
3
4
|
(gdb)
23 div++;
(gdb)
24 value--;
|
再按两次回车将显示第 21 行,wib() 调用。'info locals' 将显示目前 "div" 等于 "value",这就意味着将发生问题。若是有兴趣,可使用 'step' 命令(与 'next' 造成对比,'next' 将跳过函数调用)来继续执行 wib() 函数,以再次查看除法错误,而后使用 'next' 来计算 "result"。
如今已完成了调试,可使用 'quit' 命令退出 gdb。因为程序仍在运行,这个操做会终止它,gdb 将提示您确认。
因为咱们想要知道在调用 wib() 函数以前 "value" 何时等于 "div",所以在上一示例中咱们在第 21 行中设置断点。咱们必须继续执行两次程序才会发生这种状况,可是只要在断点上设置一个条件就可使 gdb 只在 "value" 与 "div" 真正相等时暂停。要设置条件,能够在定义断点时指定 "break <line number> if <conditional expression>"。将 eg1 再次装入 gdb,并输入:
1
2
|
(gdb) break 21 if value==div
Breakpoint 1 at 0x8048428: file eg1.c, line 21.
|
若是已经在第 21 行中设置了断点,如 1 号断点,则可使用 'condition' 命令来代替在断点上设置条件:
1
|
(gdb) condition 1 value==div
|
使用 'run' 运行 eg1.c 时,若是 "value" 等于 "div",gdb 将中断,从而避免了在它们相等以前必须手工执行 'continue'。调试 C 程序时,断点条件能够是任何有效的 C 表达式,必定要是程序所使用语言的任意有效表达式。条件中指定的变量必须在设置了断点的行中,不然表达式就没有什么意义!
使用 'condition' 命令时,若是指定断点编号但又不指定表达式,能够将断点设置成无条件断点,例如,'condition 1' 就将 1 号断点设置成无条件断点。
要查看当前定义了什么断点及其条件,请发出命令 'info break':
1
2
3
4
5
|
(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
|
除了全部条件和已经遇到断点多少次以外,断点信息还在 'Enb' 列中指定了是否启用该断点。可使用命令 'disable <breakpoint number>'、'enable <breakpoint number>' 或 'delete <breakpoint number>' 来禁用、启用和完全删除断点,例如 'disable 1' 将阻止在 1 号断点处中断。
若是咱们对 "value" 何时变得与 "div" 相等更感兴趣,那么可使用另外一种断点,称做监视。当指定表达式的值改变时,监视点将中断程序执行,但必须在表达式中所使用的变量在做用域中时设置监视点。要获取做用域中的 "value" 和 "div",能够在 main 函数上设置断点,而后运行程序,当遇到 main() 断点时设置监视点。从新启动 gdb,并装入 eg1,而后输入:
1
2
3
4
5
6
|
(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;
|
要了解 "div" 什么时候更改,可使用 'watch div',但因为要在 "div" 等于 "value" 时中断,那么应输入:
1
2
|
(gdb) watch div==value
Hardware watchpoint 2: div == value
|
若是继续执行,那么当表达式 "div==value" 的值从 0(假)变成 1(真)时,gdb 将中断:
1
2
3
4
5
6
7
|
(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 locals' 命令将验证 "value" 是否确实等于 "div"(再次声明,是 8)。
'info watch' 命令将列出已定义的监视点和断点(此命令等价于 'info break'),并且可使用与断点相同的语法来启用、禁用和删除监视点。
在 gdb 下运行程序可使俘获错误变得更容易,但在调试器外运行的程序一般会停止而只留下一个 core 文件。gdb 能够装入 core 文件,并让您检查程序停止以前的状态。
在 gdb 外运行示例程序 eg1 将会致使核心信息转储:
1
2
|
$ ./eg1
Floating point exception (core dumped)
|
要使用 core 文件启动 gdb,在 shell 中发出命令 'gdb eg1 core' 或 'gdb eg1 -c core'。gdb 将装入 core 文件,eg1 的程序清单,显示程序是如何终止的,并显示很是相似于咱们刚才在 gdb 下运行程序时看到的消息:
1
2
3
4
5
6
|
...
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;
|
此时,能够发出 'info locals'、'print'、'info args' 和 'list' 命令来查看引发除数为零的值。'info variables' 命令将打印出全部程序变量的值,但这要进行很长时间,由于 gdb 将打印 C 库和程序代码中的变量。为了更容易地查明在调用 wib() 的函数中发生了什么状况,可使用 gdb 的堆栈命令。
程序“调用堆栈”是当前函数以前的全部已调用函数的列表(包括当前函数)。每一个函数及其变量都被分配了一个“帧”,最近调用的函数在 0 号帧中(“底部”帧)。要打印堆栈,发出命令 'bt'('backtrace' [回溯] 的缩写):
1
2
3
|
(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
|
此结果显示了在 main() 的第 21 行中调用了函数 wib()(只要使用 'list 21' 就能证明这一点),并且 wib() 在 0 号帧中,main() 在 1 号帧中。因为 wib() 在 0 号帧中,那么它就是执行程序时发生算术错误的函数。
实际上,发出 'info locals' 命令时,gdb 会打印出当前帧中的局部变量,缺省状况下,这个帧中的函数就是被中断的函数(0 号帧)。可使用命令 'frame' 打印当前帧。要查看 main 函数(在 1 号帧中)中的变量,能够发出 'frame 1' 切换到 1 号帧,而后发出 'info locals' 命令:
1
2
3
4
5
6
7
8
9
|
(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 文件中使用,所以对于复杂的程序,能够在程序运行时跟踪它是如何转到函数的。
除了调试 core 文件或程序以外,gdb 还能够链接到已经运行的进程(它的程序已通过编译,并加入了调试信息),并中断该进程。只需用但愿 gdb 链接的进程标识替换 core 文件名就能够执行此操做。如下是一个执行循环并睡眠的示例程序:
1 #include <unistd.h> 2 3 int main(int argc, char *argv[]) 4 { 5 int i; 6 for(i = 0; i < 60; i++) 7 { 8 sleep(1); 9 } 10 return 0; 11 }
使用 'gcc -g eg2.c -o eg2' 编译该程序并使用 './eg2 &' 运行该程序。请留意在启动该程序时在背景上打印的进程标识,在本例中是 1283:
1
2
|
./eg2 &
[3] 1283
|
启动 gdb 并指定进程标识,在我举的这个例子中是 'gdb eg2 1283'。gdb 会查找一个叫做 "1283" 的 core 文件。若是没有找到,那么只要进程 1283 正在运行(在本例中可能在 sleep() 中),gdb 就会链接并中断该进程:
1
2
3
4
5
6
|
...
/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' 来查看当前位置与 main() 的相对关系,以及 mian() 的帧号是什么,而后切换到 main() 所在的帧,查看已经在 "for" 循环中运行了多少次:
1
2
3
4
5
6
7
8
9
|
(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
|
若是已经完成了对程序的修改,能够 'detach' 命令继续执行程序,或者 'kill' 命令杀死进程。还能够首先使用 'file eg2' 装入文件,而后发出 'attach 1283' 命令链接到进程标识 1283 下的 eg2。
gdb 可让您经过使用 shell 命令在不退出调试环境的状况下运行 shell 命令,调用形式是 'shell [commandline]',这有助于在调试时更改源代码。
最后,在程序运行时,可使用 'set' 命令修改变量的值。在 gdb 下再次运行 eg1,使用命令 'break 7 if diff==0' 在第 7 行(将在此处计算结果)设置条件断点,而后运行程序。当 gdb 中断执行时,能够将 "diff" 设置成非零值,使程序继续运行直至结束:
1
2
3
4
5
6
7
8
9
|
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 调试器手册。