• GreatSQL社区原创内容未经受权不得随便应用,转载请分割小编并注明起源。

[toc]

介绍

首先,先介绍一下 Sanitizer 我的项目,该我的项目是谷歌出品的一个开源我的项目,该我的项目蕴含了 ASANLSANMSANTSAN等内存、线程谬误的检测工具,这里简略介绍一下这几个工具的作用:

  • ASAN: 内存谬误检测工具,在编译命令中增加-fsanitize=address启用
  • LSAN: 内存透露检测工具,曾经集成到 ASAN 中,能够通过设置环境变量ASAN_OPTIONS=detect_leaks=0来敞开ASAN上的LSAN,也能够应用-fsanitize=leak编译选项代替-fsanitize=address来敞开ASAN的内存谬误检测,只开启内存透露查看。
  • MSAN: 对程序中未初始化内存读取的检测工具,能够在编译命令中增加-fsanitize=memory -fPIE -pie启用,还能够增加-fsanitize-memory-track-origins选项来追溯到创立内存的地位
  • TSAN: 对线程间数据竞争的检测工具,在编译命令中增加-fsanitize=thread启用
    其中ASAN就是咱们明天要介绍的重头戏。

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

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

依据检测结果显示可能导致性能升高2倍左右,比Valgrind(官网给的数据大略是升高10-50倍)快了一个数量级。

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

这也是 ASAN 在泛滥内存检测工具的比拟上超群绝伦的重要起因,基本上当初 C/C++ 我的项目都会应用ASAN来保障产品质量,尤其是大我的项目中更为须要。

如何应用 ASAN

作为如此弱小的神兵利器,天然是不会在程序员的战场上得宠的。

LLVM3.1GCC4.8XCode7.0MSVC16.9开始ASAN就曾经成为泛滥支流编译器的内置工具了,因而,要在我的项目中应用ASAN也是非常不便。

当初只须要在编译命令中加上-fsanitize=address检测选项就能够让ASAN在你的我的项目中大展神通,接下来通过几个例子来看一下 ASAN 到底有哪些本事。

留神:

  1. 在上面的例子中关上了调试标记-g,这是因为当发现内存谬误时调试符号能够帮忙错误报告更精确的告知谬误产生地位的堆栈信息,如果错误报告中的堆栈信息看起来不太正确,请尝试应用-fno-omit-frame-pointer来改善堆栈信息的生成状况。
  2. 如果构建代码时,编译链接阶段离开执行,则必须在编译和链接阶段都增加-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:308SUMMARY: 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:308previously 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:308SUMMARY: AddressSanitizer: heap-use-after-free ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:790 in __interceptor_memcpyShadow 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 faShadow 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

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

  • 1)通知咱们谬误的起因是:heap-use-after-free,拜访了悬空指针,该内存的地址是:0x60b0000000f0,同时还通知咱们产生谬误时的PC、BP、SP寄存器的内容,这些咱们能够不关怀,因为接下来的报告让咱们能够疏忽这些寄存器就能够定位到问题。
  • 接下来是2), 3), 4),别离报告了拜访悬空指针的地位、内存被开释地位、内存的调配地位的堆栈信息以及线程信息,从2)能够看到谬误产生在uaf.c文件的第8行代码。报告中的其余局部
  • 5)提供了谬误拜访的内存地址对应的shadow 内存的具体,其中fa示意堆区内存的red zonefd示意曾经开释的堆区内存。

检测堆溢出

// 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:308SUMMARY: AddressSanitizer: heap-buffer-overflow ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:790 in __interceptor_memcpyShadow 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 faShadow 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)       // 2allocated 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:308SUMMARY: 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 -gchenbing@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 variableHINT: 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 mainShadow 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 00Shadow 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:

  • support swapcontext
  • Replace vfork() with fork()

检测全局缓冲区溢出

// 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 0x7ffd9569d270WRITE 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 400SUMMARY: AddressSanitizer: global-buffer-overflow /home/chenbing/Code/test/gbo.c:7 in mainShadow 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 00Shadow 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的内存检测办法与ValgrindAddrCheck工具很像,都是应用shadow内存来记录应用程序的每个字节是否能够被平安的拜访,在拜访内存时都对其映射的shadow内存进行查看。

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

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

  • 代码插桩模块会批改代码使其在拜访内存时查看每块内存拜访状态,称为shadow 状态,以及在内存两侧创立redzone的内存区域。
  • 运行时库则提供一组接口用来代替mallocfree以及相干的函数,使得在调配堆空间时在其四周创立redzone,并在内存出错时报告谬误。

