近期遇到一个问题,一个在Linux下运行的C语言程序,突然因为某段代码合入后,就导致程序稳定崩溃!
程序崩溃的位置只是具有一个大方向,崩溃堆栈位置不定,而且是某些不应该出现的运行堆栈逻辑,以及曾出现过非法指令的信号异常core文件。
通过逐步试验,发现:
1、撤销掉新增的代码合入,就不会出现问题;还据此发布了版本
2、如果使用O0编译程序,就不会出现崩溃问题;但使用O3优化编译就出现崩溃。怀疑是因为某些编译器代码优化导致的问题,这个就深层次了:( 看着听吓人的!
3、旁路掉某一个模块的代码,也不会出现问题
当时,是一头雾水,搞不清楚明确的方向!
幸运的时,将研究的重点放在旁路的模块中,通过对代码不断二分旁路,找到问题的根源在于,某段代码对于栈内存的非法覆盖。
- // 出错代码示意
-
- typedef struct {
- int msgType;
- unsigned int msgLen;
- // C语言结构体的惯用法,指向头部区域后面第一个字节
- unsigned char abData[0];
- } T_SomeAck;
-
-
-
-
- void f(T_SomeAck* ptAck)
- {
- ...
-
- memcpy(ptAck->abData, srcData, someSize);
-
- ...
- }
-
-
-
- int main(int argc, char **argv)
- {
- // 栈上没有分配那么大的内存;
- // 解决办法:
- // 1、结构体定义足够内存
- // 2、符合C语言结构体此类惯用法,从大的缓冲区中转型;用具体结构体定义,区分头部和尾部
-
- T_SomeAck tAck;
-
- ...
-
- f(&tAck);
-
- ....
-
- return 0;
-
- }
-
-
回想整个问题解决过程,才逐渐明白过来,就一个非法指令的信号异常处理现象,以及其它莫名奇妙、不该运行的崩溃堆栈,就可以怀疑到是因为内存被非法覆盖后引起的崩溃问题。
因为非法覆盖的是函数返回地址等关键信息,最终导致的崩溃!
照例说,valgrind可以发现栈内存越界的问题,后面再试试故障场景是否可以使用工具发现
- # valgrind 可以检查出此处的内存异常
-
- #include
- #include
- #include
-
- static int foo(void)
- {
- int a = 1;
- char b[32];
-
- printf("before memset\n");
- memset(b, 0, 64); /* stomps the stack */
- printf("after memset\n");
-
- return 0;
- }
-
-
- int main(int argc, char **argv)
- {
- int x;
-
- printf("before foo()\n");
- x = foo();
- printf("after foo()\n");
- return 0;
- }