为什么我不能忽略SIGSEGV信号?

时间:2011-12-10 11:09:04

标签: c unix signals

这是我的代码,

#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()语句也必须执行。对?

4 个答案:

答案 0 :(得分:20)

您的代码忽略了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作为其第二个参数。

我强烈建议您不要在程序中使用此类非便携式解决方案。

答案 1 :(得分:14)

您可以忽略该信号,但您必须对此进行一些操作。我相信您在发布的代码中所做的事情(忽略SIGSEGV通过SIG_IGN将无法正常工作,原因在阅读粗体项目后会变得明显。< / p>

当你做一些导致内核向你发送SIGSEGV的东西时:

  • 如果您没有信号处理程序,则内核会终止进程,那就是
  • 如果你有信号处理程序
    • 您的处理程序被调用
    • 内核重新启动违规操作

因此,如果你没有做任何事情,它就会不断循环。如果您执行捕获SIGSEGV并且您没有退出,从而干扰正常流程,您必须:

  • 解决违规操作无法重启或
  • 的问题
  • 修复内存布局,以便攻击的内容确定无误 下次运行

答案 2 :(得分:10)

另一种选择是将风险操作括在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块。但它风险很大,如果您的风险函数超出堆栈,或者在资源被释放之前分配资源但崩溃,则不会挽救您。最好检查你的指针,而不是通过坏指针进行间接检查。

答案 3 :(得分:1)

尝试忽略或处理SIGSEGV是错误的方法。由程序始终触发的SIGSEGV表示错误。在您的代码中或您委托的代码中。触发错误后,任何事情都可能发生。信号处理程序无法执行合理的“清除”或修复操作,因为它不知道在何处触发了信号或执行了什么操作。最好的办法就是让程序快速失败,这样程序员就可以在仍然处于立即失败状态的情况下对其进行调试,而不必等到导致失败的原因使程序以后(可能)失败。模糊。通过不尝试忽略或处理信号,可以使程序快速失败。