GDB是一个强大的命令行调试工具。命令行的强大就是在于:
UNIX下的软件全是命令行的,这给程序开发提代供了极大的便利。命令行软件的优势在于,它们可以非常容易的集成在一起,使用几个简单的已有工具的命令,就可以做出一个非常强大的功能。
例如:实现自动化
可以使用shell文本包装几个简单的工具命令,让它们可以集成在一起发挥更多功能。
一般来说,GDB主要完成下面四个方面的功能:
backtrace //显示程序中的当前位置和表示如何到达当前位置的栈跟踪(同义词:where)
breakpoint //在程序中设置一个断点
cd //改变当前工作目录
clear //删除刚才停止处的断点
commands //命中断点时,列出将要执行的命令
continue //从断点开始继续执行
delete //删除一个断点或监测点;也可与其他命令一起使用
display //程序停止时显示变量和表达时
down //下移栈帧,使得另一个函数成为当前函数
frame //选择下一条
continue //命令的帧
info //显示与该程序有关的各种信息
jump //在源程序中的另一点开始运行
kill //异常终止在
gdb //控制下运行的程序
list //列出相应于正在执行的程序的原文件内容
next //执行下一个源程序行,从而执行其整体中的一个函数
print //显示变量或表达式的值
pwd //显示当前工作目录
pype //显示一个数据结构(如一个结构或C++类)的内容
quit //退出gdb
run //执行该程序
search //在源文件中搜索正规表达式
set-variable //给变量赋值
signal //将一个信号发送到正在运行的进程
step //执行下一个源程序行,必要时进入下一个函数
until //结束当前循环
up //上移栈帧,使另一函数成为当前函数
watch //在程序中设置一个监测点(即数据断点)
whatis //显示变量或函数类型
Reverse-search //在源文件中反向搜索正规表达式
gcc是Linux或其他所有unix系统自带的编译器,它也有Windows的接口,不过在Windows上还是用Visual Studio的debugger比较简单。
假设程序文件名叫main.c,可以用以下的命令来编译它:
gcc main.c -o main //若程序没有error,生成目标文件main
如果想调试程序,需要告诉编译器:
gcc main.c -g -o main //-g 为调试命令gdb
//或者
gcc main.c -g -Wall -Werror -o main
这里讲一下以上编译选项:
一般情况,【-W】【-Wall】同时使用为佳:
gcc main.c -g -W -Wall -o main
启动GDB的方法有几种:
<1> gdb main
//main是执行文件,一般在当前目录下
<2> gdb main core
//同时调试一个运行程序和core文件
//core是程序非法执行后系统为了记录错误信息而产生的文件
<3> gdb main PID
//如果程序是一个服务程序,则可以指定这个服务程序
//运行时的PID,GDB会自动attach上去,并调试它。
//main应该在PATH环境变量中搜索得到。
GDB启动时,可以添加一些GDB的启动开关gdb -help
查看详细开关。一下列举一些常用参数:
--symbols=SYMFILE : 从指定文件中读取符号表
--se=FILE : 从指定文件中读取符号表信息,并将它用在可执行文件中
--core=COREFILE : 调试时系统产生的core文件
--directory=DIR : 加入一个源文件的搜索路径。默认是PATH中定义的路径
退出:
kill //退出程序
quit //退出GDB
若以gdb main
的方式启动GDB,GDB会在PATH路径和当前目录中搜索源文件。若不确定GDB是否找到源文件,可以用命令l/list
查看是否有显示部分源代码。使用r/run
运行程序前,可能需要进行一下5方面的设置:
set args 10 20 30 40
gdb PID
格式挂接正在运行的程序attach
挂接进程PID。用delete
取消挂接。若设置了断点,执行 run 命令后程序将停在第一次遇到的断点处,此时可用
- continue
恢复运行,直到遇到下一个断点或程序结束。
- next、step
单步执行:
- next -> 下一行代码,不进入函数内部
- step -> 遇到函数时,进入函数内部
//继续执行,三个命令作用一样,其中参数表示忽略后面的断点
continue [ignore-count]
c [ignore-count]
fg [ignore-count]
//单步跟踪程序,若遇到的函数被编译了有调试信息,则进入该函数
//count表示执行后面的[count]条指令后停止
step [count]
s [count]
//next
next [count]
n [count]
//当前函数执行完后返回,并打印函数返回时的堆栈地址、返回值、参数值等
finish
//退出循环体
until/u
break function //b function,在函数处设置断点
break linenum //b linenum,在该行设置断点
break +offset/-offset //在当前行的后一行/前一行设置断点
break filename:linenum/function //进入文件
break…if condition //若果条件成立,暂停
info breakpoints[n] //查看断点,n 为短点号 ==info b[n]
delete b[n] //删除断点
watch expr //为表达式(变量设置一个断点,当发生变化时停止运行)
rwatch expr //表达式expr被读时,停止程序
awatch expr //表达式的值被读写时,暂停程序
info watchpoints[n] //查看观察点信息
可以通过设置捕捉点(陷阱)来捕捉程序运行时的一些事件,如载入共享库(动态的链接库)或是程序的异常。
catch event //event发生时,暂停
tcatch event //只设置一次捕捉点,程序暂停后,捕捉点被自动清除
event可以是以下内容:
catch assert : //捕捉ada语言的assert断言失败
catch catch : //捕捉C++收到的异常
catch exception : //捕捉ada语言的execption
catch exec : //捕捉exec调用
catch fork : //捕捉fork调用
catch syscall : //捕捉syscall调用
catch throw : //捕捉C++抛出异常
catch vfork : //捕捉vfork调用
断点、观察点和捕捉点统称为停止点,如果觉得设置的停止点没有作用了,可以使用delete、clear、disable、enable等命令来维护停止点。
clear [function/linenum/file-linenum]
delete [breakpoints/range..] // d
disable [b/range…] //dis,比以上两方式更好,相当于放入回收站
enable //让停止点生效
针对break…if condition,condition设置好后还是可以更改的,方法如下:
//将原来bnum行的停止条件condition修改为expression
condition bnum expression
condition bnum //清除改行的停止条件
ignore bnum count //忽略改行的停止条件count次,任性!
要使用GDB调试程序,必须确保被调试程序的可执行文件(.obj)附加了调试信息,为此,gcc / g++编译源码时需要使用-g选项:
$ gcc –g example.c –o example
$ gdb example
//or
$ gdb
:file example //内部装载目标文件
:run //gdb内置命令
如果一切顺利,程序运行到结束,gdb重新获得控制权;
如果中途崩溃,gdb会记录崩溃之前的堆栈信息,方便查找原因。
演示代码:(存在bug)
#include
int wib(int no1, int no2)
{
int result, diff;
diff = no1 - no2;
result = no1/diff;
return result;
}
int main(int argc, char *argv[])
{
int value, div, result, total;
int i;
value = 10;
div = 6;
total = 0;
for (i=0; i<10; i++)
{
result = wib(value, div);
total += result;
div++;
value--;
}
printf("%d wibed by %d equals %d\n", value, div, total);
return 0;
}
程序运行10次for循环,通过wib函数算出累加值,再打印出结果。
编译运行后,使用gdb内置命令调试代码:
(gdb)run
...
Program received signal SIGFPE, Arithmetic exception.
...
in wib(no1=8, no2=8) at example.c : 8
8 result = no1/diff
(gdb)
提示程序line_8收到了一个算术异常中断信号,同时给信号的代码行内容。可以通过list
列出错误代码旁边的十行代码。从gdb消息可以看出,第八行的除法运算出了错,而这一行中将变量no1/diff,
要查看变量的值,用gdb内置命令print。
(gdb)print no1
$2=8
(gdb)print diff
$3=0
由此可推断出算术异常时由除数为0的除法运算造成的。通过print no1-no2
重新估算这个变量。gdb告诉我们这两个变量都为8,所以我们需要检查调用了wib的main函数,查看在什么时候发生的。
(gdb)continue //继续执行程序
使用断点
(gdb)list main //打印main函数开始前后的代码
(gdb)break main //进入main函数时设置断点
(gdb)break 25 //在25行wib()函数出设置断点
Breakpoint 1 at ...: file example.c, line 25
(gdb)
gdb显示已经在请求的那一行设置了1号断点
(gdb)run //从头开始运行程序,直到gdb发生中断
此时,gdb会生成一条消息,指出在哪个断点中断,以及程序运行到何处。
(gdb)next //单步执行
跟多断点和观察点
要知道调用wib函数之前什么时候value等于div,因此在上一示例中第25行处设置了断点,程序必须执行两次才会发生这种状况。我们只要在断点上设置一个条件就能使gdb在value==div
时暂停。要设置条件,可以在设置断点时同时定义出发条件:
(gdb)break 25 if value==div
Breakpoint 1 at ... : file example.c, line 25
(gdb)
condition 1 div==value #修改出发条件
condition 1 #修改为无条件断点
显示断点信息
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x000000000040052a in main at example
了解div什么时候更改
(gdb)watch div==value
Hardware watchpoint 2:div==value
(gdb)
OK ,基本操作就介绍到这里,要学会怎么使用还需要自己实践。实践了才能学到根本。
以下内容截自本人根据例子编译、调试所得,可以参考
zhang@zhang:~/code$ vim example.c
zhang@zhang:~/code$ gcc -g example.c -o example
zhang@zhang:~/code$ gdb example //启动gdb调试
Type "apropos word" to search for commands related to "word"
Reading symbols from gdb_example...done.
(gdb) run //运行程序
Starting program: /home/zhang/code/gdb_example
//gdb显示程序接收到一个算术异常信号,并指出出现在哪部分代码
Program received signal SIGFPE, Arithmetic exception.
0x000000000040053d in wib (no1=8, no2=8) at example.c:8
8 result = no1/diff;
(gdb) print no1 //打印出变量no1的当前值
$1 = 8
(gdb) print diff //diff当前值
$4 = 0
(gdb) continue //继续运行程序
Continuing.
Program terminated with signal SIGFPE, Arithmetic exception.
The program no longer exists.
(gdb) list //打印出错代码的后10行
3 int wib(int no1, int no2)
4 {
5 int result, diff;
6
7 diff = no1 - no2;
8 result = no1/diff;
9
10 return result;
11 }
12
(gdb) list main //打印main函数开始前后的代码
9
10 return result;
11 }
12
13 int main(int argc, char *argv[])
14 {
15 int value, div, result, total;
16 int i;
17
18 value = 10;
(gdb) //此处只需要按一下Enter键
19 div = 6;
20 total = 0;
21
22 for (i=0; i<10; i++)
23 {
24 result = wib(value, div);
25 total += result;
26 div++;
27 value--;
28 }
(gdb) b 24 //在24行处设置断点
Breakpoint 1 at 0x400575: file gdb_example.c, line 24.
(gdb) info b //查看断点具体信息
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000400575 in main at example.c:24
(gdb) run //继续运行,程序会在1号断点处暂停
Starting program: /home/zhang/code/example
Breakpoint 1, main (argc=1, argv=0x7fffffffde38) at example.c:24
24 result = wib(value, div);
(gdb) next //单步执行,下一条指令
25 total += result;
(gdb)
26 div++;
(gdb)
27 value--;
(gdb)
(gdb) watch div==value //
Hardware watchpoint 4: div==value
(gdb) continue
Continuing.
Breakpoint 1, main (argc=1, argv=0x7fffffffde38) at gdb_example.c:24
24 result = wib(value, div);
(gdb)
Continuing.
Hardware watchpoint 4: div==value
Old value = 0
New value = 1
main (argc=1, argv=0x7fffffffde38) at gdb_example.c:22
22 for (i=0; i<10; i++)
(gdb) info locals
value = 8
div = 8
result = 4
total = 6
i = 1
(gdb) info watch
Num Type Disp Enb Address What
4 hw watchpoint keep y div==value
breakpoint already hit 1 time