二、实现
chunk header 的组成
如何判断进程是否可以访问一片内存区域?一个直观的想法是:给每个字节做个记号(poison state),将进程不能够访问的字节标记为 poisoned(中毒),然后在每次访问内存之前先检查他的 poison state,如果是 poisoned,那么就可以判断发生了非法访问。
ASAN 的核心算法就是这个原理。ASAN 会使用一片专门的内存来保存 application memory(应用内存) 每个字节的 poison state。这片内存的专业名称为 shadow memory,在访问地址之前,首先在 shadow memory 中检查地址是否中毒(poison state),如果是中毒的(poisoned),那么就可以判定发生了非法访问,ASAN 会及时报错错误。
ASAN 的实现会分为两个大部分:
一个是检查模块,在编译时发挥作用。在编译时,将程序中每个内存访问都按照上面描述的方式进行转换;并且在 stack object 和 global object 周围创建中毒区域 poisoned redzones 以检测是否发生溢出行为。
另一个是运行时库,在运行时发挥作用。在开启 ASAN 后,编译器会让程序动态链接 ASAN 运行时库。ASAN 的运行时库有如下作用:
本文着重谈就 heap-profiler 的实现原理。
1 | src/base/googleinit.h |
这个代码通过宏,C++ 的静态全局成员,使其可以在初始化阶段调用到类的构造函数,从而初始化到用户函数。
1 | src/heap-profiler.cc |
1
2
3
4
5
6
7在C++程序中使用ASAN(Address Sanitizer)和OpenSSL是可能存在兼容性问题的。ASAN是一种内存错误检测工具,它会对程序的内存使用情况进行跟踪和监测,以检测潜在的内存错误。OpenSSL是一个加密库,用于提供安全通信协议的实现,包括SSL和TLS。
由于ASAN会在程序运行时修改内存布局,可能会影响到OpenSSL的内存管理。这可能导致一些问题,例如OpenSSL中的指针可能会指向已经被ASAN修改过的地址,从而导致内存访问错误。
为了解决这个问题,可以使用特殊的编译选项来启用ASAN和OpenSSL的兼容性支持。例如,可以使用“-fsanitize=address -fno-omit-frame-pointer”编译选项来启用ASAN,同时使用“-DOPENSSL_NO_ASM -DOPENSSL_NO_INLINE_ASM”编译选项来禁用OpenSSL中的汇编代码。这些选项可以确保ASAN和OpenSSL之间的兼容性。
但是需要注意的是,即使启用了兼容性支持,仍然可能存在其他问题。因此,在使用ASAN和OpenSSL的组合时,建议仔细测试和调试程序,以确保程序的安全性和稳定性。1
2
3
4
5
6
7
8
9
10-DOPENSSL_NO_ASM -DOPENSSL_NO_INLINE_ASM
这些选项会禁用OpenSSL库中的汇编代码,并使用C代码替代。这可能会影响OpenSSL库的性能,但可以确保在使用AddressSanitizer(ASAN)等工具时的兼容性。
./config -d no-ssl2 no-ssl3 no-comp no-hw no-engine --prefix=<install_path> -DOPENSSL_NO_ASM -DOPENSSL_NO_INLINE_ASM
make
make install
这将使用禁用了汇编代码的选项进行编译,生成OpenSSL库并将其安装在指定的安装路径下。
需要注意的是,禁用汇编代码可能会影响OpenSSL库的性能。因此,在实际使用中,建议在评估性能和安全性之间做出权衡,根据具体情况选择是否禁用汇编代码。1
2
3
4
5
6
7
8
9
10OPENSSL_NO_ASAN是一个OpenSSL库中的编译选项,它用于禁用AddressSanitizer(ASAN)内存错误检测工具。
AddressSanitizer是一种内存错误检测工具,它可以帮助开发者在程序运行时发现内存错误,例如使用未初始化的内存、内存泄漏、缓冲区溢出等等。在C++程序中,使用ASAN工具可以帮助开发者检测潜在的内存错误,提高程序的安全性和稳定性。
然而,当OpenSSL库与ASAN工具一起使用时,可能会出现一些问题。因为ASAN工具会修改程序的内存布局,这可能会影响到OpenSSL库的内存管理。为了避免这种问题,OpenSSL库中提供了OPENSSL_NO_ASAN编译选项,可以禁用ASAN工具。
如果在编译OpenSSL库时定义了OPENSSL_NO_ASAN宏,那么OpenSSL库将不会使用ASAN工具进行内存错误检测。这可以避免ASAN工具可能会对OpenSSL库的影响,确保OpenSSL库的稳定性和安全性。
需要注意的是,禁用ASAN工具可能会降低OpenSSL库的安全性。因此,在使用OPENSSL_NO_ASAN编译选项时,建议使用其他安全检测工具或策略来确保程序的安全性。
选项:
1 | export ASAN_OPTIONS="quarantine_size_mb=15:malloc_context_size=5:detect_leaks=false:alloc_dealloc_mismatch=0:max_redzone=1024:report_globals=0" |
本文简单介绍线程局部缓存的原理,以及实现。本文代码来自于 glibc 2.28。
线程局部缓存(Thread Local Storage),他的特性就是线程私有的内存数据,每个线程都有,多线程场景中不会产生竞争,效率较高。
显式的 TLS 的 API 定义:
1 | /* Functions for handling thread-specific data. */ |
我曾经在编码开发时遇到过这样一个问题,代码中产生了死锁,可是我 review 了多次加锁、解锁的地方,却暂时没有发现有什么问题。最终发现是因为在代码中使用了 popen 系统调用,我把遇到的这个问题浓缩成如下的代码。
1 | pthread_mutex_t mtx; |
代码很简单,正常的业务加锁、解锁逻辑中,出现过一个 my_fork() 函数,而这个函数中会调用 fork,并且也同样使用了锁。此时就导致了死锁。如下会输出:
1 | id 18923 (parent) |