简单的问题调试(gdb)
对于编写代码来说, 这个过程出现什么错误是开发者难以预料的‘突发’事件, 但这又是在开发者的预期之内, 毕竟谁都不敢保证经过手的代码没一点‘问题’。 既然遇到问题是一定的,那么解决问题就显得尤为重要了,它决定着项目的周期当然也包括其他的些许东西。 一个好的开发者在查找问题使用工具方面也应该是优秀的, 所以在这里为了我们能向成为一个合格的创造者迈进特意总结了些关于调试的资料。下面是将要是测试的源文件
源文件链接出处:https://bbs.bccn.net/thread-387308-1-1.html
1、调试前的准备工作
在编写好源文件之后,接下来是编译连接获得可执行文件, 一般如果不需要进行代码的调试工作,编译工作只需要执行如下简单命令(当然也可以包含一些优化的命令):
$ gcc bccn.c -o bccn
上述命令过后就会在当前目录下生成指定名称的可执行文件 bccn 。 这样生成的文件是不包含调试信息的所以无法进行gdb的调试,如果是要支持gdb的调试在编译的时候必须带上-g参数,如下命令:
$ gcc -g bccn.c -o bccn
此时生成的可执行文件就包含了调试信息(后者生成的可执行文件比前者大上许多,其中包含了我们调试过程中需要的调试信息),然后就可以利用该可执行文件进行调试工作。
2、启动调试
如果是前面的工作都得以顺利进行,接下来启动调试工作就是相当简单的一件事情,执行如下命令:
$ gdb bccn
随后系统会输出如下信息
GNU gdb (GDB) 7.2-ubuntu
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://
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-linux-gnu".
For bug reporting instructions, please see:
<http://www.
Reading symbols from /home/hfzdxy/work_station/bccn...done.
Function "g" not defined.
Make breakpoint pending on future shared library load? (y or [n]) [answered N; input not from terminal]
(gdb) <—————— 等待命令的输入
3、调试
在上面的“等待命令的输入”位置, 输入一些调试需要的命令,一般都会在这儿设置断点信息(不然输入r命令之后程序无法暂停), 然后再键入r命令运行程序, 如果是命令行输入参数的, 则r命令后面就跟着参数列表:r arg1 arg2 arg3 ..., 在因为这里没什么需求,我们就一步一步地执行遇到具体问题然后再来分析,介绍
(gdb) b main
Breakpoint 1 at 0x8048641: file bccn.c, line 87.
(gdb) r
Starting program: /home/hfzdxy/work_station/bccn
Breakpoint 1, main () at bccn.c:87
87 printf("(1) 初始化栈 S\n");
在main函数处下一个断点 然后再让程序运行起来,因为现在不知道将要执行的代码具体是什么所有我们可以使用 gdb 的 l 名令来进行查看,默认是10行
(gdb) l
82
83 int main(void)
84 {
85 SElemType e;
86 SqStack S;
87 printf("(1) 初始化栈 S\n");
88 InitStack(&S);
89 printf("(2)栈为 %s\n", (StackEmpty(S)? "空" : "非空"));
90 printf("(3) 依次进栈元素 a, b, c, d, e\n");
91 Push(&S, 'a');
(gdb)
4、查看变量
通过ptype命令我们可以很方便地快速浏览类或结构, 例如当我们想知道e和S的具体类型内容则我们可以执行如下的操作:
(gdb) ptype e
type = char
(gdb) ptype S
type = struct {
char *base;
char *top;
int stacksize;
}
(gdb)
还可以通过print 来查看变量的具体值, 例如想知道S初始化后的值到底正不正确,可以执行如下操作:
(gdb) n
(1) 初始化栈 S
88 InitStack(&S);
(gdb) p S
$2 = {base = 0x15e985 "\203\304\020[]Í\213\314\f",
top = 0x11eb80 "U\211\345WV1\366S\350\036\214", stacksize = 134514811}
(gdb) n
89 printf("(2)栈为 %s\n", (StackEmpty(S)? "空" : "非空"));
(gdb) p S
$3 = {base = 0x11eb80 "U\211\345WV1\366S\350\036\214",
top = 0x11eb80 "U\211\345WV1\366S\350\036\214", stacksize = 100}
(gdb)
上面的命令 n 是单步执行的意思,下面输出的 88 InitStack(&S); 表示执行到这儿(尚未被执行),我们还可以从下面执行的两个 (gdb) p S 命令对比中看出来。接下来再让我们来尝试下display命令,在这里我们把main函数中的Push调用地方都下一个断点,然后查看每次到达断点时S的变化情况(其实不下断点单步执行下去也是可以的)
(gdb) b 91
Breakpoint 2 at 0x80486a2: file bccn.c, line 91.
(gdb) b 92
Breakpoint 3 at 0x80486b6: file bccn.c, line 92.
(gdb) b 93
Breakpoint 4 at 0x80486ca: file bccn.c, line 93.
(gdb) b 94
Breakpoint 5 at 0x80486de: file bccn.c, line 94.
(gdb) b 95
Breakpoint 6 at 0x80486f2: file bccn.c, line 95.
(gdb) disp S
1: S = {base = 0x11eb80 "U\211\345WV1\366S\350\036\214",
top = 0x11eb80 "U\211\345WV1\366S\350\036\214", stacksize = 100}
(gdb)
5、断点
通知gdb在程序中的特定位置暂停执行,break命令一般会使得每次程序执行到指定行时都会暂停。可以用命令info break 来查询要查找的断点的编号。可以使用clear和delete来删除断点。
(gdb) delete breakpoint_list
删除断点使用数值标识符,断点可以是一个数字, 比如delete 2 删除第二个断点;也可以是数字列表,比如delete 2 4删除第二个和第四个断点。clear清除gdb将执行下一个指令处的断点。这种方法适用于要删除gdb已经到达的断点的情况。每个断点都可以被启用和禁用,只有当gdb遇到启用的断点时,才会暂停程序的执行;它会忽略禁用的断点。默认情况下,断点的生命期从启用时开始。
(gdb) disable 3 将禁用第三个断点
(gdb) enable 1 5 将启用第一个和第5个断点。
不带任何参数地执行disable命令将禁用所有现有的断点。类似地,不带参数地执行enable命令会启用所有现有断点。
info breakpoints的输出:
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x08048641 in main at bccn.c:87
breakpoint already hit 1 time
2 breakpoint keep y 0x080486a2 in main at bccn.c:91
3 breakpoint keep y 0x080486b6 in main at bccn.c:92
4 breakpoint keep y 0x080486ca in main at bccn.c:93
5 breakpoint keep y 0x080486de in main at bccn.c:94
6 breakpoint keep y 0x080486f2 in main at bccn.c:95
(gdb)
(1)标识符(Num):断点的唯一标识符。
(2)类型(Type):这个字段指出该断点是断点、监视点还是捕获点。
(3)部署(Disp):每个断点都有一个部署,只是断点下次引用gdb暂停程序的执行后该断点上会发生什么事情。保持(keep),下次到达断点后不改变断点。这是新建断点的默认部署;删除(del),下次到达断点后删除该断点。使用tbreak命令创建的任何断点都是这样的断点;禁用(dis),下次到达断点时会禁用该断点。这是使用enable once命令设置的断点
(4)启用状态(Enb):这个字段说明断点当前是启用还是禁用的。
(5)地址(Address):这是内存中设置断点的位置。它主要用于汇编语言程序员,或者试图调用没有用扩充的符号表编译的可执行文件的人。
(6)位置(What):正如我们讨论过的,各个断点位于源代码的特定行上。what字段显示了断点所在的位置的行号和文件名。
/////////////////////////////////////////////////////////////////////
接下来单步执行让程序经过这些事先设置好的断点信息。
(gdb) n
(2)栈为 空
90 printf("(3) 依次进栈元素 a, b, c, d, e\n");
1: S = {base = 0x11eb80 "U\211\345WV1\366S\350\036\214",
top = 0x11eb80 "U\211\345WV1\366S\350\036\214", stacksize = 100}
(gdb) n
(3) 依次进栈元素 a, b, c, d, e
Breakpoint 2, main () at bccn.c:91
91 Push(&S, 'a');
1: S = {base = 0x11eb80 "U\211\345WV1\366S\350\036\214",
top = 0x11eb80 "U\211\345WV1\366S\350\036\214", stacksize = 100}
(gdb) n
Program received signal SIGSEGV, Segmentation fault.
0x08048580 in Push (S=0xbffff3d0, e=97 'a') at bccn.c:39
39 *S->top = e;
(gdb)
可惜到了这儿我们就无法再进行下去了, 因为我们的程序产生了段错误(Segmentation fault)。 既然遇到了问题, 那我们还是先把问题解决了然后才好去干别的事情。 下面就让我们带着问题来认识一下新的东西吧。
6、程序崩溃
程序的崩溃就是当某个错误导致程序突然和异常地停止执行时, 程序崩溃。迄今最常见的导致程序崩溃的原因是试图在未允许的情况下访问一个内存单元。硬件会感知这件事,并执行对操作系统的跳转。在Unix系列的平台上,操作系统一般会宣布程序导致了段错误(seg fault), 并停止程序的执行。在Windows系统上,对应的术语一般是保护错误(general protection fault)。无论是哪个名称,硬件都必须支持虚拟内存,而且操作系统必须使用虚拟内存才会发生这个错误。
当程序违反内存访问权限时, 在进程上发出SIGSEGV信号。默认段错误处理程序终止该进程,并向磁盘上写一个“核心文件”
核心文件包含程序崩溃时对程序状态的详细描述:栈的内容(或者,如果是多线程的,则是各个线程的栈),cpu寄存器的内容(同样,如果是多线程的,则是每个现成的上的一组寄存器值),程序的静态分配变量的值(全局与static变量),等等。
大多数现代shell都会在一开始就防止编写核心文件,在bash中, 可以使用ulimit命令控制核心文件的创建, 对于tcsh或csh则使用limit -c检查。
使用ulimit -c命令可查看core文件的生成开关。若结果为0,则表示关闭了此功能,不会生成core文件,在我这儿因为shell并没有开启此功能, 所以并没有看到核心文件的产生, 下面就让我们把生成核心文件的开关打开;使用ulimit -c filesize命令,可以限制core文件的大小(filesize的单位为kbyte)。若ulimit -c unlimited,则表示core文件的大小不受限制。如果生成的信息超过此大小,将会被裁剪,最终生成一个不完整的core文件。在调试此core文件的时候,gdb会提示错误。
$ ulimit -c
0
$ ulimit -c unlimited
$ ulimit -c
unlimited
这里不需要关闭之前的gdb调试窗口, 我们可以另外再新起一个终端。 现在让我们运行一下可执行程序使其产生我们需要的核心文件:
$ ./bccn
(1) 初始化栈 S
(2)栈为 空
(3) 依次进栈元素 a, b, c, d, e
Segmentation fault (core dumped)
现在就可以使用生成的core文件来定位错误信息(虽然我们在gdb调试的时候已经知道错误的位置)
$ gdb bccn core
GNU gdb (GDB) 7.2-ubuntu
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://
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-linux-gnu".
For bug reporting instructions, please see:
<http://www.
Reading symbols from /home/hfzdxy/work_station/bccn...done.
[New Thread 25201]
warning: Can't read pathname for load map: Input/output error.
Reading symbols from /lib/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `./bccn'.
Program terminated with signal 11, Segmentation fault.
#0 0x08048580 in Push (S=0xbfc57590, e=97 'a') at bccn.c:39
39 *S->top = e;
Function "g" not defined.
Make breakpoint pending on future shared library load? (y or [n]) [answered N; input not from terminal]
(gdb) bt
#0 0x08048580 in Push (S=0xbfc57590, e=97 'a') at bccn.c:39
#1 0x080486b6 in main () at bccn.c:91
(gdb) fram 0
#0 0x08048580 in Push (S=0xbfc57590, e=97 'a') at bccn.c:39
39 *S->top = e;
(gdb)
通过上面的现象可以很快地等处程序是在什么位置“死掉”的, 既然已经知道了程序是死在39 *S->top = e; 这一句中,但是我们还需要继续确认为什么这里会死掉, 虽然知道是非法访问内存,然而需要进行确认是如何在Push函数调用期间引进该段错误的, 确定错误的来源问题。
(gdb) p *S
$5 = {base = 0x11eb80 "U\211\345WV1\366S\350\036\214",
top = 0x11eb80 "U\211\345WV1\366S\350\036\214", stacksize = 100}
(gdb)
通过输出 S 指向的结构体发现 并不是我们期待的错误:访问空指针造成的, 那我们只有进一步向是否是访问非法地址造成的方向进行确认。从上面很明显可以知道 top 成员的地址, 那就让我们来检查这个地址指向的内容是否合法了。
(gdb) x /lxw 0x0011eb80
0x11eb80: 0x57e58955
(gdb) x /lxw 0x57e58955
0x57e58955: Cannot access memory at address 0x57e58955
(gdb)
通过 x /lxw 命令我们得出了我们访问的 S->top 地址是非法的地址, 下面来介绍下 x 命令的一些用法:
x /luw addr //打印addr地址的十进制值
x /lxw addr //打印addr地址的十六进制值
x /20 addr //打印addr地址开始的20个整形值--------分析结构体时使用
x /20c addr //打印addr地址开始的20个字符。-----分析串时使用
分析到这儿我们可以确定的是:由于访问了S->top指向的非法地址,而产生了段错误...... 但是我们还需要进一步确认这个错误产生的地方在哪儿。根据Push函数中代码调用的情况,可能的情况是:1、if条件中赋值给 S->top 的时候出现了错误 2、传递给Push函数的参数结构 S 中的值有问题。 我们先来确认第二种的猜测情况, 因为第一种情况在正常的逻辑下产生的情况非常低。
(gdb) x /lxw $ebp+8
0xbffff3c0: 0xbffff3d0
(gdb) x /10 0xbffff3d0
0xbffff3d0: 0x0011eb80 0x0011eb80 0x00000064 0x00288ff4
0xbffff3e0: 0x08048870 0x00000000 0xbffff468 0x00145ce7
0xbffff3f0: 0x00000002 0xbffff494
(gdb)
上面执行的步骤是, 打印第一个参数的地址, 然后再打印 0xbffff3d0地址开始的10个整形值, 可以分析去, 0x0011eb80 0x0011eb80 0x00000064 分别为
struct {
char *base;
char *top;
int stacksize;
};结构的成员值
综合上面的分析情况可以等处结论, S->top 这个非法地址 是由main函数调用Push的实参传进来的。通过上面介绍的内容, 我们会很快地找到问题的出现是在
int InitStack(SqStack *S)
{
S->base = (SElemType *)malloc(STACK_INIT_SIZE * sizeof(SElemType));
if(!S->base)
return 0;
S->base = S->top;
S->stacksize = STACK_INIT_SIZE;
return 1;
}
中的 19 S->base = S->top; 这一句上出了问题, 赋值的两个变量方向才弄反了, 那么我们还是先把这个问题改正一下:
S->top = S->base;
通常情况下我们不可能这么迅速地定位出问题, 因为很多程序运行虽然有core文件但是源文件的编译过程中是不加-g命令的, 所以没有办法向上面这样直接定位到出错的某一行上这么的‘精确’。我们只能是通过反汇编来进行进一步的处理
(gdb) disassemble
Dump of assembler code for function Push:
0x0804850c <+0>: push %ebp
0x0804850d <+1>: mov %esp,%ebp
0x0804850f <+3>: sub $0x28,%esp
0x08048512 <+6>: mov 0xc(%ebp),%eax
0x08048515 <+9>: mov %al,-0xc(%ebp)
0x08048518 <+12>: mov 0x8(%ebp),%eax
0x0804851b <+15>: mov 0x4(%eax),%eax
0x0804851e <+18>: mov %eax,%edx
0x08048520 <+20>: mov 0x8(%ebp),%eax
0x08048523 <+23>: mov (%eax),%eax
0x08048525 <+25>: sub %eax,%edx
0x08048527 <+27>: mov 0x8(%ebp),%eax
0x0804852a <+30>: mov 0x8(%eax),%eax
0x0804852d <+33>: cmp %eax,%edx
0x0804852f <+35>: jl 0x8048576 <Push+106>
0x08048531 <+37>: mov 0x8(%ebp),%eax
0x08048534 <+40>: mov 0x8(%eax),%eax
0x08048537 <+43>: add $0xa,%eax
0x0804853a <+46>: mov %eax,%edx
0x0804853c <+48>: mov 0x8(%ebp),%eax
0x0804853f <+51>: mov (%eax),%eax
0x08048541 <+53>: mov %edx,0x4(%esp)
---Type <return> to continue, or q <return> to quit---r
0x08048545 <+57>: mov %eax,(%esp)
0x08048548 <+60>: call 0x8048388 <>
0x0804854d <+65>: mov %eax,%edx
0x0804854f <+67>: mov 0x8(%ebp),%eax
0x08048552 <+70>: mov %edx,(%eax)
0x08048554 <+72>: mov 0x8(%ebp),%eax
0x08048557 <+75>: mov (%eax),%edx
0x08048559 <+77>: mov 0x8(%ebp),%eax
0x0804855c <+80>: mov 0x8(%eax),%eax
0x0804855f <+83>: add %eax,%edx
0x08048561 <+85>: mov 0x8(%ebp),%eax
0x08048564 <+88>: mov %edx,0x4(%eax)
0x08048567 <+91>: mov 0x8(%ebp),%eax
0x0804856a <+94>: mov 0x8(%eax),%eax
0x0804856d <+97>: lea 0xa(%eax),%edx
0x08048570 <+100>: mov 0x8(%ebp),%eax
0x08048573 <+103>: mov %edx,0x8(%eax)
0x08048576 <+106>: mov 0x8(%ebp),%eax
0x08048579 <+109>: mov 0x4(%eax),%eax
0x0804857c <+112>: movzbl -0xc(%ebp),%edx
=> 0x08048580 <+116>: mov %dl,(%eax)
0x08048582 <+118>: mov 0x8(%ebp),%eax
0x08048585 <+121>: mov 0x4(%eax),%eax
---Type <return> to continue, or q <return> to quit---
0x08048588 <+124>: lea 0x1(%eax),%edx
0x0804858b <+127>: mov 0x8(%ebp),%eax
0x0804858e <+130>: mov %edx,0x4(%eax)
0x08048591 <+133>: mov $0x1,%eax
0x08048596 <+138>: leave
0x08048597 <+139>: ret
End of assembler dump.
(gdb)
上面是反汇编的结果, 再根据堆栈栈顶的地址来确定程序是‘死’在那条汇编指令处
(gdb) bt
#0 0x08048580 in Push (S=0xbffff3d0, e=97 'a') at bccn.c:39
#1 0x080486b6 in main () at bccn.c:91
(gdb)
通过bt我们打印出了栈的信息, 地址是: 0x08048580, 对应汇编程序的指令为:
=> 0x08048580 <+116>: mov %dl,(%eax)
找到了出错的地方, 现在就是要对照源文件和汇编进行确认究竟代码是‘死’在了哪里,这个就从这里打住咯......
7、上下移动调用栈
在函数调用期间, 与调用关联的运行时信息存储在称为栈帧的内存区域中。帧中包含函数的局部变量的值、其形参,以及调用改函数的位置的记录。每次发生函数调用时,都会创建一个新帧,并将其推到一个系统维护的栈上;栈最上方的帧表示当前正在执行的函数, 当函数推出时,这个帧被弹出栈,并且释放。
/////////////////////////////////////////////////////////////////////
我们把程序修改好之后,重新编译,记住我们原来的gdb调试的终端一直都还在, 因为当我们遇到问题后是在新起的终端上操作的。
$ vi bccn.c
$ gcc -g bccn.c -o bccn
完成操作后转会到原来的gdb调试窗口重新执行下 r 命令
(gdb) r
`/home/hfzdxy/work_station/bccn' has changed; re-reading symbols.
Starting program: /home/hfzdxy/work_station/bccn
Breakpoint 1, main () at bccn.c:87
87 printf("(1) 初始化栈 S\n");
(gdb)
这样重新启动, gdb可以检测到文件的修改并且会保留原来的断点信息, 避免繁琐的重复操作。现在就让我们恢复原来的试验操作(因为段错误而终止的)。接下来继续disp S 操作
(gdb) disp S
2: S = {base = 0x15e985 "\203\304\020[]Í\213\314\f",
top = 0x11eb80 "U\211\345WV1\366S\350\036\214", stacksize = 134514811}
(gdb) n
(1) 初始化栈 S
88 InitStack(&S);
2: S = {base = 0x15e985 "\203\304\020[]Í\213\314\f",
top = 0x11eb80 "U\211\345WV1\366S\350\036\214", stacksize = 134514811}
(gdb)
89 printf("(2)栈为 %s\n", (StackEmpty(S)? "空" : "非空"));
2: S = {base = 0x804b008 "", top = 0x804b008 "", stacksize = 100}
(gdb)
(2)栈为 空
90 printf("(3) 依次进栈元素 a, b, c, d, e\n");
2: S = {base = 0x804b008 "", top = 0x804b008 "", stacksize = 100}
(gdb)
(3) 依次进栈元素 a, b, c, d, e
Breakpoint 2, main () at bccn.c:91
91 Push(&S, 'a');
2: S = {base = 0x804b008 "", top = 0x804b008 "", stacksize = 100}
(gdb)
Breakpoint 3, main () at bccn.c:92
92 Push(&S, 'b');
2: S = {base = 0x804b008 "a", top = 0x804b009 "", stacksize = 100}
(gdb)
Breakpoint 4, main () at bccn.c:93
93 Push(&S, 'c');
2: S = {base = 0x804b008 "ab", top = 0x804b00a "", stacksize = 100}
(gdb)
Breakpoint 5, main () at bccn.c:94
94 Push(&S, 'd');
2: S = {base = 0x804b008 "abc", top = 0x804b00b "", stacksize = 100}
(gdb)
Breakpoint 6, main () at bccn.c:95
95 Push(&S, 'e');
2: S = {base = 0x804b008 "abcd", top = 0x804b00c "", stacksize = 100}
(gdb)
96 printf("(4)栈为 %s\n", (StackEmpty(S)? "空" : "非空"));
2: S = {base = 0x804b008 "abcde", top = 0x804b00d "", stacksize = 100}
(gdb)
从上面可以看出,修改之后到目前位置我们的程序运行的都还不错。 因为执行了display命令,gdb在遇到断点以后自动输出 S。当然,前提是变量在作用域中时, 才会将显示列表中的变量显示出来。当觉得每执行一步输出这么多信息感觉很乱, 我么可以采取下面措施把它禁用掉(就像上面处理断点一样)。
(gdb) info disp
Auto-display expressions now in effect:
Num Enb Expression
2: y S
(gdb) dis disp
(gdb) info disp
Auto-display expressions now in effect:
Num Enb Expression
2: n S
(gdb) n
(5) 栈长度: 5
98 printf("(6) 从栈顶到栈底元素f: ");
(gdb) n
99 DispStack(S);
(gdb)
运行到这儿我们暂时还不知道DispStack(S);到底实现什么逻辑, 于是我们想进去看看, 可以使用 s 单步执行命令, s 和 n 都是单步的命令, 但是 s 会进入调用函数内去单步,而 n 则不会。
(gdb) s
DispStack (s=...) at bccn.c:77
77 for(i =(int) s.top; i >= 0; i--)
(gdb) l 77
72 }
73
74 int DispStack(SqStack s)
75 {
76 int i;
77 for(i =(int) s.top; i >= 0; i--)
78 printf("%c", *(s.top - 1));
79
80 return 1;
81 }
(gdb)
好了, 在这儿我们遇到了一个for循环, 意图是想遍历栈里面的数据。 现在我们想看看这个循环到底在干些什么, 如果在for循环处下断点, 可以但是不好控制, 因为如果循环次数太多的话你会感到很疲惫的, 所以我们在这儿试试监视点吧。
8、监视点
监视点,通知gdb当特定内存位置(或者涉及一个或多个位置的表达式)的值发生变化时暂停执行。例如, 在程序的执行过程中,假设要在变量i改变值时查看程序的状态。在gdb中,可以执行如下命令:
(gdb) watch i
当运行程序时, 每当 i 的值发生变化, gdb都会暂停执行。也可以基于条件表达式来设置检视点。 例如, 假设要检查程序执行期间 i 的值等于 5 的时候,, 因为我们这儿好像只有5个元素呀, 可以通过设置一个基于表达式 (i == 5)的检视点来完成这件事。在gdb中输入:
(gdb) watch (i == 5)
监视点对局部变量的用途一般没有对作用域更宽的变量的用途大,因为一旦变量超出作用域(即当在其中定义变量的函数结束时), 在局部变量上设置的监控点就会被取消。 然而, main()中的局部变量然是一个例外, 因为这样的变量只有程序完成执行时才会被释放。
由于这里的逻辑很明显是错误的, 输出太多的东西我们并不想要的, 所以我们把这个儿修改一下, 重新编译调试(这一切的进行都是在新起的终端上进行的)
(gdb) s
DispStack (s=...) at bccn.c:77
77 for(i = s.top - s.base; i > 0; i--)
(gdb) l
72 }
73
74 int DispStack(SqStack s)
75 {
76 int i;
77 for(i = s.top - s.base; i > 0; i--)
78 printf("%c", *(s.top - i));
79
80 return 1;
81 }
(gdb)
然后我们在这儿安个检视点
(gdb) watch i==5
Hardware watchpoint 10: i==5
(gdb) c
Continuing.
Hardware watchpoint 10: i==5
Old value = 0
New value = 1
0x0804861c in DispStack (s=...) at bccn.c:77
77 for(i = s.top - s.base; i > 0; i--)
(gdb) p i
$6 = 5
(gdb)
顺利的停住了在 当 i == 5 的时候, 然后我们再安一个监视点, 如下:
(gdb) watch i == 0
Hardware watchpoint 11: i == 0
(gdb) c
Continuing.
Hardware watchpoint 11: i == 0
Old value = 0
New value = 1
0x0804863a in DispStack (s=...) at bccn.c:77
77 for(i = s.top - s.base; i > 0; i--)
(gdb) p i
$8 = 0
(gdb)
程序调试到这儿先让我们暂停调试, 去跑下修改完后的结果怎么样
$ ./bccn
(1) 初始化栈 S
(2)栈为 空
(3) 依次进栈元素 a, b, c, d, e
(4)栈为 非空
(5) 栈长度: 5
(6) 从栈顶到栈底元素f: abcde(7)出栈序列:
(8)栈为空
(9) 释放栈
发现第(6)(7)有问题, 但是第(6)点的问题我们是知道的, 刚刚修改出来的, 这里就不再修改了。但是(7)为什么会没有数据输出呢, 下面让我们再去看下发生了什么情况......
我们可以对输出的数据e进行跟踪
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0804860b in DispStack at bccn.c:77
breakpoint already hit 1 time
2 breakpoint keep y 0x080485b8 in Pop at bccn.c:60
3 breakpoint keep y 0x080487b5 in main at bccn.c:101
(gdb) delete 1
(gdb) info b
Num Type Disp Enb Address What
2 breakpoint keep y 0x080485b8 in Pop at bccn.c:60
3 breakpoint keep y 0x080487b5 in main at bccn.c:101
(gdb)
因为1号断点我们已经不再需要了所以把它删掉
101 while(! StackEmpty(S))
(gdb) n
103 Pop(&S, &e);
(gdb) p e
$3 = 0 '\000'
(gdb) s
Breakpoint 2, Pop (S=0xbffff3d0, e=0xbffff3df "") at bccn.c:60
60 if(S->base == S->top )
(gdb) n
62 e = --S->top;
(gdb) p e
$4 = 0xbffff3df ""
(gdb) n
64 return 1;
(gdb) p e
$5 = 0x804b00c "e"
(gdb) n
65 }
(gdb) n
main () at bccn.c:104
104 printf("%c ", e);
(gdb) p e
$6 = 0 '\000'
(gdb)
这里我们发现e的值在调用Pop函数后并没有得到想象中的改变,这里的错误是, 修改了指针参数的地址,而不是修改了指针参数的地址所指向的内容, 所以无法得到想要的结果, 我们可以把这儿再进行修改:
Breakpoint 2, Pop (S=0xbffff3d0, e=0xbffff3df "") at bccn.c:60
60 if(S->base == S->top )
(gdb) n
62 *e = *--S->top;
(gdb) p e
$7 = 0xbffff3df ""
(gdb) n
64 return 1;
(gdb) p e
$8 = 0xbffff3df "e\220\210\004\b"
(gdb) fin
Run till exit from #0 Pop (S=0xbffff3d0, e=0xbffff3df "e\220\210\004\b") at bccn.c:64
main () at bccn.c:104
104 printf("%c ", e);
Value returned is $9 = 1
(gdb) p e
$10 = 101 'e'
(gdb)
修改之后已经可以得到想要的值了, 到此再看看整体的运行结果如何:
$ ./bccn
(1) 初始化栈 S
(2)栈为 空
(3) 依次进栈元素 a, b, c, d, e
(4)栈为 非空
(5) 栈长度: 5
(6) 从栈顶到栈底元素f: abcde(7)出栈序列: e d c b a
(8)栈为空
(9) 释放栈
[ 本帖最后由 寒风中的细雨 于 2012-11-19 16:00 编辑 ]