* GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源,下面我们就来聊聊关于性能测试如何判断内存溢出 面向开发的内存调试神器?接下来我们就一起去了解一下吧!

性能测试如何判断内存溢出 面向开发的内存调试神器

性能测试如何判断内存溢出 面向开发的内存调试神器

* GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源。

介绍

首先,先介绍一下 Sanitizer 项目,该项目是谷歌出品的一个开源项目,该项目包含了 ASAN、LSAN、MSAN、TSAN等内存、线程错误的检测工具,这里简单介绍一下这几个工具的作用:

ASAN,全称 AddressSanitizer,可以用来检测内存问题,例如缓冲区溢出或对悬空指针的非法访问等。

根据谷歌的工程师介绍 ASAN 已经在 chromium 项目上检测出了300多个潜在的未知bug,而且在使用 ASAN 作为内存错误检测工具对程序性能损耗也是及其可观的。

根据检测结果显示可能导致性能降低2倍左右,比Valgrind(官方给的数据大概是降低10-50倍)快了一个数量级。

而且相比于Valgrind只能检查到堆内存的越界访问和悬空指针的访问,ASAN 不仅可以检测到堆内存的越界和悬空指针的访问,还能检测到栈和全局对象的越界访问。

这也是 ASAN 在众多内存检测工具的比较上出类拔萃的重要原因,基本上现在 C/C 项目都会使用ASAN来保证产品质量,尤其是大项目中更为需要。

如何使用 ASAN

作为如此强大的神兵利器,自然是不会在程序员的战场上失宠的。

从LLVM3.1、GCC4.8、XCode7.0、MSVC16.9开始ASAN就已经成为众多主流编译器的内置工具了,因此,要在项目中使用ASAN也是十分方便。

现在只需要在编译命令中加上-fsanitize=address检测选项就可以让ASAN在你的项目中大展神通,接下来通过几个例子来看一下 ASAN 到底有哪些本领。

注意:

在下面的例子中打开了调试标志-g,这是因为当发现内存错误时调试符号可以帮助错误报告更准确的告知错误发生位置的堆栈信息,如果错误报告中的堆栈信息看起来不太正确,请尝试使用-fno-omit-frame-pointer来改善堆栈信息的生成情况。

如果构建代码时,编译链接阶段分开执行,则必须在编译和链接阶段都添加-fsanitize=address选项。

检测内存泄漏

// leak.c #include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc, const char *argv[]) { char *s = (char*)malloc(100); strcpy(s, "Hello world!"); printf("string is: %s\n", s); return 0; }

上述代码中我们分配了100个字节的内存空间,但在main函数返回前始终没有释放,接下来我们使用ASAN看一下是否能够检测出来,添加-fsanitize=address -g参数构建代码并执行:

~/Code/test$ gcc noleak.c -o noleak -fsanitize=address -g ~/Code/test$ ./leak string is: Hello world! ================================================================= ==1621572==ERROR: LeakSanitizer: detected memory leaks // 1) Direct leak of 100 byte(s) in 1 object(s) allocated from: // 2) #0 0x7f5b986bc808 in __interceptor_malloc ../../../../src/libsanitizer/ASAN/ASAN_malloc_linux.cc:144 #1 0x562d866b5225 in main /home/chenbing/Code/test/leak.c:7 #2 0x7f5b983e1082 in __libc_start_main ../csu/libc-start.c:308 SUMMARY: AddressSanitizer: 100 byte(s) leaked in 1 allocation(s).

这里,ASAN 提供的报告说明了错误原因是detected memory leaks内存泄漏了1),同时,2)说明ASAN检测到应用程序分配了100个字节,并捕获到了内存分配位置的堆栈信息,还告诉了我们内存是在leak.c:7分配的。

有了这么详细的且准确的错误报告,内存问题是不是不那么头疼了?

检测悬空指针访问

// uaf.c #include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc, const char *argv[]) { char *s = (char*)malloc(100); free(s); strcpy(s, "Hello world!"); // use-after-free printf("string is: %s\n", s); return 0; }

上述代码中我们分配了100个字节的内存空间,紧接着将其释放,但接下来我们对之前分配的内存地址执行写入操作,这是典型的悬空指针非法访问,同样,让我们使用ASAN看一下是否能够检测出来,添加-fsanitize=address -g参数构建代码并执行:

