About Segmentation Fault in Linux ( SIGSEGV )

Jan 20, 2010

刚拜读了一篇《Segmentation fault in linux.pdf》,下面是自己总结的一些内容:

segmentation fault引发内核产生SIGSEGV信号;SIGSEGV是在访问内存时发生的错误,当用户态程序访问不允许访问内存,或者以错误的方式访问允许访问的内存时产生SIGSEGV。

下面来说下常见的几种引发SIGSEGV的情况:

1. 错误的访问类型引发SIGSEGV

#include <stdio.h>
#include <stdlib.h>
int main() {
     char* s = "hello world";
     s[1] = 'H';
}

上面的函数引发SIGSEGV是因为“hello world”作为一个常量字符串,在编译链接后会放在ELF可执行目标文件的.rodata(存储只读数据,比如printf语句中的格式和开关语句的跳转表)部分,由于这片内存区域是只读的,这就引起可SIGSEGV.

2.访问了不属于进程地址空间的内存

#include <stdio.h>
#include <stdlib.h>

int main() {
     int* p = (int*)0xC0000fff;
     *p = 10;
}

众所周知,linux地址空间顶部的四分之一是预留给内核的。即0xffffffff到0xc0000000是用户代码不可见的存储器,上述程序访问的0xc0000fff刚好访问的是这片地址空间。

3.访问了不存在的内存

1 #include 2 #include 3 4 int main () { 5     int *a = NULL; 6     *a = 1; 7 } *   这个很显然呐....

4.栈溢出了,有可能引发SIGSEGV(栈溢出是缓存区溢出的一种)

#include <stdio.h>
#include <stdlib.h>

int* foo() {
     int a = 10;

     return &a;
}

int main() {
     int* b;

     b = foo();
     printf ("%d\n", *b);
}

如上程序编译时会报:“warning: function returns address of local variable”,这是编译器在提醒你,该程序由栈溢出的危险。按照常理,&a在foo运行结束后应该被是释放,再以*b访问,相对于当前的程 序是发生了栈溢出,但是该沉序实际运行正常,这是因为linux中典型的页的大小为4K,当栈溢的幅度小于页的大小时,不会产生SIGSEGV.

#include <stdio.h>
#include <stdlib.h>


char* foo() {
      char buf[8192];

      memset (buf, 0x55, sizeof(buf));
      return buf;
}

int main() {
      char* c;

      c = foo();
      printf ("%#x\n", c[5000]);
}

虽然上面程序的栈溢已经超出了 4K 大小,可运行仍然正常。这是因为 C 教程中提到的“栈自动释放”实际上是改变栈指针,而其指向的内存,并不是在函数返回时就被回收了。在我们的例子中,所访问的栈溢处内存仍然存在。无效的栈 内存(即栈指针范围外未被回收的栈内存)是由操作系统在需要时回收的,这是无法预测的,也就无法预测何时访问非法的栈内容会引发 SIGSEGV。
再看下面的程序,它访问一个未分配的栈内存:

#include <stdio.h>
#include <stdlib.h>

int main() {
      char* c;

     c = (char*)&c – 8192 *2;
     *c = 'a';
     printf ("%c\n", *c);
}

该函数的栈溢为16K,但是我们依旧没有看到SIGSEGIV,这是因为在内核的page fault处理函数规定,栈溢小于64k左右都是没有问题的,栈会自动扩展。

5.关于堆

#include <stdio.h>
#include <stdlib.h>

#define K 1024
int main () {
     char* c;
     int i = 0;

     c = malloc (1);
     while (1) {
        c += i*K;
        *c = 'a';
        printf ("overflow %dK\n", i);
        i ++;
     }
}

如上函数,对应malloc的大小不同,SIGSEGV推迟溢出的大小也不同。
再看一个:

#include <stdio.h>
#include <stdlib.h>
#define K 1024
int main () {
     int* a;

     a = malloc (sizeof(int));
     *a = 100;
     printf ("%d\n", *a);
     free (a);
     printf ("%d\n", *a);
}

上面的程序不一定会出现SIGSEGV,至于这其中的原委,看了栈溢出的话大家应该已经明白了吧:free后的内存空间不会立即归还给操作系统。

6.函数跳转到非法的地址上执行。

这个跟上次的那个缓存区溢出实验中 Featherain的做法差不多,就是通过某种手段把函数的返回地址给改了...

Comments

  1. 小伙子很有前途呀

    Avatar
  2. ........留言审核取消了好么

    Avatar
  3. 小白,你去帮我过二级吧…呜呜

    Avatar
  4. 小伙子有前途!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!才怪!

    Avatar

Get In Touch With Us ...