Debugging with gdb: https://www.eecs.umich.edu/courses/eecs373/readings/Debugger.pdf
http://sourceware.org/gdb/current/onlinedocs/gdb/ 内容较新
gdb概述:
UNIX及UNIX-like下的调试工具。或许,各位比较喜欢那种图形界面方式的,像VC、BCB等IDE的调试,但如果你是在 UNIX平台下做软件,你会发现GDB这个调试工具相比于VC、z的优点是具有修复网络断点以及恢复链接等功能,比BCB的图形化调试器有更强大的功能。所谓“尺有所短,寸有所长”就是这个道理。
注意,如果要使用gdb调试指定的C/C++程序,则该程序在编译时要添加:-g选项,如果没有-g,你将看不见程序的函数名、变量名,所代替的全是运行时的内存地址。
gdb使用技巧:
https://wizardforcel.gitbooks.io/100-gdb-tips/content/set-watchpoint.html
https://sourceware.org/gdb/onlinedocs/gdb/index.html#SEC_Contents
如何生成可调试程序
以使用gcc为例,例如编写好test.c文件
1、最简单的
gcc test.c 会在当前路径下直接生成a.out
2、分步骤编译
将源文件编译成目标文件:gcc - c test.c,将生成test.o文件
再将目标文件编译成可执行文件:gcc -o test test.o
3、一步到位,并可通过 -o 设置生成文件名
gcc test.c -o test //test.c是文件名
-o 表示输出,test是输出的文件名
但是通过上述命令编译生成test,在使用gdb进行调试、加断点的时候,会出现:No symbol table is loaded. Use the "file" command.
因为在编译的时候没有使用 -g 选项,通过命令:gcc -g test.c -o test 编译后,即可通过gdb进行调试,并且添加断点也可以。
gdb调试相关命令
调试
- s:step,即跳进调用的函数
- n:next,继续执行下一行代码,单条语句执行
- c:继续运行程序,continue的缩写
断点相关
添加断点
- 在文件特定行加断点:b file:linenum,如果在程序当前执行的文件上的某行加断点,则可简单的使用:
b linenum
即可;弊端:如果你修改了源程序,则之前通过行号设置的断点可能就不是你想要的了,需要重新设置断点 - 指定函数:b func,或 break func
- 退出函数:finish
设置断点的方法:
break <function>
在进入指定函数时停住。C++中可以使用class::function或function(type,type)格式来指定函数名。
break <linenum>
在指定行号停住。
break +offset
break -offset
在当前行号的前面或后面的offset行停住。offiset为自然数。
break filename:linenum
在源文件filename的linenum行处停住。
break filename:function
在源文件filename的function函数的入口处停住。
break *address
在程序运行的内存地址处停住。
break
break命令没有参数时,表示在下一条指令处停住。
break ... if <condition>
...可以是上述的参数,condition表示条件,在条件成立时停住。比如在循环境体中,可以设置break if i==100,表示当i为100时停住程序。
查看断点时,可使用info命令,如下所示:(注:n表示断点号)
info breakpoints [n]
info break [n]
保存断点信息为文件 & 加载含断点信息的文件
在gdb中,可以使用如下命令将设置的断点保存下来:
(gdb) save breakpoints file-name-to-save
下次调试时,可以使用如下命令批量设置保存的断点:
(gdb) source file-name-to-save
设置临时断点
设置临时断点后,该断点只执行一次,通过tbreak(缩写:tb)进行临时断点设置:tb file:linenum
条件断点
gdb可以设置条件断点,也就是只有在条件满足时,断点才会被触发,命令是“break … if cond
”,只有在表达式为true的时候断点才会生效
查看:
- 查看断点:info b回车,或者info break
- 查看堆栈:bt
断点管理
查看断点:info b回车后,会打印断点的编号及所在位置、是否使能等;如果要删除某个断点,可以:a)d 编号,b)delete 编号;如果要使某个断点disable/enable,可以:disable/enable 编号;
观察点
参考:https://wizardforcel.gitbooks.io/100-gdb-tips/content/set-watchpoint.html
gdb可以使用“watch
”命令设置观察点,缩写:wa,也就是当一个变量值发生变化时,程序会停下来。通过 watch 变量名 的形式,当变量的值发生改变时,程序就会停下来。
观察点一般来观察某个表达式(变量也是一种表达式)的值是否有变化了,如果有变化,马上停住程序。有下面的几种方法:
watch <expr>
为表达式(变量)expr设置一个观察点。一量表达式值有变化时,马上停住程序。
rwatch <expr>
当表达式(变量)expr被读时,停住程序。
awatch <expr>
当表达式(变量)的值被读或被写时,停住程序。
info watchpoints
列出当前所设置了的所有观察点。
打印
打印变量
- p 变量名,print的缩写
- 以指定的格式打印变量:print /<f> expr,例如对于一些数据,可能要按16进制打印,就可以使用命令:print /x <expr>
打印的格式包括:
x 按十六进制格式显示变量。
d 按十进制格式显示变量。
u 按十六进制格式显示无符号整型。
o 按八进制格式显示变量。
t 按二进制格式显示变量。
a 按十六进制格式显示变量。
c 按字符格式显示变量。
f 按浮点数格式显示变量。
如果想要打印格式更好看些,可以输入命令:set print pretty on
有时候在调试时,打印的变量并没有完全显示,显示完整的变量信息,输入命令:set print element 0
打印ASCII和宽字符字符串
- ASCII字符串:x/s 变量名
- 打印宽字符字符串,首先确认宽字符的长度
p sizeof(wchar_t)
由于当前平台宽字符的长度为4个字节,则用“x/ws 变量名
”命令。如果是2个字节,则用“x/hs 变量名
”命令进行打印。
打印大数组
在gdb中,如果要打印大数组的内容,缺省最多会显示200个元素:
可以使用如下命令,设置这个最大限制数:
(gdb) set print elements number-of-elements
也可以使用如下命令,设置为没有限制:
(gdb) set print elements 0
或
(gdb) set print elements unlimited (gdb) p array
打印变量类型和所在的文件
在gdb中,可以使用如下命令查看变量的类型,下面的he为变量名:
(gdb) whatis he type = struct child
如果想查看详细的类型信息:
(gdb) ptype he type = struct child { char name[10]; enum {boy, girl} gender; }
如果想查看定义该变量的文件:
(gdb) i variables he All variables matching regular expression "he": File variable.c: struct child he; Non-debugging symbols: 0x0000000000402030 she 0x00007ffff7dd3380 __check_rhosts_file
gdb会显示所有包含(匹配)该表达式的变量。如果只想查看完全匹配给定名字的变量:
(gdb) i variables ^he$ All variables matching regular expression "^he$": File variable.c: struct child he;
函数调用
退出正在调试的函数
- 输入finish
- 输入return
- 输入:return 值,例如:return 10,退出了函数并且修改了函数的返回值
直接执行函数
通过 call func(),或者 print func() 的形式,直接调用并执行某个函数
列出附近的代码:
GDB 可以打印出所调试程序的源代码,当然,在程序编译时一定要加上-g的参数,把源程序信息编译到执行文件中。不然就看不到源程序了。当程序停下来以后,GDB会报告程序停在了那个文件的第几行上。你可以用list命令来打印程序的源代码。还是来看一看查看源代码的GDB命令吧。
list <linenum>
显示程序第linenum行的周围的源程序。
list <function>
显示函数名为function的函数的源程序。
list
显示当前行后面的源程序。
list -
显示当前行前面的源程序。
一般是打印当前行的上5行和下5行,如果显示函数是是上2行下8行,默认是10行,当然,你也可以定制显示的范围,使用下面命令可以设置一次显示源程序的行数。
set listsize <count>
设置一次显示源代码的行数。
show listsize
查看当前listsize的设置。
list命令还有下面的用法:
list <first>, <last>
显示从first行到last行之间的源代码。
list , <last>
显示从当前行到last行之间的源代码。
list +
往后显示源代码。
一般来说在list后面可以跟以下这们的参数:
<linenum> 行号。
<+offset> 当前行号的正偏移量。
<-offset> 当前行号的负偏移量。
<filename:linenum> 哪个文件的哪一行。
<function> 函数名。
<filename:function> 哪个文件中的哪个函数。
<*address> 程序运行时的语句在内存中的地址。
退出程序:
- q:quit的缩写
调试正在运行的进程
已知进程号,通过gdb调试并查看问题:gdb -p 进程号
调试中使用已编写好的断点文件
- 在gdb调试过程中,可以通过 source xx文件的形式,这样不用每次都手动输入断点信息
启动gdb
启动gdb有以下三种方式:
1、gdb <program>
program也就是你的执行文件,一般在当然目录下。
2、gdb <program> core
用gdb同时调试一个运行程序和core文件,core是程序非法执行后core dump后产生的文件。
3、gdb <program> <PID>
如果你的程序是一个服务程序,那么你可以指定这个服务程序运行时的进程ID。gdb会自动attach上去,并调试它。program应该在PATH环境变量中搜索得到。
信号(Signals)
信号是一种软中断,是一种处理异步事件的方法。一般来说,操作系统都支持许多信号。尤其是UNIX,比较重要应用程序一般都会处理信号。UNIX定义了许多信号,比如SIGINT表示中断字符信号,也就是Ctrl+C的信号,SIGBUS表示硬件故障的信号;SIGCHLD表示子进程状态改变信号;SIGKILL表示终止程序运行的信号,等等。信号量编程是UNIX下非常重要的一种技术。
GDB有能力在你调试程序的时候处理任何一种信号,你可以告诉GDB需要处理哪一种信号。你可以要求GDB收到你所指定的信号时,马上停住正在运行的程序,以供你进行调试。你可以用GDB的handle命令来完成这一功能。
handle <signal> <keywords...>
在GDB中定义一个信号处理。信号<signal>可以以SIG开头或不以SIG开头,可以用定义一个要处理信号的范围(如:SIGIO-SIGKILL,表示处理从SIGIO信号到SIGKILL的信号,其中包括SIGIO,SIGIOT,SIGKILL三个信号),也可以使用关键字all来标明要处理所有的信号。一旦被调试的程序接收到信号,运行程序马上会被GDB停住,以供调试。其<keywords>可以是以下几种关键字的一个或多个。
nostop
当被调试的程序收到信号时,GDB不会停住程序的运行,但会打出消息告诉你收到这种信号。
stop
当被调试的程序收到信号时,GDB会停住你的程序。
print
当被调试的程序收到信号时,GDB会显示出一条信息。
noprint
当被调试的程序收到信号时,GDB不会告诉你收到信号的信息。
pass
noignore
当被调试的程序收到信号时,GDB不处理信号。这表示,GDB会把这个信号交给被调试程序会处理。
nopass
ignore
当被调试的程序收到信号时,GDB不会让被调试程序来处理这个信号。
info signals
info handle
查看有哪些信号在被GDB检测中。
例如,对于SIGUSR1信号,如果不想处理该信号,可以输入:handle SIGUSR1 nostop noprint
数组
有时候,你需要查看一段连续的内存空间的值。比如数组的一段,或是动态分配的数据的大小。你可以使用GDB的“@”操作符,“@”的左边是第一个内存的地址的值,“@”的右边则你你想查看内存的长度。例如,你的程序中有这样的语句:
int *array = (int *) malloc (len * sizeof (int));
于是,在GDB调试过程中,你可以以如下命令显示出这个动态数组的取值:
p *array@len
@的左边是数组的首地址的值,也就是变量array所指向的内容,右边则是数据的长度,其保存在变量len中,其输出结果,大约是下面这个样子的:
(gdb) p *array@len
$1 = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40}
如果是静态数组的话,可以直接用print数组名,就可以显示数组中所有数据的内容了。
参考:
https://blog.csdn.net/haoel/article/details/2880