什么是Segment fault

本文介绍Segment fault的概念,并通过代码进行demo。

写一段代码,命名为segfault.c

#include <stdio.h>
#include <unistd.h>

int main() {
  printf("PID of the process: %d\n", getpid());
  char *p = "hello";
  printf("addr of string: %p\n", p);
  
  printf("Press [Enter] key to generate segment fault...\n");
  getchar();
  
  *p = 'H'; // 尝试对只读区域的内存进行写入操作时,会产生Segment fault
}

这段代码可以用来生成segment fault。因为”hello, world”这串字符,是一个常量,C的编译器会把它放到只读的内存区域里面。当试图修改只读区域的内存数据时,就会产生Segment fault。

因此,理解process的内存模型非常重要,要理解process的虚拟内存是如何由操作系统分配并使用的。下面这张图要在脑子里很清楚才可以:

这个图来自于这篇文章:

https://manybutfinite.com/post/anatomy-of-a-program-in-memory/

上面这篇文章里的内容要读透。

接下来编译上面这段代码:

$ cc segfault.c -o segfault

这样会生成可执行文件。然后我们来执行编译后的程序:

可以看到,这个程序产生了段错误并退出了。

我们可以使用MacOS下的vmmap命令,查看process的内存状况。为了查看进程的内存使用状况,我们不可以让这个segfault的程序退出。那么就跑起来这个程序,不按回车键,不让它退出即可:

$ ./segfault
PID of the process: 53545
addr of string: 0x109930f62
Press [Enter] key to generate segment fault...

此时我们可以使用vmmap命令查看这个process,把对应的pid编号传给vmmap指令即可:

$ vmmap 53545

此时你会看到非常详细的关于这个process的内存使用状况。因为输出的报告非常长,大家可以在自己的机器上跑一跑,看一下具体内容。这篇文章里关心的部分是这里:

注意上面截取的内存部分是Non-writable regions,其中TEXT,也就是程序段的内存是:

__TEXT                 0000000109930000-0000000109931000 [    4K     4K     0K     0K] r-x/rwx SM=COW          /Users/weli/Desktop/segfa

注意到上面的地址区间,再回过头看我们程序的输出:

addr of string: 0x109930f62

可以看到,hello, world这串字符的地址,落在了上面的TEXT的Non-writeable区间里面。所以,当我们试图往只读的内存区间写入数据的时候,就会产生segment fault了。

本文介绍了Segment fault的概念,但并没有介绍如何debug段错误的方法,后续会为大家介绍调试Segment fault的方法,也就是coredump的使用。