~/Code/test$ gcc uaf.c -o uaf -fsanitize=address -g ~/Code/test$ ./uaf ================================================================= ==1624341==ERROR: AddressSanitizer: heap-use-after-free on address 0x60b0000000f0 at PC 0x7f9f776bb58d bp 0x7fffabad8280 sp 0x7fffabad7a28 // 1) WRITE of size 13 at 0x60b0000000f0 thread T0 // 2) #0 0x7f9f776bb58c in __interceptor_memcpy ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:790 #1 0x55b9cf56e26d in main /home/chenbing/Code/test/uaf.c:9 #2 0x7f9f77452082 in __libc_start_main ../csu/libc-start.c:308 #3 0x55b9cf56e16d in _start (/home/chenbing/Code/test/uaf 0x116d) 0x60b0000000f0 is located 0 bytes inside of 100-byte region [0x60b0000000f0,0x60b000000154) // 3) freed by thread T0 here: #0 0x7f9f7772d40f in __interceptor_free ../../../../src/libsanitizer/ASAN/ASAN_malloc_linux.cc:122 #1 0x55b9cf56e255 in main /home/chenbing/Code/test/uaf.c:8 #2 0x7f9f77452082 in __libc_start_main ../csu/libc-start.c:308 previously allocated by thread T0 here: // 4) #0 0x7f9f7772d808 in __interceptor_malloc ../../../../src/libsanitizer/ASAN/ASAN_malloc_linux.cc:144 #1 0x55b9cf56e245 in main /home/chenbing/Code/test/uaf.c:7 #2 0x7f9f77452082 in __libc_start_main ../csu/libc-start.c:308 SUMMARY: AddressSanitizer: heap-use-after-free ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:790 in __interceptor_memcpy Shadow bytes around the buggy address: // 5) 0x0c167fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c167fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c167fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c167fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c167fff8000: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd =>0x0c167fff8010: fd fd fd fd fd fa fa fa fa fa fa fa fa fa[fd]fd 0x0c167fff8020: fd fd fd fd fd fd fd fd fd fd fd fa fa fa fa fa 0x0c167fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c167fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c167fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c167fff8060: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASAN internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc ==1624341==ABORTING

这个错误报告看起来很长,但实际上并不复杂,

检测堆溢出

// overflow.c #include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc, const char *argv[]) { char *s = (char*)malloc(12); strcpy(s, "Hello world!"); printf("string is: %s\n", s); free(s); return 0; }

上面这段代码我们只分配了2个字节,但在随后操作中写入了13字节的数据(字符串还包含\0做为终止符),此时,数据的写入显然是溢出分配的内存块了,同样,添加-fsanitize=address -g参数构建代码并执行:

~/Code/test$ gcc overflow.c -o overflow -fsanitize=address -g ~/Code/test$ ./overflow ================================================================= ==2172878==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60200000001c at pc 0x7f1cd3d3d58d bp 0x7ffee78e6500 sp 0x7ffee78e5ca8 //1) WRITE of size 13 at 0x60200000001c thread T0 // 2) #0 0x7f1cd3d3d58c in __interceptor_memcpy ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:790 #1 0x555593131261 in main /home/chenbing/Code/test/overflow.c:7 #2 0x7f1cd3ad4082 in __libc_start_main ../csu/libc-start.c:308 #3 0x55559313116d in _start (/home/chenbing/Code/test/overflow 0x116d) 0x60200000001c is located 0 bytes to the right of 12-byte region [0x602000000010,0x60200000001c) // 3) allocated by thread T0 here: #0 0x7f1cd3daf808 in __interceptor_malloc ../../../../src/libsanitizer/ASAN/ASAN_malloc_linux.cc:144 #1 0x555593131245 in main /home/chenbing/Code/test/overflow.c:6 #2 0x7f1cd3ad4082 in __libc_start_main ../csu/libc-start.c:308 SUMMARY: AddressSanitizer: heap-buffer-overflow ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:790 in __interceptor_memcpy Shadow bytes around the buggy address: // 4) 0x0c047fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c047fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c047fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c047fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c047fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x0c047fff8000: fa fa 00[04]fa fa fa fa fa fa fa fa fa fa fa fa 0x0c047fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c047fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c047fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c047fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c047fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASAN internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc ==2172878==ABORTING

