C语言常见的程序崩溃问题分析
1. 常见的程序崩溃示例
常见的崩溃类型有以下几种:
-
对空指针指向的内存非法写操作 对空指针指向的内存非法读操作 除0操作 大的临时变量或者递归等导致栈溢出 对只读内存进行写操作 堆越界写操作 对已经释放的内存进行写操作
代码示例:
//corruption.c #include <stdlib.h> #include <stdio.h> void make_corruption(int type) { printf("case %d: ", type); switch (type) { case 0: { //对空指针指向的内存非法写操作 char *str = NULL; str[0] = 0; break; } case 1: { //对空指针指向的内存非法读操作 char *str = NULL; int i = str[0]; printf("i=%d", i); break; } case 2: { //除0操作 int i = 10; int j = 0; int ret = i / j; printf("ret=%d", ret); break; } case 3: { //大的临时变量导致栈溢出 //char str[8 * 1024 * 1024] = {0}; //需要验证时再放开该行,否则栈越界必现死机导致其他case无法验证 break; } case 4: { //对只读内存进行写操作 char *str = "abc"; str[0] = 10; break; } case 5: { //堆越界写操作 int *a = (int *)malloc(sizeof(int) * 4); int *b = (int *)malloc(sizeof(int) * 4); printf("a:%p, b:%p ", a, b); memset(a, 0, sizeof(int) * 8); free(a); free(b); break; } case 6: { //对已经释放的内存进行写操作。 #if 0 //没有死机 char *str = (char *)malloc(SMALL_MEMORY); free(str); memset(str, 0, SMALL_MEMORY); #else //coredump char *str = (char *)malloc(LARGE_MEMORY); free(str); memset(str, 0, LARGE_MEMORY); #endif break; } default: { printf("to do. "); } } return; } int main() { int type = 0; scanf("%d", &type); //制造程序崩溃 make_corruption(type); return 0; }
2. 执行结果分析
[root@localhost all_kinds_corrupt]# gcc -g corruption.c -o corrupt [root@localhost all_kinds_corrupt]# ./corrupt 0 case 0: Segmentation fault (core dumped) [root@localhost all_kinds_corrupt]# ./corrupt 1 case 1: Segmentation fault (core dumped) [root@localhost all_kinds_corrupt]# ./corrupt 2 case 2: Floating point exception (core dumped) [root@localhost all_kinds_corrupt]# ./corrupt 4 case 4: Segmentation fault (core dumped) [root@localhost all_kinds_corrupt]# ./corrupt 5 case 5: a:0x1ca9ac0, b:0x1ca9ae0 free(): invalid pointer Segmentation fault (core dumped) [root@localhost all_kinds_corrupt]# ./corrupt 6 case 6:
我们可以看到,case 6在两种不同内存大小情况下表现一同: (1)小内存情况下没有死机,这是因为Glibc会将该内存放到unsorted bin,此时内存没有进行合并或者重新分配出去,可以进行写操作。 (2)对于大内存情况,Glibc会通过调用mmap(大于128K)从系统申请内存,在free之后会直接归还给操作系统,此时进行写操作是非法的。 至于Glibc对于不同大小的内存管理,则需要另外一篇文章来进行详细讲解了。
我们把case 3的代码放开,重新编译执行:
[root@localhost all_kinds_corrupt]# ./corrupt 100 段错误 (核心已转储)
type=100时应该是走进default分支,但实际上程序产生了段错误,其原因就是栈溢出。我们可以通过ulimit -s查看栈的默认大小,通常为8M。
[root@localhost all_kinds_corrupt]# ulimit -s 8192