一、背景
GDB调试代码时,偶尔会遇到一些奇怪的现象:函数的参数地址在函数内部被传递给另外的函数,然后发现地址发生了改变,这样的情况称之为函数的栈被毁坏,导致无法重入。
然后被调用的函数里面,访问了非法的地址导致了segment fault,产生core dump文件。
这类问题一般都比较棘手。
二、如何解决
此类问题,可以从gdb的栈保护设置开始着手。
编译的时候添加编译选项:-fstack-protector 和 -fstack-protector-all 这两个选项指示编译器开启栈保护,这样在栈乱序的第一时间可以dump出来现场。可加在Makefile里面。
三、举例
1、问题
- 以多线程应用程序中由于线程间的冲突导致的栈破坏为例,讲解调试方法。由于存在栈破坏,可以说backtrace信息并不完整
- 问题:某个进行线程间通信的程序中含有bug,生成了coredump文件
2、解决方法
1、假设出现以下现象
(gdb)bt
#0 0x00000003b4869ac80 in nanossleep () from /lib64/libc.so.6
#1 000ee1c2000ee1c1 in ?? ()
#2 000ee1c2000ee1c3 in ?? ()
#3 000ee1c2000ee1c5 in ?? ()
#4 000ee1c2000ee1c7 in ?? ()
- gdb的bt信息是依据栈里保存的函数返回地址来显示的。gdb的bt信息来自进程的栈上。
- 上面的bt不正确的情况,基本上可以认为是栈被破坏了
3、查看寄存器和栈
(gdb)info reg
...
rsp 0x4162f0c8 0x4162f0c8
...
rip 0x3b4869ac80 0x3b4869ac80 <nanosleep+96>
...(gdb)x/i 0x3b4869ac80
0x3b4869ac80 <nanosleep+96> retq <-这是函数的返回指令
/*
1.在x86中,函数返回是从栈指针sp的地址处取出返回地址,然后跳转到该地址;
*/(gdb)x/g 0x4162f0c8
0x4162f0c8: 0x000ee1c2000ee1c1
/*
1.然而,返回的地址成了0x000ee1c2000ee1c1,也就是说跳转到该地址,可怀疑
返回地址被破坏了。可认为是栈被破坏了。
2.因此,可以认为跳转地址不正确,即:栈上的返回地址不正确
3.从bt也可以看出,最后的nanpsleep()是从地址0x000ee1c2000ee1c1调用的,而0x000ee1c2000ee1c1
不是正确的地址
*/
- 由于栈空间还被用做局部变量的保存空间,因此,局部变量的内容也有可能被破坏
- 调查栈破坏的方法:依据被破坏的数据内容,判断执行写入的位置,看看有没有对栈空间(局部变量)的引用,指针传递的处理
- 栈破坏原因分析:示例中:在线程间的数据处理上传递了栈的指针,导致了其他线程向该地址写入了数据。而这个其他线程向栈内写入数据的操作被推迟了,从而导致了栈破坏。
正常时,应用程序行为如下:
出现问题时,应用程序的行为:
进阶
《GDB/Debug.Hacks中文版:深入调试的技术和工具》