GDB调试
GDB中的命令固然很多,但我们只需掌握其中十个左右的命令,就大致可以完成日常的基本的程序调试工作。
命令 | 解释 | 示例 |
file <文件名> | 加载被调试的可执行程序文件。 因为一般都在被调试程序所在目录下执行GDB,因而文本名不需要带路径。 | (gdb) file gdb-sample |
r | Run的简写,运行被调试的程序。 如果此前没有下过断点,则执行完整个程序;如果有断点,则程序暂停在第一个可用断点处。 | (gdb) r |
c | Continue的简写,继续执行被调试程序,直至下一个断点或程序结束。 | (gdb) c |
b <行号> b <函数名称> b *<函数名称> b *<代码地址> d [编号] | b: Breakpoint的简写,设置断点。两可以使用“行号”“函数名称”“执行地址”等方式指定断点位置。 其中在函数名称前面加“*”符号表示将断点设置在“由编译器生成的prolog代码处”。如果不了解汇编,可以不予理会此用法。 d: Delete breakpoint的简写,删除指定编号的某个断点,或删除所有断点。断点编号从1开始递增。 | (gdb) b 8 (gdb) b main (gdb) b *main (gdb) b *0x804835c (gdb) d |
s, n | s: 执行一行源程序代码,如果此行代码中有函数调用,则进入该函数; n: 执行一行源程序代码,此行代码中的函数调用也一并执行。 s 相当于其它调试器中的“Step Into (单步跟踪进入)”; 这两个命令必须在有源代码调试信息的情况下才可以使用(GCC编译时使用“-g”参数)。 | (gdb) s (gdb) n |
si, ni | si命令类似于s命令,ni命令类似于n命令。所不同的是,这两个命令(si/ni)所针对的是汇编指令,而s/n针对的是源代码。 | (gdb) si (gdb) ni |
p <变量名称> | Print的简写,显示指定变量(临时变量或全局变量)的值。 | (gdb) p i (gdb) p nGlobalVar |
display ... undisplay <编号> | display,设置程序中断后欲显示的数据及其格式。 例如,如果希望每次程序中断后可以看到即将被执行的下一条汇编指令,可以使用命令 “display /i $pc” 其中 $pc 代表当前汇编指令,/i 表示以十六进行显示。当需要关心汇编代码时,此命令相当有用。 undispaly,取消先前的display设置,编号从1开始递增。 | (gdb) display /i $pc (gdb) undisplay 1 |
i | Info的简写,用于显示各类信息,详情请查阅“help i”。 | (gdb) i r |
q | Quit的简写,退出GDB调试环境。 | (gdb) q |
help [命令名称] | GDB帮助命令,提供对GDB名种命令的解释说明。 如果指定了“命令名称”参数,则显示该命令的详细说明;如果没有指定参数,则分类显示所有GDB命令,供用户进一步浏览和查询。 | (gdb) help display |
/add************************************/
j命令 回跳
ret 设置返回值 (例ret 0/ret -1)
/add************************************/废话不多说,下面开始实践。
先给出一个示例用的小程序,C语言代码
- #include <stdio.h>
- int nGlobalVar = 0;
- int tempFunction(int a, int b)
- {
- printf("tempFunction is called, a = %d, b = %d /n", a, b);
- return (a + b);
- }
- int main()
- {
- int n;
- n = 1;
- n++;
- n--;
- nGlobalVar += 100;
- nGlobalVar -= 12;
- printf("n = %d, nGlobalVar = %d /n", n, nGlobalVar);
- n = tempFunction(1, 2);
- printf("n = %d", n);
- return 0;
- }
请将此代码复制出来并保存到文件 gdb-sample.c 中,然后切换到此文件所在目录,用GCC编译之:
- [root@localhost test]# gcc gdb-sample.c -o gdb-sample.c -g
在上面的命令行中,使用 -o 参数指定了编译生成的可执行文件名为 gdb-sample,使用参数 -g 表示将源代码信息编译到可执行文件中。如果不使用参数 -g,会给后面的GDB调试造成不便。当然,如果我们没有程序的源代码,自然也无从使用 -g 参数,调试/跟踪时也只能是汇编代码级别的调试/跟踪。
下面“gdb”命令启动GDB,将首先显示GDB说明,不管它:
- [root@localhost test]# gdb
- GNU gdb (GDB) Red Hat Enterprise Linux (7.2-60.el6_4.1)
- Copyright (C) 2010 Free Software Foundation, Inc.
- License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
- This is free software: you are free to change and redistribute it.
- There is NO WARRANTY, to the extent permitted by law. Type "show copying"
- and "show warranty" for details.
- This GDB was configured as "i686-redhat-linux-gnu".
- For bug reporting instructions, please see:
- <http://www.gnu.org/software/gdb/bugs/>.
- (gdb)
上面最后一行“(gdb) ”为GDB内部命令引导符,等待用户输入GDB命令。
下面使用“file”命令载入被调试程序 gdb-sample(这里的 gdb-sample 即前面 GCC 编译输出的可执行文件):
- (gdb) file gdb-sample
- Reading symbols from /home/test/gdb-sample...done.
- (gdb)
上面最后一行提示已经加载成功。
下面使用“r”命令执行(Run)被调试文件,因为尚未设置任何断点,将直接执行到程序结束
- (gdb) r
- Starting program: /home/test/gdb-sample
- n = 1, nGlobalVar = 88 /ntempFunction is called, a = 1, b = 2 /nn = 3
- Program exited normally.
- (gdb)
下面使用“b”命令在 main 函数开头设置一个断点(Breakpoint):
- (gdb) b main
- Breakpoint 1 at 0x80483f9: file gdb-sample.c, line 14.
- (gdb)
上面最后一行提示已经成功设置断点,并给出了该断点信息:在源文件 gdb-sample.c 第14行处设置断点;这是本程序的第一个断点(序号为1);断点处的代码地址为 0x80483f9(此值可能仅在本次调试过程中有效)。回过头去看源代码,第14行中的代码为“n = 1”,恰好是 main 函数中的第一个可执行语句(前面的“int n;”为变量定义语句,并非可执行语句)。
再次使用“r”命令执行(Run)被调试程序:
- (gdb) r
- Starting program: /home/test/gdb-sample
- Breakpoint 1, main () at gdb-sample.c:14
- 14 n = 1;
- (gdb)
程序中断在gdb-sample.c第14行处,即main函数是第一个可执行语句处。
上面最后一行信息为:下一条将要执行的源代码为“n = 1;”,它是源代码文件gdb-sample.c中的第14行。
下面使用“s”命令(Step)执行下一行代码(即第14行“n = 1;”):
- (gdb) s
- 15 n++;
- (gdb)
上面的信息表示已经执行完“n = 1;”,并显示下一条要执行的代码为第15行的“n++;”。
既然已经执行了“n = 1;”,即给变量 n 赋值为 1,那我们用“p”命令(Print)看一下变量 n 的值是不是 1 :
- (gdb) p n
- $1 = 1
- (gdb)
果然是 1。($1大致是表示这是第一次使用“p”命令——再次执行“p n”将显示“$2 = 1”——此信息应该没有什么用处。)
下面我们分别在第21行、tempFunction 函数开头各设置一个断点(分别使用命令“b 21”“b tempFunction”)
- (gdb) b 21
- Breakpoint 2 at 0x8048425: file gdb-sample.c, line 21.
- (gdb) b tempFunction
- Breakpoint 3 at 0x80483ca: file gdb-sample.c, line 7.
- (gdb)
使用“c”命令继续(Continue)执行被调试程序,程序将中断在第二 个断点(26行),此时全局变量 nGlobalVar 的值应该是 88;再一次执行“c”命令,程序将中断于第三个断点(7行,tempFunction 函数开头处),此时tempFunction 函数的两个参数 a、b 的值应分别是 1 和 2:
- (gdb) c
- Continuing.
- Breakpoint 2, main () at gdb-sample.c:21
- 21 printf("n = %d, nGlobalVar = %d /n", n, nGlobalVar);
- (gdb) p nGlobalVar
- $2 = 88
- (gdb) c
- Continuing.
- Breakpoint 3, tempFunction (a=1, b=2) at gdb-sample.c:7
- 7 printf("tempFunction is called, a = %d, b = %d /n", a, b);
- (gdb) p a
- $3 = 1
- (gdb) p b
- $4 = 2
- (gdb)
上面反馈的信息一切都在我们预料之中,哈哈~~~
再一次执行“c”命令(Continue),因为后面再也没有其它断点,程序将一直执行到结束:
- (gdb) c
- Continuing.
- n = 1, nGlobalVar = 88 /ntempFunction is called, a = 1, b = 2 /nn = 3
- Program exited normally.
- (gdb)
有时候需要看到编译器生成的汇编代码,以进行汇编级的调试或跟踪,又该如何操作呢?
这就要用到display命令“display /i $pc”了(此命令前面已有详细解释)
- (gdb) display /i $pc
- (gdb)
此后程序再中断时,就可以显示出汇编代码了:
- (gdb) r
- Starting program: /home/test/gdb-sample
- Breakpoint 1, main () at gdb-sample.c:14
- 14 n = 1;
- 1: x/i $pc
- => 0x80483f9 <main+9>: movl $0x1,0x1c(%esp)
- (gdb)
看到了汇编代码,“n = 1;”对应的汇编代码是“movl $0x1,0x1c(%esp)”。
并且以后程序每次中断都将显示下一条汇编指定(“si”命令用于执行一条汇编代码——区别于“s”执行一行C代码):
- (gdb) si
- 15 n++;
- 1: x/i $pc
- => 0x8048401 <main+17>:addl $0x1,0x1c(%esp)
- (gdb) si
- 16 n--;
- 1: x/i $pc
- => 0x8048406 <main+22>:subl $0x1,0x1c(%esp)
- (gdb) si
- 18 nGlobalVar += 100;
- 1: x/i $pc
- => 0x804840b <main+27>:mov 0x8049734,%eax
- (gdb)
接下来我们试一下命令“b *<函数名称>”。
为了更简明,有必要先删除目前所有断点(使用“d”命令——Delete breakpoint):
- (gdb) d
- Delete all breakpoints? (y or n) y
- (gdb)
当被询问是否删除所有断点时,输入“y”并按回车键即可。
下面使用命令“b *main”在 main 函数的 prolog 代码处设置断点(prolog、epilog,分别表示编译器在每个函数的开头和结尾自行插入的代码):
- (gdb) b *main
- Breakpoint 4 at 0x80483f0: file gdb-sample.c, line 12.
- (gdb) r
- The program being debugged has been started already.
- Start it from the beginning? (y or n) y
- Starting program: /home/test/gdb-sample
- Breakpoint 4, main () at gdb-sample.c:12
- 12 {
- 1: x/i $pc
- => 0x80483f0 <main>: push %ebp
- (gdb) si
- 0x080483f1 12 {
- 1: x/i $pc
- => 0x80483f1 <main+1>: mov %esp,%ebp
- (gdb) si
- 0x080483f3 12 {
- 1: x/i $pc
- => 0x80483f3 <main+3>:and $0xfffffff0,%esp
- (gdb) si
- 0x080483f6 12 {
- 1: x/i $pc
- => 0x80483f6 <main+6>:sub $0x20,%esp
- (gdb) si
- 14 n = 1;
- 1: x/i $pc
- => 0x80483f9 <main+9>:movl $0x1,0x1c(%esp)
- (gdb) si
- 15 n++;
- 1: x/i $pc
- => 0x8048401 <main+17>addl $0x1,0x1c(%esp)
- (gdb)
此时可以使用“i r”命令显示寄存器中的当前值———“i r”即“Infomation Register”:
- (gdb) i r
- eax 0xbffff244 -1073745340
- ecx 0x3a1c6d89 974941577
- edx 0x1 1
- ebx 0x8c5ff4 9199604
- esp 0xbffff170 0xbffff170
- ebp 0xbffff198 0xbffff198
- esi 0x0 0
- edi 0x0 0
- eip 0x8048401 0x8048401 <main+17>
- eflags 0x282 [ SF IF ]
- cs 0x73 115
- ss 0x7b 123
- ds 0x7b 123
- es 0x7b 123
- fs 0x0 0
- gs 0x33 51
- (gdb)
当然也可以显示任意一个指定的寄存器值:
- (gdb) i r eax
- eax 0xbffff244 -1073745340
- (gdb)
最后一个要介绍的命令是“q”,退出(Quit)GDB调试环境:
(gdb) qThe program is running. Exit anyway? (y or n)