博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
linux下的gdb调试工具--内存调试
阅读量:5908 次
发布时间:2019-06-19

本文共 5079 字,大约阅读时间需要 16 分钟。

接着上一节的代码,在while(1)的循环里面增加代码:sum=0

#include 
int main(void){ int sum = 0, i = 0; char input[5]; while (1) { sum = 0; scanf("%s", input); for (i = 0; input[i] != '\0'; i++) sum = sum * 10 + input[i] - '0'; printf("input = %d\n", sum); } return 0;}

使用scanf函数是非常凶险的,即使修正了这个Bug也还存在很多问题。如果输入的字符串超长了会怎么样?我们知道数组访问越界是不会检查的,所以scanf会写出界。现象是这样的:

$ ./main123input=12367input=6712345input=123407

下面用调试器看看最后这个诡异的结果是怎么出来的。

$ gdb main...(gdb) startBreakpoint 1 at 0x80483b5: file main.c, line 5.Starting program: /home/akaedu/main main () at main.c:55		int sum = 0, i = 0;(gdb) n9			sum = 0;(gdb) (直接回车)10			scanf("%s", input);(gdb) (直接回车)1234511			for (i = 0; input[i] != '\0'; i++)(gdb) p input$1 = "12345"

input数组只有5个元素,写出界的是scanf自动添的'\0',用x命令看会更清楚一些:

(gdb) x/7b input0xbfb8f0a7:	0x31	0x32	0x33	0x34	0x35	0x00	0x00

x命令打印指定存储单元的内容。7b是打印格式,b表示每个字节一组,7表示打印7组,从input数组的第一个字节开始连续打印7个字节。前5个字节是input数组的存储单元,打印的正是十六进制ASCII码的'1''5',第6个字节是写出界的'\0'。根据运行结果,前4个字符转成数字都没错,第5个错了,也就是i从0到3的循环都没错,我们设一个条件断点从i等于4开始单步调试:

(gdb) l6		char input[5];7	8		while (1) {9			sum = 0;10			scanf("%s", input);11			for (i = 0; input[i] != '\0'; i++)12				sum = sum*10 + input[i] - '0';13			printf("input=%d\n", sum);14		}15		return 0;(gdb) b 12 if i == 4Breakpoint 2 at 0x80483e6: file main.c, line 12.(gdb) cContinuing.Breakpoint 2, main () at main.c:1212				sum = sum*10 + input[i] - '0';(gdb) p sum$2 = 1234

现在sum是1234没错,根据运行结果是123407我们知道即将进行的这步计算肯定要出错,算出来应该是12340,那就是说input[4]肯定不是'5'了,事实证明这个推理是不严谨的:

(gdb) x/7b input0xbfb8f0a7:	0x31	0x32	0x33	0x34	0x35	0x04	0x00

input[4]的确是0x35,产生123407还有另外一种可能,就是在下一次循环中123450不是加上而是减去一个数得到123407。可现在不是到字符串末尾了吗?怎么会有下一次循环呢?注意到循环控制条件是input[i] != '\0',而本来应该是0x00的位置现在莫名其妙地变成了0x04,因此循环不会结束。继续单步:

(gdb) n11			for (i = 0; input[i] != '\0'; i++)(gdb) p sum$3 = 12345(gdb) n12				sum = sum*10 + input[i] - '0';(gdb) x/7b input0xbfb8f0a7:	0x31	0x32	0x33	0x34	0x35	0x05	0x00

进入下一次循环,原来的0x04又莫名其妙地变成了0x05,这是怎么回事?这个暂时解释不了,但123407这个结果可以解释了,是12345*10 + 0x05 - 0x30得到的,虽然多循环了一次,但下次一定会退出循环了,因为0x05的后面是'\0'

input[4]后面那个字节到底是什么时候变的?可以用观察点(Watchpoint)来跟踪。我们知道断点是当程序执行到某一代码行时中断,而观察点是当程序访问某个存储单元时中断如果我们不知道某个存储单元是在哪里被改动的,这时候观察点尤其有用。下面删除原来设的断点,从头执行程序,重复上次的输入,用watch命令设置观察点,跟踪input[4]后面那个字节(可以用input[5]表示,虽然这是访问越界):

gdb) delete breakpoints Delete all breakpoints? (y or n) y(gdb) startBreakpoint 1 at 0x80483b5: file main.c, line 5.Starting program: /home/akaedu/main main () at main.c:55		int sum = 0, i = 0;(gdb) n9			sum = 0;(gdb) (直接回车)10			scanf("%s", input);(gdb) (直接回车)1234511			for (i = 0; input[i] != '\0'; i++)(gdb) watch input[5]Hardware watchpoint 2: input[5](gdb) i watchpoints Num     Type           Disp Enb Address    What2       hw watchpoint  keep y              input[5](gdb) cContinuing.Hardware watchpoint 2: input[5]Old value = 0 '\0'New value = 1 '\001'0x0804840c in main () at main.c:1111			for (i = 0; input[i] != '\0'; i++)(gdb) cContinuing.Hardware watchpoint 2: input[5]Old value = 1 '\001'New value = 2 '\002'0x0804840c in main () at main.c:1111			for (i = 0; input[i] != '\0'; i++)(gdb) cContinuing.Hardware watchpoint 2: input[5]Old value = 2 '\002'New value = 3 '\003'0x0804840c in main () at main.c:1111			for (i = 0; input[i] != '\0'; i++)

