如何在运行时检测Sanitizer是否启用及适配信号处理
我有各类单元测试,比如验证向进程发送SIGUSR1信号后,进程是否按预期执行相应操作。这类测试正常运行,但用Sanitizer编译时,ppoll()等函数会被中断。原因是创建线程时我屏蔽了诸多信号,其中部分是Sanitizer或Catch2依赖的信号,得让底层库别干扰这些信号。
以下是经测试的信号列表:
// SIGHUP, -- 与catch2和/或sanitizer不兼容 // SIGINT, -- 与catch2和/或sanitizer不兼容 SIGQUIT, // SIGILL, -- 与catch2和/或sanitizer不兼容 SIGTRAP, // SIGABRT, -- 与catch2和/或sanitizer不兼容 SIGBUS, // SIGFPE, -- 与catch2和/或sanitizer不兼容 // SIGKILL, -- 无法忽略 SIGUSR1, // SIGSEGV, -- 与catch2和/或sanitizer不兼容 SIGUSR2, SIGPIPE, SIGALRM, // SIGTERM, -- 与catch2和/或sanitizer不兼容 SIGSTKFLT, SIGCHLD, // SIGCONT, -- 无法忽略 // SIGSTOP, -- 无法忽略 //SIGTSTP, -- 与catch2和/或sanitizer不兼容 SIGTTIN, SIGTTOU, SIGURG, SIGXCPU, SIGXFSZ, SIGVTALRM, // SIGPROF, -- 与catch2和/或sanitizer不兼容 SIGWINCH, // SIGIO, -- 与catch2和/或sanitizer不兼容 SIGPWR, SIGSYS, // 32 -- pthread实现使用 // 33 -- pthread实现使用 SIGRTMIN, SIGRTMIN+1, SIGRTMIN+2, SIGRTMIN+3, SIGRTMIN+4, SIGRTMIN+5, SIGRTMIN+6, SIGRTMIN+7, SIGRTMIN+8, SIGRTMIN+9, SIGRTMIN+10, SIGRTMIN+11, SIGRTMIN+12, SIGRTMIN+13, SIGRTMIN+14, SIGRTMIN+15, SIGRTMAX-14, SIGRTMAX-13, SIGRTMAX-12, SIGRTMAX-11, SIGRTMAX-10, SIGRTMAX-9, SIGRTMAX-8, SIGRTMAX-7, SIGRTMAX-6, SIGRTMAX-5, SIGRTMAX-4, SIGRTMAX-3, SIGRTMAX-2, SIGRTMAX-1, SIGRTMAX,
我希望仅在单元测试处于Sanitizer环境运行时,移除列表中标记为“与catch2和/或sanitizer不兼容”的信号。但处理这些信号的库可能没按Sanitizer编译,链接时无法得知当前是否启用了Sanitizer。目前只能通过编译时#ifdef判断:
#if defined(__SANITIZE_ADDRESS__) || defined(__SANITIZE_THREAD__) ... 进程启用sanitizer时执行的代码 ... #endif
我需要在代码中实现运行时检测,比如:
if(sanitizer_is_running()) { ... 从列表中移除冲突信号 ... }
请问如何实现这个sanitizer_is_running()函数?
方法1:检查Sanitizer环境变量
Sanitizer运行时会自动设置特定环境变量,比如ASAN_OPTIONS(AddressSanitizer)、TSAN_OPTIONS(ThreadSanitizer)、UBSAN_OPTIONS(UndefinedBehaviorSanitizer)。通过检查这些变量是否存在即可判断:
#include <stdlib.h> int sanitizer_is_running(void) { // 检查AddressSanitizer if (getenv("ASAN_OPTIONS") != NULL) { return 1; } // 检查ThreadSanitizer if (getenv("TSAN_OPTIONS") != NULL) { return 1; } // 检查UndefinedBehaviorSanitizer if (getenv("UBSAN_OPTIONS") != NULL) { return 1; } return 0; }
优点:实现简单,无需额外依赖;缺点:若用户手动清除环境变量,检测会失效,但这种场景极少。
方法2:检测进程加载的Sanitizer动态库
Linux系统下,Sanitizer会加载对应的动态库(如libasan.so、libtsan.so),可以读取/proc/self/maps文件遍历已加载库列表:
#include <stdio.h> #include <string.h> int sanitizer_is_running(void) { FILE* maps = fopen("/proc/self/maps", "r"); if (maps == NULL) { return 0; } char line[256]; while (fgets(line, sizeof(line), maps) != NULL) { if (strstr(line, "libasan.so") != NULL || strstr(line, "libtsan.so") != NULL || strstr(line, "libubsan.so") != NULL) { fclose(maps); return 1; } } fclose(maps); return 0; }
优点:检测可靠,不受环境变量影响;缺点:仅适用于Linux系统,依赖/proc文件系统。
方法3:调用Sanitizer运行时API
部分Sanitizer提供了官方检测API,结合弱符号声明可避免未编译Sanitizer时的链接错误:
#include <stddef.h> // 用弱符号声明,未链接Sanitizer时返回默认值 __attribute__((weak)) void* __asan_get_current_fake_stack(void) { return NULL; } __attribute__((weak)) int __tsan_get_current_thread_id(void) { return 0; } __attribute__((weak)) void __ubsan_get_current_report_data(void) {} int sanitizer_is_running(void) { // 检测AddressSanitizer if (__asan_get_current_fake_stack() != NULL) { return 1; } // 检测ThreadSanitizer if (__tsan_get_current_thread_id() != 0) { return 1; } // 检测UndefinedBehaviorSanitizer:尝试调用API,若未崩溃则说明UBSAN在运行 // 注:该方式需确保UBSAN不会因空调用触发报错,可根据实际情况调整 __ubsan_get_current_report_data(); return 1; return 0; }
优点:官方支持,检测精准;缺点:不同Sanitizer的API差异较大,需要针对性适配。
内容的提问来源于stack exchange,提问作者Alexis Wilke

