Sgementationfault原理深入分析前言在我们日常的编程中,很容易遇到的一个程序崩溃的错误就是segmentationfault。在本文中,我们将主要分析段错误的原因!Sgementationfault的原因Sgementationfault的直接原因是程序收到了内核发来的SIGSEGV信号。如果您的程序导致内核向进程发送此信号,那么您的程序正在读取或写入未分配的页面,或者您没有读取或写入权限。这个信号的来源有两个:程序的非法访问,以及自身程序的指令引起的Sgementationfault。另一种是通过其他程序直接向进程发送SIGSEGV信号。在类Linux系统中,内核向进程发送的信号是SIGGEV,信号对应的数字是11。Linux中信号对应的数字大致如下:1)SIGHUP2)SIGINT3)SIGQUIT4)SIGILL5)SIGTRAP6)SIGABRT7)SIGBUS8)SIGFPE9)SIGKILL10)SIGUSR111)SIGSEGV12)SIGUSR213)SIGPIPE14)SIGALRM15)SIGTERM16)SIGSTKFLT17)SIGCHLD18)SIGUSR111)SIGSEGV19))SIGTTIN)24)SIGXCPU25)SIGXFSZ26)SIGVTALRM27)SIGPROF28)SIGWINCH29)SIGIO30)SIGPWR31)SIGSYS34)SIGRTMIN35)SIGRTMIN+136)SIGRTMIN+237)SIGRTMIN+338)SIGRTMIN+539)4SIGRTMIN)SIGRTMIN+641)SIGRTMIN+742)SIGRTMIN+843)SIGRTMIN+944)SIGRTMIN+1045)SIGRTMIN+1146)SIGRTMIN+1247)SIGRTMIN+1348)SIGRTMIN+1449)SIGRTMIN+1550)SIGRTMAX-1451)SIGRTMAX-1352)SIGRTMAX-1253)SIGRTMAX-1154)SIGRTMAX-1055)SIGRTMAX-956)SIGRTMAX-857)SIGRTMAX-758)SIGRTMAX-659)SIGRTMAX-560)SIGRTMAX-461)SIGRTMAX-362)SIGRTMAX-263)SIGRTMAX-164)SIGRTMAX当程序出现段错误时,该程序的退出代码等于139!出现segmentationfault的主要原因之一是我们自己的程序非法访问内存,同时其他程序向这个进程发送SIGSGEV信号也会导致我们的程序出现segmentationfault错误。例如下面的程序是发生越界访问的段错误):#includeintmain(){intarr[10];arr[1<<20]=100;//会导致段错误printf("arr[12]=%d\n",arr[1<<20]);//会导致segmentationfaultreturn0;}下面是另外一个程序,向其他程序发送SIGSGEV信号,会导致其他进程出现segmentfault(下面的终端给出了上面终端的进程编号相等的程序给504092发送了一个信号值等于11(也就是SIGGSGEV)的信号,导致他出现segfault):自定义信号处理函数操作系统允许我们自己定义函数。当向进程发送某些信号时,进程将执行这些函数,而不是系统默认的程序(例如SIGSEGV的默认函数是退出程序)。再来看看我们重写的SIGINT信号处理函数。当程序在终端中执行时,我们按下ctrl+c,正在执行的程序会收到来自内核的SIGINT信号:#include#include#include#include#includevoidsig(intn){//参数n代表值char*str="signalnumber=%d\n";char*out=malloc(128);sprintf(out,海峡,n);写入(STDOUT_FILENO,输出,strlen(输出));free(out);}intmain(){signal(SIGINT,sig);//这行代码是注册函数,当进程收到SIGINT信号时执行sig函数printf("pid=%d\n",getpid());而(1){睡眠(1);}return0;}首先我们要知道,当我们在终端中启动一个程序时,如果我们在终端中按下ctrl+c,终端会向当前运行的进程及其子进程发送一个SIGINT信号,SIGINT信号默认的处理程序是退出程序,但是我们可以捕获这个信号并重写处理程序。在上面的程序中,我们自己重写了SIGINT处理函数。当进程接收到SIGINT信号时,函数sig将被触发。上述程序的输出证实了我们的结果。终端中最常用的是ctrl+c和ctrl+z来中断终端当前正在执行的程序。其实这些也是给我们的程序发送信号,ctrl+c发送SIGINT信号,ctrl+z发送SIGTSTP信号。因此,类似于上述机制,我们可以通过重写处理函数来覆盖相应信号的行为。例如下面的程序使用处理函数的重写方法来处理信号:#include#include#include#include#include#includevoidsig(intno){charout[128];switch(no){caseSIGINT:sprintf(out,"收到SIGINT信号\n");休息;caseSIGTSTP:sprintf(out,"收到SIGSTOP信号\n");休息;}write(STDOUT_FILENO,out,strlen(out));}intmain(){signal(SIGINT,sig);信号(SIGTSTP,信号);while(1){sleep(1);}return0;}现在我们执行这个程序,看看我们何时键入ctrl+z和ctrl+c将显示任何输出。从上面的输出可以看出我们想要的输出已经实现了,说明我们的函数重写已经生效了。segmentationfault的魔力下面是另一个生成SIGSEGV信号的程序,让我们看看这个程序的输出是什么:#include#include#includevoidsig(intn){write(STDOUT_FILENO,"a",1);//这个函数是输出一个字符a到标准输出}intmain(){signal(SIGSEGV,sig);//这是一个注册SIGSEGV错误的函数当操作系统向进程发送SIGSEGV信号时,该函数将被执行int*p;printf("%d\n",*p);//Dereferencinganundefinedpointercausessegmentationfaultreturn0;}我们知道上面的程序肯定会产生segmentationfault错误,会收到SIGSGEV信号,肯定会执行函数sig。但是上面的程序会不断输出a产生死循环。上面程序的结果是不是有点难理解?如果我们想了解这个程序的行为,我们需要了解操作系统是如何处理分段错误的。了解了处理过程后,上面程序的输出就很容易理解了。信号处理函数的执行过程当我们的进程接收到信号时,就会执行我们重写的信号处理函数。如果我们的信号处理函数中没有exit程序或者transfer程序执行流程(可以使用setjmp和longjmp实现),即调用函数正常返回。信号处理函数返回后,会重新执行信号发生处的指令,也就是说,是哪条指令导致操作系统向进程发送信号,当信号处理函数返回时,仍会执行该指令returns,所以我们看到上面的输出结果,因为系统会继续执行发生segmentationfault的那条指令。那么我们如何修复我们的代码,让程序不进入死循环,让程序可以被我们接管。有两种方式:一种是在信号处理函数中进行一些逻辑处理后,使用系统调用_exit直接退出。另一种使用setjmp和longjmp来跳转执行流程的方法。直接用_exit退出#include#include#includevoidsig(intn){printf("直接从这里退出\n");_退出(1);//使用系统调用直接退出}intmain(){signal(SIGSEGV,sig);*(int*)NULL=0;printf("结束\n");//这个打印不会输出return0;}使用控制流跳转#include#include#includejmp_bufenv;voidsig(intn){printf("Ready返回主函数\n");longjmp(env,1);}intmain(){信号(SIGSEGV,sig);if(!setjmp(env)){printf("分段错误\n");*(int*)NULL=0;}else{printf("回到主函数\n");}return0;}总结这篇文章主要介绍了Sgementationfault的原理,自己写了他的信号处理函数,在信号处理函数中发现如果信号处理函数正常退出,那么程序就会进入无限loop,永不停止,会不断产生Sgementationfault,所以我们用两种方式结束程序,一种是不在信号处理函数Exit中直接返回,但是这种情况会有一个缺点。如果我们原来的程序后面还有一些操作,是无法执行的。如果某些程序非常重要,这可能会导致很多错误。第二种方式是我们可以使用setjmp和longjmp来转移控制流,重新回到main函数执行。以上就是本文的全部内容,我是LeHung,我们下期再见!!!更多精彩内容合集可以访问项目:https://github.com/Chang-LeHu...关注公众号:一个没用的研究僧,学习更多计算机知识(Java,Python,计算机系统基础,算法和数据结构)知识。