上面的报告访问悬空指针的错误报告很相似,同样

1)告诉我们错误的原因是:heap-buffer-overflow,堆区内存溢出了,该内存的地址是:0x60200000001c。

2)描述了写入数据导致溢出的位置堆栈,

3)则是对应的内存分配位置堆栈,4)还是shadow内存快照。

C 中的new/delete不匹配

// bad_delete.cpp #include <iostream> #include <cstring> int main(int argc, const char *argv[]) { char *cstr = new char[100]; strcpy(cstr, "Hello World"); std::cout << cstr << std::endl; delete cstr; return 0; }

这段代码通过new[]关键字分配了一块内存,但是在函数返回前却是使用delete堆内存进行释放,而不是delete[],这将导致分配的内存没有被完全释放,还是添加-fsanitize=address -g参数构建代码并执行:

~/Code/test$ g bad_delete.cpp -o bad_delete -fsanitize=address -g ~/Code/test$ ./bad_delete Hello World ================================================================= ==2180936==ERROR: AddressSanitizer: alloc-dealloc-mismatch (operator new [] vs operator delete) on 0x60b0000000f0 // 1 #0 0x7fa9f877cc65 in operator delete(void*, unsigned long) ../../../../src/libsanitizer/ASAN/ASAN_new_delete.cc:177 #1 0x55d09d3fe33f in main /home/chenbing/Code/test/bad_delete.cpp:10 #2 0x7fa9f8152082 in __libc_start_main ../csu/libc-start.c:308 #3 0x55d09d3fe20d in _start (/home/chenbing/Code/test/bad_delete 0x120d) 0x60b0000000f0 is located 0 bytes inside of 100-byte region [0x60b0000000f0,0x60b000000154) // 2 allocated by thread T0 here: #0 0x7fa9f877b787 in operator new[](unsigned long) ../../../../src/libsanitizer/ASAN/ASAN_new_delete.cc:107 #1 0x55d09d3fe2e5 in main /home/chenbing/Code/test/bad_delete.cpp:6 #2 0x7fa9f8152082 in __libc_start_main ../csu/libc-start.c:308 SUMMARY: AddressSanitizer: alloc-dealloc-mismatch ../../../../src/libsanitizer/ASAN/ASAN_new_delete.cc:177 in operator delete(void*, unsigned long) ==2180936==HINT: if you don't care about these errors you may set ASAN_OPTIONS=alloc_dealloc_mismatch=0 ==2180936==ABORTING

这份错误报告比上面两个要简要的多,但提供的信息已经完全足够定位问题了:

1)汇报了错误类型:alloc-dealloc-mismatch,分配和释放操作不匹配,该内存的地址是:0x60b0000000f0,

2)是对应的内存分配位置堆栈,该报告不会明确告诉错误的位置应该使用delete[]对内存进行释放,因为在C 中分配和释放关键字可以被重写或者其他特定场景不匹配的关键字也能完全释放内存。

因此,ASAN不能保证alloc-dealloc-mismatch一定符合用户的期望,所以,在该报告中ASAN说明了:如果这对用户来说这是一个误报的错误,那么可以使用ASAN_OPTIONS=alloc_dealloc_mismatch=0来禁用该报告的触发,

例如:

~/Code/test$ ASAN_OPTIONS=alloc_dealloc_mismatch=0 ./bad_delete Hello World

上面执行代码时添加了ASAN_OPTIONS=alloc_dealloc_mismatch=0参数,因此,ASAN不会认为alloc-dealloc-mismatch是一个错误,从而发出错误报告。

检测栈溢出

// sbo.c #include <stdio.h> int main(int argc, const char *argv[]) { int stack_array[100]; stack_array[101] = 1; return 0; }

上面的代码,我们在栈上创建了一个容量为100的数组,但在随后的写入操作中在超过数据容量的地址上写入数据,导致了栈溢出,添加-fsanitize=address -g参数构建代码并执行:

