关于在多线程环境中捕获SIGSEGV
我想知道在多线程环境中是否有可能/推荐使用SIGSEGV信号。 我特别感兴趣的是处理由'*((int *)0)= 0'之类的东西引发的SIGSEGV。
关于这个主题的一些阅读让我发信号()和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 failedn");
exit(EXIT_FAILURE);
}
}
void *buggy_thread(void *param)
{
char *ptr = NULL;
block_signal();
printf("Thread %lu createdn", pthread_self());
// Sleep for some random time
{ ... }
printf("About to raise from %lun", 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 failedn");
else
printf("Received signal SIGSEGV from %un", 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 dispatchern");
exit(EXIT_FAILURE);
}
for (i = 0; i < 10; ++i) {
if (pthread_create(&tid, NULL, buggy_thread, NULL) {
fprintf(stderr, "Cannot create threadn");
exit(EXIT_FAILURE);
}
}
pause();
}
意外的是,程序死于分段错误,而不是打印提升者的线程ID。
你的代码不会调用sigaction(2),我相信它应该调用它。 再读信号(7)。 和信号作用(直通sa_sigaction
场应该做的事(机专用),其siginfo_t
跳过有问题的机器指令,或者mmap
违规地址,或拨打siglongjmp
从信号处理函数返回时,你会得到的,否则SIGSEGV
再次因为违规的机器指令重新启动。
您不能在另一个线程中处理SIGSEGV
,因为异步信号是线程特定的(请参阅此答案),所以您尝试使用sigwaitinfo
实现的功能无法工作。 特别是SIGSEGV
是针对有问题的线程 。
还请阅读关于Linux信号的所有信息
SIGSEGV
由断层存储器访问引起的信号传递是执行无效访问的线程。 根据POSIX(XSH 2.4.1):
在生成时,应确定是否为进程或进程内的特定线程生成了信号。 应该为引起信号产生的线程产生由可归因于特定线程的某些动作产生的信号,例如硬件故障。 应为过程生成与进程ID或进程组ID或异步事件(如终端活动)关联生成的信号。
试图在多线程程序中处理SIGSEGV
的问题是,虽然传递和信号掩码是线程本地的,但信号处置(即要调用的处理程序)是全局的。 换句话说, sigaction
为整个进程设置了一个信号处理程序,而不仅仅是调用线程。 这意味着每个尝试设置自己的SIGSEGV
处理程序的多个线程都会打断对方的设置。
我可以提出的最佳解决方案是使用sigaction
为SIGSEGV
设置一个全局信号处理程序,最好使用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_create
和pthread_setspecific
/ pthread_getspecific
)。 严格地说,后者不是异步信号安全的,因此如果非法访问发生在标准库中的非异步信号安全函数内,则从信号处理程序调用它们将调用UB。 但是,如果它发生在你自己的代码中,你可以确定没有非异步信号安全函数被信号处理程序中断,因此这些函数具有定义良好的行为(模块化的事实是你的整个程序可能已经拥有UB,无论它如何生成SIGSEGV
...)。
“你为什么想要抓住SIGSEGV?抓到它后你会做什么?”
最常见的答案是:退出/中止。 但是,那么甚至将这个信号传递给一个过程而不是任意终止它的原因是什么呢?
答案是:因为包括SIGSEGV在内的信号只是例外 - 对于一些应用来说,将硬件输出设置为“安全模式”或确保一些重要数据在终止过程之前保持一致状态是非常重要的。
通常有两种段错误:由写或读操作引起的。
读取操作导致的Segfaults在一些情况下完全可以安全地捕获并且甚至忽略(1)。 失败的写操作需要更多的关注和努力来安全地处理(数据/内存损坏的风险),但这也是可能的(通过避免在段错误后动态分配内存)。
“关键信号”(传递给特定线程,如SIGFPE或SIGSEGV)的问题是通常程序不会“知道”信号的上下文是什么 - 也就是说,哪个操作或函数触发了信号。
至少有几种可能的方式来获取这些信息,例如:
(1)Fe是ESRCH和pthread_kill()发出的针对已经退出的线程的着名问题:)
链接地址: http://www.djcxy.com/p/80297.html上一篇: About catching the SIGSEGV in multithreaded environment