程序调试的基本思想是“分析现象->假设错误原因->产生新的现象去验证假设”这样一个循环过程,根据现象如何假设错误原因,以及如何设计新的现象去验证假设,需要非常严密的分析和思考。程序中除了一目了然的Bug之外都需要一定的调试手段来分析到底错在哪,到目前为止自己使用过的调试手段只有一种:
根据程序执行时的出错现象假设错误原因,然后在代码中适当的位置插入printf(驱动使用printk函数),执行程序并分析打印结果,如果结果和心里预期的一样,就基本上证明了自己假设的错误原因,就可以动手修正Bug了,如果结果和预期的不一样,就根据结果做进一步的假设和分析。
printf这种方法对于小程序简单有效,面对一些较大的程序时难免有些力不从心。GDB是Unix/Linux下非常强大的程序调试工具,最近对其基本使用方法进行学习,做一下总结。
1、gdb工具基本使用
#include <stdio.h>
int add(int low,int high)
{
int i,sum;
for(i=low;i<=high;i++)
sum = sum + i;
return sum;
}
int main(int argc,char **argv)
{
int result[100];
result[0] = add(1,10);
result[1] = add(1,100);
printf("result[0] is %d\nresult[1] is %d \n",result[0],result[1]);
return 0;
}
将这段程序利用gcc gdb.c -o gdb
进行编译,运行结果如下:
[peterwang@localhost gdb]$ gcc gdb.c -o gdb
[peterwang@localhost gdb]$ ./gdb
result[0] is 11413250
result[1] is 11418300
[peterwang@localhost gdb]$
很明显打印出来的结果是错误的,程序完成的功能是计算1加到10和1加到100的和,打印出来的结果应该是55和5050(这个例子只是为了展示gdb的调试步骤,程序本身的问题很轻松就可以发现),下面利用gdb对程序进行调试。
利用gdb调试,需要在gcc编译过程中加上-g
选项,这样编译生成的可执行文件才可以利用gdb进行源码调试。
[peterwang@localhost gdb]$ gdb gdb
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-83.el6)
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/>...
Reading symbols from /home/peterwang/TestProgram/gdb/gdb...done.
(gdb)
-g
选项的作用是在可执行文件中加入源代码的信息,比如可执行文件中第几条机器指令对应源代码的第几行,但并不是把整个源文件嵌入到可执行文件中,所以在调试时必须保证gdb能找到源文件。如果把当前的gdb.c改名为g.c或者将gdb.c移动到其他地方,则gdb无法进行调试。
[peterwang@localhost gdb]$ mv gdb.c g.c
[peterwang@localhost gdb]$ gdb gdb
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-83.el6)
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/>...
Reading symbols from /home/peterwang/TestProgram/gdb/gdb...done.
(gdb) start
Temporary breakpoint 1 at 0x80483f5: file gdb.c, line 18.
Starting program: /home/peterwang/TestProgram/gdb/gdb
Temporary breakpoint 1, main (argc=1, argv=0xbffff324) at gdb.c:18
18 gdb.c: No such file or directory.
in gdb.c
(gdb)
gdb提供一个类似Shell的命令行环境,上面的(gdb)就是提示符,在这个提示符下输入help
可以查看命令的类别:
(gdb) help
List of classes of commands:
aliases -- Aliases of other commands
breakpoints -- Making program stop at certain points
data -- Examining data
files -- Specifying and examining files
internals -- Maintenance commands
obscure -- Obscure features
running -- Running the program
stack -- Examining the stack
status -- Status inquiries
support -- Support facilities
tracepoints -- Tracing of program execution without stopping the program
user-defined -- User-defined commands
Type "help" followed by a class name for a list of commands in that class.
Type "help all" for the list of all commands.
Type "help" followed by command name for full documentation.
Type "apropos word" to search for commands related to "word".
Command name abbreviations are allowed if unambiguous.
(gdb)
可以看出来gdb的命令是分类的,利用help 类别(比如 help data)
可以进一步查看data类别下的命令帮助。
我们可以利用list(简写为l)
命令列出当前程序的代码,默认列出10行,如果想列出更多,可以再次输入list,也可以直接敲击回车键,gdb有个很好用的功能,直接敲击回车键表示执行上一条命令。
(gdb) list
9 {
10 int i,sum;
11 for(i=low;i<=high;i++)
12 sum = sum + i;
13 return sum;
14 }
15 int main(int argc,char **argv)
16 {
17 int result[100];
18 result[0] = add(1,10);
(gdb)
19 result[1] = add(1,100);
20 printf("result[0] is %d\nresult[1] is %d \n",result[0],result[1]);
21 return 0;
22 }
(gdb)
gdb调试工具需要首先用start
命令开始执行程序,
(gdb) start
Temporary breakpoint 1 at 0x80483f5: file gdb.c, line 18.
Starting program: /home/peterwang/TestProgram/gdb/gdb
Temporary breakpoint 1, main (argc=1, argv=0xbffff324) at gdb.c:18
18 result[0] = add(1,10);
(gdb)
gdb停在main函数中变量定义之后的第一条语句处等待我们发命令, gdb列出的这条语句是即将执行
的下一条语句。退出gdb调试环境可以利用quit
命令
(gdb) quit
A debugging session is active.
Inferior 1 [process 3878] will be killed.
Quit anyway? (y or n) y
[peterwang@localhost gdb]$
2、单步执行和跟踪函数调用
根据上一小节的介绍,我们已经知道如何进入gdb调试工具以及基本知识,本小节将通过gdb调试工具找出上一小节函数输出错误的原因。
利用start
命令开始gdb调试,我们可以看到程序停在了main函数的result[0] = add(1,10);
这一行:
[peterwang@localhost gdb]$ gdb gdb
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-83.el6)
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/>...
Reading symbols from /home/peterwang/TestProgram/gdb/gdb...done.
(gdb) start
Temporary breakpoint 1 at 0x80483f5: file gdb.c, line 18.
Starting program: /home/peterwang/TestProgram/gdb/gdb
Temporary breakpoint 1, main (argc=1, argv=0xbffff324) at gdb.c:18
18 result[0] = add(1,10);
下面我们利用next(简写为n)
命令控制程序向下执行:
(gdb) n
19 result[1] = add(1,100);
(gdb)
20 printf("result[0] is %d\nresult[1] is %d \n",result[0],result[1]);
(gdb)
result[0] is 1218306
result[1] is 1223356
21 return 0;
(gdb)
22 }
(gdb)
__libc_start_main (main=0x80483e9 <main>, argc=1, ubp_av=0xbffff324, init=0x8048460 <__libc_csu_init>, fini=0x8048450 <__libc_csu_fini>, rtld_fini=0x11f4c0 <_dl_fini>, stack_end=0xbffff31c) at libc-start.c:258
258 exit (result);
(gdb)
Program exited normally.
虽然程序正常打印,并且显示正常退出,可以并没有找到程序的问题所在。重新运行start
命令,利用step(简写s)
跳入add(1,10)函数中进行调试,在add()函数中利用backtrace(简写为bt
命令查看函数调用的栈帧:
(gdb) start
Temporary breakpoint 2 at 0x80483f5: file gdb.c, line 18.
Starting program: /home/peterwang/TestProgram/gdb/gdb
Temporary breakpoint 2, main (argc=1, argv=0xbffff324) at gdb.c:18
18 result[0] = add(1,10);
(gdb) s
add (low=1, high=10) at gdb.c:11
11 for(i=low;i<=high;i++)
(gdb) bt
#0 add (low=1, high=10) at gdb.c:11
#1 0x08048409 in main (argc=1, argv=0xbffff324) at gdb.c:18
(gdb)
可以看出函数add()被main()函数调用,main传进来的参数是low=1,high=10。main函数的栈帧编号为1, add_range的栈帧编号为0。现在可以利用info(简写为i)
查看add()函数中局部变量的值:
(gdb) i locals
i = 1224252
sum = 1218251
可以看到当前add()函数中变量i
和变量sum
为很大的数,看到这里基本就可以猜出程序错误是由于i
和sum
未进行初始化导致。
如果想查看main函数当前局部变量的值也可以做到,先用frame(简写为f)
选择1号栈帧然后再查看局部变量:
(gdb) frame 1
#1 0x08048409 in main (argc=1, argv=0xbffff324) at gdb.c:18
18 result[0] = add(1,10);
(gdb) i locals
result = {136, 1241028, 0, 1114836, 1114932, 7, 0, 1114112, 0, 1152850, 1325276, 134513214, -1207961936, -1208025086, 1177933, 134513164, 1244128, 1241028, 1264592, 1, -1073745444, 1154825, 61145, 1241028, -1073745288, 1128753,
1242396, 1243824, 0, 0, 0, 0, 0, 0, 0, 0, -1207961984, 0, 1245544, 1265112, 1223968, -1073745500, 1300312, 14, 129100401, -1207961984, -163754450, 0, 3, 1243384, 0, 0, 1, 2200, -1207961936, -1207962672, 134513196, 1302392,
134513100, 1, 1241028, -1073745200, 1243824, -1073745244, 1155338, -1073745260, 134513100, -1073745272, 1243732, 0, -1207961936, 1, 0, 1, 1243384, 1, 13238272, 0, 15773951, 1, 194, 16372, 2892252, 0, -1073745200, 134518456,
-1073745336, 134513344, 6291456, 134518456, -1073745288, 134513785, 2892252, 134513196, 2903264, 2899956, 134513760, 134513424, 134513771, 2899956}
由于result[]数组也没有进行初始化操作,数组中的数据都是杂乱无章的。继续利用s
或者n
命令往下走,然后用print(简写为p)打印出变量sum的值:
(gdb)
12 sum = sum + i;
(gdb) p sum
$1 = 1218251
(gdb)
这里sum
值打印已经出错了,可以利用finish
命令让程序一直运行到从当前函数返回为止或者利用continue(简写为c)
命令运行到程序结束,然后修改源代码。
(gdb) finish
Run till exit from #0 add (low=1, high=10) at gdb.c:12
0x08048409 in main (argc=1, argv=0xbffff324) at gdb.c:18
18 result[0] = add(1,10);
Value returned is $2 = 1218306
(gdb) c
Continuing.
result[0] is 1218306
result[1] is 1223356
Program exited normally
也可以利用set var 变量=XX
命令进行变量赋值,运行调试程序,验证思路正确性。
(gdb) set var sum=0
(gdb) s
11 for(i=low;i<=high;i++)
(gdb)
12 sum = sum + i;
(gdb) p sum
$10 = 1
(gdb) finish
Run till exit from #0 add (low=1, high=10) at gdb.c:12
0x08048409 in main (argc=1, argv=0xbffff324) at gdb.c:18
18 result[0] = add(1,10);
Value returned is $11 = 55
从运行结果来看,当把sum
变量赋值为0后,add()函数结束时sum
的值为55,符合预期的结果。
3、断点调试
断点调试是指自己在程序的某一行设置一个断点,调试时,程序运行到这一行就会停住,然后可以一步一步往下调试。断点调试是一种非常有效的调试方法。本小节将通过例子进行断点调试的学习。
#include <stdio.h>
int main(void)
{
int sum = 0, i = 0;
char input[5];
while (1) {
scanf("%s", input);
for (i = 0; input[i] != '\0'; i++)
sum = sum*10 + input[i] - '0'; //字符型'2'-'0'的ASCII码正好是整形2
printf("input=%d\n", sum);
}
return 0;
}
程序运行结果如下:
[peterwang@localhost gdb]$ ./gdb_breakpoint
123
input=123
123
input=123123
^C
[peterwang@localhost gdb]$
可以看到程序运行第一次输入结果是正确的,第二次输入123,却输出123123(例子只是单纯的进行gdb测试,高手勿喷),利用gcc -g gdb_breakpoint.c -o gdb_breakpoint
编译该代码,运行./gdb gdb_breadpoint
并执行start
命令
[peterwang@localhost gdb]$ gdb gdb_breakpoint
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-83.el6)
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/>...
Reading symbols from /home/peterwang/TestProgram/gdb/gdb_breakpoint...done.
(gdb) start
Temporary breakpoint 1 at 0x804841d: file gdb_breakpoint.c, line 10.
Starting program: /home/peterwang/TestProgram/gdb/gdb_breakpoint
Temporary breakpoint 1, main () at gdb_breakpoint.c:10
10 int sum = 0, i = 0;
(gdb)
执行next(n)
命令,根据之前的经验,需要重点怀疑sum
和input数组
的值的变化,可以利用display sum/input
命令时刻观察值的变化。通过undisplay 编号
进行取消跟踪显示。
(gdb) display sum
1: sum = 0
(gdb) display input
2: input = "\b`\203\004\b"
(gdb) n
123
14 for (i = 0; input[i] != '\0'; i++)
2: input = "123\000\b"
1: sum = 0
(gdb)
15 sum = sum*10 + input[i] - '0';
2: input = "123\000\b"
1: sum = 0
(gdb)
14 for (i = 0; input[i] != '\0'; i++)
2: input = "123\000\b"
1: sum = 1
(gdb)
15 sum = sum*10 + input[i] - '0';
2: input = "123\000\b"
1: sum = 1
(gdb)
14 for (i = 0; input[i] != '\0'; i++)
2: input = "123\000\b"
1: sum = 12
(gdb)
15 sum = sum*10 + input[i] - '0';
2: input = "123\000\b"
1: sum = 12
(gdb)
14 for (i = 0; input[i] != '\0'; i++)
2: input = "123\000\b"
1: sum = 123
(gdb)
16 printf("input=%d\n", sum);
2: input = "123\000\b"
1: sum = 123
(gdb)
input=123
18 }
2: input = "123\000\b"
1: sum = 123
(gdb)
123
14 for (i = 0; input[i] != '\0'; i++)
2: input = "123\000\b"
1: sum = 123
(gdb)
可以看出第14步第二次输入数据的时候sum=123
这显然不符合程序的本意,可以推断出问题出在sum
变量在每次while(1)
的时候没有进行赋0操作。问题是找出来了,可是可以看出这样调试效率不是很高,我们可以利用break命令(简写为b)
设置断点。break
命令可以跟行数也可以跟函数名设置断点。利用info命令(简写为i) breakpoints
查看当前的断点信息,利用delete breakpoints 断点编号(info 出来的编号)
进行断点删除,delete breakpoints
命令是删除所有断点,如果一个断点我们暂时不想使用可以利用disable breakpoints 断点编号
直接禁用,利用enbale 断点编号
启用断点。
(gdb) l
5 > Created Time: Tue 12 Apr 2016 05:21:15 PM CST
6 ************************************************************************/
7 #include <stdio.h>
8 int main(void)
9 {
10 int sum = 0, i = 0;
11 char input[5];
12 while (1) {
13 printf("请输入一个6位以下的数字:\n");
14 scanf("%s", input);
(gdb)
15 for (i = 0; input[i] != '\0'; i++)
16 sum = sum*10 + input[i] - '0';
17 printf("input=%d\n", sum);
18
19 }
20 return 0;
21 }
(gdb) b 15
Breakpoint 2 at 0x804847e: file gdb_breakpoint.c, line 15.
(gdb) c
Continuing.
请输入一个6位以下的数字:
123
Breakpoint 2, main () at gdb_breakpoint.c:15
15 for (i = 0; input[i] != '\0'; i++)
2: input = "123\000\b"
1: sum = 0
(gdb) c
Continuing.
input=123
请输入一个6位以下的数字:
123
Breakpoint 2, main () at gdb_breakpoint.c:15
15 for (i = 0; input[i] != '\0'; i++)
2: input = "123\000\b"
1: sum = 123
(gdb)
(gdb) info breakpoints
Num Type Disp Enb Address What
2 breakpoint keep y 0x0804847e in main at gdb_breakpoint.c:15
breakpoint already hit 2 times
(gdb) disable breakpoints 2
(gdb) enable 2
(gdb) delete breakpoints
Delete all breakpoints? (y or n) y
(gdb)
我们在程序的15行设置断点,利用continue(c)
命令执行程序,直到断点处停下,可以看到第二次输入123的时候,sum
的值为123,按照程序逻辑这时候sum
应该为0,这里也可以判断出,while(1)
中缺少了sum
的赋0操作。
break
命令非常灵活,我们还可以利用条件语句设置断点,比如break 15 if sum!=0
仅当sum!=0的时候在15行设置断点,然后利用run(r)
命令从头进行调试。
gdb) b 15 if sum!=0
Breakpoint 2 at 0x804847e: file gdb_breakpoint.c, line 15.
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/peterwang/TestProgram/gdb/gdb_breakpoint
请输入一个6位以下的数字:
123
input=123
请输入一个6位以下的数字:
123
Breakpoint 2, main () at gdb_breakpoint.c:15
15 for (i = 0; input[i] != '\0'; i++)
2: input = "123\000\b"
1: sum = 123
3、观察点调试
根据上一小节,我们知道在while(1)
中加入sum=0
程序可以得到正确的结果,可是如果scanf
输入数组越界会是什么情况呢?
[peterwang@localhost gdb]$ ./gdb_breakpoint
请输入一个5位以下的数字:
123
input=123
请输入一个5位以下的数字:
12
input=12
请输入一个5位以下的数字:
12345
input=12345090
请输入一个5位以下的数字:
可以看到输出了一个诡异的结果,进入gdb
调试,利用x/7 input
命令查看input
数组值的变化情况,x
代表打印指定存储单元的内容,7
代表打印7组。
(gdb) start
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Temporary breakpoint 2 at 0x804844d: file gdb_breakpoint.c, line 10.
Starting program: /home/peterwang/TestProgram/gdb/gdb_breakpoint
Temporary breakpoint 2, main () at gdb_breakpoint.c:10
10 int sum = 0, i = 0;
(gdb) n
13 sum = 0;
(gdb)
14 printf("请输入一个5位以下的数字:\n");
(gdb)
请输入一个5位以下的数字:
15 scanf("%s", input);
(gdb)
12345
16 for (i = 0; input[i] != '\0'; i++)
(gdb) p input
$2 = "12345"
(gdb) x/7 input
0xbffff253: 0x31 0x32 0x33 0x34 0x35 0x00 0x00
(gdb)
我们知道断点是当程序执行到某一代码行时中断,而观察点是当程序访问某个存储单
元时中断,如果我们不知道某个存储单元是在哪里被改动的,这时候观察点尤其有
用。用watch
命令设置观察点,跟踪input[4]后面那个字节(可以用input[5]表示,虽然这是访问越界),利用 info(i) watchpoints
查询当前观察点。
(gdb) start
Temporary breakpoint 1 at 0x804844d: file gdb_breakpoint.c, line 10.
Starting program: /home/peterwang/TestProgram/gdb/gdb_breakpoint
Temporary breakpoint 1, main () at gdb_breakpoint.c:10
10 int sum = 0, i = 0;
(gdb) n
13 sum = 0;
(gdb)
14 printf("请输入一个5位以下的数字:\n");
(gdb)
请输入一个5位以下的数字:
15 scanf("%s", input);
(gdb)
1234
16 for (i = 0; input[i] != '\0'; i++)
(gdb) watch input[4]
Hardware watchpoint 2: input[4]
(gdb) c
Continuing.
input=1234
请输入一个5位以下的数字:
12345
Hardware watchpoint 2: input[4]
Old value = 0 '\000'
New value = 53 '5'
0x00183f74 in _IO_vfscanf_internal (s=0x35, format=0x400 <Address 0x400 out of bounds>, argptr=0x4850000 <Address 0x4850000 out of bounds>, errp=0x8) at vfscanf.c:1031
1031 *str++ = c;
(gdb)
4、段错误调试
#include <stdio.h>
int main(void)
{
int man = 0;
scanf("%d", man);
return 0;
}
利用命令gcc -g gdb_segdefault.c -o gdb_segdefault
编译这段程序,运行出现段错误提示。
[peterwang@localhost gdb]$ ./gdb_segdefault
123
Segmentation fault (core dumped)
[peterwang@localhost gdb]$
进入gdb
调试
(gdb) start
Temporary breakpoint 1 at 0x80483ed: file gdb_segdefault.c, line 10.
Starting program: /home/peterwang/TestProgram/gdb/gdb_segdefault
Temporary breakpoint 1, main () at gdb_segdefault.c:10
10 int man = 0;
(gdb) n
11 scanf("%d", man);
(gdb)
123
Program received signal SIGSEGV, Segmentation fault.
0x00182aa5 in _IO_vfscanf_internal (s=0x333231, format=0xffffffff <Address 0xffffffff out of bounds>, argptr=0x0, errp=0xb) at vfscanf.c:1772
1772 *ARG (unsigned int *) = (unsigned int) num.ul;
(gdb) bt
#0 0x00182aa5 in _IO_vfscanf_internal (s=0x333231, format=0xffffffff <Address 0xffffffff out of bounds>, argptr=0x0, errp=0xb) at vfscanf.c:1772
#1 0x0018f939 in __isoc99_scanf (format=0x80484e4 "%d") at isoc99_scanf.c:37
#2 0x0804840a in main () at gdb_segdefault.c:11
(gdb)
可以看到,gdb
提示Program received signal SIGSEGV, Segmentation fault.
错误,利用bt
命令查看_IO_vfscanf_internal
是被__isoc99_scanf
调用,可以确定main
函数中的scanf
出现问题,可以检查出scanf
函数中忘记加入&
导致段错误。
还有一种经常发生的段错误,算是一条规律,如果某个函数的局部变量发生访问越界
,有可能并不立即产生段错误,而是在函数返回时产生段错误
。
附录(常用的gdb命令)
一.基本命令
1)进入GDB #gdb test
test是要调试的程序,由gcc test.c -g -o test生成。进入后提示符变为(gdb)
2)查看源码 (gdb) l
源码会进行行号提示。如果需要查看在其他文件中定义的函数,在l后加上函数名即可定位到这个函数的定义及查看附近的其他源码。或者:使用断点或单步运行,到某个函数处使用s进入这个函数。
3)启动gdb,并且分屏显示源代码 gdb -tui
这样,使用了'-tui'选项,启动可以直接将屏幕分成两个部分,上面显示源代码,比用list方便多了。这时候使用上下方向键可以查看源代码,想要命令行使用上下键就用[Ctrl]n和[Ctrl]p
4)设置断点 (gdb) b 6
这样会在运行到源码第6行时停止,可以查看变量的值、堆栈情况等;这个行号是gdb的行号。
5)查看断点处情况 (gdb) info b
可以键入"info b"来查看断点处情况,可以设置多个断点;
6)运行代码 (gdb) r
7)显示变量值 (gdb) p n
在程序暂停时,键入"p 变量名"(print)即可;
GDB在显示变量值时都会在对应值之前加上"$N"标记,它是当前变量值的引用标记,以后若想再次引用此变量,就可以直接写作"$N",而无需写冗长的变量名;
8)观察变量 (gdb) watch n
在某一循环处,往往希望能够观察一个变量的变化情况,这时就可以键入命令"watch"来观察变量的变化情况,GDB在"n"设置了观察点;
9)单步运行(不进入函数) (gdb) n
10)单步运行(进入函数) (gdb) s
11)程序继续运行 (gdb) c
使程序继续往下运行,直到再次遇到断点或程序结束;
12)退出GDB (gdb) q
二.断点调试
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 只设置一次捕捉点,当程序停住以后,应点被自动删除
三.数据命令
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:浮点数。
四.调试运行环境相关命令
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 在当前位置执行所要运行的函数
五.堆栈相关命令
backtrace/bt bt 用来打印栈帧指针,也可以在该命令后加上要打印的栈帧指针的个数,查看程序执行到此时,是经过哪些函数呼叫的程序,程序“调用堆栈”是当前函数之前的所有已调用函数的列表(包括当前函数)。每个函数及其变量都被分配了一个“帧”,最近调用的函数在 0 号帧中(“底部”帧)
frame frame 1 用于打印指定栈帧
info reg info reg 查看寄存器使用情况
info stack info stack 查看堆栈使用情况
up/down up/down 跳到上一层/下一层函数
六.跳转执行
jump 指定下一条语句的运行点。可以是文件的行号,可以是file:line格式,可以是+num这种偏移量格式。表式着下一条运行语句从哪里开始。相当于改变了PC寄存器内容,堆栈内容并没有改变,跨函数跳转容易发生错误。
七.信号命令
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不会告诉你收到信号的信息
info signals
info handle
可以查看哪些信号被GDB处理,并且可以看到缺省的处理方式
single命令和shell的kill命令不同,系统的kill命令发信号给被调试程序时,是由GDB截获的,而single命令所发出一信号则是直接发给被调试程序的。
8.运行Shell命令
如(gdb)shell ls来运行ls。
九.更多程序运行选项和调试
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停住时,所有的运行线程都会被停住。这方便查看运行程序的总体情况。而在你恢复程序运行时,所有的线程也会被恢复运行。
参考文档
《Linux C编程一站式学习》
http://www.jb51.net/article/36393.htm
http://blog.chinaunix.net/uid-9525959-id-2001805.html
网友评论