~/Code/test$ g sbo.c -o sbo -fsanitize=address -g chenbing@GreatDB-CB:~/Code/test$ ./sbo ================================================================= ==2196928==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffc33777f24 at pc 0x562dccb592b6 bp 0x7ffc33777d40 sp 0x7ffc33777d30 1) WRITE of size 4 at 0x7ffc33777f24 thread T0 #0 0x562dccb592b5 in main /home/chenbing/Code/test/sbo.c:6 #1 0x7f45bf52d082 in __libc_start_main ../csu/libc-start.c:308 #2 0x562dccb5910d in _start (/home/chenbing/Code/test/sbo 0x110d) Address 0x7ffc33777f24 is located in stack of thread T0 at offset 452 in frame 2) #0 0x562dccb591d8 in main /home/chenbing/Code/test/sbo.c:4 This frame has 1 object(s): 3) [48, 448) 'stack_array' (line 5) <== Memory access at offset 452 overflows this variable HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork 4) (longjmp and C exceptions *are* supported) SUMMARY: AddressSanitizer: stack-buffer-overflow /home/chenbing/Code/test/sbo.c:6 in main Shadow bytes around the buggy address: 5) 0x1000066e6f90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000066e6fa0: 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1 0x1000066e6fb0: f1 f1 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000066e6fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000066e6fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x1000066e6fe0: 00 00 00 00[f3]f3 f3 f3 f3 f3 f3 f3 00 00 00 00 0x1000066e6ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000066e7000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000066e7010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000066e7020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000066e7030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASAN internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc ==2196928==ABORTING

这份报告的内容基本与上面几本报告的内容相似,这里不再做过多解释,我们来关注几个不同的地方,

3)说明了栈对象的在函数栈区的偏移范围是[48, 448)(左闭右开),而代码中通过栈对象访问的位置却是512导致了栈溢出。

还有一个地方需要在注意:报告中提到了一个可能错报的栈溢出场景:如果程序使用一些特殊的堆栈展开机制,swapcontext或者vfork则可能出现误报,关于误报的更多说明可以参阅下面两个issue:

检测全局缓冲区溢出

// gbo.c #include <stdio.h> int global_array[100] = {-1}; int main(int argc, char **argv) { global_array[101] = 1; return 0; }

上面的代码与栈溢出案例的代码相似,不同仅仅只是的是我们在全局数据段上创建了一个容量为100的数组,接下来添加-fsanitize=address -g参数构建代码并执行:

~/Code/test$ g gbo.c -o gbo -fsanitize=address -g ~/Code/test$ ./gbo ================================================================= ==2213117==ERROR: AddressSanitizer: global-buffer-overflow on address 0x558855e231b4 at pc 0x558855e20216 bp 0x7ffd9569d280 sp 0x7ffd9569d270 WRITE of size 4 at 0x558855e231b4 thread T0 #0 0x558855e20215 in main /home/chenbing/Code/test/gbo.c:7 #1 0x7efd3da4f082 in __libc_start_main ../csu/libc-start.c:308 #2 0x558855e2010d in _start (/home/chenbing/Code/test/gbo 0x110d) 0x558855e231b4 is located 4 bytes to the right of global variable 'global_array' defined in 'gbo.c:4:5' (0x558855e23020) of size 400 SUMMARY: AddressSanitizer: global-buffer-overflow /home/chenbing/Code/test/gbo.c:7 in main Shadow bytes around the buggy address: 0x0ab18abbc5e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0ab18abbc5f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0ab18abbc600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0ab18abbc610: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0ab18abbc620: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x0ab18abbc630: 00 00 00 00 00 00[f9]f9 f9 f9 f9 f9 00 00 00 00 0x0ab18abbc640: f9 f9 f9 f9 f9 f9 f9 f9 00 00 00 00 00 00 00 00 0x0ab18abbc650: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0ab18abbc660: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0ab18abbc670: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0ab18abbc680: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASAN internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc ==2213117==ABORTING

上面的报告基本与栈溢出案例的报告相同,不同的只是错误类型和全局对象代码位置的报告方式,这里不再过多介绍。

好了,关于 ASAN 的使用案例我们就介绍到这里,更多内容可以自行到ASAN的项目中去寻找

ASAN 的基本原理

ASAN的内存检测方法与Valgrind的AddrCheck工具很像,都是使用shadow内存来记录应用程序的每个字节是否可以被安全的访问,在访问内存时都对其映射的shadow内存进行检查。

