GDB调试

摘要:
GDB中有很多命令,但我们只需要掌握其中的十个命令就可以完成基本的例行调试工作。因为GDB通常在调试程序的目录中执行,所以文本名称不需要有路径。irqQuit的缩写,退出GDB调试环境。Qhelp[command-name]GDB帮助命令,它提供了GDB命令的解释。如果指定了“命令名称”参数,将显示命令的详细描述;如果未指定参数,则按类别显示所有GDB命令,以便进一步浏览和查询。如果不使用参数-g,将给后续的GDB调试带来不便。


GDB中的命令固然很多,但我们只需掌握其中十个左右的命令,就大致可以完成日常的基本的程序调试工作。

命令解释示例
file <文件名>加载被调试的可执行程序文件。
因为一般都在被调试程序所在目录下执行GDB,因而文本名不需要带路径。
(gdb) file gdb-sample
rRun的简写,运行被调试的程序。
如果此前没有下过断点,则执行完整个程序;如果有断点,则程序暂停在第一个可用断点处。
(gdb) r
cContinue的简写,继续执行被调试程序,直至下一个断点或程序结束。(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, ns: 执行一行源程序代码,如果此行代码中有函数调用,则进入该函数;
n: 执行一行源程序代码,此行代码中的函数调用也一并执行。

s 相当于其它调试器中的“Step Into (单步跟踪进入)”;
n 相当于其它调试器中的“Step Over (单步跟踪)”。

这两个命令必须在有源代码调试信息的情况下才可以使用(GCC编译时使用“-g”参数)。

(gdb) s
(gdb) n
si, nisi命令类似于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

iInfo的简写,用于显示各类信息,详情请查阅“help i”。(gdb) i r
qQuit的简写,退出GDB调试环境。(gdb) q
help [命令名称]GDB帮助命令,提供对GDB名种命令的解释说明。
如果指定了“命令名称”参数,则显示该命令的详细说明;如果没有指定参数,则分类显示所有GDB命令,供用户进一步浏览和查询。
(gdb) help display

/add************************************/

j命令              回跳

ret               设置返回值        (例ret 0/ret -1)

/add************************************/

废话不多说,下面开始实践。

先给出一个示例用的小程序,C语言代码

  1. #include <stdio.h>  
  2.   
  3. int nGlobalVar = 0;  
  4.   
  5. int tempFunction(int a, int b)  
  6. {  
  7.     printf("tempFunction is called, a = %d, b = %d /n", a, b);  
  8.     return (a + b);  
  9. }  
  10.   
  11. int main()  
  12. {  
  13.     int n;  
  14.         n = 1;  
  15.         n++;  
  16.         n--;  
  17.   
  18.         nGlobalVar += 100;  
  19.         nGlobalVar -= 12;  
  20.   
  21.     printf("n = %d, nGlobalVar = %d /n", n, nGlobalVar);  
  22.   
  23.         n = tempFunction(1, 2);  
  24.     printf("n = %d", n);  
  25.   
  26.     return 0;  

请将此代码复制出来并保存到文件 gdb-sample.c 中,然后切换到此文件所在目录,用GCC编译之:

  1. [root@localhost test]# gcc gdb-sample.c -o gdb-sample.c -g 

在上面的命令行中,使用 -o 参数指定了编译生成的可执行文件名为 gdb-sample,使用参数 -g 表示将源代码信息编译到可执行文件中。如果不使用参数 -g,会给后面的GDB调试造成不便。当然,如果我们没有程序的源代码,自然也无从使用 -g 参数,调试/跟踪时也只能是汇编代码级别的调试/跟踪。

下面“gdb”命令启动GDB,将首先显示GDB说明,不管它:

  1. [root@localhost test]# gdb  
  2. GNU gdb (GDB) Red Hat Enterprise Linux (7.2-60.el6_4.1)  
  3. Copyright (C) 2010 Free Software Foundation, Inc.  
  4. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>  
  5. This is free software: you are free to change and redistribute it.  
  6. There is NO WARRANTY, to the extent permitted by law.  Type "show copying"  
  7. and "show warranty" for details.  
  8. This GDB was configured as "i686-redhat-linux-gnu".  
  9. For bug reporting instructions, please see:  
  10. <http://www.gnu.org/software/gdb/bugs/>.  
  11. (gdb)

上面最后一行“(gdb) ”为GDB内部命令引导符,等待用户输入GDB命令。

下面使用“file”命令载入被调试程序 gdb-sample(这里的 gdb-sample 即前面 GCC 编译输出的可执行文件):

  1. (gdb) file gdb-sample  
  2. Reading symbols from /home/test/gdb-sample...done.  
  3. (gdb) 

上面最后一行提示已经加载成功。

下面使用“r”命令执行(Run)被调试文件,因为尚未设置任何断点,将直接执行到程序结束

  1. (gdb) r  
  2. Starting program: /home/test/gdb-sample   
  3. n = 1, nGlobalVar = 88 /ntempFunction is called, a = 1, b = 2 /nn = 3  
  4. Program exited normally.  
  5. (gdb) 

 下面使用“b”命令在 main 函数开头设置一个断点(Breakpoint):

  1. (gdb) b main  
  2. Breakpoint 1 at 0x80483f9: file gdb-sample.c, line 14.  
  3. (gdb)

 上面最后一行提示已经成功设置断点,并给出了该断点信息:在源文件 gdb-sample.c 第14行处设置断点;这是本程序的第一个断点(序号为1);断点处的代码地址为 0x80483f9(此值可能仅在本次调试过程中有效)。回过头去看源代码,第14行中的代码为“n = 1”,恰好是 main 函数中的第一个可执行语句(前面的“int n;”为变量定义语句,并非可执行语句)。

 再次使用“r”命令执行(Run)被调试程序:

  1. (gdb) r  
  2. Starting program: /home/test/gdb-sample    
  3.   
  4. Breakpoint 1, main () at gdb-sample.c:14  
  5. 14       n = 1;  
  6. (gdb)

程序中断在gdb-sample.c第14行处,即main函数是第一个可执行语句处。

上面最后一行信息为:下一条将要执行的源代码为“n = 1;”,它是源代码文件gdb-sample.c中的第14行。

下面使用“s”命令(Step)执行下一行代码(即第14行“n = 1;”):

  1. (gdb) s  
  2. 15        n++;  
  3. (gdb) 

上面的信息表示已经执行完“n = 1;”,并显示下一条要执行的代码为第15行的“n++;”。

既然已经执行了“n = 1;”,即给变量 n 赋值为 1,那我们用“p”命令(Print)看一下变量 n 的值是不是 1 :

  1. (gdb) p n  
  2. $1 = 1  
  3. (gdb)

果然是 1。($1大致是表示这是第一次使用“p”命令——再次执行“p n”将显示“$2 = 1”——此信息应该没有什么用处。)

下面我们分别在第21行、tempFunction 函数开头各设置一个断点(分别使用命令“b 21”“b tempFunction”)

  1. (gdb) b 21  
  2. Breakpoint 2 at 0x8048425: file gdb-sample.c, line 21.  
  3. (gdb) b tempFunction  
  4. Breakpoint 3 at 0x80483ca: file gdb-sample.c, line 7.  
  5. (gdb) 

 使用“c”命令继续(Continue)执行被调试程序,程序将中断在第二 个断点(26行),此时全局变量 nGlobalVar 的值应该是 88;再一次执行“c”命令,程序将中断于第三个断点(7行,tempFunction 函数开头处),此时tempFunction 函数的两个参数 a、b 的值应分别是 1 和 2:

  1. (gdb) c  
  2. Continuing.  
  3.   
  4.   
  5. Breakpoint 2, main () at gdb-sample.c:21  
  6. 21    printf("n = %d, nGlobalVar = %d /n", n, nGlobalVar);  
  7. (gdb) p nGlobalVar  
  8. $2 = 88  
  9. (gdb) c  
  10. Continuing.  
  11.   
  12.   
  13. Breakpoint 3, tempFunction (a=1, b=2) at gdb-sample.c:7  
  14. 7    printf("tempFunction is called, a = %d, b = %d /n", a, b);  
  15. (gdb) p a  
  16. $3 = 1  
  17. (gdb) p b  
  18. $4 = 2  
  19. (gdb)

上面反馈的信息一切都在我们预料之中,哈哈~~~

再一次执行“c”命令(Continue),因为后面再也没有其它断点,程序将一直执行到结束:

  1. (gdb) c  
  2. Continuing.  
  3. n = 1, nGlobalVar = 88 /ntempFunction is called, a = 1, b = 2 /nn = 3  
  4. Program exited normally.  
  5. (gdb)

有时候需要看到编译器生成的汇编代码,以进行汇编级的调试或跟踪,又该如何操作呢?

这就要用到display命令“display /i $pc”了(此命令前面已有详细解释)

  1. (gdb) display /i $pc  
  2. (gdb) 

 此后程序再中断时,就可以显示出汇编代码了:

  1. (gdb) r  
  2. Starting program: /home/test/gdb-sample   
  3.   
  4.   
  5. Breakpoint 1, main () at gdb-sample.c:14  
  6. 14        n = 1;  
  7. 1: x/i $pc  
  8. => 0x80483f9 <main+9>:movl   $0x1,0x1c(%esp)  
  9. (gdb)

看到了汇编代码,“n = 1;”对应的汇编代码是“movl   $0x1,0x1c(%esp)”。

并且以后程序每次中断都将显示下一条汇编指定(“si”命令用于执行一条汇编代码——区别于“s”执行一行C代码):

  1. (gdb) si  
  2. 15        n++;  
  3. 1: x/i $pc  
  4. => 0x8048401 <main+17>:addl   $0x1,0x1c(%esp)  
  5. (gdb) si  
  6. 16        n--;  
  7. 1: x/i $pc  
  8. => 0x8048406 <main+22>:subl   $0x1,0x1c(%esp)  
  9. (gdb) si  
  10. 18        nGlobalVar += 100;  
  11. 1: x/i $pc  
  12. => 0x804840b <main+27>:mov    0x8049734,%eax  
  13. (gdb) 

接下来我们试一下命令“b *<函数名称>”。

为了更简明,有必要先删除目前所有断点(使用“d”命令——Delete breakpoint):

  1. (gdb) d  
  2. Delete all breakpoints? (y or n) y  
  3. (gdb)

当被询问是否删除所有断点时,输入“y”并按回车键即可。

下面使用命令“b *main”在 main 函数的 prolog 代码处设置断点(prolog、epilog,分别表示编译器在每个函数的开头和结尾自行插入的代码):

  1. (gdb) b *main  
  2. Breakpoint 4 at 0x80483f0: file gdb-sample.c, line 12.  
  3. (gdb) r  
  4. The program being debugged has been started already.  
  5. Start it from the beginning? (y or n) y  
  6. Starting program: /home/test/gdb-sample   
  7.   
  8.   
  9. Breakpoint 4, main () at gdb-sample.c:12  
  10. 12  {  
  11. 1: x/i $pc  
  12. => 0x80483f0 <main>:   push   %ebp  
  13. (gdb) si  
  14. 0x080483f1  12 {  
  15. 1: x/i $pc  
  16. => 0x80483f1 <main+1>: mov    %esp,%ebp  
  17. (gdb) si  
  18. 0x080483f3  12 {  
  19. 1: x/i $pc  
  20. => 0x80483f3 <main+3>:and    $0xfffffff0,%esp  
  21. (gdb) si  
  22. 0x080483f6  12    {  
  23. 1: x/i $pc  
  24. => 0x80483f6 <main+6>:sub    $0x20,%esp  
  25. (gdb) si  
  26. 14        n = 1;  
  27. 1: x/i $pc  
  28. => 0x80483f9 <main+9>:movl   $0x1,0x1c(%esp)  
  29. (gdb) si  
  30. 15        n++;  
  31. 1: x/i $pc  
  32. => 0x8048401 <main+17>addl   $0x1,0x1c(%esp)  
  33. (gdb)

 此时可以使用“i r”命令显示寄存器中的当前值———“i r”即“Infomation Register”:

  1. (gdb) i r  
  2. eax            0xbffff244   -1073745340  
  3. ecx            0x3a1c6d89   974941577  
  4. edx            0x1  1  
  5. ebx            0x8c5ff4 9199604  
  6. esp            0xbffff170   0xbffff170  
  7. ebp            0xbffff198   0xbffff198  
  8. esi            0x0  0  
  9. edi            0x0  0  
  10. eip            0x8048401    0x8048401 <main+17>  
  11. eflags         0x282    [ SF IF ]  
  12. cs             0x73 115  
  13. ss             0x7b 123  
  14. ds             0x7b 123  
  15. es             0x7b 123  
  16. fs             0x0  0  
  17. gs             0x33 51  
  18. (gdb)

 当然也可以显示任意一个指定的寄存器值:

  1. (gdb) i r eax  
  2. eax            0xbffff244   -1073745340  
  3. (gdb)

最后一个要介绍的命令是“q”,退出(Quit)GDB调试环境:

(gdb) q
The program is running. Exit anyway? (y or n)

免责声明:文章转载自《GDB调试》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇[转]CURL常用命令QPushButton/QLabel在鼠标悬浮(划过, hover)、选中(单击, pressed)状态下更换图标样式下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

[转载]注册机破解法的原理以及应对方法

[--- 资料是从免费网站上获取的,上载在这里,只为交流学习目的,文章原作者保留所有权力,如本博客的内容侵犯了你的权益,请与以下地址联系,本人获知后,马上删除。同时本人深表歉意,并致以崇高的谢意!erwin_609#msn.com---] 顾名思义,写注册机来破解软件注册的方法,就是模仿你的注册码生成算法或者逆向注 册码验证算法而写出来的和你一模一样的注册...

【转】dbx用法讲解

http://blog.chinaunix.net/uid-25544300-id-328735.html dbx 命令 用途 提供了一个调试和运行程序的环境。 语法 dbx [ -a ProcessID ] [ -c CommandFile ] [ -d NestingDepth ] [ -I Directory ] [ -E DebugEnvironm...

charles和fiddler代理抓包和弱网设置

怎么设置代理 Charles:WEB端抓包: 1.设置charles代理端口。进入Proxy Settings中设置端口如8080.8888 2.打开chrome浏览器,下载switchy proxy 3.配置proxy SwitchyOmega 4.通过charles抓chrome访问包.抓取https请求 5.安装Charles的伪造证书(Help--...

VC下发布的Release版程序崩溃后的异常捕捉与查找

寻找Release版程发生异常退出的地方比Debug版麻烦得多。发生异常的时候windows通常会弹出一个错误对话框,点击详细信息,我们能获得出错的地址和大概的出错信息,然后可以用以下办法分析我们的程序。   一.     用MAP文件定位异常代码位置。 1.         如何生成map文件 打开“Project →Project Settings...

Android调试工具及方法

转自:http://www.cnblogs.com/feisky/archive/2010/01/01/1637566.html Logcat Dump一份系统消息的日志。这些消息包括模拟器抛出错误时的堆栈跟踪。 Android Log 一个记录日志的类,用来将消息写入模拟器上的日志文件中。如果你在DDMS上运行logcat的话你可以就实时查看消息。在你的...

symbol lookup error *** , undefined symbol 错误

在重装samba过程后遇到一些问题,使用 gdb 时产生报错: gdb: symbol lookup error: gdb: undefined symbol: PyUnicodeUCS2_FromEncodedObject 原因是 gdb 依赖了python的一些包,而那些包版本太旧,使用时就会发生一些未定义的错误。 使用 nm 命令和 ldd 命令可辅...