原理实现

一、概述

如何判断进程是否可以访问一片内存区域?一个直观的想法是:给每个字节做个记号(poison state),将进程不能够访问的字节标记为 poisoned(中毒),然后在每次访问内存之前先检查他的 poison state,如果是 poisoned,那么就可以判断发生了非法访问。

ASAN 的核心算法就是这个原理。ASAN 会使用一片专门的内存来保存 application memory(应用内存) 每个字节的 poison state。这片内存的专业名称为 shadow memory,在访问地址之前,首先在 shadow memory 中检查地址是否中毒(poison state),如果是中毒的(poisoned),那么就可以判定发生了非法访问,ASAN 会及时报错错误。

ASAN 的实现会分为两个大部分:

一个是检查模块,在编译时发挥作用。在编译时,将程序中每个内存访问都按照上面描述的方式进行转换;并且在 stack objectglobal object 周围创建中毒区域 poisoned redzones 以检测是否发生溢出行为。

另一个是运行时库,在运行时发挥作用。在开启 ASAN 后,编译器会让程序动态链接 ASAN 运行时库。ASAN 的运行时库有如下作用:

查看更多

gperftools 的 heap-profiler 原理探究

gperftools 的 heap-profiler 原理探究

本文着重谈就 heap-profiler 的实现原理。

1
2
3
4
5
6
7
8
9
10
src/base/googleinit.h

#define REGISTER_MODULE_INITIALIZER(name, body) \
namespace { \
static void google_init_module_##name () { body; } \
GoogleInitializer google_initializer_module_##name(#name, \
google_init_module_##name, NULL); \
}

REGISTER_MODULE_INITIALIZER(heapprofiler, HeapProfilerInit());

这个代码通过宏,C++ 的静态全局成员,使其可以在初始化阶段调用到类的构造函数,从而初始化到用户函数。

1
2
3
4
5
6
7
8
src/heap-profiler.cc

# 初始化函数
1. 初始化 profiler 文件存储位置
2. 以及信号驱动
3. 清除旧的 profiler 文件
4. 开始正式的分析 HeapProfilerStart
HeapProfilerInit()

查看更多

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
10
OPENSSL_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编译选项时,建议使用其他安全检测工具或策略来确保程序的安全性。

查看更多

ASAN 原理

选项:

1
2
3
4
5
export ASAN_OPTIONS="quarantine_size_mb=15:malloc_context_size=5:detect_leaks=false:alloc_dealloc_mismatch=0:max_redzone=1024:report_globals=0"

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -fsanitize=address -fno-omit-frame-pointer -DEIGEN_MALLOC_ALREADY_ALIGNED=0")
set(CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} -lasan")

源码浅析:https://github.com/lishuhuakai/libsantilizer_reading/blob/main/doc/asan%E4%BB%A3%E7%A0%81%E6%B5%85%E6%9E%90.md

概述

一、介绍

AddressSanitizer(又名 ASAN)可以检测 C/C++ 的内存错误。可以发现如下的内存错误:

  • Use after free (dangling pointer dereference),使用 free 后的指针。默认开启
  • Heap buffer overflow,堆内存越界。默认开启
  • Stack buffer overflow,栈内存越界。默认开启
查看更多

线程局部缓存的原理与实现

本文简单介绍线程局部缓存的原理,以及实现。本文代码来自于 glibc 2.28

一、简单介绍

线程局部缓存(Thread Local Storage),他的特性就是线程私有的内存数据,每个线程都有,多线程场景中不会产生竞争,效率较高。

显式的 TLS 的 API 定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* Functions for handling thread-specific data.  */

/* Create a key value identifying a location in the thread-specific
data area. Each thread maintains a distinct thread-specific data
area. DESTR_FUNCTION, if non-NULL, is called with the value
associated to that key when the key is destroyed.
DESTR_FUNCTION is not called if the value associated is NULL when
the key is destroyed. */
extern int pthread_key_create (pthread_key_t *__key,
void (*__destr_function) (void *))
__THROW __nonnull ((1));

/* Destroy KEY. */
extern int pthread_key_delete (pthread_key_t __key) __THROW;

/* Return current value of the thread-specific data slot identified by KEY. */
extern void *pthread_getspecific (pthread_key_t __key) __THROW;

/* Store POINTER in the thread-specific data slot identified by KEY. */
extern int pthread_setspecific (pthread_key_t __key,
const void *__pointer) __THROW ;

查看更多

多进程的死锁问题

多进程的死锁问题

我曾经在编码开发时遇到过这样一个问题,代码中产生了死锁,可是我 review 了多次加锁、解锁的地方,却暂时没有发现有什么问题。最终发现是因为在代码中使用了 popen 系统调用,我把遇到的这个问题浓缩成如下的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
pthread_mutex_t mtx;

void my_fork() {
int pid;
if ((pid = fork()) < 0) {
printf("fork failed\n");
} else if (pid == 0) {
printf("id %d (child)\n", getpid());
for (;;) {
printf("child start lock\n");
pthread_mutex_lock(&mtx);
sleep(1);
printf("child run business magic\n");
pthread_mutex_unlock(&mtx);
printf("child end lock\n");
}
}
}

int main(void) {
pthread_mutex_init(&mtx, nullptr);
pthread_mutex_lock(&mtx);

my_fork();
for (;;) {
printf("id %d (parent)\n", getpid());
pthread_mutex_unlock(&mtx);
printf("parent end lock\n");
sleep(1);
printf("parent start lock\n");
pthread_mutex_lock(&mtx);
}
return 0;
}

代码很简单,正常的业务加锁、解锁逻辑中,出现过一个 my_fork() 函数,而这个函数中会调用 fork,并且也同样使用了锁。此时就导致了死锁。如下会输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
id 18923 (parent)
parent end lock
id 18924 (child)
child start lock
parent start lock
id 18923 (parent)
parent end lock
parent start lock
id 18923 (parent)
parent end lock
parent start lock
id 18923 (parent)
parent end lock
...

查看更多