SIGSEGV 处理程序中的段错误

Segfault in SIGSEGV handler

假设我有以下 C 代码:

static void handler(int sig, siginfo_t *si, void *unused)
{
    printf("BOOM!\n");

    //another segfault here

    exit(-1);
}

int main(int argc, char *argv[])
{
    struct sigaction sa;

    sa.sa_flags = SA_SIGINFO;
    sigemptyset(&sa.sa_mask);
    sa.sa_sigaction = handler;
    if (sigaction(SIGSEGV, &sa, NULL) == -1)
        perror("failed to set handler");

   // call other stuff here, somewhere in the callstack a segfault happens 

}

如果执行遇到第一个分段错误,将触发处理程序。但是当处理程序本身由于某种原因发生分段错误时会发生什么?它会用新的 siginfo_t 再次调用处理程序还是会立即终止程序?

如果处理程序再次被调用,会像这样工作:

int in_handler = 0;
static void handler(int sig, siginfo_t *si, void *unused)
{
    printf("BOOM!\n");
    if(!in_handler) 
    {
         in_handler = 1;
         //code that could possibly segfault
    } else {
         //totally safe code
    }
    exit(-1);
}

我问的原因是我目前正在用 C 编写某种单元测试框架。如果测试用例由于某些非法内存访问而失败,我想将所有剩余测试标记为失败。如果出于某种原因,测试真的把堆搞砸了,这个操作可能会失败,因为我必须遍历数据结构并将该信息写入 xml 文件。但是,如果将所有剩余的测试标记为失败,我仍然想保留哪个测试失败的信息。

来自 sigaction(2) 的手册页:

#include <signal.h>

int sigaction(int signum, const struct sigaction *act, struct sigaction
    *oldact);  

....

The sigaction structure is defined as something like

          struct sigaction {
              void (*sa_handler)(int);
              void (*sa_sigaction)(int, siginfo_t *, void *);
              sigset_t sa_mask;
              int sa_flags;
              void (*sa_restorer)(void);
          }

....

sa_mask gives a mask of signals which should be blocked during execu- tion of the signal handler. In addition, the signal which triggered the handler will be blocked, unless the SA_NODEFER flag is used.

因为您在设置信号处理程序时没有设置 SA_NODEFER 标志,所以如果发生另一个段错误,它不会被再次调用 而仍在信号处理程序中.一旦你退出,之前被阻止的信号就会被传递。

如果 SIGSEGV 发生在 SIGSEGV 处理程序中,至少 Linux 将使用默认信号处理程序,请参阅 kernel/signal.c,因此不会再次调用您的处理程序。 引用的代码似乎仅在内核尝试为信号处理程序设置堆栈帧时发生段错误时使用(例如,因为堆栈指针无效)。

当您的段错误处理程序出现段错误时,您的程序将处于非常糟糕的状态,因此我不会依赖任何东西并使用一些包装器来处理这种情况。

除此之外,为了符合 POSIX,您不能在信号处理程序中使用 printf 或任何类似的东西。对于可以使用的函数,请检查 man 7 signal(查找安全函数)。 write() 在该列表中,因此您可以使用它编写自己的输出函数。