日期:2014-05-16  浏览次数:20608 次

《coredump问题原理探究》Linux x86版3.1节栈布局之概述

在定位core dump问题,看栈是必要的一步。因为栈是反映了程序崩溃那一瞬间的情况,包括函数调用关系,参数,局部变量。要找出程序崩溃的地方,必须以栈为依据。而在开发过程中,一般会有调试版本和发布版本,其中调试版本会包含很多符号信息便于调试,而发布版本往往为了商业秘密的保护,在产品发布时去掉了调试信息。这两种方式会导致栈在可读性上不一样。

调试版本:

(gdb) bt
#0  recurse (num=1, level=9) at xuzhina_dump_c3.cpp:9
#1  0x080484a0 in recurse (num=2, level=8) at xuzhina_dump_c3.cpp:6
#2  0x080484a0 in recurse (num=3, level=7) at xuzhina_dump_c3.cpp:6
#3  0x080484a0 in recurse (num=4, level=6) at xuzhina_dump_c3.cpp:6
#4  0x080484a0 in recurse (num=5, level=5) at xuzhina_dump_c3.cpp:6
#5  0x080484a0 in recurse (num=6, level=4) at xuzhina_dump_c3.cpp:6
#6  0x080484a0 in recurse (num=7, level=3) at xuzhina_dump_c3.cpp:6
#7  0x080484a0 in recurse (num=8, level=2) at xuzhina_dump_c3.cpp:6
#8  0x080484a0 in recurse (num=9, level=1) at xuzhina_dump_c3.cpp:6
#9  0x080484a0 in recurse (num=10, level=0) at xuzhina_dump_c3.cpp:6
#10 0x080484d5 in main () at xuzhina_dump_c3.cpp:14

发布版本:

(gdb) bt
#0  0x080484a9 in recurse(int, int) ()
#1  0x080484a0 in recurse(int, int) ()
#2  0x080484a0 in recurse(int, int) ()
#3  0x080484a0 in recurse(int, int) ()
#4  0x080484a0 in recurse(int, int) ()
#5  0x080484a0 in recurse(int, int) ()
#6  0x080484a0 in recurse(int, int) ()
#7  0x080484a0 in recurse(int, int) ()
#8  0x080484a0 in recurse(int, int) ()
#9  0x080484a0 in recurse(int, int) ()
#10 0x080484d5 in main ()

同时在开发过程中,由于逻辑太过复杂,不小心引入了栈溢出,导致栈混乱,如前言里:

(gdb) bt
#0  0x6f745374 in ?? ()
#1  0x57735571 in ?? ()
#2  0xbff80065 in ?? ()
Backtrace stopped: previous frame inner to this frame (corrupt stack?)

第一种栈非常简单,也非常容易定位。但生活并不是那么容易。在产品的生命周期中,调试版本的时间是非常短的,而且在调试版本阶段,产品测试环境无论怎么模拟都没有产品发布后的客户环境那么复杂,很多潜在问题往往并不能在这个阶段发现。第二种栈比较麻烦,因为不知道函数的参数和局部变量,也不和代码行对应,出现问题非常难确定原因,且它在产品的生命周期的时间就非常长,从发布之前(一般发布版本在发布前一个阶段已经制作了)到产品该版本完全退出市场,甚至长达几年时间,而这几年时间内,相应的调试版本或调试信息文件可能已经由于历经几个版本的迭代而无从查找或者删除。第三种栈是最麻烦,因为根本就不知道问题出在哪个函数,特别在大型项目中,面对几百,几千万行的代码,更加无从入手,虽然它出现的机率可能很低,它的时间有可能从第一版本到整个产品退出市场为止,长达十几年,甚至二十几年。

由于第二,三种栈复杂性,所以希望找出栈布局的规律, 在遇到这种问题时不会措手不及。由于函数调用树在调试版本和发布版本一样,所以发布版本和调试版本的堆栈是一样的。

 

栈存放着函数相关的信息。这种情况叫做”调用约定(CallingConvention)”。如果阅读x86的规范,就会知道栈存放着函数桢指针,函数返回地址,函数参数,局部变量,及它们之间的布局。但是,也可以自己找出这种关系,这样印象会更深刻一些。

可以通过下面的步骤来探究”调用约定”:

1.      构造一些没有参数和局部变量的空函数来找出桢指针,返回地址的布局

2.      构造一些没有参数但有局部变量的函数来找出桢指针,返回地址,局部变量的布局

3.      构造一些有参数和局部变量的函数来看一下桢指针,返回地址,局部变量,参数的布局