segment fault 段错误 (core dumped)的起因分析(转)

内核使用内存描述符结构体表示进程的地址空间,该结构体包含了和进程地址空间有关的全部信息。内存描述符由mm_struct结构体表示,定义在文件<linux/sched.h>中。进程地址空间由每个进程的线性地址区(vm_area_struct)组成。通过内核,进程可以给自己的地址空间动态的添加或减少线性区域。

 

 

 

mm_users域记录正在使用该地址的进程数目。比如,如果两个进程共享该地址空间,那么mm_users的值便等于2;mm_count域是mm_struct的结构体的主引用计数,只要 mm_users不为0,那么mm_count值就等于1.当mm_users值减为0(两个线程都退出)时,mm_count域的值才变为0。如果mm_count的值等于0,说明已经没有任何指向该mm_struct结构 体的引用了,这时该结构体会被销毁。mmap和mm_rb这两个不同的数据结构描述的对象是相同的:该地址空间中的全部内存区域。

而fork系统调用产生的子进程中的mm_struct结构体实际是通过文件kernel/fork.c中的alloc_mm()宏从mm_cachep slab缓存中分配得到的。通常,每个进程都有一个唯一的 mm_struct结构体,即唯一的进程地址空间。是否共享地址空间,几乎是进程和Linux中所谓线程间本质上的唯一区别。

进程就是通过mm_struct 来描述3G的线性地址空间,mm_struct通过

划分为一个个线性区,而每个线性区用一个 vm_area_struct来描述,其实我们的mm_struct是与我们的可执行文件的结构体相关联的,首先看我们的mm_struct:

 

 

从上面可以看出mm_struct 包含了进程start_code,end_code,start_data,end_data等section段描述的结构,我们来看一个可执行文件的构成来看看,就拿上面的hello

[qiqi@localhost test]$ objdump -h hello

hello: file format elf32-i386

Sections:
Idx Name Size VMA LMA File off Algn
0 .interp 00000013 08048134 08048134 00000134 2**0

VMA代表虚拟地址,这里表示我们的程序运行是的虚拟地址起为 0x08048134

  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .note.ABI-tag 00000020  08048148  08048148  00000148  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  ......
 10 .init         00000030  080482f4  080482f4  000002f4  2**2          init加载头最开始执行的
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 11 .plt          00000070  08048324  08048324  00000324  2**2       跳转我们程序执行的头
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 12 .text         000001cc  080483a0  080483a0  000003a0  2**4   ..即我们的代码段
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 13 .fini         0000001c  0804856c  0804856c  0000056c  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 14 .rodata       00000018  08048588  08048588  00000588  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
....
 20 .dynamic      000000c8  08049654  08049654  00000654  2**2 
                  CONTENTS, ALLOC, LOAD, DATA
 21 .got          00000004  0804971c  0804971c  0000071c  2**2
                  CONTENTS, ALLOC, LOAD, DATA
....
 23 .data         00000004  08049744  08049744  00000744  2**2            数据段
                  CONTENTS, ALLOC, LOAD, DATA
 24 .bss          00000008  08049748  08049748  00000748  2**2         未初始化段
                  ALLOC
 25 .comment      0000002c  00000000  00000000  00000748  2**0    未初始化的符号表
                  CONTENTS, READONLY

由链接过程可以看出main并不是真正的入口,是从.plt段中跳转过来的所有的程序都是从这里进入的,有一相当于jmp *main的跳转指令,

同时可以看出我们的可执行文件被分成以一个不同的section,这就和我们mm_struct中的线性区描述符对应起来了,比如我们的.data段就被加载到start_data与end_data地址空间的地方,其它的段与此类似!!!!!!,

 

那么为什么我们产生segment fault 呢,???因为我们访问的地址没有映射啊,!!!就是地址没有在mm_struct中描述的空间中,因为我们的

mm_struct并没有映射整个3G的空间啊,因为我们程序的每个段都是有大小限制的,.data,.text,.bss,.common等已经指名的section以外,

还有两个非常重要的线性区start_stack,end_stack(栈)与start_brk,end_brk(堆),如果我们访问的线性地址没有在这些地址空间中,那么就会产生segment fault,



造成程序core dump的原因很多,这里根据以往的经验总结一下:

1 内存访问越界

  a) 由于使用错误的下标,导致数组访问越界

  b) 搜索字符串时,依靠字符串结束符来判断字符串是否结束,但是字符串没有正常的使用结束符

  c) 使用strcpy, strcat, sprintf, strcmp, strcasecmp等字符串操作函数,将目标字符串读/写爆。应该使用strncpy, strlcpy, strncat, strlcat, snprintf, strncmp, strncasecmp等函数防止读写越界。

 

2 多线程程序使用了线程不安全的函数。

应该使用下面这些可重入的函数,尤其注意红色标示出来的函数,它们很容易被用错:

asctime_r(3c) gethostbyname_r(3n) getservbyname_r(3n) ctermid_r(3s) gethostent_r(3n) getservbyport_r(3n) ctime_r(3c) getlogin_r(3c) getservent_r(3n) fgetgrent_r(3c) getnetbyaddr_r(3n) getspent_r(3c) fgetpwent_r(3c) getnetbyname_r(3n) getspnam_r(3c) fgetspent_r(3c) getnetent_r(3n) gmtime_r(3c) gamma_r(3m) getnetgrent_r(3n) lgamma_r(3m) getauclassent_r(3) getprotobyname_r(3n) localtime_r(3c) getauclassnam_r(3) etprotobynumber_r(3n) nis_sperror_r(3n) getauevent_r(3) getprotoent_r(3n) rand_r(3c) getauevnam_r(3) getpwent_r(3c) readdir_r(3c) getauevnum_r(3) getpwnam_r(3c) strtok_r(3c) getgrent_r(3c) getpwuid_r(3c) tmpnam_r(3s) getgrgid_r(3c) getrpcbyname_r(3n) ttyname_r(3c) getgrnam_r(3c) getrpcbynumber_r(3n) gethostbyaddr_r(3n) getrpcent_r(3n)

 

3 多线程读写的数据未加锁保护。

对于会被多个线程同时访问的全局数据,应该注意加锁保护,否则很容易造成core dump

 

4 非法指针

  a) 使用空指针

  b) 随意使用指针转换。一个指向一段内存的指针,除非确定这段内存原先就分配为某种结构或类型,或者这种结构或类型的数组,否则不要将它转换为这种结构或类型的指针,而应该将这段内存拷贝到一个这种结构或类型中,再访问这个结构或类型。这是因为如果这段内存的开始地址不是按照这种结构或类型对齐的,那么访问它时就很容易因为bus error而core dump.

 

5 堆栈溢出

不要使用大的局部变量(因为局部变量都分配在栈上),这样容易造成堆栈溢出,破坏系统的栈和堆结构,导致出现莫名其妙的错误。

 

转自:https://blog.csdn.net/melong100/article/details/6433047

https://blog.csdn.net/bzhxuexi/article/details/17840941

posted @ 2022-06-05 18:08  鸭子船长  阅读(410)  评论(0编辑  收藏  举报