如何为Clang AddressSanitizer编写精准抑制文件,解决gtest未测时的OpenSSL泄漏
问题背景
使用gtest_main驱动单元测试(已链接gtest_main库),执行无测试用例的命令(如./UnitTests --gtest_filter=NotExistingTest)时,AddressSanitizer(ASAN)会报告OpenSSL相关的间接内存泄漏(总泄漏量6213字节),导致程序退出码始终为1,干扰CI流程的结果判断。直接使用Clang提供的通用抑制规则(如interceptor_via_fun:CRYPTO_zalloc)会过度屏蔽,可能遗漏自身代码的真实内存泄漏,因此需要基于调用栈的精准抑制规则。涉及的OpenSSL版本为1.1.1g、3.0.4、3.0.8,均为自定义静态库构建。
方案一:编写精准栈级抑制规则
1. 生成完整泄漏调用栈
运行测试程序时,通过ASAN环境变量输出详细泄漏日志:
ASAN_OPTIONS=detect_leaks=1,log_path=asan_leak.log,leak_check_at_exit=1 ./UnitTests --gtest_filter=NotExistingTest
打开asan_leak.log,找到OpenSSL相关泄漏的完整调用栈(示例栈帧):
Direct leak of 128 bytes in 1 object allocated from: #0 0x7f9b12345678 in malloc (/lib64/libasan.so.5+0x108678) #1 0x7f9b11234567 in CRYPTO_zalloc crypto/mem.c:209 #2 0x7f9b11234abc in OPENSSL_init_crypto crypto/init.c:456 #3 0x7f9b11234def in openssl_init libcrypto.a(init.o) #4 0x7f9b10abcdef in __libc_start_main (/lib64/libc.so.6+0x27045) #5 0x55aabcdef123 in _start (/path/to/UnitTests+0x123)
2. 编写针对性抑制规则
创建抑制文件asan_openssl_suppressions.txt,针对上述栈帧编写规则(只匹配OpenSSL初始化阶段的泄漏,不影响业务代码的CRYPTO_zalloc调用):
leak: name: OpenSSL_Global_Init_Leak stack: - CRYPTO_zalloc - OPENSSL_init_crypto - openssl_init - __libc_start_main - _start
注意:不同OpenSSL版本的栈帧名称可能略有差异,需根据实际日志调整栈帧内容,确保只匹配无测试执行时触发的初始化泄漏。
3. 验证抑制效果
运行测试时指定抑制文件,确认OpenSSL泄漏不再被报告:
ASAN_OPTIONS=suppressions=asan_openssl_suppressions.txt ./UnitTests --gtest_filter=NotExistingTest
此时程序退出码应为0,且自身代码的内存泄漏仍会被ASAN正常检测。
方案二:手动清理OpenSSL全局资源(无需抑制)
如果不想依赖抑制文件,可通过自定义main函数替换gtest_main,在测试执行完成后手动调用OpenSSL的清理函数,释放初始化阶段分配的内存:
1. 编写自定义main函数
#include <gtest/gtest.h> #include <openssl/crypto.h> int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); int test_result = RUN_ALL_TESTS(); // 清理OpenSSL全局资源,适配1.1.1+版本 OPENSSL_cleanup(); // 针对旧版本可补充调用:CRYPTO_cleanup_all_ex_data(); return test_result; }
2. 编译链接调整
编译时不再链接gtest_main库,改为链接gtest库,确保自定义main函数被执行:
# 原配置:target_link_libraries(UnitTests gtest_main) target_link_libraries(UnitTests gtest)
此方案通过主动释放OpenSSL初始化内存,从根源消除ASAN的泄漏报告,同时不影响测试用例中的OpenSSL使用(清理操作在RUN_ALL_TESTS()之后执行)。
CI流程适配
在CI脚本中添加ASAN环境变量配置,确保抑制规则或清理逻辑生效:
# 使用抑制方案时: export ASAN_OPTIONS="suppressions=asan_openssl_suppressions.txt:detect_leaks=1" ./UnitTests --gtest_filter=$TEST_FILTER # 使用自定义main方案时,直接运行即可: ./UnitTests --gtest_filter=$TEST_FILTER
这样既保证OpenSSL的误报泄漏被处理,又能让ASAN正常检测自身代码的真实泄漏,确保CI流程能通过退出码判断测试结果。
内容的提问来源于stack exchange,提问作者Marek R

