来源:http://www.jb51.net/article/36393.htm
参考:http://www.cnblogs.com/hankers/archive/2012/12/07/2806836.html
参考:http://wiki.ubuntu.org.cn/%E7%94%A8GDB%E8%B0%83%E8%AF%95%E7%A8%8B%E5%BA%8F
100个gdb技巧:https://github.com/hellogcc/100-gdb-tips/blob/master/src/index.md
编译时必须添加-g才能利用GDB进行调试,如:gcc -g test.c -o test 1. gdb -tui test打开调试程序,界面分页,上面是代码,下面是命令; 2. gdbtui的开关快捷键:ctrl+x ctrl+a或者ctrl+x A 3. file test在运行gdb下打开某个文件 4. run/r 运行 5. continue/c 继续运行 6. step/s 如果有函数则进入函数执行 7. finish 跳出当前的函数 8. stop 停止运行 9. until xxx 可用于跳出循环 10. guit/ctrl+d 退出GDB 11. print/p var 打印变量的值 12. print/p &var 打印变量地址 13. printf/p *addr 打印地址的值 14. printf/p /x var 用16进制显示数据 x十六进制/d十进制/u十六进制无符号/t二进制/c字符/f浮点 15. break/b xxx 在某行打断点 16. break/b fun 在某个函数处加断点 17. break/b 30 if n==100 //当变量n等于100的时候在30行处加断点 18. break fileName:N 在某个文件的N行加断点 19. info break/b 查看断点 20. clear N 删除N行断点 21. delete N 删除N号断点 22. delete 删除所有断点 23. disable xxx 失能断点 24. enable xxx 使能断点 25. info b 查看断点 26. info source 查看当前程序 27. info stack 查看堆栈信息 28. info args 查看当前参数值 29. display args 查看当前参数值 30. bt 查看函数堆栈 31. pwd查看程序路径 32. ctrl+p 前一条命令 33. ctrl+n 下一条命令 34. watch xxx 设置监控点,在变量改变的时候停下来。(不可直接设置,先加断点在监测) 35. ctrl+l可能layout会造成控制台花屏,使用ctrl+L清屏 36. list linenum:以linenum指定的行号为中心,显示10行 37. list function:以指定的函数为中心,显示10行 38. list:重复上一次的list指令,也可以直接按回车键,重复上次指令。 39. set listsize count:设置每次显示的行数。 40. show listsize:显示已设置的显示行数。 41. list first,last:显示指定起始行到结束结束行的源文件。 42. list ,last:显示以指定的last为结束行,显示10行。 43. list first,:以first为第一行,显示10行。 44. list +:以上次显示的结束行为起始行显示后10行 45. list –:以上次显示的起始行为结束行,显示前10行
gdb常用命令:
命令 | 描述 |
---|---|
backtrace(或bt) | 查看各级函数调用及参数 |
finish | 连续运行到当前函数返回为止,然后停下来等待命令 |
frame(或f) 帧编号 | 选择栈帧 |
info(或i) locals | 查看当前栈帧局部变量的值 |
list(或l) | 列出源代码,接着上次的位置往下列,每次列10行 |
list 行号 | 列出从第几行开始的源代码 |
list 函数名 | 列出某个函数的源代码 |
next(或n) | 执行下一行语句 |
print(或p) | 打印表达式的值,通过表达式可以修改变量的值或者调用函数 |
quit(或q) | 退出gdb 调试环境 |
set var | 修改变量的值 |
start | 开始执行程序,停在main 函数第一行语句前面等待命令 |
step(或s) | 执行下一行语句,如果有函数调用则进入到函数中 |
1.基本命令
1)进入GDB #gdb test test是要调试的程序,由gcc test.c -g -o test生成。进入后提示符变为(gdb) 。 2)查看源码 (gdb) l 源码会进行行号提示。 如果需要查看其他文件中定义的函数,在l后加上函数名即可定位到这个函数的定义及查看附近的其他源码。或者使用断点或单步运行,到某个函数处使用s进入这个函数。 3)设置断点 (gdb) b 6 这样会在运行到源码第6行时停止,可以查看变量的值、堆栈情况等;这个行号是gdb的行号。 4)查看断点处情况 (gdb) info b 可以键入"info b"来查看断点处情况,可以设置多个断点; 5)运行代码 (gdb) r 6)显示变量值 (gdb) p n 在程序暂停时,键入"p 变量名"(print)即可; GDB在显示变量值时都会在对应值之前加上"$N"标记,它是当前变量值的引用标记,以后若想再次引用此变量,就可以直接写作"$N",而无需写冗长的变量名; 7)观察变量 (gdb) watch n 在某一循环处,往往希望能够观察一个变量的变化情况,这时就可以键入命令"watch"来观察变量的变化情况,GDB在"n"设置了观察点; 8)单步运行 (gdb) n 9)程序继续运行 (gdb) c 使程序继续往下运行,直到再次遇到断点或程序结束; 10)退出GDB (gdb) q
2.断点调试
命令格式 例子 作用 break + 设置断点的行号 break n 在n行处设置断点 tbreak + 行号或函数名 tbreak n/func 设置临时断点,到达后被自动删除 break + filename + 行号 break main.c:10 用于在指定文件对应行设置断点 break + <0x...> break 0x3400a 用于在内存某一位置处暂停 break + 行号 + if + 条件 break 10 if i==3 用于设置条件断点,在循环中使用非常方便 info breakpoints/watchpoints [n] info break n表示断点号,查看断点/观察点的情况 clear + 要清除的断点行号 clear 10 用于清除对应行的断点,要给出断点的行号,清除时GDB会给出提示 delete + 要清除的断点编号 delete 3 用于清除断点和自动显示的表达式的命令,要给出断点的编号,清除时GDB不会给出任何提示 disable/enable + 断点编号 disable 3 让所设断点暂时失效/使能,如果要让多个编号处的断点失效/使能,可将编号之间用空格隔开 awatch/watch + 变量 awatch/watch i 设置一个观察点,当变量被读出或写入时程序被暂停 rwatch + 变量 rwatch i 设置一个观察点,当变量被读出时,程序被暂停 catch 设置捕捉点来补捉程序运行时的一些事件。如:载入共享库(动态链接库)或是C++的异常 tcatch 只设置一次捕捉点,当程序停住以后,应点被自动删除
3.数据命令
display +表达式 display a 用于显示表达式的值,每当程序运行到断点处都会显示表达式的值 info display 用于显示当前所有要显示值的表达式的情况 delete + display 编号 delete 3 用于删除一个要显示值的表达式,被删除的表达式将不被显示 disable/enable + display 编号 disable/enable 3 使一个要显示值的表达式暂时失效/使能 undisplay + display 编号 undisplay 3 用于结束某个表达式值的显示 whatis + 变量 whatis i 显示某个表达式的数据类型 print(p) + 变量/表达式 p n 用于打印变量或表达式的值 set + 变量 = 变量值 set i = 3 改变程序中某个变量的值 在使用print命令时,可以对变量按指定格式进行输出,其命令格式为print /变量名 + 格式 其中常用的变量格式:x:十六进制;d:十进制;u:无符号数;o:八进制;c:字符格式;f:浮点数。
4.调试运行环境相关命令
set args set args arg1 arg2 设置运行参数 show args show args 参看运行参数 set width + 数目 set width 70 设置GDB的行宽 cd + 工作目录 cd ../ 切换工作目录 run r/run 程序开始执行 step(s) s 进入式(会进入到所调用的子函数中)单步执行,进入函数的前提是,此函数被编译有debug信息 next(n) n 非进入式(不会进入到所调用的子函数中)单步执行 finish finish 一直运行到函数返回并打印函数返回时的堆栈地址和返回值及参数值等信息 until + 行数 u 3 运行到函数某一行 continue(c) c 执行到下一个断点或程序结束 return <返回值> return 5 改变程序流程,直接结束当前函数,并将指定值返回 call + 函数 call func 在当前位置执行所要运行的函数
5.堆栈相关命令
backtrace/bt bt 用来打印栈帧指针,也可以在该命令后加上要打印的栈帧指针的个数,查看程序执行到此时,是经过哪些函数呼叫的程序,程序“调用堆栈”是当前函数之前的所有已调用函数的列表(包括当前函数)。每个函数及其变量都被分配了一个“帧”,最近调用的函数在 0 号帧中(“底部”帧)
frame frame 1 用于打印指定栈帧
info reg info reg 查看寄存器使用情况
info stack info stack 查看堆栈使用情况
up/down up/down 跳到上一层/下一层函数
6.跳转执行
jump 指定下一条语句的运行点。可以是文件的行号,可以是file:line格式,可以是+num这种偏移量格式。表式着下一条运行语句从哪里开始。相当于改变了PC寄存器内容,堆栈内容并没有改变,跨函数跳转容易发生错误。 jump 指定下一条语句的运行点。可以是文件的行号,可以是file:line格式,可以是+num这种偏移量格式。表式着下一条运行语句从哪里开始。相当于改变了PC寄存器内容,堆栈内容并没有改变,跨函数跳转容易发生错误。
7.信号命令
signal signal SIGXXX 产生XXX信号,如SIGINT。一种速查Linux查询信号的方法:# kill -l handle 在GDB中定义一个信号处理。信号可以以SIG开头或不以SIG开头,可以用定义一个要处理信号的范围(如:SIGIO-SIGKILL,表示处理从SIGIO信号到SIGKILL的信号,其中包括SIGIO,SIGIOT,SIGKILL三个信号),也可以使用关键字all来标明要处理所有的信号。一旦被调试的程序接收到信号,运行程序马上会被GDB停住,以供调试。其可以是以下几种关键字的一个或多个: nostop/stop 当被调试的程序收到信号时,GDB不会停住程序的运行,但会打出消息告诉你收到这种信号/GDB会停住你的程序 print/noprint 当被调试的程序收到信号时,GDB会显示出一条信息/GDB不会告诉你收到信号的信息 pass noignore 当被调试的程序收到信号时,GDB不处理信号。这表示,GDB会把这个信号交给被调试程序会处理。 nopass ignore 当被调试的程序收到信号时,GDB不会让被调试程序来处理这个信号。 info signals info handle 可以查看哪些信号被GDB处理,并且可以看到缺省的处理方式 single命令和shell的kill命令不同,系统的kill命令发信号给被调试程序时,是由GDB截获的,而single命令所发出一信号则是直接发给被调试程序的。
8.运行Shell命令
如(gdb)shell ls来运行ls。
9.更多程序运行选项和调试
1、程序运行参数。 set args 可指定运行时参数。(如:set args 10 20 30 40 50) show args 命令可以查看设置好的运行参数。 2、运行环境。 path 可设定程序的运行路径。 show paths 查看程序的运行路径。 set environment varname [=value] 设置环境变量。如:set env USER=hchen show environment [varname] 查看环境变量。 3、工作目录。 cd 相当于shell的cd命令。 pwd 显示当前的所在目录。 4、程序的输入输出。 info terminal 显示你程序用到的终端的模式。 使用重定向控制程序输出。如:run > outfile tty命令可以指写输入输出的终端设备。如:tty /dev/ttyb 5、调试已运行的程序 两种方法: (1)在UNIX下用ps查看正在运行的程序的PID(进程ID),然后用gdb PID格式挂接正在运行的程序。 (2)先用gdb 关联上源代码,并进行gdb,在gdb中用attach命令来挂接进程的PID。并用detach来取消挂接的进程。 6、暂停 / 恢复程序运行 当进程被gdb停住时,你可以使用info program 来查看程序的是否在运行,进程号,被暂停的原因。 在gdb中,我们可以有以下几种暂停方式:断点(BreakPoint)、观察点(WatchPoint)、捕捉点(CatchPoint)、信号(Signals)、线程停止(Thread Stops),如果要恢复程序运行,可以使用c或是continue命令。 7、线程(Thread Stops) 如果程序是多线程,可以定义断点是否在所有的线程上,或是在某个特定的线程。 break thread break thread if ... linespec指定了断点设置在的源程序的行号。threadno指定了线程的ID,注意,这个ID是GDB分配的,可以通过“info threads”命令来查看正在运行程序中的线程信息。如果不指定thread 则表示断点设在所有线程上面。还可以为某线程指定断点条件。如: (gdb) break frik.c:13 thread 28 if bartab > lim 当你的程序被GDB停住时,所有的运行线程都会被停住。这方便查看运行程序的总体情况。而在你恢复程序运行时,所有的线程也会被恢复运行。
10.调试core文件
Core Dump:Core的意思是内存,Dump的意思是扔出来,堆出来。开发和使用Unix程序时,有时程序莫名其妙的down了,却没有任何的提示(有时候会提示core dumped),这时候可以查看一下有没有形如core.进程号的文件生成,这个文件便是操作系统把程序down掉时的内存内容扔出来生成的, 它可以做为调试程序的参考 (1)生成Core文件 一般默认情况下,core file的大小被设置为了0,这样系统就不dump出core file了。修改后才能生成core文件。 #设置core大小为无限 ulimit -c unlimited #设置文件大小为无限 ulimit unlimited 这些需要有root权限, 在ubuntu下每次重新打开中断都需要重新输入上面的第一条命令, 来设置core大小为无限 core文件生成路径:输入可执行文件运行命令的同一路径下。若系统生成的core文件不带其他任何扩展名称,则全部命名为core。新的core文件生成将覆盖原来的core文件。 1)/proc/sys/kernel/core_uses_pid可以控制core文件的文件名中是否添加pid作为扩展。文件内容为1,表示添加pid作为扩展名,生成的core文件格式为core.xxxx;为0则表示生成的core文件同一命名为core。 可通过以下命令修改此文件: echo "1" > /proc/sys/kernel/core_uses_pid 2)proc/sys/kernel/core_pattern可以控制core文件保存位置和文件名格式。 可通过以下命令修改此文件: echo "/corefile/core-%e-%p-%t" > core_pattern,可以将core文件统一生成到/corefile目录下,产生的文件名为core-命令名-pid-时间戳 以下是参数列表: %p - insert pid into filename 添加pid %u - insert current uid into filename 添加当前uid %g - insert current gid into filename 添加当前gid %s - insert signal that caused the coredump into the filename 添加导致产生core的信号 %t - insert UNIX time that the coredump occurred into filename 添加core文件生成时的unix时间 %h - insert hostname where the coredump happened into filename 添加主机名 %e - insert coredumping executable name into filename 添加命令名 (2)用gdb查看core文件 发生core dump之后, 用gdb进行查看core文件的内容, 以定位文件中引发core dump的行. gdb [exec file] [core file] 如: gdb ./test core 或gdb ./a.out core-file core.xxxx gdb后, 用bt命令backtrace或where查看程序运行到哪里, 来定位core dump的文件->行. 待调试的可执行文件,在编译的时候需要加-g,core文件才能正常显示出错信息 1)gdb -core=core.xxxx file ./a.out bt 2)gdb -c core.xxxx file ./a.out bt (3)用gdb实时观察某进程crash信息 启动进程 gdb -p PID c 运行进程至crash gdb会显示crash信息 bt
list 显示源文件:
list 没有参数,显示当前行之后或周围的10多行
list start,end 显示从行号start到end之间的代码行。
print 查看运行时的数据
print i 显示当前变量i的值
print &i 显示变量i的存放地址
print array@5 显示从array(数组名)开始的5个值
print array[2]@3 显示从array第二个元素开始的3个数组元素的值
whatis i 显示变量i的数据类型
设置断点 break
break linenum 在当前文件指定行linenum处设置断点
break function 在当前文件函数function的入口处设置断点
显示断点 info
info breakpoints/break/watchpoints [num]
单步跟踪
step [N] 如果遇到函数调用,并且该函数编译时有调试信息,则会进入该函数内执行。
next [N] 遇到函数调用时,执行整个函数。
连续执行
continue 从当前行开始,连续执行到下一个断点处,或者到达该程序结束。命令中可以给出一个数字N,忽略其后N-1次断点
运行程序 run
另外几个:clear(清除所有已定义的断点),delete(删除所有断点,后面可给出断点号码,多个断点用空格分开),disable(停用指定断点,停用多个则用空格分开),enable(激活被停用的断点,各断点号码用空格分开)
11.一个调试例子
#include <stdio.h> int add_range(int low, int high) { int i, sum; for (i = low; i <= high; i++) sum = sum + i; return sum; } int main(void) { int result[100]; result[0] = add_range(1, 10); result[1] = add_range(1, 100); printf("result[0]=%d\nresult[1]=%d\n", result[0], result[1]); return 0; }
add_range函数从low加到high,在main函数中首先从1加到10,把结果保存下来,然后从1加到100,再把结果保存下来,最后打印的两个结果是:
result[0]=55
result[1]=5105
第一个结果正确,第二个结果显然不正确,这是很常见的一类错误现象,这种情况不应该怀疑代码而应该怀疑数据,因为第一次和第二次运行的都是同一段代码,如果代码是错的,那为什么第一次的结果能对呢?然而第一次和第二次运行时相关的数据却有可能不同,错误的数据会导致错误的结果。
在编译时要加上-g选项,生成的可执行文件才能用gdb进行源码级调试:
$ gcc -g main.c -o main
$ gdb main
-g选项的作用是在可执行文件中加入源代码的信息,比如可执行文件中第几条机器指令对应源代码的第几行,但并不是把整个源文件嵌入到可执行文件中,所以在调试时必须保证gdb能找到源文件。gdb提供一个类似Shell的命令行环境,上面的(gdb)就是提示符,在这个提示符下输入help可以查看命令的类别:
(gdb) help
也可以进一步查看某一类别中有哪些命令,例如查看files类别下有哪些命令可用:
(gdb) help files
现在试试用list命令从第一行开始列出源代码:
(gdb) list 1 1 #include <stdio.h> 2 3 int add_range(int low, int high) 4 { 5 int i, sum; 6 for (i = low; i <= high; i++) 7 sum = sum + i; 8 return sum; 9 } 10一次只列10行,如果要从第11行开始继续列源代码可以输入
(gdb) list也可以什么都不输直接敲回车, gdb提供了一个很方便的功能,在提示符下直接敲回车表示重复上一条命令。
(gdb) (直接回车) 11 int main(void) 12 { 13 int result[100]; 14 result[0] = add_range(1, 10); 15 result[1] = add_range(1, 100); 16 printf("result[0]=%d\nresult[1]=%d\n", result[0], result[1]); 17 return 0; 18gdb的很多常用命令有简写形式,例如list命令可以写成l,要列一个函数的源代码也可以用函数名做参数:
(gdb) l add_range 1 #include <stdio.h> 2 3 int add_range(int low, int high) 4 { 5 int i, sum; 6 for (i = low; i <= high; i++) 7 sum = sum + i; 8 return sum; 9 } 10现在退出gdb的环境:
(gdb) quit我们做一个实验,把源代码改名或移到别处再用gdb调试,这样就列不出源代码了:
$ mv main.c mian.c $ gdb main ... (gdb) l 5 main.c: No such file or directory. in main.c可见gcc的-g选项并不是把源代码嵌入到可执行文件中的,在调试时也需要源文件。
现在把源代码恢复原样,我们继续调试。首先用start命令开始执行程序:
$ gdb main ... (gdb) start Breakpoint 1 at 0x80483ad: file main.c, line 14. Starting program: /home/akaedu/main main () at main.c:14 14 result[0] = add_range(1, 10); (gdb) gdb停在main函数中变量定义之后的第一条语句处等待我们发命令,gdb列出的这条语句是即将执行的下一条语句。 我们可以用next命令(简写为n)控制这些语句一条一条地执行: (gdb) n 15 result[1] = add_range(1, 100); (gdb) (直接回车) 16 printf("result[0]=%d\nresult[1]=%d\n", result[0], result[1]); (gdb) (直接回车) result[0]=55 result[1]=5105 17 return 0; 用n命令依次执行两行赋值语句和一行打印语句,在执行打印语句时结果立刻打出来了,然后停在return语句之前等待我们发命令。虽然我们完全控制了程序的执行,但仍然看不出哪里错了,因为错误不在main函数中而在add_range函数中,
现在用start命令重新来过,这次用step命令(简写为s)钻进add_range函数中去跟踪执行:
(gdb) start
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Breakpoint 2 at 0x80483ad: file main.c, line 14.
Starting program: /home/akaedu/main
main () at main.c:14
14 result[0] = add_range(1, 10);
(gdb) s
add_range (low=1, high=10) at main.c:6
6 for (i = low; i <= high; i++)
这次停在了add_range函数中变量定义之后的第一条语句处。
在函数中有几种查看状态的办法,backtrace命令(简写为bt)可以查看函数调用的栈帧:
(gdb) bt
#0 add_range (low=1, high=10) at main.c:6
#1 0x080483c1 in main () at main.c:14
可见当前的add_range函数是被main函数调用的,main传进来的参数是low=1,high=10。
main函数的栈帧编号为1,add_range的栈帧编号为0。
现在可以用info命令(简写为i)查看add_range函数局部变量的值:
(gdb) i locals
i = 0
sum = 0
如果想查看main函数当前局部变量的值也可以做到,先用frame命令(简写为f)选择1号栈帧然后再查看局部变量:
(gdb) f 1
#1 0x080483c1 in main () at main.c:14
14 result[0] = add_range(1, 10);
(gdb) i locals
result = {0, 0, 0, 0, 0, 0, 134513196, 225011984, -1208685768, -1081160480,
...
-1208623680}
注意到result数组中有很多元素具有杂乱无章的值,我们知道未经初始化的局部变量具有不确定的值。
到目前为止一切正常。用s或n往下走几步,然后用print命令(简写为p)打印出变量sum的值:
(gdb) s
7 sum = sum + i;
(gdb) (直接回车)
6 for (i = low; i <= high; i++)
(gdb) (直接回车)
7 sum = sum + i;
(gdb) (直接回车)
6 for (i = low; i <= high; i++)
(gdb) p sum
$1 = 3
第一次循环i是1,第二次循环i是2,加起来是3,没错。这里的$1表示gdb保存着这些中间结果,
$后面的编号会自动增长,在命令中可以用$1、$2、$3等编号代替相应的值。
由于我们本来就知道第一次调用的结果是正确的,再往下跟也没意义了,可以用finish命令让程序一直运行到从当前函数返回为止:
(gdb) finish
Run till exit from #0 add_range (low=1, high=10) at main.c:6
0x080483c1 in main () at main.c:14
14 result[0] = add_range(1, 10);
Value returned is $2 = 55
返回值是55,当前正准备执行赋值操作,用s命令赋值,然后查看result数组:
(gdb) s
15 result[1] = add_range(1, 100);
(gdb) p result
$3 = {55, 0, 0, 0, 0, 0, 134513196, 225011984, -1208685768, -1081160480,
...
-1208623680}
第一个值55确实赋给了result数组的第0个元素。下面用s命令进入第二次add_range调用,进入之后首先查看参数和局部变量:
(gdb) s
add_range (low=1, high=100) at main.c:6
6 for (i = low; i <= high; i++)
(gdb) bt
#0 add_range (low=1, high=100) at main.c:6
#1 0x080483db in main () at main.c:15
(gdb) i locals
i = 11
sum = 55
由于局部变量i和sum没初始化,所以具有不确定的值,又由于两次调用是挨着的,i和sum正好取了上次调用时的值,
“验证局部变量存储空间的分配和释放”是一样的道理,只不过我这次举的例子设法让局部变量sum在第一次调用时初值为0了。
i的初值不是0倒没关系,在for循环中会赋值为0的,但sum如果初值不是0,累加得到的结果就错了。
好了,我们已经找到错误原因,可以退出gdb修改源代码了。
如果我们不想浪费这次调试机会,可以在gdb中马上把sum的初值改为0继续运行,看看这一处改了之后还有没有别的Bug:
(gdb) set var sum=0
(gdb) finish
Run till exit from #0 add_range (low=1, high=100) at main.c:6
0x080483db in main () at main.c:15
15 result[1] = add_range(1, 100);
Value returned is $4 = 5050
(gdb) n
16 printf("result[0]=%d\nresult[1]=%d\n", result[0], result[1]);
(gdb) (直接回车)
result[0]=55
result[1]=5050
17 return 0;
这样结果就对了。修改变量的值除了用set命令之外也可以用print命令,
因为print命令后面跟的是表达式,而我们知道赋值和函数调用也都是表达式,所以也可以用print命令修改变量的值或者调用函数:
(gdb) p result[2]=33
$5 = 33
(gdb) p printf("result[2]=%d\n", result[2])
result[2]=33
$6 = 13