已经很明显了,每次都是回到for循环开头的时候改变了input[5]的值,而且是每次加1,而循环变量i正是在每次回到循环开头之前加1,原来input[5]就是变量i的存储单元,换句话说,i的存储单元是紧跟在input数组后面的。

修正这个Bug对初学者来说有一定难度。如果你发现了这个Bug却没想到数组访问越界这一点,也许一时想不出原因,就会先去处理另外一个更容易修正的Bug:如果输入的不是数字而是字母或别的符号也能算出结果来,这显然是不对的,可以在循环中加上判断条件检查非法字符:

while (1) {	sum = 0;	scanf("%s", input);	for (i = 0; input[i] != '\0'; i++) 	{		if (input[i] < '0' || input[i] > '9') 		{			printf("Invalid input!\n");			sum = -1;			break;		}		sum = sum * 10 + input[i] - '0';	}	printf("input = %d\n", sum);}

然后你会惊喜地发现,不仅输入字母会报错,输入超长也会报错:

$ ./main123aInvalid input!input=-1deadInvalid input!input=-11234578Invalid input!input=-11234567890abcdefInvalid input!input=-123input=23

似乎是两个Bug一起解决掉了,但这是治标不治本的解决方法。看起来输入超长的错误是不出现了,但只要没有找到根本原因就不可能真的解决掉,等到条件一变,它可能又冒出来了,在下一节你会看到它又以一种新的形式冒出来了。

总结一下,用到的gdb的命令:

不得不承认,在有些平台和操作系统上也未必得到这个结果,产生Bug的往往都是一些平台相关的问题,举这样的例子才比较像是真实软件开发中遇到的Bug,如果您的程序跑不出我这样的结果,那这一节您就凑合着看吧。


前面提出修正Bug的方法是在循环中加上判断条件,如果不是数字就报错退出,不仅输入字母可以报错退出,输入超长的字符串也会报错退出。表面上看这个程序无论怎么运行都不出错了,但假如我们把while (1)循环去掉,每次执行程序只转换一个数: 

#include 
int main(void){ int sum = 0, i = 0; char input[5]; scanf("%s", input); for (i = 0; input[i] != '\0'; i++) { if (input[i] < '0' || input[i] > '9') { printf("Invalid input!\n"); sum = -1; break; } sum = sum * 10 + input[i] - '0'; } printf("input = %d\n", sum); return 0;}

然后输入一个超长的字符串,看看会发生什么:

$ ./main 1234567890Invalid input!input=-1

看起来正常。再来一次,这次输个更长的:

$ ./main 1234567890abcdefInvalid input!input=-1Segmentation fault

又出段错误了。我们按同样的方法用gdb调试看看:

$ gdb main...(gdb) rStarting program: /home/akaedu/main 1234567890abcdefInvalid input!input=-1Program received signal SIGSEGV, Segmentation fault.0x0804848e in main () at main.c:1919	}(gdb) l14			}15			sum = sum*10 + input[i] - '0';16		}17		printf("input=%d\n", sum);18		return 0;19	}

gdb指出,段错误发生在第19行。可是这一行什么都没有啊,只有表示main函数结束的}括号。这可以算是一条规律:如果某个函数的局部变量发生访问越界,有可能并不立即产生段错误,而是在函数返回时产生段错误

 

转载于:https://www.cnblogs.com/stemon/p/5139580.html

你可能感兴趣的文章
关于CSS层叠、CSS继承、CSS盒模型概述
查看>>
mybatis-自定义TypeHandler 的坑
查看>>
测试聊天机器人的新方法——Botfuel Dialog
查看>>
web 如何工作
查看>>
Android面试之Java设计模式
查看>>
聊一聊Javascript中的Promise对象
查看>>
DataBinding绑定android:onClick出错
查看>>
Element :upload组件实例
查看>>
[swift 进阶]读书笔记-第六章:函数 C6P0_ 函数(总体介绍)
查看>>
随行付微服务测试之性能测试
查看>>
springcloud入门之断路器Hystrix(四)
查看>>
凭借UGC壮大的马蜂窝,亦是喜忧参半
查看>>
JavaScript全局作用域下,变量加var和不加var的区别。
查看>>
Python 3 新特性:类型注解
查看>>
零基础学习 Python 之细说类属性 & 实例
查看>>
JavaScript设计模式与开发实践笔记
查看>>
日常工作中关于前后端分离的简单介绍
查看>>
wx小程序(3) - 自定义组件及参数传输
查看>>
Redux-Middleware-源码解析
查看>>
cmake常用命令
查看>>