但是,ASAN使用一个更具效率的shadow内存映射机制和更加紧凑的内存编码来实现,并且除了堆内存外还能检测栈和全局对象中的错误访问,且比AddrCheck快一个数量级。

ASAN由两部分组成:代码插桩模块运行时库

首先,我们先介绍一下什么是shadow 内存 和 redzone。

代码插桩

ASAN 会在应用程序访问内存的位置进行插桩,对于访问完整8字节内存的位置,插入以下代码检查内存对应的 shadow 内存,以此判断是否访问异常:

ShadowAddr = (Addr >> 3) Offset; if (*ShadowAddr != 0) ReportAndCrash(Addr);

由于应用程序访问8字节的内存,因此,其映射的shadow 内存的存储值必须是0,表示该8字节内存完全可用,否则,报错。

应用程序对 1、2、或者 4 字节内存的访问要复杂一些,如果访问的内存块对应的shadow 内存的存储值如果不是负数,且不为0,或者将要访问内存块超过了shadow 内存表示的可用范围,意味着本次将访问到不可使用的内存:

ShadowAddr = (Addr >> 3) Offset; k = *ShadowAddr; if (k != 0 && ((Addr & 7) AccessSize > k)) ReportAndCrash(Addr);

需要注意的是,ASAN对源代码的插桩时机是在LLVM对代码编译优化之后,也就意味着ASAN只能检测 LLVM 优化后幸存下来的内存访问,例如:被 LLVM 优化掉的对栈对象进行访问的代码将不会被ASAN所识别。

同时,ASAN也不会对 LLVM 生成的内存访问代码进行插桩,例如:寄存器溢出检查等等。

另外,即使错误报告代码ReportAndCrash(Addr)只会被调用一次,但由于会在代码中的许多位置进行插入,因此,错误报告代码也必须相当紧凑。

目前 ASAN 使用了一个简单的函数调用来处理错误报告,当然还有另一个选择是插入一个硬件异常。

运行时库

在应用程序启动时,将映射整个shadow 内存,因此程序的其他部分不能使用它。BAD 区域也是受保护的,应用程序也不能访问。

在 linux 操作系统中,shadow 内存区域不会被占用,因此,映射总是成功的。但在 MacOS 中可能需要禁用地址空间布局(ASLR)。

另外,根据 GOOGLE 工程师介绍,shadow 内存区域的布局也适用于 windows 操作系统。

启用 ASAN 时,源代码中的 malloc 和 free 函数将会被替换为运行时库中的 malloc 和 free 函数。

malloc 分配的内存区域被组织为为一个与对象大小相对应的空闲列表数组。当对应于所请求内存大小的空闲列表为空时,从操作系统(例如,使用mmap)分配带有redzone的内存区域。n个内存块,将分配n 1个redzone:

| redzone-1 | memory-1 | redzone-2 | memory-2 | redzone-3 |

free 函数会将整个内存区域置成不可使用并将其放入隔离区,这样该区域就不会马上被 malloc 分配给应用程序。

目前,隔离区是使用一个 FIFO 队列实现的,它在任何时候都拥有一定数量的内存。

默认情况下,malloc 和 free 记录当前调用堆栈,以便提供更多信息的错误报告。 malloc 调用堆栈存储在左侧 redzone 中(redzone 越大,可以存储的帧数越多),而 free 调用堆栈存储在内存区域本身的开头。

到这里你应该已经明白了对于动态分配的内存,ASAN是怎么实现检测的,但你可能会产生疑惑:动态分配是通过 malloc 函数分配redzone来支持错误检测,那栈对象和全局对象这类没有malloc分类内存的对象是怎么实现的呢?其实原理也很简单:

总结

ASAN 使用shadow 内存和redzone来提供准确和即时的错误检测。

传统观点认为,shadow 内存和redzone要么通过多级映射方案产生高开销,要么占用大量的程序内存。但,ASAN的使用的shadow映射机制和shadow 状态编码减少了对内存空间占用。

最后,如果你觉得ASAN插桩代码和检测的对你某些的代码来说太慢了,那么可以使用编译器标志来禁用特定函数的,使ASAN跳过对代码中某个函数的插桩和检测, 跳过分析函数的编译器指令是:

__attribute__((no_sanitize_address))

Enjoy GreatSQL :)

,