7

我想知道是否有可能/推荐的方法SIGSEGV在多线程环境中捕获信号。我对处理SIGSEGV诸如*((int *)0) = 0.

一些关于这个主题的阅读让我找到了signal()and sigaction(),它安装了一个信号处理程序。虽然在多线程环境中似乎都没有希望。然后我尝试了sigwaitinfo(),在一个线程中接收信号,之前的pthread_sigmask()调用阻止了其他线程上的信号。SIGSEGV它在使用 raise()、在线程内或当它通过类似的东西发送到进程时,在信号被引发的范围内起作用kill -SIGSEGV;但是,\*((int*)0) = 0仍然会终止该进程。我的测试程序如下

void block_signal()
{
        sigset_t set;

        sigemptyset(&set);
        sigaddset(&set, SIGSEGV);
        sigprocmask(SIG_BLOCK, &set, NULL);

        if (pthread_sigmask(SIG_BLOCK, &set, NULL)) {
                fprintf(stderr, "pthread_sigmask failed\n");
                exit(EXIT_FAILURE);
        }
    }

void *buggy_thread(void *param)
{
        char *ptr = NULL;
        block_signal();

        printf("Thread %lu created\n", pthread_self());

        // Sleep for some random time
        { ... }

        printf("About to raise from %lu\n", pthread_self());

        // Raise a SIGSEGV
        *ptr = 0;

        pthread_exit(NULL);
}

void *dispatcher(void *param)
{
        sigset_t set;
        siginfo_t info;
        int sig;

        sigemptyset(&set);
        sigaddset(&set, SIGSEGV);

        for (;;) {
                sig = sigwaitinfo(&set, &info);
                if (sig == -1)
                        fprintf(stderr, "sigwaitinfo failed\n");
                else
                        printf("Received signal SIGSEGV from %u\n", info.si_pid);
        }
}

int main()
{
        int i;
        pthread_t tid;
        pthread_t disp_tid;

        block_signal();

        if (pthread_create(&disp_tid, NULL, dispatcher, NULL)) {
                fprintf(stderr, "Cannot create dispatcher\n");
                exit(EXIT_FAILURE);
        }

        for (i = 0; i < 10; ++i) {
                if (pthread_create(&tid, NULL, buggy_thread, NULL) {
                        fprintf(stderr, "Cannot create thread\n");
                        exit(EXIT_FAILURE);
                }
        }

        pause();
}

出乎意料的是,程序因分段错误而死,而不是打印引发者的线程 ID。

4

3 回答 3

10

您的代码不调用sigaction(2),我相信它应该调用它。另请阅读signal(7)signal-safety(7)。并且信号操作(通过sa_sigaction字段应该做一些事情(机器特定)siginfo_t以跳过有问题的机器指令,或者到mmap有问题的地址,或者 call siglongjmp,否则当从信号处理程序返回时,你会得到SIGSEGV再次因为有问题的机器指令重新启动。

您无法SIGSEGV在另一个线程中处理 ,因为同步信号(例如SIGSEGVSIGSYS)是特定于线程的(请参阅此答案),因此您尝试使用的sigwaitinfo方法无法实现。特别SIGSEGV是针对有问题的线程

另请阅读有关 Linux 信号的所有内容

PS。SIGSEGV不再维护的(2019 年 5 月)Ravenbrook MPS垃圾收集器库提供了一个巧妙处理的示例。还要注意 Linux 特定的和最近的userfaultfd(2)signalfd(2)系统调用。

于 2013-04-25T00:55:14.723 回答
8

SIGSEGV由错误的内存访问引起的信号传递到执行无效访问的线程。根据 POSIX ( XSH 2.4.1 ):

在生成时,应确定该信号是为进程还是为进程内的特定线程生成的。由可归因于特定线程的某些动作(例如硬件故障)生成的信号应为导致生成信号的线程生成。应为进程生成与进程 ID 或进程组 ID 或异步事件(例如终端活动)相关联的信号。

尝试SIGSEGV在多线程程序中处理的问题在于,虽然传递和信号掩码是线程本地的,但信号处置(即调用什么处理程序)是进程全局的。换句话说,sigaction为整个进程设置一个信号处理程序,而不仅仅是调用线程。这意味着每个尝试设置自己的SIGSEGV处理程序的多个线程将破坏彼此的设置。

我可以提出的最佳解决方案是设置一个全局信号处理程序来SIGSEGV使用sigaction,最好是使用,SA_SIGINFO以便您获得有关故障的其他信息,然后为特定线程的处理程序设置一个线程局部变量。然后,实际的信号处理程序可以是:

_Thread_local void (*thread_local_sigsegv_handler)(int, siginfo_t *, void *);
static void sigsegv_handler(int sig, siginfo_t *si, void *ctx)
{
    thread_local_sigsegv_handler(sig, si, ctx);
}

请注意,这使用了 C11 线程本地存储。如果您没有可用的,您可以回退到“GNU C”__thread线程本地存储,或 POSIX 线程特定数据(使用pthread_key_createpthread_setspecific/ pthread_getspecific)。严格来说,后者不是异步信号安全的,因此如果非法访问发生在标准库中的非异步信号安全函数内,则从信号处理程序调用它们会调用 UB。但是,如果它发生在您自己的代码中,您可以确定没有非异步信号安全函数被信号处理程序中断,因此这些函数具有明确定义的行为(嗯,以您的整个程序为模可能已经从它生成的任何东西中获得了 UB SIGSEGV...)。

于 2013-04-25T01:55:52.310 回答
2

“你为什么要抓 SIGSEGV ?抓了之后怎么办?”

最常见的答案是:退出/中止。但是,将这个信号传递给进程而不是任意终止它的原因是什么?

答案是:因为包括 SIGSEGV 在内的信号只是例外——对于某些应用程序来说,将硬件输出设置为“安全模式”或确保某些重要数据在终止进程之前保持一致状态是非常重要的.

一般有2种segfaults:由写或读操作引起。

在某些情况下,由读取操作引起的段错误可以完全安全地捕获甚至忽略 (1)。失败的写入操作需要更多的关注和努力才能安全处理(数据/内存损坏的风险),但这也是可能的(通过避免在段错误后动态分配内存)。

“关键信号”(传递给特定线程,如 SIGFPE 或 SIGSEGV)的问题是程序通常不“知道”信号的上下文是什么——也就是说,哪个操作或函数触发了信号。

至少有几种可能的方法来获取这些信息,例如:

  1. 每个线程只能执行一类小操作——所以如果它得到一个信号,那么很容易判断发生了什么——>终止线程,验证处理的数据等——>安全终止。
  2. 使用C 异常- 很少有现成的解决方案,我的是:libcxc

(1) Fe 著名的 ESRCH 问题和 pthread_kill() 为已经自行退出的线程发出:)

于 2016-09-17T21:39:06.110 回答