关于mysql:面向开发的内存调试神器如何使用ASAN检测内存泄漏堆栈溢出等问题

65次阅读

共计 18283 个字符,预计需要花费 46 分钟才能阅读完成。

  • 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: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

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

  • 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: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:

  • 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 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的内存检测办法与 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 公布!

正文完
 0