我想知道在多线程环境中捕获SIGSEGV信号的推荐方法是否可行。我特别感兴趣的是处理由类似于SIGSEGV的东西引发的*((int *)0) = 0。
关于这个主题的一些阅读使我了解到signal()和sigaction(),它们安装了一个信号处理程序。虽然在多线程环境中这两者都不是很有希望的。然后我尝试使用sigwaitinfo(),在一个线程中接收信号,并使用先前的pthread_sigmask()调用阻止其他线程上的信号。它的工作范围是使用raise()在线程中或当信号是由类似于SIGSEGV的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。
发布于 2013-04-25 00:55:14
您的代码不调用印钞(2),我认为它应该调用它。也可以阅读信号(7)和信号-安全(7)。而信号操作(通过sa_sigaction字段)应该使用其siginfo_t执行(特定于机器的)操作,以跳过违规的机器指令,或者调用siglongjmp,否则当从信号处理程序返回时,您将再次获得SIGSEGV,因为违规的机器指令是重新启动的。
您无法在另一个线程中处理SIGSEGV,因为同步信号(如SIGSEGV或SIGSYS)是线程特定的(请参阅这个答案),因此您试图用sigwaitinfo实现的功能无法工作。特别地,SIGSEGV 被定向到违规的线程。
也可以阅读关于Linux信号。
PS。维护不多(2019年5月)的SIGSEGV垃圾收集器库提供了一个聪明的拉文布鲁克议员处理示例。还请注意Linux特定的和最近的userfaultfd(2)和signalfd(2)系统调用。
发布于 2013-04-25 01:55:52
由错误内存访问引起的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“__thread线程本地存储,或者POSIX线程特定的数据(使用pthread_key_create和pthread_setspecific/pthread_getspecific)。严格地说,后者并不是异步信号安全的,因此,如果非法访问发生在标准库中的非异步信号安全函数中,则从信号处理程序调用它们将调用UB。但是,如果它是在您自己的代码中发生的,您可以确保信号处理程序没有中断任何非异步信号安全函数,因此这些函数具有定义良好的行为(嗯,模块化的事实是,您的整个程序可能已经拥有UB,不管它生成SIGSEGV.)。
发布于 2016-09-17 21:39:06
“你为什么要抓SIGSEGV ?抓住它之后你会怎么做?”
最常见的答案是:退出/中止。但是,为什么要把这个信号传递给一个进程,而不是任意地终止它呢?
答案是:因为信号,包括SIGSEGV,只是例外--对于f.e的某些应用来说,这是非常重要的。将硬件输出设置为“安全模式”,或在终止进程之前确保某些重要数据处于一致状态。
一般有两种分段故障:由写或读操作引起的。
读取操作引起的段错误在某些情况下是完全安全的,甚至可以忽略(1)。失败的编写操作需要更多的注意力和精力来安全地处理(数据/内存损坏的风险),但这也是可能的(f.e。避免在发生分段故障后动态分配内存)。
“临界信号”(传递到特定线程,如SIGFPE或SIGSEGV)的问题是,程序通常不“知道”信号的上下文是什么--即哪种操作或功能触发了信号。
至少很少有可能获得这些信息的方法,例如:
(1) F.e.ESRCH和pthread_kill()为已经独立退出的线程发布的著名问题:)
https://stackoverflow.com/questions/16204271
复制相似问题