关于在multithreading环境中捕获SIGSEGV

我想知道是否有可能/推荐的方式来捕捉multithreading环境中的SIGSEGV信号。 我特别感兴趣的是处理由'*((int *)0)= 0'所引发的SIGSEGV。

一些关于这个主题的阅读让我感到信号()和sigaction(),它们安装了一个信号处理器。 虽然在multithreading环境下看起来并不乐观。 然后我尝试了sigwaitinfo(),在一个线程中接收到信号,而之前的pthread_sigmask()调用阻塞了其他信号。 它在SIGSEGV信号被引发的程度上,使用raise(),在一个线程内或者当它被发送到进程的时候,像'kill -SIGSEGV'; 然而,*((int *)0)= 0'仍然杀死进程。 我的testing程序如下

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。

Solutions Collecting From Web of "关于在multithreading环境中捕获SIGSEGV"

你的代码不会调用sigaction(2) ,我相信它应该调用它。 也读信号(7) 。 信号动作(通过sa_sigaction字段应该用sa_sigaction做一些事情(特定于机器)来跳过违规的机器指令,或者mmap违规的地址,或者调用siglongjmp ,否则当从信号处理程序返回时,会再次得到SIGSEGV因为违规的机器指令被重新启动。

你不能在另一个线程中处理SIGSEGV ,因为异步信号是线程特定的(见这个答案 ),所以你试图用sigwaitinfo实现不了。 特别是SIGSEGV是针对有问题的线程

还请阅读关于Linux信号的所有信息

SIGSEGV由断层存储器访问导致的信号传递是执行无效访问的线程。 根据POSIX( XSH 2.4.1 ):

在生成时,应确定是否为进程或进程内的特定线程生成了信号。 应该为引起信号产生的线程产生由某个特定线程引起的一些动作产生的信号,例如硬件故障。 为进程生成与进程ID或进程组ID或异步事件(如终端活动)关联的信号。

尝试在多线程程序中处理SIGSEGV的问题是,交付和信号掩码是线程本地的,信号处置 (即要调用的处理程序)是全局的。 换句话说, sigaction为整个进程设置了一个信号处理程序,而不仅仅是调用线程。 这意味着每个尝试设置自己的SIGSEGV处理程序的多个线程将会打断对方的设置。

我可以建议的最好的解决方案是使用sigactionSIGSEGV设置全局信号处理程序,最好使用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 thread线程本地存储或POSIX线程特定的数据(使用pthread_key_createpthread_setspecific / pthread_getspecific )。 严格来说,后者不是异步信号安全的,所以如果非法访问发生在标准库中的非异步信号安全函数内,则从信号处理程序调用它们将调用UB。 但是,如果它发生在你自己的代码中,你可以确定没有非异步信号安全函数被信号处理程序中断,因此这些函数具有明确定义的行为(模块化的事实是你的整个程序可能已经有UB,无论它做什么来生成SIGSEGV …)。

“你为什么要抓住SIGSEGV?抓到后你会做什么?

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

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

通常有两种段错误:由写或读操作引起的。

操作引起的Segfaults在一些情况下是完全安全的,甚至被忽略(1)。 失败的操作需要更多的关注和努力来安全处理(数据/内存损坏的风险),但是这也是可能的(通过避免在段错误之后动态分配内存)。

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

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

  1. 每个线程只能执行一个小类的操作 – 所以如果它得到一个信号,那么很容易知道发生了什么 – >终止线程,验证处理的数据等等 – >安全地终止。
  2. 使用C例外 – 有几个准备使用的解决方案,我的是: libcxc

(1)Fe是ESRCH和pthread_kill()发出的一个已经退出的线程的着名问题:)