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

Din*_*esh 17 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;
    }
Run Code Online (Sandbox Code Playgroud)

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

Ada*_*man 21

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

如果你打算抓住信号改变这个

signal(SIGSEGV, SIG_IGN);
Run Code Online (Sandbox Code Playgroud)

对此

signal(SIGSEGV, sighandler);
Run Code Online (Sandbox Code Playgroud)

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

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

printf("%d", *p);
Run Code Online (Sandbox Code Playgroud)

以下内容完全取决于您的平台.

您可以使用它gdb来确定特定汇编指令触发信号的内容.如果您的平台与我的平台一样,您会发现指令是

movl    (%rax), %esi
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

该程序显示:

Handler executed for signal 11
100
Run Code Online (Sandbox Code Playgroud)

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

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


cni*_*tar 14

您可以忽略该信号,但您必须对此进行一些操作.我相信你在发布的代码中所做的事情(忽略SIGSEGV通过SIG_IGN)根本不会起作用,原因在阅读粗体子弹后会变得明显.

当您执行某些操作导致内核向您发送SIGSEGV:

  • 如果你没有信号处理程序,那么内核会杀死进程,就是这样
  • 如果你有一个信号处理程序
    • 你的处理程序被调用
    • 内核重新​​启动违规操作

因此,如果你没有做任何事情,它就会不断循环.如果你SIGSEGV你不退出,从而与正常流量的干扰,你必须:

  • 修复违规操作不会重启的事情
  • 修复内存布局,以便下次运行时可能会出现问题


小智 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;
}
Run Code Online (Sandbox Code Playgroud)

这里的setjmp/longjmp模式类似于try/catch块.但是它风险很大,如果风险函数超出堆栈,或者在释放之前分配资源但崩溃,则无法保存.最好检查你的指针,而不是间接通过坏指针.

  • 据我所知,如果您多次遇到段错误(第二次进程仍然是段错误),这将无效.AFAIU`longjmp` /`setjmp`不能正确处理的信号范围内,和`sigsetjmp` /`siglongjmp`应改为使用.请参阅https://linux.die.net/man/2/setcontext中的"注释" (4认同)