关于在多线程环境中捕获SIGSEGV

Zil*_*Tan 6 c linux signals

我想知道是否有可能/建议的方法来捕获SIGSEGV多线程环境中的信号.我特别感兴趣的是SIGSEGV通过类似的东西来处理*((int *)0) = 0.

关于这个主题的一些阅读导致我signal()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();
}
Run Code Online (Sandbox Code Playgroud)

出乎意料的是,程序死于分段错误而不是打印提升者的线程ID.

Bas*_*tch 9

您的代码不会调用sigaction(2),我相信它应该调用它.同时读取信号(7)信号安全(7).并且信号动作(通过sa_sigaction字段应该执行某些操作(特定于机器),siginfo_t以便跳过违规机器指令,或者mmap触发违规地址,或者调用siglongjmp,否则当从信号处理程序返回时,SIGSEGV由于违规机器指令,您将再次获取重新启动.

您无法处理SIGSEGV另一个线程,因为异步信号是特定于线程的(请参阅此答案),因此您尝试实现的SIGSEGV功能无法正常工作.特别SIGSYS是涉及违规的线程.

阅读所有关于Linux信号的内容.


R..*_*R.. 6

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

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