如何确定代码是否在信号处理程序环境中运行?

我刚刚发现有人正在从一个信号处理程序调用一个绝对不是我写的asynchronous信号安全函数。 而且,当然,我受到指责(尽pipe在我的文档中有警告)。 (同一个编码器正在从他的信号处理程序中调用各种非asynchronous信号安全函数。)

所以,现在我很好奇:如何规避这种情况再次发生? 我希望能够轻松地确定我的代码是否在信号处理程序上下文中运行(语言是C,但解决scheme是否适用于任何语言?):

int myfunc( void ) { if( in_signal_handler_context() ) { return(-1) } // rest of function goes here return( 0 ); } 

这是在Linux下。 希望这不是一个简单的答案,否则我会觉得自己像一个白痴。

很显然,较新的Linux / x86(可能来自一些2.6.x内核)从vdso调用信号处理程序。 你可以用这个事实来对这个毫无戒心的世界进行如下可怕的破坏:

 #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <signal.h> #include <unistd.h> uintmax_t vdso_start = 0; uintmax_t vdso_end = 0; /* actually, next byte */ int check_stack_for_vdso(uint32_t *esp, size_t len) { size_t i; for (i = 0; i < len; i++, esp++) if (*esp >= vdso_start && *esp < vdso_end) return 1; return 0; } void handler(int signo) { uint32_t *esp; __asm__ __volatile__ ("mov %%esp, %0" : "=r"(esp)); /* XXX only for demonstration, don't call printf from a signal handler */ printf("handler: check_stack_for_vdso() = %d\n", check_stack_for_vdso(esp, 20)); } void parse_maps() { FILE *maps; char buf[256]; char path[7]; uintmax_t start, end, offset, inode; char r, w, x, p; unsigned major, minor; maps = fopen("/proc/self/maps", "rt"); if (maps == NULL) return; while (!feof(maps) && !ferror(maps)) { if (fgets(buf, 256, maps) != NULL) { if (sscanf(buf, "%jx-%jx %c%c%c%c %jx %u:%u %ju %6s", &start, &end, &r, &w, &x, &p, &offset, &major, &minor, &inode, path) == 11) { if (!strcmp(path, "[vdso]")) { vdso_start = start; vdso_end = end; break; } } } } fclose(maps); printf("[vdso] at %jx-%jx\n", vdso_start, vdso_end); } int main() { struct sigaction sa; uint32_t *esp; parse_maps(); memset(&sa, 0, sizeof(struct sigaction)); sa.sa_handler = handler; sa.sa_flags = SA_RESTART; if (sigaction(SIGUSR1, &sa, NULL) < 0) { perror("sigaction"); exit(1); } __asm__ __volatile__ ("mov %%esp, %0" : "=r"(esp)); printf("before kill: check_stack_for_vdso() = %d\n", check_stack_for_vdso(esp, 20)); kill(getpid(), SIGUSR1); __asm__ __volatile__ ("mov %%esp, %0" : "=r"(esp)); printf("after kill: check_stack_for_vdso() = %d\n", check_stack_for_vdso(esp, 20)); return 0; } 

SCNR。

如果我们可以假设你的应用程序不使用sigprocmask()pthread_sigmask()手动阻塞信号,那么这很简单:获取当前的线程ID( tid )。 打开/proc/tid/status并获取SigBlkSigCgt的值。 AND这两个值。 如果AND的结果非零,那么该线程当前正在信号处理程序中运行。 我已经测试了这个自己,它的工作原理。

有两个适当的方法来处理这个问题:

  • 让你的同事停止做错事。 祝你好运,把这个与老板,但…

  • 使您的功能重入和异步安全。 如有必要,提供一个具有不同签名的函数(例如,使用广泛使用的*_r命名约定)以及状态保存所需的附加参数。

至于不正确的方法,在GNU libc的Linux上,你可以使用backtrace()和朋友通过函数的调用者列表。 要获得正确,安全或便携并不容易,但可能会有一段时间:

 /* * *** Warning *** * * Black, fragile and unportable magic ahead * * Do not use this, lest the daemons of hell be unleashed upon you */ int in_signal_handler_context() { int i, n; void *bt[1000]; char **bts = NULL; n = backtrace(bt, 1000); bts = backtrace_symbols(bt, n); for (i = 0; i < n; ++i) printf("%i - %s\n", i, bts[i]); /* Have a look at the caller chain */ for (i = 0; i < n; ++i) { /* Far more checks are needed here to avoid misfires */ if (strstr(bts[i], "(__libc_start_main+") != NULL) return 0; if (strstr(bts[i], "libc.so.6(+") != NULL) return 1; } return 0; } void unsafe() { if (in_signal_handler_context()) printf("John, you know you are an idiot, right?\n"); } 

在我看来, 退出而不是被迫写这样的代码可能会更好。

你可以用sigaltstack解决一些问题。 设置一个替代信号堆栈,以某种异步安全的方式获取堆栈指针,如果在替代堆栈中继续,否则为abort()。

我想你需要做以下事情。 这是一个复杂的解决方案,它不仅结合了编码的最佳实践,还结合了软件工程的最佳实践!

  1. 说服你的老板,信号处理程序的命名约定是件好事。 例如,提出一个匈牙利的符号 ,并告诉它在微软中使用,取得了巨大的成功。 所以,所有的信号处理程序都将以sighnd开始,如sighndInterrupt
  2. 检测信号处理上下文的函数将执行以下操作:
    1. 获取backtrace()
    2. 看看它里面的任何功能是以sighnd...开始的。 如果是这样,那么恭喜你,你在信号处理程序里面!
    3. 否则,你不是。
  3. 尽量避免与吉米在同一家公司工作。 “只能有一个”,你知道的。

对于在-O2或更好(istr)优化的代码已经发现需要添加-fno-omit-frame-pointer

否则gcc会优化堆栈上下文信息