最近的项目因为引入了合作方的代码,多了很多疑难问题,但这些问题也非常有意思,Debug这些问题的过程给了我很多乐趣。
这篇博客是关于一个由#pragma引起的问题,Debug过程中充满了挑战和惊喜,不停的需找线索的过程,也是一个不断提升自身水平的过程。
Segmentaion Fault
在某次提交代码以后,我们跑回归测试用例,出现了segmentation fault的情况。马上gdb attach上去看进程调用栈。
|
|
可以看到,第7行的赋值语句导致了SegFault,细看就能看到this指针是一个非法值0x18000000,定位到这一步以后,我们做了第一次推测,很有可能是踩内存了。
为了证明我们的推测,gdb返回上一层堆栈打印Component类的指针,结果出乎我们意料。
这时看到的Component类指针_com是正常的,我一度非常怀疑g++的编译器出问题了,我甚至翻阅了很多关于C++编译成员函数的资料,事实证明:一切问题都应该先从自身上找原因。
所以,我又把board的内存打印出来,看是不是有什么线索。
细心的话会发现,原本_com的指针为0x804b018,即第一行后4个字节。而this指针为0x18000000,正好是第一行从第二个字节开始的4个字节。似乎有点头绪了…
汇编
从上面的初步分析,我们基本锁定了问题的范围,在函数调用前,指针是正常的,进入函数以后指针变了。难道是函数调用出了问题?为了进一步分析,可以使用gdb的汇编调试功能,如果不熟悉的可以参考Lenky的博客。以下打印出调用set的汇编代码:
在0x08048547行,这一行代码就是获得_com指针,前面我们看到board的偏移4个字节以后取到Component的指针,而这一行汇编代码只偏移1个字节。刹那间,4个字突然跳到我的脑海里 – 字节对齐。
我们知道在C语言里,在没有声明字节对齐的情况下,编译器会按4字节或8字节对齐。
Board类的结构如下
这里没有声明字节对齐的方式,系统是32位的,理应按4字节对齐,这个与我们看到的Board的内存是一致的。可是为什么在调用的地方,汇编代码是按1字节来偏移呢?编译器编译调用点时理解这个结构是1字节对齐,而该结构定义并没有声明1字节对齐,那肯定是别的地方影响了。结果一翻看前面几个头文件,居然有一处这样的代码:
这一出#pragma push没有对应的#pragma pop,这个头文件在Board的头文件之前,所以导致Board结构也是按1字节对齐,而由于board申请内存定义的地方没有这个问题,是按4字节对齐,所以才导致了this指针错乱的结果。
总结
终于真相大白了,#pragma引发的血案宣布告破。
对于该编程问题的引入,我就不说什么了。对于该问题的调试,我总结出的结论是:以怀疑一切的眼光看待问题,同时要多方面去考虑问题,这样“问题”就不是什么“问题”了。