Segmentation fault in Linux

来源:互联网 发布:淘宝买精密管警察找我 编辑:程序博客网 时间:2024/06/06 02:03
使用映射可能涉及到如下信号
SIGSEGV    试图对只读映射区域进行写操作

SIGBUS     试图访问一块无文件内容对应的内存区域,比如超过文件尾的内存区域,或者以前有文件内容对应,现在为另一进程截断过的内存区域。

  A segmentation fault (often shortened to SIGSEGV) is a particular error condition that can occur during the operation of computer software. A segmentation fault occurs when a program attempts to access a memory location that it is not allowed to access, or attempts to access a memory location in a way that is not allowed (for example, attempting to write to a read-only location, or to overwrite part of the operating system).

    Segmentation is one approach to memory management and protection in the operating system. It has been superseded by paging for most purposes, but much of the terminology of segmentation is still used, "segmentation fault" being an example. Some operating systems still have segmentation at some logical level although paging is used as the main memory management policy.

    On Unix-like operating systems, a process that accesses an invalid memory address receives the SIGSEGV signal. On Microsoft Windows, a process that accesses invalid memory receives the STATUS_ACCESS_VIOLATION exception.

        就是:所谓的段错误就是指访问的内存超出了系统所给这个程序的内存空间,通常这个值是由gdtr来保存的,他是一个48位的寄存器,其中的32位是保存由它指向的gdt表,后13位保存相应于gdt的下标,最后3位包括了程序是否在内存中以及程序的在cpu中的运行级别,指向的gdt是由以64位为一个单位的表,在这张表中就保存着程序运行的代码段以及数据段的起始地址以及与此相应的段限和页面交换还有程序运行级别还有内存粒度等等的信息。一旦一个程序发生了越界访问,cpu就会产生相应的异常保护,于是segmentation fault就出现了。

        即“当程序试图访问不被允许访问的内存区域(比如,尝试写一块属于操作系统的内存),或以错误的类型访问内存区域(比如,尝试写一块只读内存)。这个描述是准确的。为了加深理解,我们再更加详细的概括一下SIGSEGV。段错误应该就是访问了不可访问的内存,这个内存区要么是不存在的,要么是受到系统保护的。

Ø  SIGSEGV是在访问内存时发生的错误,它属于内存管理的范畴

Ø  SIGSEGV是一个用户态的概念,是操作系统在用户态程序错误访问内存时所做出的处理。

Ø  当用户态程序访问(访问表示读、写或执行)不允许访问的内存时,产生SIGSEGV。

Ø  当用户态程序以错误的方式访问允许访问的内存时,产生SIGSEGV。

用户态程序地址空间,特指程序可以访问的地址空间范围。

如果广义的说,一个进程的地址空间应该包括内核空间部分,只是它不能访问而已

二、SIGSEGV产生的可能情况
        指针越界和SIGSEGV是最常出现的情况,经常看到有帖子把两者混淆,而这两者的关系也确实微妙。

在此,我们把指针运算(加减)引起的越界、野指针、空指针都归为指针越界。

SIGSEGV在很多时候是由于指针越界引起的,但并不是所有的指针越界都会引发SIGSEGV。

一个越界的指针,如果不解引用它,是不会引起SIGSEGV的。而即使解引用了一个越界的指针,也不一定会引起SIGSEGV。

这听上去让人发疯,而实际情况确实如此。

SIGSEGV涉及到操作系统、C库、编译器、链接器各方面的内容,我们以一些具体的例子来说明。

(1)错误的访问类型引起

#include <stdio.h>#include <stdlib.h>int main(){    char *c = "hello world";    c[1] = 'H';}
 上述程序编译没有问题,但是运行时弹出SIGSEGV。此例中,”hello world”作为一个常量字符串,
在编译后会被放在.rodata节(GCC),最后链接生成目标程序时.rodata节会被合并到text segment与代码段放在一起,
故其所处内存区域是只读的。这就是错误的访问类型引起的SIGSEGV。

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

#include <stdio.h>#include <stdlib.h>int main(){    int* p = (int*)0xC0000fff;    *p = 10;} 

(3)访问了不存在的内存

最常见的情况不外乎解引用空指针了,如:int *p = null;*p = 1;在实际情况中,此例中的空指针可能指向用户态地址空间,但其所指向的页面实际不存在。

(4)内存越界,数组越界,变量类型不一致等

#include <stdio.h>int  main(){        char test[1];        printf("%c", test[10]);        return 0;} 

(5)试图把一个整数按照字符串的方式输出

int  main(){      int b = 10;        printf("%s\n", b);        return 0;} 
这是什么问题呢?由于还不熟悉调试动态链接库,所以我只是找到了printf的源代码的这里。
声明部分:
    int pos =0 ,cnt_printed_chars =0 ,i ;
  unsigned char *chptr ;
  va_list ap ;
%s格式控制部分:
case 's':
      chptr =va_arg (ap ,unsigned char *);
      i =0 ;
      while (chptr [i ])
      {...
          cnt_printed_chars ++;
          putchar (chptr [i ++]);
  }
​仔细看看,发现了这样一个问题,在打印字符串的时候,实际上是打印某个地址开始的所有字符,但是当你想把整数当字符串打印的时候,这个整数被当成了一个地址,然后printf从这个地址开始去打印字符,直到某个位置上的值为\0。所以,如果这个整数代表的地址不存在或者不可访问,自然也是访问了不该访问的内存——segmentation fault。
    ​    ​类似的,还有诸如:sprintf等的格式控制问题,比如,试图把char型或者是int的按照%s输出或存放起来,如:
#include <stdio.h>#include <string.h>char c='c';int i=10;char buf[100];printf("%s", c);        //试图把char型按照字符串格式输出,这里的字符会解释成整数,再解释成地址,所以原因同上面那个例子printf("%s", i);            //试图把int型按照字符串输出memset(buf, 0, 100);sprintf(buf, "%s", c);    //试图把char型按照字符串格式转换memset(buf, 0, 100);sprintf(buf, "%s", i);   //试图把int型按照字符串转换

(6)栈溢出了,有时SIGSEGV,有时却啥都没发生

    ​    ​大部分C语言教材都会告诉你,当从一个函数返回后,该函数栈上的内容会被自动“释放”。“释放”给大多数初学者的印象是free(),似乎这块内存不存在了,于是当他访问这块应该不存在的内存时,发现一切都好,便陷入了深深的疑惑。




原创粉丝点击