gdb 的用法
在Linux应用程序开发中,最常用的调试器是gdb,它可以在程序中设置断点、查看变量值、一步一步跟踪程序的执行过程。利用调试器的这些功能可以方便地找出程序中存在的非语法错误。
一、启动和退出gdb
gdb调试的对象是可执行文件,而不是程序的源代码。
如果要使一个可执行文件可以被gdb调试,那么在使用编译器gcc编译程序时需要加入-g选项。-g选项告诉gcc在编译程序时加入调试信息,这样才可以调试这个被编译的程序。
例:计算1~100的和,应该输出5050
# cat -n test.c
1 #include <stdio.h>
2
3 int get_sum(int n)
4 {
5 int sum = 0, i;
6 for (i=0;i<n;i++)
7 sum +=i;
8 return sum;
9 }
10
11 int main()
12 {
13 int i = 100, result;
14 result = get_sum(i);
15 printf("1+2+...+%d=%d\n",i,result);
16 return 0;
17 }
编译并运行该程序:
# gcc -g test.c -o test
# ./test
1+2+...+100=4950
(一)、调试一个程序的命令格式
格式:gdb <可执程序文件名>
例:#gdb test
或 #gdb
(gdb) file test
(二)、退出
(gdb) quit
二、显示和查找程序的源代码
在调试时,一般要查看程序的源代码。list 命令用于列出程序的源代码,使用格式如下:
l list 显示10行代码,若再次运行该命令则显示接下来的10行代码
l list 5,10 显示源代文件test.c中的第5行到第10行的代码,
l listtest.c:5,10 显示源文件test.c中第5行到第10行的代码,在调试含有多个源文件的程序时使用
l listget_sum 显示get_sum函数周围的代码
l listtest.c:get_sum 显示源文件test.c中get_sum函数周围的代码,在调试含多个源文件的程序时使用
l search <字符串> 用来从当前行向后查找第一个匹配的字符串
l reverse-search<字符串> 用来从当前行向前查找第一个匹配的字符串
三、执行程序和获得帮助
使用gdb test或(gdb) fieltest只是装入程序,程序并没有运行,如果要使程序运行,在gdb提示符下输入run即可。
(gdb) run
Starting program:/tmp/test
1+2+...+100=4950
Program exitednormally.
如果想要详细了解gdb某个命令的使用方法,可以使用help命令
(gdb)help list
(gdb)help all
四、设置和管理断点
在调试程序时,往往需要程序在运行到某行、某个函数或某个条件发生时暂停下来,然后查看此时程序的状态,如各个变量的值、某个表达式的值等。为此,可以设置断点(break)。断点使程序运行到某个位置时暂停下来,以便检查和分析程序。
1、 以行号设置断点
在gdb中,大部分都是使用break命令为程序设置断点。而指定断点时,最常用的是为某行设置断点。例:
(gdb) break 7
Breakpoint 1 at0x80483c0: file test.c, line 7.
然后我们输入run命令运行程序:
(gdb) run
Starting program:/tmp/test
Breakpoint 1,get_sum (n=100) at test.c:7
7 sum +=i;
可以看到,程序运行完第6行的指令后就暂停了,第7行的代码并没有执行而是被gdb的断点中断了。此时,我们可以查看各个变量和表达式的值,以了解程序的当前状态。
2、以函数名设置断点
在break命令后跟上函数名,就可以为函数设置断点。例:
(gdb) break get_sum
Breakpoint 1 at0x80483aa: file test.c, line 5.
(gdb) run
Starting program:/tmp/test
Breakpoint 1,get_sum (n=100) at test.c:5
5 int sum = 0,i;
可以看到,程序在第5行停了上来。
3、以条件表达式设置断点
程序在运行过程中,当某个条件满足时,程序在某行中断暂停执行
方法1 命令格式:break行号或函数名 if 条件
例:
(gdb) clear
Deleted breakpoint1
(gdb) break7 if i==99
Breakpoint 2 at0x80483c0: file test.c, line 7.
(gdb) run
The program beingdebugged has been started already.
Start it from thebeginning? (y or n) y
Starting program:/tmp/test
Breakpoint 2,get_sum (n=100) at test.c:7
7 sum +=i;
可以看到,运行程序后在i==99时,程序中断在第7行。
方法2 watch <条件表达式>
例:(gdb) watchi==99
No symbol"i" in current context.
(gdb) list 6
1 #include <stdio.h>
2
3 int get_sum(int n)
4 {
5 int sum = 0,i;
6 for (i=0;i<n;i++)
7 sum +=i;
8 return sum;
9 }
10
(gdb) break 6
Breakpoint 1 at0x80483b1: file test.c, line 6.
(gdb) run
Starting program:/tmp/test
Breakpoint 1,get_sum (n=100) at test.c:6
6 for (i=0;i<n;i++)
Gdb运行后,以命令watch i==99设置条件断点,但是失败了,gdb提示在当前程序的上下文中没有符号i,这是因为此时test程序没有运行,变量i还没有被定义。
为了解决这个问题,首先在第6行设置断点,然后使用run命令运行程序,程序暂停在第6行,此时第5行的语句已经被执行,所以变量i已经定义。这时就可以使用watch i==99设置断点了。
4、查看当前设置的断点
使用info breakpoints命令可以查看当前所有的中断点,例如:
(gdb) break 7
Breakpoint 1 at0x80483c0: file test.c, line 7.
(gdb) break 15 ifresult=5050
Breakpoint 2 at0x8048405: file test.c, line 15.
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x080483c0 in get_sum at test.c:7
2 breakpoint keep y 0x08048405 in main at test.c:15
stop only if result = 5050
其中:Num 表示断点的编号
Type 说明类型,类型为breakpoint是指中断
Disp 指明中断点在生效一次后是否失去作用,是则为disp,不是则为keep
Enb 说明中断点是否有效,有效则为y,无效则为n
Address列 说明中断所处的内存地址
What列 列出中断发生在哪个函数的第几行
Stop only if result==5050 说明这是一个条件中断
5、使中断失效或有效
disable <断点编号> 可以使某个断点失效,程序运行到该断点时不会停下来而是继续运行
enable <断点编号> 可以使某个断点恢复有效
例:
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x080483c0 in get_sum at test.c:7
2 breakpoint keep y 0x08048405 in main at test.c:15
stop only if result = 5050
(gdb) disable 2
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x080483c0 in get_sum at test.c:7
2 breakpoint keep n 0x08048405 in main at test.c:15
stop only if result = 5050
(gdb) enable 2
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x080483c0 in get_sum at test.c:7
2 breakpoint keep y 0x08048405 in main at test.c:15
stop only if result = 5050
6、删除断点
l clear 删除程序中所有的断点
l clear<行号> 删除此行的断点
l clear<函数名> 删除该函数的断点
l delete<断点编号> 删除指定编号的断点。如果一次要删除多个断点,各个断点编号以空格隔开
例:
(gdb) break 6
Breakpoint 1 at0x80483b1: file test.c, line 6.
(gdb) break 7
Breakpoint 2 at0x80483c0: file test.c, line 7.
(gdb) break 8 ifsum==5050
Breakpoint 3 at0x80483cf: file test.c, line 8.
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x080483b1 in get_sum at test.c:6
2 breakpoint keep y 0x080483c0 in get_sum at test.c:7
3 breakpoint keep y 0x080483cf in get_sum at test.c:8
stop only if sum == 5050
(gdb) clear 6
Deleted breakpoint1
(gdb) info breakpoints
Num Type Disp Enb Address What
2 breakpoint keep y 0x080483c0 in get_sum at test.c:7
3 breakpoint keep y 0x080483cf in get_sum at test.c:8
stop only if sum == 5050
(gdb) delete 2 3
(gdb) info breakpoints
No breakpoints orwatchpoints.
7、display <表示式> 在每次程序停在断点位置时,自动显示表达式中的内容
8、commands
>命令
>end 结束命令输入
作用:指定在程序到达断点位置时需要执行的调试器命令
五、查看和设置变量的值
当程序执行到中断点暂停执行时,需要查看变量或表达式的值,借此了解程序的执行状态
1、print命令
作用:print命令一般用来打印变量或表达式的值,也可以用来打印内存中从某个变量开始的一段
存区域的内容,还可以用来对某个变量进行赋值。
格式:
print <变量或表达式> 打印变量或表达式的当前值,gdb会用伪变量($n)来保存输出值以备用
Print <变量=值>; 对变量进行赋值
Print <表达式@要打印的值的个数n> 打印以表达式值开始的n个数
例:
(gdb) break 7
Breakpoint 4 at0x80483c0: file test.c, line 7.
(gdb) run
Starting program:/tmp/test
Breakpoint 4,get_sum (n=100) at test.c:7
7 sum += i;
(gdb)print i<n 打印出i<n表达式的值,显然这个表达式为真,因此值为1
$1 = 1
(gdb) print i
$2 = 0
(gdb) print sum
$3 = 0
(gdb) print i=200
$4 = 200
(gdb) continue
Continuing.
1+2+...+100=200
Program exitednormally.
2、whatis 命令
作用:用来显示某个变量或表达式值的数据类型
格式:whatis <变量或表达式>
例:
(gdb) break 7
Note: breakpoint 4also set at pc 0x80483c0.
Breakpoint 5 at0x80483c0: file test.c, line 7.
(gdb) run
Starting program:/tmp/test
Breakpoint 4,get_sum (n=100) at test.c:7
7 sum += i;
(gdb) whatis i
type = int
(gdb) whatis sum+0.5
type = double
3、set 命令
作用:用来给变量赋值
格式:set variable 变量=值 相当于print 变量=值
例:set variable i=200,相当于print i=200
六、控制程序的执行
当程序执行到指定的中断点,查看了变量或表达式的值后,可以让程序继续运行。也可以让程序一步一步地执行,或可以让程序地直运行下去直到下一个断点或运行完为止。
1、 continue命令
作用:让程序继续运行,直到下一个断点或运行完为止。
格式:continue
2、 kill命令
作用:用于结束当前程序的调试
例:(gdb) kill
Kill the program being debugged? (y or n) y
3、 next和step命令
作用:一次一条地执行程序代码
next和step的区别:
1)、如果遇到函数调用,next会把该函数调用当作一条语句来执行,再次输入next会执行函数调用
后的语句
2)、step则会跟踪进入函数,一次一条地执行函数内的代码,直到函数的代码执行完,才执行函数调
用后的语句
例:next语句的用法
(gdb) list 1,17
1 #include <stdio.h>
2
3 int get_sum(int n)
4 {
5 int sum = 0,i;
6 for(i=0;i<n;i++)
7 sum += i;
8 return sum;
9 }
10
11 int main()
12 {
13 int i=100,result;
14 result = get_sum(i);
15 printf("1+2+...+%d=%d\n",i,result);
16 return 0;
17 }
(gdb) break 13
Breakpoint 1 at 0x80483f0: file test.c, line13.
(gdb) run
Starting program: /tmp/test
Breakpoint 1, main () at test.c:13
13 int i=100,result;
(gdb) next
14 result = get_sum(i);
(gdb) next
15 printf("1+2+...+%d=%d\n",i,result);
(gdb) next
1+2+...+100=4950
16 return 0;
(gdb) next
17 }
(gdb) next
0x4003328b in __libc_start_main () from/lib/libc.so.6
(gdb) next
Single stepping until exit from function__libc_start_main,
which has no line number information.
Program exited normally.
例:step语句的用法
(gdb) list 1,17
1 #include <stdio.h>
2
3 int get_sum(int n)
4 {
5 int sum = 0,i;
6 for(i=0;i<n;i++)
7 sum += i;
8 return sum;
9 }
10
11 int main()
12 {
13 int i=100,result;
14 result = get_sum(i);
15 printf("1+2+...+%d=%d\n",i,result);
16 return 0;
17 }
(gdb) break 13
Breakpoint 1 at 0x80483f0: filetest.c, line 13.
(gdb) run
Starting program: /tmp/test
Breakpoint 1, main () at test.c:13
13 int i=100,result;
(gdb) step
14 result = get_sum(i);
(gdb) step
get_sum (n=100) at test.c:5
5 int sum = 0,i;
(gdb) step
6 for(i=0;i<n;i++)
(gdb) step
7 sum += i;
(gdb) step
6 for(i=0;i<n;i++)
(gdb) step
7 sum += i;
(gdb) step
6 for(i=0;i<n;i++)
(gdb) step
7 sum += i;
4、 nexti和stepi命令
作用:用来单步执行一条机器指令,注意不是单步执行一行语句。单步执行一行语句的命令是
next和step命令
通常一条语句由多条机器指令构成
注意:nexti和next类似,不会跟踪进入函数内部去执行
Stepi和step类似,跟踪进入函数执行。
第二部分 程序调试
一、常用调试技巧
1、代码检查-试运行-出错法:
先运行程序,并观察其输出结果,如果不能正常工作,修改程序后重新尝试
2、取样法:在程序中增加一些语句来获得更多关于程序内部运行情况的信息
3、受控执行法:直接检查程序的执行情况
程序调试可以分为5个阶段
l 测试:找出程序中存在的缺陷或错误
l 固化:让程序的错误可重现
l 定位:确定相关的代码行
l 纠正:修改代码纠正错误
l 验证:确定修改解决了问题
二、有漏洞的程序
如下程序是通过“冒泡排序算法”对一个类型为item的结构数组进行排序,排序方法为基于结构中的成员key以升序排列数组成员。程序用一个样本来数组来测试该程序。
程序:debug2.c
/* 1 */ typedef struct {
/* 2 */ char *data;
/* 3 */ int key;
/* 4 */ } item;
/* 5 *///给结构体赋值
/* 6 */ item array[] = {
/* 7 */ {"bill", 3},
/* 8 */ {"neil", 4},
/* 9 */ {"john", 2},
/* 10 */ {"rick", 5},
/* 11 */ {"alex", 1},
/* 12 */ };
/* 13 */
/* 14 */ sort(a,n)//默认返回值是int,函数声明,若无item *a会有警告
/* 15 */ item *a;//声明上面a是指针
/* 16 */ {
/* 17 */ int i = 0, j = 0;
/* 18 */ int s = 1;
/* 19 */
/* 20 */ for(; i < n && s != 0; i++) {
/* 21 */ s = 0;
/* 22 */ for(j = 0; j < n; j++) {
/* 23 */ if(a[j].key >a[j+1].key) {
/* 24 */ item t = a[j];
/* 25 */ a[j] = a[j+1];
/* 26 */ a[j+1] = t;
/* 27 */ s++;
/* 28 */ }
/* 29 */ }
/* 30 */ n--;
/* 31 */ }
/* 32 */ }
/* 33 */ #include <stdio.h>
/* 34 */ main()
/* 35 */ {
/* 36 */ int i;
/* 37 */ sort(array,5);
/* 38 */ for(i = 0; i < 5; i++)
/* 39 */ printf("array[%d] = {%s,%d}\n",
/* 40 */ i, array[i].data,array[i].key);
/* 41 */ }
编译:cc –o debug2 debug2.c
输出结果:
# ./debug2
array[0] = {john, 2}
array[1] = {alex, 1}
array[2] = {(null), 0}
array[3] = {bill, 3}
array[4] = {neil, 4}
我们希望的结果是:
array[0] = {alex, 1}
array[1] = {john, 2}
array[2] = {bill, 3}
array[3] = {neil, 4}
array[4] = {rick, 5}
三、程序调试
(一)、捕获数组访问错误
如果想捕获数组方面的错误,最好的方法是增加数组元素的大小,因为这样同时也增加了错误的大小。如果只是在数组的结尾之后读取一个字节,可能会看不到有错误发生,因为分配给程序的内存大小会取整数到操作系统的特定边界,一般分配的内存大小以8K为单位递增。
如果增加数组元素的大小,如在debug.c程序中将item结构中的成员data扩大为一个可以容纳4096个字符的数组,对不存在的数组进行访问时,内存地址就有可能落在分配给这个程序的内存之外的地方,因为数组的每个元素大小为4K,所以错误使用的内存将落在数组结尾之后0K~4K范围内。
1. 测试:将item结构中的成员data扩大为可容纳4096个字符的数组
/* 1 */ typedef struct {
/* 2 */ char *data; _____________________
/* 3 */ int key;
/* 4 */ } item;
2. 编译:cc –o debug3 debug3.c
3. 执行结果:#./debug3
________________________________________________________
(二)、代码检查
当程序的运行情况和预期不同时,最好重新阅读程序。
编译器可以用来帮助完成代码检查
gcc -Wall -pedantic –ansi
使用man gcc,阅读这三个参数的含义
-ansi Thisturns off certain features of GCC that are incompatible
with ISO C90 (when compiling C code)
-pedantic
Issueall the warnings demanded by strict ISO C and ISO C++;
reject all programs that use forbidden extensions, and some otherprograms that do not follow
ISOC and ISO C++.
-Wall
This enables all thewarnings about constructions that some users consider questionable and that areeasy
to avoid(or modify toprevent the warning), even in conjunction with macros.
使用gcc的三个选项重新编译debug2.c
#gcc –Wall –pedantic –ansi –odebug2 debug2.c
debug2.c:15:warning:return typedefaults to “int”
debug2.c: in function “sort”
debug2.c:32: warning:controlreaches end of non-void function
debug2.c: At top level:
debug2.c:35:warning:return typedefaults to “int”
debug2.c:in function “main”
debug2.c:41:warning:controlreaches end of non-void function
(三)、取样法
在程序中添加一些代码来收集更多与程序运行相关的信息的方法。
取样法的常见做法是,在程序中添加printf函数调用以打印出变量在程序运行的不同阶段的值,但需要注意,无论何时程序发生了改动,这一过程都将带来更多的编辑和编译次数,而且,在程序错误被修复后,我们还要把这些额外的代码删除掉。
程序调试时最常用的取样方法有:
1、用C语言的预处理器有选择地包括取样代码,这样只需要重新编译程序就可以包含或去除调试代码。
实现方法: #ifdef DEBUG
Printf(“variable x has value =%d\n”,x);
#endif
在编译程序时加上编译器标志-DDEBUG。
如果加上这个标志,就定义了DEBUG符号,从而可以在程序中包含额外的调试代码,如果未
加上该标志,这些调试将被删除。
还可以用数值调试宏来完成更复杂的调试应用,例:
#define BASIC_DEBUG 1
#define EXTRA_DEBUG 2
#define SUPER_DEBUG 4
#if (DEBUG & EXTRA_DEBUG)
Prinf ();
#endif
这样,当编译器标志-DDEBUG=5,将启用BASIC_DEBUG和SUPER_DEBUG,但不包括EXTRA_DEBUG。标志-DDEBUG=0将禁用所有的调试信息。
C语言预处理器定义的一些宏可以帮助调试:
C语言预处理器定义的宏
宏 |
说明 |
__LINE__ |
代表当前行号的十进制常数 |
__FILE__ |
代表当前文件名的字符串 |
__DATE__ |
mmm dd yyyy格式的字符串,代表当前日期 |
__TIME__ |
hh:mm:ss格式的字符串,代表当前时间 |
例:cinfo.c程序
#include <stdio.h>
#include <stdlib.h>
int main()
{
#ifdef DEBUG
printf("Compiled: " __DATE__ " at " __TIME__"\n");
printf("This is line %d of file %s\n", __LINE__, __FILE__);
#endif
printf("hello world\n");
exit(0);
}
编译:cc –o cinfo –DDEBUG cinfo.c
执行# ./cinfo
Compiled: Jul 23 2012 at 07:45:37
This is line 8 of file cinfo.c
hello world
2、使用printf函数帮助调试
在程序中增加一个用为调试标志的全局变量,这使得用户要吧在命令行上通过-d选项切换是否启用调试模式,即使程序已经发布了,仍然可以这么做,访方法同时还会在程序中增加一个用于记录调试信息的函数,例:
if(debug){
sprint(msg, …);
write_debug(msg);
}
应该将调试信息输出到标准错误输出stderr。
当然这样做也有一个明显的不足,就是程序长度会有所增加。程序的长度可能会增加20%~30%。
(四)、程序的受权执行---使用gdb进行调试
使用gdb运行debug3.c程序
编译:#cc –g –o debug3 debug3.c
调试:#gdb debug3
(gdb) run
Starting program:/root/course/chapter10/debug3
Program receivedsignal SIGSEGV, Segmentation fault.
0x0804845f in sort(a=0x80497e0, n=5) at debug3.c:23
23 /* 23 */ if(a[j].key >a[j+1].key) {
可以看到程序停在了debug3.c的第23行
1、栈跟踪---backtrace(缩写为bt)
(gdb) backtrace
#0 0x0804845f in sort (a=0x80497e0, n=5) atdebug3.c:23
#1 0x08048573 in main () at debug3.c:37
可以看出sort函数由同一个文件中的main函数在第37行调用的
2、 检查变量
(gdb) print j
$1 = 4 将值4赋给了伪变量$1,后续的命令将输出的结果依次顽抗到保存到$2, $3等中
局部变量j的值是4意味着程序尝试执行的这样的一条指令:
if(a[4].key > a[4+1].key)
我们传递给sort函数的数组array只有5个元素,它们的下标从0~4,说明这条语句读的是一个不存在的数组元素array[5]。循环计数器变量j取了一个错误的值。
打印数组元素中的值
(gdb) print a[3]
$3 = {data ="alex", '\0' <repeats 4091 times>, key = 1}
3、 更改源代码----更改后的源程序为debug4.c
将第22行代码由for(j=0; j<n; j++)
更改为:for(j=0; j<n-1; j++)
重新编译debug4.c #cc –g –o debug4 debug4.c,编程完成后重新执行:
array[0] = {john, 2}
array[1] = {alex, 1}
array[2] = {bill, 3}
array[3] = {neil, 4}
array[4] = {rick, 5} 程序运行仍然不正确
四、调试程序debug4
在sort()函数中有两个循环。外层循环针对每个数组元素执行一次,它的循环计数变量是i。内层循环的作用是交换相邻的两个元素。总的效果是让比较小的元素像“气泡”一样“冒”到数组的顶部。外层循环每执行一次,数组中的最大的元素就会“下沉”到数组的底部。我们可以通过在外层循环中停止此程序的运行并检查数组的状态来核实这一点
1. 设置断点
#gdb debug4
(gdb) break 22
Breakpoint 1 at0x804841e: file debug4.c, line 22.
2.设置到达断点时显示的内容
(gdb) display array[0]@5
3. 设置程序运行到达断点时,显示要查看的数据,然后继续执行
(gdb) commands
Type commands forwhen breakpoint 1 is hit, one per line.
End with a linesaying just "end".
>cont
>end
4、设置完成后运行程序
(gdb) run
Starting program:/root/course/chapter10/debug4
Breakpoint 1, sort (a=0x8049800,n=5) at debug4.c:22
22 /* 22 */ for(j = 0; j < n-1; j++) {
1: array[0]@5 = {{data ="bill", '\0' <repeats 4091 times>, key = 3}, {
data = "neil", '\0' <repeats 4091 times>, key = 4}, {
data = "john", '\0' <repeats 4091 times>, key = 2}, {
data = "rick", '\0' <repeats 4091 times>, key = 5}, {
data = "alex", '\0' <repeats 4091 times>, key = 1}}
Breakpoint 1, sort (a=0x8049800,n=4) at debug4.c:22
22 /* 22 */ for(j = 0; j < n-1; j++) {
1: array[0]@5 = {{data ="bill", '\0' <repeats 4091 times>, key = 3}, {
data = "john", '\0' <repeats 4091 times>, key = 2}, {
data = "neil", '\0' <repeats 4091 times>, key = 4}, {
data = "alex", '\0' <repeats 4091 times>, key = 1}, {
data = "rick", '\0' <repeats 4091 times>, key = 5}}
Breakpoint 1, sort (a=0x8049800,n=3) at debug4.c:22
22 /* 22 */ for(j = 0; j < n-1; j++) {
1: array[0]@5 = {{data ="john", '\0' <repeats 4091 times>, key = 2}, {
data = "bill", '\0' <repeats 4091 times>, key = 3}, {
data = "alex", '\0' <repeats 4091 times>, key = 1}, {
data = "neil", '\0' <repeats 4091 times>, key = 4}, {
data = "rick", '\0' <repeats 4091 times>, key = 5}}
array[0] = {john, 2}
array[1] = {alex, 1}
array[2] = {bill, 3}
array[3] = {neil, 4}
array[4] = {rick, 5}
Program exitedwith code 025. ---不常见的通出码,因为程序本身未调用exit函数,并且也没有从main函数返回一个值,该退出码没有实际意义。
好象
5、用调试器打补丁
用调试器打补丁:通过将断点的设置与相应的操作结合起来来尝试修改程序,而不需要改变程序的源
码和重新编译。
针对debug4,需要在程序的第30行中断程序,增加变量n的值,以使程序执行到第30行时,n的值并未发生变化
重新开始执行这个程序,首先必须删除刚才设置的断点和display命令的内容。可以用info查看曾经设置过的断点及display命令的内容
(gdb) info display
Auto-display expressions now ineffect:
Num Enb Expression
1: y array[0]@5
(gdb) info break
Num Type Disp EnbAddress What
1 breakpoint keep y 0x08048417 in sort at debug4.c:21
breakpoint already hit 3 times
cont
禁用上述断点与display,可以在今后必要的时候重新启用这些保留的设置
(gdb) disable break 1
(gdb) disable display 1
(gdb) break 30
Breakpoint 2 at0x8048535: file debug4.c, line 30.
(gdb) commands 2
Type commands forwhen breakpoint 2 is hit, one per line.
End with a linesaying just "end".
>set variable n=n+1
>cont
>end
(gdb) run
Starting program:/root/course/chapter10/debug4
Breakpoint 2, sort (a=0x8049800, n=5) atdebug4.c:30
30 /* 30 */ n--;
Breakpoint 2, sort (a=0x8049800, n=5) atdebug4.c:30
30 /* 30 */ n--;
Breakpoint 2, sort (a=0x8049800, n=5) atdebug4.c:30
30 /* 30 */ n--;
Breakpoint 2, sort (a=0x8049800, n=5) atdebug4.c:30
30 /* 30 */ n--;
Breakpoint 2, sort (a=0x8049800, n=5) atdebug4.c:30
30 /* 30 */ n--;
array[0] = {alex, 1}
array[1] = {john, 2}
array[2] = {bill, 3}
array[3] = {neil, 4}
array[4] = {rick, 5}
Program exited with code 025.
五、断言
1、断言
在程序的编写过程中,有时系统的内部逻辑需要被确认没有错误。X/Open提供了assert宏,它的作用是测试某个假设是否成立,如果不成立就停止程序的运行
#include <assert.h>
Void assert (int expression);
assert宏对表达式进行求值,如果结果非零,它就往标准错误写一些诊断信息,然后调用abort函数结束程序的运行。
例:程序assert.c定义了一个函数,它的参数必须是一个正数,它用断来来保护程序不受非法参数的影响
1 #include <stdio.h>
2 #include <math.h>
3 #include <assert.h>
4 #include <stdlib.h>
5
6 double my_sqrt(double x)
7 {
8 assert(x >= 0.0);
9 return sqrt(x);
10 }
11
12 int main()
13 {
14 printf("sqrt +2 =%g\n", my_sqrt(2.0));
15 printf("sqrt -2 =%g\n", my_sqrt(-2.0));
16 exit(0);
17 }
编译: # cc -o assert assert.c-lm
执行: # ./assert
sqrt +2 = 1.41421
assert: assert.c:8: my_sqrt:Assertion `x >= 0.0' failed.
Aborted
试图用一个负数来调用函数my_sqrt时,断言失败了,aasert宏给出了发生断言冲突的文件名和行号,还给出了失败的条件。程序被一个abort中断陷阱终止了运行。这就是assert调用abort的结果
2. 使用-DNDEBUG选项编译
使用-DNDEBUG选项重新编译这个程序,断言功能将被排除在编译结果之外。当在my_sqrt()中调用sqrt函数时,得到的将是一个NaN(不是一个数字),表明一个无效结果
编译: # cc -o assert -DNDEBUG assert.c-lm
执行: # ./assert
sqrt +2 = 1.41421
sqrt -2 = nan