过程
近期遇到一个问题,一个在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 <stdlib.h>
#include <stdio.h>
#include <string.h>
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;
}