深入剖析Sgementation fault-icode9原理


前言

我们在日常的编程当中,我们很容易遇到的一个程序崩溃的错误就是segmentation fault,在本篇文章当中将主要分析段错误发生的原因!

Sgementation fault发生的原因

发生Sgementation fault的直接原因是,程序收到一个来自内核的SIGSEGV信号,如果是你的程序导致的内核给进程发送这个信号的话,那么就是你的程序正在读或者写一个没有分配的页面或者你没有读或者写的权限。这个信号的来源有两个:

  • 程序的非法访问,自身程序的指令导致的Sgementation fault。
  • 另外一种是由别的程序直接发送SIGSEGV信号给这个进程。

在类Linux系统中,内核给进程发送的信号为SIGGEV,信号对应数字为11,在Linux当中信号对应的数字情况大致如下所示:


 
  1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
  6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
  11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
  16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
  21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
  26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
  31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
  38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
  43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
  48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
  53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
  58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
  63) SIGRTMAX-1 64) SIGRTMAX

当一个程序发生 segmentation fault 的时候,这个程序的退出码 exitcode 等于 139!
深入剖析Sgementation fault-icode9原理

发生 segmentation fault 的一个主要的原因是我们自己的程序发生非法访问内存,同时别的程序给这个进程发送 SIGSGEV 信号也会导致我们的程序发生 segmentation fault 错误。

比如下面的程序就是自己发生的段错误(发生了越界访问):


 
  #include <stdio.h>
   
  int main() {
   
  int arr[10];
  arr[1 << 20] = 100; // 会导致 segmentation fault
  printf("arr[12] = %d/n", arr[1 << 20]); // 会导致 segmentation fault
  return 0;
  }

下面是一个别的程序给其他程序发送SIGSGEV信号会导致其他进程出现段错误(下面的终端给上面终端的进程号等于504092的程序发送了一个信号值等于11(就是SIGGSGEV)信号,让他发生段错误):
深入剖析Sgementation fault-icode9原理

自定义信号处理函数

操作系统允许我们自己定义函数,当某些信号被发送到进程之后,进程就会去执行这些函数,而不是系统默认的程序(比如说SIGSEGV默认函数是退出程序)。下面来看我们重写SIGINT信号的处理函数,当一个程序在终端执行的时候我们按下ctrl+c,这个正在执行的程序就会收到一个来自内核的SIGINT信号:


 
  #include <stdio.h>
  #include <unistd.h>
  #include <stdlib.h>
  #include <signal.h>
  #include <string.h>
   
  void sig(int n) { // 参数 n 表示代表信号的数值
  char* str = "signal number = %d/n";
  char* out = malloc(128);
  sprintf(out, str, n);
  write(STDOUT_FILENO, out, strlen(out));
  free(out);
  }
   
  int main() {
  signal(SIGINT, sig); // 这行代码就是注册函数 当进程收到 SIGINT 信号的时候就执行 sig 函数
  printf("pid = %d/n", getpid());
  while (1)
  {
  sleep(1);
  }
   
  return 0;
  }

深入剖析Sgementation fault-icode9原理

首先我们需要知道,当我们在终端启动一个程序之后,如果我们在终端按下ctrl+c终端会给当前正在运行的进程以及他的子进程发送SIGINT信号,SIGINT信号的默认处理函数就是退出程序,但是我们可以捕获这个信号,重写处理函数。在上面的程序当中我们就自己重写了SIGINT的处理函数,当进程接收到 SIGINT 信号的时候就会触发函数 sig 。上面程序的输出印证了我们的结果。

我们在终端当中最常用的就是ctrl+c 和 ctrl + z 去中断当前终端正在执行的程序,其实这些也是给我们的程序发送信号,ctrl+c发送SIGINT信号ctrl+z发送SIGTSTP信号。因此和上面的机制类似,我们可以使用处理函数重写的方式,覆盖对应的信号的行为,比如下面的程序就是使用处理函数重写的方式进行信号处理:


 
  #include <stdio.h>
  #include <signal.h>
  #include <string.h>
  #include <stdlib.h>
  #include <fcntl.h>
  #include <unistd.h>
   
  void sig(int no) {
  char out[128];
  switch(no) {
  case SIGINT:
  sprintf(out, "received SIGINT signal/n");
  break;
  case SIGTSTP:
  sprintf(out, "received SIGSTOP signal/n");
  break;
  }
  write(STDOUT_FILENO, out, strlen(out));
  }
   
  int main() {
  signal(SIGINT, sig);
  signal(SIGTSTP, sig);
  while(1) {sleep(1);}
  return 0;
  }

现在我们执行这个程序然后看看当我们输入ctrl+z和ctrl+c会出现有什么输出。
深入剖析Sgementation fault-icode9原理

从上面的输出我们可以看到实现了我们想要的输出结果,说明我们的函数重写生效了。

本站声明:
1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;

2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;

3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;

4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;

5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/294622.html

(0)
上一篇 2022年12月5日
下一篇 2022年12月5日

相关推荐

发表回复

登录后才能评论