首先,咱们先介绍一下什么是shadow 内存redzone

  • shadow 内存

    ASANmalloc函数返回的内存地址通常至多是8个字节对齐,比方malloc(15)将调配失去2块大小为8字节的内存,在这个场景中,第二块8字节内存的前5个字节是能够拜访,但剩下的3个字节是不能拜访的。

    所谓的shadow 内存就是在应用程序的虚拟地址空间中预留一段地址空间,用来存储映射应用程序拜访的内存块中哪些字节能够被应用的信息,这些信息就是shadow 状态。其中每1个字节的shadow 内存,映射到8个字节的应用程序内存,因而,shadow状态可能有3种:

    1. 0: 示意映射的8个字节均能够应用
    2. k(1<=k<=7): 示意示意映射的8个字节中只有前k个字节能够应用
    3. 负值: 示意映射的8个字节均不可应用,且不同的值示意所映射不同的内存类型(堆、栈、全局对象或已开释内存)

      ASAN应用带有比例和偏移量的间接映射将应用程序地址转换为其对应的shadow内存地址:

      shadow_address = (addr >> 3) + offset

      假如max - 1是虚拟地址空间中的最大无效地址,则offset的值应抉择为在启动时不被占用的从offsetoffset+Max/8的区域。

    • 在 32 位 linux 零碎中,虚拟地址空间为:0x00000000-0xffffffffoffset = 0x20000000(2^29)
    • 在 64 位零碎中,ofsset = 0x0000100000000000(2^44)
    • 在某些状况下(例如,在 Linux 上应用 -fPIE/-pie 编译器标记)能够应用零偏移来进一步简化检测。

    以下是 32 位 linux 零碎中的地址空间散布

    0x1 0000 0000 ---------------              |   HIGH      |              |   MEMORY    |  0x4000 0000 ---------------              | HIGH SHADOW |  0x2800 0000 ---------------              | BAD REGION  |  0x2400 0000 ---------------              | LOW SHADOW  |  0x2000 0000 ---------------              | LOW MEMORY  |  0x0000 0000 ---------------  

    虚拟地址空间被划分为高下两局部,每个局部的内存地址映射到相应的shadow 内存。留神:将shadow 内存中的地址进行映射会失去Bad 区域中的地址,Bad 区域是被页面爱护标记为不可拜访的地址空间。

    shadow映射形式能够推导为(addr >> scale) + offset的模式,其中scale是的取值范畴是1~7,当 scale=N时,shadow 内存占用虚拟地址空间的1/2^N, red-zone的最小大小为2^N字节(保障malloc()的对齐要求)。shadow 内存中的每个字节形容了2^N个内存字节的状态并有2^N + 1个不同的值。

  • redzone

    ASAN会在应用程序应用的堆、栈、全局对象的内存四周调配额定内存,这个额定的内存叫做redzoneredzone会被shadow 内存标记为不可应用状态,当应用程序拜访redzone内存时阐明曾经溢出拜访了,此时,ASAN检测redzoneshadow 状态后就会报告相应谬误。readzone越大,检测内存下溢和上溢的范畴越大。具体的调配策略将在上面波及。

代码插桩

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 时,源代码中的 mallocfree 函数将会被替换为运行时库中的 mallocfree 函数。

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

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

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

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

默认状况下,mallocfree 记录以后调用堆栈,以便提供更多信息的错误报告。 malloc 调用堆栈存储在左侧 redzone 中(redzone 越大,能够存储的帧数越多),而 free 调用堆栈存储在内存区域自身的结尾。

到这里你应该曾经明确了对于动态分配的内存,ASAN是怎么实现检测的,但你可能会产生纳闷:动态分配是通过 malloc 函数调配redzone来反对谬误检测,那栈对象和全局对象这类没有malloc分类内存的对象是怎么实现的呢?其实原理也很简略:

  • 对于全局变量,redzone 在编译时创立,redzone 的地址在应用程序启动时传递给运行时库。 运行时库函数会将redzone 设置为不可应用并记录地址以供进一步错误报告。
  • 对于栈对象,redzone 是在运行时创立和置为不可应用。 目前,应用32字节的 redzone。例如以下代码片段:

    void foo() {  char a[10];  <function body> }

    ASAN 解决后的代码大抵如下:

    void foo() {  char rz1[32]  char arr[10];  char rz2[32-10+32];  unsigned * shadow = (unsigned*)(((long)rz1>>8)+Offset);  // 将 redzone 设置为不可应用  shadow[0] = 0xffffffff; // rz1  shadow[1] = 0xffff0200; // arr and rz2  shadow[2] = 0xffffffff; // rz2  <function body>  // 将所有内存设置成能够应用  shadow[0] = shadow[1] = shadow[2] = 0; }

总结

ASAN 应用shadow 内存redzone来提供精确和即时的谬误检测。

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

最初,如果你感觉ASAN插桩代码和检测的对你某些的代码来说太慢了,那么能够应用编译器标记来禁用特定函数的,使ASAN跳过对代码中某个函数的插桩和检测, 跳过剖析函数的编译器指令是:

__attribute__((no_sanitize_address))

Enjoy GreatSQL :)

文章举荐:

面向金融级利用的GreatSQL正式开源
https://mp.weixin.qq.com/s/cI...

Changes in GreatSQL 8.0.25 (2021-8-18)
https://mp.weixin.qq.com/s/qc...

MGR及GreatSQL资源汇总
https://mp.weixin.qq.com/s/qX...

GreatSQL MGR FAQ
https://mp.weixin.qq.com/s/J6...

在Linux下源码编译装置GreatSQL/MySQL
https://mp.weixin.qq.com/s/WZ...

# 对于 GreatSQL

GreatSQL是由万里数据库保护的MySQL分支,专一于晋升MGR可靠性及性能,反对InnoDB并行查问个性,是实用于金融级利用的MySQL分支版本。

Gitee:

https://gitee.com/GreatSQL/Gr...

GitHub:

https://github.com/GreatSQL/G...

Bilibili:

https://space.bilibili.com/13...

微信&QQ群:

可搜寻增加GreatSQL社区助手微信好友,发送验证信息“加群”退出GreatSQL/MGR交换微信群

QQ群:533341697

微信小助手:wanlidbc

本文由博客一文多发平台 OpenWrite 公布!