- gdb 基本命令3
命令 | 描述 |
---|---|
watch | 设置观察点 |
info ( 或 i ) watchpoints | 查看当前设置了哪些观察点 |
x | 从某个位置开始打印存储单元的内容,全部当成字节来看,而不区分哪个字节属于哪个变量 |
接着上次的内容:
修改后的代码
1 #include <stdio.h>
2
3 int main(void)
4 {
5
6 int sum = 0, i = 0;
7 char input[5];
8
9 while(1){
10 sum = 0;
11 scanf("%s",input);
12 for (i = 0;input[i] != '\0';i++)
13 sum = sum*10 + input[i] - '0';
14 printf("input=%d\n",sum);
15 }
16 return 0;
17 }
使用scanf函数是非常凶险的,即使修正了这个Bug也还存在很多问题。如果输入的字符串超长了会怎么样?我们知道,数组访问越界是不会检查的,所以scanf会写出界。
# ./main
123
input=123
67
input=67
12345
input=123407
下面用gdb来调试看看这个结果是怎么出来的
- x命令 按字节来打印某个位置的存储单元的内容
# gdb main
...
...
(gdb) start
Temporary breakpoint 1 at 0x40059e: file main.c, line 4.
Starting program: /home/liyongfeng/code/gdb_Demo/Demo2/main
Temporary breakpoint 1, main () at main.c:4
4 {
(gdb) n
6 int sum = 0, i = 0;
(gdb)
10 sum = 0;
(gdb)
11 scanf("%s",input);
(gdb)
12345
12 for (i = 0;input[i] != '\0';i++)
(gdb) p input
$1 = "12345"
(gdb) x/7bx input
0x7fffffffde50: 0x31 0x32 0x33 0x34 0x35 0x00 0x00
x 命令打印指定存储单元里保存的内容,后缀7bx是打印格式,7表示打印7组,b表示每个字节一组,x表示按十六进制格式打印,x/7bx这条命令从input数组的第一个字节开始连续打印7个字节。前5个字节是input数组的存储单元,打印的是十六进制的ASCII码的‘1’到‘5’,第6个字节是写出界的’\0’.
根据运行结果,前4个字符转成数字都没错,第5个错了,也就是说i从0到3的循环都没错,可以设置一个断点从i等于4开始单步调试:
(gdb) b 13 if i == 4
Breakpoint 2 at 0x4005e1: file main.c, line 13.
(gdb) c
Continuing.
Breakpoint 2, main () at main.c:13
13 sum = sum*10 + input[i] - '0';
(gdb) p sum
$2 = 1234
现在sum是1234没有错,根据运行结果是123407,我们知道,即将进行的下一步计算肯定要出错,算出来应该是12340,那就是说input[4]肯定不是‘5’了,下面的代码证明这个推理是不正确的:
(gdb) x/7bx input
0x7fffffffde50: 0x31 0x32 0x33 0x34 0x35 0x04 0x00
input[4]确实是0x35,再分析一下,可以发现,产生123407这个结果还有另外一种可能,就是在下一次循环中123450不是加上而是减去一个数,得到123407,因为循环结束的条件是input[i]!=’\0’,而本来应该是0x00的位置现在莫名其妙地变成了0x04,因此循环还没结束。继续单步调试:
(gdb) n
12 for (i = 0;input[i] != '\0';i++)
(gdb) p sum
$3 = 12345
(gdb) n
13 sum = sum*10 + input[i] - '0';
(gdb) x/7bx input
0x7fffffffde50: 0x31 0x32 0x33 0x34 0x35 0x05 0x00
进入下一次循环,原来的0x04又莫名其妙地变成了0x05,这是怎么回事?这个暂时解释不了,但123407这个结果可以解释了,是12345x10 + 0x05 - 0x30得到的,虽然多循环了一次,但下次一定会退出循环了,因为0x05的后面是‘\0’。
input[4]后面的那个字节到底是什么时候改变的?可以用观察点(Watchpoint)来跟踪。断点是当程序执行到某一代码行时中断,而观察点是当程序访问某个存储单元时中断,如果我们不知道某个存储单元是在哪里被改动的,这时候观察点尤其有用。
下面删除原来设的断点,从头执行程序,重复上次的输入,用watch命令设置观察点,跟踪input[4]后面的字节。
(gdb) delete breakpoints
Delete all breakpoints? (y or n) y
(gdb) start
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Temporary breakpoint 3 at 0x40059e: file main.c, line 4.
Starting program: /home/liyongfeng/code/gdb_Demo/Demo2/main
Temporary breakpoint 3, main () at main.c:4
4 {
(gdb) n
6 int sum = 0, i = 0;
(gdb)
10 sum = 0;
(gdb)
11 scanf("%s",input);
(gdb)
12345
12 for (i = 0;input[i] != '\0';i++)
(gdb) watch input[5]
Hardware watchpoint 4: input[5]
(gdb) i watchpoints
Num Type Disp Enb Address What
4 hw watchpoint keep y input[5]
(gdb) c
Continuing.
Hardware watchpoint 4: input[5]
Old value = 0 '\000'
New value = 1 '\001'
0x4005ae in main () at main.c:12
12 for (i = 0;input[i] != '\0';i++)
(gdb) c
Continuing.
Hardware watchpoint 4: input[5]
Old value = 0 '\001'
New value = 1 '\002'
0x4005ae in main () at main.c:12
12 for (i = 0;input[i] != '\0';i++)
(gdb) c
Continuing.
Hardware watchpoint 4: input[5]
Old value = 0 '\003'
New value = 1 '\004'
0x4005ae in main () at main.c:12
12 for (i = 0;input[i] != '\0';i++)
这里可以看出,每次回到for循环开头的时候,改变了input[5]的值,而且每次加1,而循环变量i正是在每次回到循环开头之前加1,原来input[5]就是变量i的存储单元,换句话说,i的存储单元是紧跟在input数组后面的。