为啥我不能忽略 SIGSEGV 信号?

Posted

技术标签:

【中文标题】为啥我不能忽略 SIGSEGV 信号?【英文标题】:Why can't I ignore SIGSEGV signal?为什么我不能忽略 SIGSEGV 信号? 【发布时间】:2011-12-10 11:09:04 【问题描述】:

这是我的代码,

#include<signal.h>
#include<stdio.h>

int main(int argc,char ** argv)
   
     char *p=NULL;
     signal(SIGSEGV,SIG_IGN); //Ignoring the Signal
     printf("%d",*p);
     printf("Stack Overflow"); //This has to be printed. Right?
   return 0;
    

在执行代码时,我遇到了分段错误。我忽略了使用 SIG_IGN 的信号。所以我不应该得到分段错误。对?然后,打印 '*p' 值后的printf() 语句也必须执行。对?

【问题讨论】:

会有一段时间,编写吞下段错误的代码会被认为足以让程序员入狱。 【参考方案1】:

您的代码忽略了 SIGSEGV 而不是捕获它。回想一下,触发信号的指令在处理信号后重新启动。在你的情况下,处理信号并没有改变任何东西,所以下一次尝试违规指令时,它会以同样的方式失败。

如果你打算捕捉信号改变这个

signal(SIGSEGV, SIG_IGN);

到这里

signal(SIGSEGV, sighandler);

您可能还应该使用sigaction() 而不是signal()。请参阅相关手册页。

在您的情况下,违规指令是尝试取消引用 NULL 指针的指令。

printf("%d", *p);

接下来的内容完全取决于您的平台。

您可以使用gdb 来确定触发信号的特定汇编指令。如果你的平台和我的一样,你会发现指令是

movl    (%rax), %esi

rax 寄存器的值为 0,即NULL。在信号处理程序中解决此问题的一种(不可移植!)方法是使用处理程序获得的第三个参数信号,即用户上下文。这是一个例子:

#include <signal.h>
#include <stdio.h>

#define __USE_GNU
#include <ucontext.h>

int *p = NULL;
int n = 100;

void sighandler(int signo, siginfo_t *si, ucontext_t* context)

  printf("Handler executed for signal %d\n", signo);
  context->uc_mcontext.gregs[REG_RAX] = &n;


int main(int argc,char ** argv)

  signal(SIGSEGV, sighandler);
  printf("%d\n", *p); // ... movl (%rax), %esi ...
  return 0;

这个程序显示:

Handler executed for signal 11
100

它首先通过尝试取消引用 NULL 地址来执行处理程序。然后处理程序通过将 rax 设置为变量n 的地址来解决此问题。一旦处理程序返回系统重试违规指令,这次成功。 printf() 接收 100 作为其第二个参数。

不过,我强烈建议不要在您的程序中使用此类不可移植的解决方案。

【讨论】:

感谢您的详细解释亚当 :)【参考方案2】:

你可以忽略这个信号,但你必须对它做点什么。我相信您在发布的代码中所做的事情(通过 SIG_IGN 忽略 SIGSEGV根本不起作用,原因在阅读粗体项目符号后将变得显而易见。

当你做了一些导致内核向你发送 SIGSEGV 的事情时:

如果您没有信号处理程序,内核会终止进程,仅此而已 如果您有信号处理程序 您的处理程序被调用 内核重新启动有问题的操作

所以如果你不做任何事情,它就会不断循环。如果你确实捕获了SIGSEGV,但你没有退出,从而干扰了正常流程,你必须:

修复问题,使有问题的操作不会重新启动或 修复内存布局,使有问题的内容在 下次运行

【讨论】:

好的,那我到底该怎么做呢? @Dinesh,你想要完成什么? @ibid,在代码中,我正在尝试访问内存空内存。所以它会导致产生 SIGSEGV 信号。但我为它制作了一个处理程序,它只会打印“Catching the signal”。然后必须执行位于 main() 中的语句“return 0”。对吗? @Dinesh 目前,您发布的代码只是忽略了信号,它没有为它建立信号处理程序 (sighandler)。即使是这样,我怀疑它会不断打印“捕捉信号”。 @cnicutar,哦,那你能告诉我如何为此添加 sighandler 吗?你能给我任何可以了解的链接吗【参考方案3】:

另一种选择是用 setjmp/longjmp 括起来有风险的操作,即

#include <setjmp.h>
#include <signal.h>

static jmp_buf jbuf;
static void catch_segv()

    longjmp(jbuf, 1);


int main()

    int *p = NULL;

    signal(SIGSEGV, catch_segv);
    if (setjmp(jbuf) == 0) 
        printf("%d\n", *p);
     else 
        printf("Ouch! I crashed!\n");
    
    return 0;

这里的 setjmp/longjmp 模式类似于 try/catch 块。虽然它非常冒险,如果您的风险函数超出堆栈,或者分配资源但在它们被释放之前崩溃,则不会拯救您。最好检查你的指针,而不是通过坏指针间接检查。

【讨论】:

据我所知,如果您多次遇到段错误(第二次进程仍然段错误),这将不起作用。 AFAIU longjmp/setjmp 没有正确处理信号上下文,应该使用 sigsetjmp/siglongjmp 代替。参见linux.die.net/man/2/setcontext中的“注释”【参考方案4】:

试图忽略或处理 SIGSEGV 是错误的方法。由您的程序总是触发的 SIGSEGV 表示存在错误。在您的代码或您委托的代码中。一旦触发了错误,任何事情都可能发生。信号处理程序无法执行合理的“清理”或修复操作,因为它不知道信号在哪里触发或执行什么操作。你能做的最好的事情就是让程序快速失败,这样程序员就有机会在它仍然处于立即失败状态时对其进行调试,而不是在失败的原因已经确定后让它(可能)失败模糊不清。如果不尝试忽略或处理信号,您可能会导致程序快速失败。

【讨论】:

这不是 总是 一个错误。一些 JVM 或 javascript 引擎有时会将数组的末尾放在一个页面的末尾,然后是一个未映射的页面,从而将边界检查卸载到硬件。 SIGSEGV 意味着来宾 Java 或 Javascript 代码发生了数组越界异常,而不是 JVM 本身有问题。但是,是的,在这样的计划案例之外,这是一个错误,不应被忽视。 程序还可以专门 mprotect() 一个内存范围,并使用 SIGSEGV 处理程序来了解该范围内的地址被访问过。

以上是关于为啥我不能忽略 SIGSEGV 信号?的主要内容,如果未能解决你的问题,请参考以下文章

当我尝试从双向链表中删除最后一个元素时,为啥会收到“信号 SIGSEGV,分段错误”?

为啥启用 NEON SIMDization 时 Android 会崩溃?信号 11 (SIGSEGV),代码 1 (SEGV_MAPERR)

Django 1.6 + RabbitMQ 3.2.3 + Celery 3.1.9 - 为啥我的芹菜工人死于:WorkerLostError:工人过早退出:信号11(SIGSEGV)

Fatal signal xx (SIGSEGV) at

如何在不通过信号的情况下进行申请?

如何在tid xxxxx(Thread-X)中解决Android致命信号11(SIGSEGV),代码1,故障地址0x0?