美文网首页
查内存泄漏试试AScan

查内存泄漏试试AScan

作者: 明翼 | 来源:发表于2020-05-27 15:54 被阅读0次

    我用最多的排查c程序的内存问题的工具就是valgrind了,但是它这个工具有时候不是太好用,比如说,速度很慢,平时运行一次1s的程序,用valgrind排查可能要10s左右,还有没有好用的工具那,这不就发现了一个快速内存错误检测工具:
    Address Sanitizer。

    一 简单介绍

    Address Sanitizer是谷歌的快速的内存错误检测工具,它非常快只拖慢程序2倍左右的速度,在这次使用过程中,也是深有体会。在GCC 4.9版本以上,就可以很好的使用了。

    Sanitizers是谷歌发起的开源工具集,包括了AddressSanitizer, MemorySanitizer, ThreadSanitizer, LeakSanitizer,Sanitizers项目本是LLVM项目的一部分,但GNU也将该系列工具加入到了自家的GCC编译器中。GCC从4.8版本开始支持Address和Thread Sanitizer,4.9版本开始支持Leak Sanitizer和UB Sanitizer,这些都是查找隐藏Bug的利器。

    可以支持的内存检测:

    • Use after free
    • Heap buffer overflow
    • Stack buffer overflow
    • Global buffer overflow
    • Use after return
    • Use after scope
    • Initialization order bugs
    • Memory leaks

    二 使用方法

    1. 确保自己的gcc版本在4.9以上,4.8版本上缺少符号信息,我的测试机器:
      centos 7.x ,gcc的版本是4.8.5版本,需要升级。
    gcc -v
    gcc version 4.8.5 20150623 (Red Hat 4.8.5-39) (GCC) 
    

    升级过程:

    wget https://mirrors.ustc.edu.cn/gnu/gcc/gcc-4.9.4/gcc-4.9.4.tar.gz
    tar  xvf  gcc-4.9.4.tar.gz
    cd gcc-4.9.4
    ./contrib/download_prerequisites
    mkdir build-gcc
    cd build-gcc/
    ../gcc-4.9.4/configure --enable-checking=release --enable-languages=c,c++ --disable-multilib
    make -j 8
    make install
    

    注意编译安装gcc的过程,超级慢要1-2个小时。

    1. 编译时候添加选项:
    -fsanitize=address  -fno-omit-frame-pointer -fno-optimize-sibling-calls  -O0
    

    编译的时候说缺少个依赖的库。
    通过:

    yum install libasan -y
    

    命令可以安装下。

    注意:
    Address Sanitizer 会替换malloc和free,如果采用第三方的内存申请库,则无法替换,会造成功能缺失。

    主要可以检查的内存问题有:

    1. Out-of-bounds accesses to heap, stack and globals
    2. Use-after-free
    3. Use-after-return (runtime flag ASAN_OPTIONS=detect_stack_use_after_return=1)
    4. Use-after-scope (clang flag -fsanitize-address-use-after-scope)
    5. Double-free, invalid free
    6. Memory leaks (experimental)

    三 实践测试

    3.1 栈溢出

      1 #include <stdio.h>
      2 #include <stdlib.h>
      3 
      4 int func0(void)
      5 {
      6    char str[4] = {0};
      7    strcpy(str,"1234");
      8    return 0;
      9 }
     10 int main(int argc,char *argv[])
     11 {
     12    func0();
     13    return 0;
     14 }  
    

    命令:

    gcc -g main.c -o t1  -fsanitize=leak -fsanitize=address  -fno-omit-frame-pointer  
    

    运行报错:

    [root@localhost tests]# ./t1
    ./t1: error while loading shared libraries: libasan.so.1: cannot open shared object file: No such file or directory
    

    缺少动态库,解决办法:

    [root@localhost tests]# find / -name libasan.so.1
    /usr/local/lib64/libasan.so.1
    [root@localhost tests]# echo /usr/local/lib64 >> /etc/ld.so.conf
    [root@localhost tests]# ldconfig
    

    运行显示:

    [root@localhost tests]# ./t1
    =================================================================
    ==2890==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffed0cfce50 at pc 0x400965 bp 0x7ffed0cfce20 sp 0x7ffed0cfce18
    WRITE of size 5 at 0x7ffed0cfce50 thread T0
        #0 0x400964 in func0 /home/miaohq/tests/main.c:7
        #1 0x4009d2 in main /home/miaohq/tests/main.c:12
        #2 0x7f73dfa19444 in __libc_start_main (/lib64/libc.so.6+0x22444)
        #3 0x400738 (/home/miaohq/tests/t1+0x400738)
    
    Address 0x7ffed0cfce50 is located in stack of thread T0 at offset 32 in frame
        #0 0x400815 in func0 /home/miaohq/tests/main.c:5
    
      This frame has 1 object(s):
        [32, 36) 'str' <== Memory access at offset 32 partially overflows this variable
    HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
          (longjmp and C++ exceptions *are* supported)
    SUMMARY: AddressSanitizer: stack-buffer-overflow /home/miaohq/tests/main.c:7 func0
    Shadow bytes around the buggy address:
    ....
    

    说明:

    1. 错误类型是 stack-buffer-overflow
    2. 不合法操作WRITE发生在线程T0
      WRITE of size 5 at 0x7ffed0cfce50 thread T0
    3. 具体发生的位置:/home/miaohq/tests/main.c:7
    4. 后面还有影子内存一些指示,后续再开一篇聊下,主要我也不熟悉哈哈:)。

    3.2 堆溢出

    示例代码如下:

     11 void func1(void)
     12 {
     13    char * p = (char*) malloc(sizeof(char)*4);
     14    char chs[] ={"12345"};
     15    memset(p,0x0,4);
     16    if (p != NULL) {
     17      memcpy(p,chs,5);
     18    }
     19 }
    

    按照同样办法编译,测试如下:

    ==37125==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60200000eff0 at pc 0x400cbe bp 0x7ffc7be10c20 sp 0x7ffc7be10c18
    WRITE of size 5 at 0x60200000eff0 thread T0
        #0 0x400cbd in func1 /home/miaohq/tests/main.c:17
        #1 0x400d3d in main /home/miaohq/tests/main.c:24
        #2 0x7fe88958d444 in __libc_start_main (/lib64/libc.so.6+0x22444)
        #3 0x400858 (/home/miaohq/tests/t1+0x400858)
    
    0x60200000eff4 is located 0 bytes to the right of 4-byte region [0x60200000eff0,0x60200000eff4)
    allocated by thread T0 here:
        #0 0x7fe88998c0f2 in __interceptor_malloc ../../../../gcc-4.9.4/libsanitizer/asan/asan_malloc_linux.cc:96
        #1 0x400b60 in func1 /home/miaohq/tests/main.c:13
        #2 0x400d3d in main /home/miaohq/tests/main.c:24
        #3 0x7fe88958d444 in __libc_start_main (/lib64/libc.so.6+0x22444)
    
    

    说明:

    1. 错误类型:heap-buffer-overflow
    2. 错误原因:WRITE of size 5 at 0x60200000eff0 thread T0
    3. 发生位置: #0 0x400cbd in func1 /home/miaohq/tests/main.c:17

    3.3 释放后使用

    就是申请了一块内存区域,释放后没有设置为NULL,后续继续使用了。
    代码示例:

     21 void func2(void)
     22 {
     23     int * a = (int*)malloc(sizeof(int)*1);
     24    if ( a != NULL ) {
     25       *a = 1;
     26       printf("a is:%d.",*a);
     27      free(a);
     28      *a = 2;
     29      printf("error a is:%d.",*a);
     30    }
     31 }
    
    

    同样的编译方法,报错如下:

    ==37556==ERROR: AddressSanitizer: heap-use-after-free on address 0x60200000eff0 at pc 0x400ed6 bp 0x7ffd2dafb6d0 sp 0x7ffd2dafb6c8
    WRITE of size 4 at 0x60200000eff0 thread T0
        #0 0x400ed5 in func2 /home/miaohq/tests/main.c:28
        #1 0x400f0c in main /home/miaohq/tests/main.c:36
        #2 0x7f5632169444 in __libc_start_main (/lib64/libc.so.6+0x22444)
        #3 0x400948 (/home/miaohq/tests/t1+0x400948)
    
    0x60200000eff0 is located 0 bytes inside of 4-byte region [0x60200000eff0,0x60200000eff4)
    freed by thread T0 here:
        #0 0x7f5632567ec1 in __interceptor_free ../../../../gcc-4.9.4/libsanitizer/asan/asan_malloc_linux.cc:79
        #1 0x400e9e in func2 /home/miaohq/tests/main.c:27
        #2 0x400f0c in main /home/miaohq/tests/main.c:36
        #3 0x7f5632169444 in __libc_start_main (/lib64/libc.so.6+0x22444)
    
    previously allocated by thread T0 here:
        #0 0x7f56325680f2 in __interceptor_malloc ../../../../gcc-4.9.4/libsanitizer/asan/asan_malloc_linux.cc:96
        #1 0x400e2b in func2 /home/miaohq/tests/main.c:23
        #2 0x400f0c in main /home/miaohq/tests/main.c:36
        #3 0x7f5632169444 in __libc_start_main (/lib64/libc.so.6+0x22444)
    
    SUMMARY: AddressSanitizer: heap-use-after-free /home/miaohq/tests/main.c:28 func2
    

    说明:

    1. 错误类型:heap-use-after-free
    2. 错误原因:WRITE of size 4 at 0x60200000eff0 thread T0
    3. 发生位置: #0 0x400ed5 in func2 /home/miaohq/tests/main.c:28
      这个挺好的,有明确的错误类型,指示位置也很准确。

    3.4 全局缓存溢出

    这些错误类型比valgrind分的更细致点,让我们来看看代码:

    int g_abc[11];
    
     36 int func3(void)
     37 {
     38     int i = 0;
     39     for (i = 0; i <= 100; i++) {
     40       printf("value:%d\t",g_abc[i]);
     41       if (i%10 == 0 && i != 0) {
     42          printf("\n");
     43       }
     44    }
     45    return g_abc[12];
     46 }
    
    

    很不幸,这个翻车了,就算我把循环改成了100也是可以正常处理的,另外在gcc下没有告警,在clang中倒是在编译的时候就给出了告警信息:

    clang: warning: argument unused during compilation: '-u se-after-free'
    main.c:45:11: warning: array index 12 is past the end of the array (which contains 11 elements) [-Warray-bounds]
       return g_abc[12];
              ^     ~~
    main.c:5:1: note: array 'g_abc' declared here
    int g_abc[11];
    

    编译器改成g++,编译命令如下:

    g++    -fsanitize=address  -fno-omit-frame-pointer -fsanitize=leak    -use-after-free  -g main.c -o t1
    

    再次执行的时候报错:

    [root@localhost tests]# ./t1
    value:0 value:0 value:0 value:0 value:0 value:0 value:0 value:0 value:0 value:0 value:0 
    =================================================================
    ==25944==ERROR: AddressSanitizer: global-buffer-overflow on address 0x00000060224c at pc 0x401079 bp 0x7ffce4e9ffe0 sp 0x7ffce4e9ffd8
    READ of size 4 at 0x00000060224c thread T0
        #0 0x401078 in func3() /home/miaohq/tests/main.c:40
        #1 0x401299 in main /home/miaohq/tests/main.c:60
        #2 0x7f3727c92444 in __libc_start_main (/lib64/libc.so.6+0x22444)
        #3 0x400a68 (/home/miaohq/tests/t1+0x400a68)
    
    0x00000060224c is located 0 bytes to the right of global variable 'g_abc' from 'main.c' (0x602220) of size 44
    SUMMARY: AddressSanitizer: global-buffer-overflow /home/miaohq/tests/main.c:40 func3()
    
    

    说明:

    1. 错误类型:global-buffer-overflow
    2. 错误原因: READ of size 4 at 0x00000060224c thread T0 即发生了越界读
    3. 错误位置:#0 0x401078 in func3() /home/miaohq/tests/main.c:40

    3.5 内存泄漏

    内存泄漏代码:

     47 int func4(void)
     48 {
     49    char * p = (char*) malloc(5);
     50    memset(p,0x0,5);
     51    memcpy(p,"1234",4),
     52    printf("%s\n",p);
     53 }
    

    在我gcc的4.9.4版本情况下,如果按照上述编译和运行后,并没有看到任何内存泄漏的提示,
    在clang的编译器下,可以通过:

    ASAN_OPTIONS=detect_leaks=1 ./t1
    

    运行程序来显示内存泄漏,但是我的gcc的版本还是低了,还是无法显示内存泄漏,索性直接升级到最新版本,升级过程太慢,这个无法忍受,我还是先用clang编译器测试下。

    yum install clang -y
    clang    -fsanitize=address  -fno-omit-frame-pointer -fsanitize=leak    -use-after-free  -g main.c -o t1
    ASAN_OPTIONS=detect_leaks=1 ./t1
    

    显示信息如下:

    root@localhost tests]# ASAN_OPTIONS=detect_leaks=1 ./t1
    1234
    =================================================================
    ==108674==ERROR: LeakSanitizer: detected memory leaks
    
    Direct leak of 5 byte(s) in 1 object(s) allocated from:
        #0 0x465229 in __interceptor_malloc (/home/miaohq/tests/t1+0x465229)
        #1 0x47c59e in func4 /home/miaohq/tests/main.c:49
        #2 0x47cabb in main /home/miaohq/tests/main.c:58
        #3 0x7efd6b207444 in __libc_start_main (/lib64/libc.so.6+0x22444)
    
    SUMMARY: AddressSanitizer: 5 byte(s) leaked in 1 allocation(s).
    

    说明:

    1. 显示错误原因为:detected memory leaks
    2. 被泄漏的内存:Direct leak of 5 byte(s) in 1 object(s) allocated from
    3. 泄漏的具体位置: 0x47c59e in func4 /home/miaohq/tests/main.c:49
    4. 总结信息:AddressSanitizer: 5 byte(s) leaked in 1 allocation(s).

    提示: 有时候我们编译程序通过-g 或-ggdb调试仍然有问题,只显示地址,这里面可以试试这个办法:
    [root@localhost tests]# addr2line -a -C -e ./t1 0x47c46b
    0x000000000047c46b
    /home/miaohq/tests/main.c:51

    五 升级gcc到最新版本

    按照上面的方法升级到9.3.0,运行缺少库:
    安装:

    ./bin/suricata: error while loading shared libraries: libasan.so.5: cannot open shared object file: No such file or directory
    ## 解决办法
    yum install centos-release-scl-rh
    yum --enablerepo=centos-sclo-rh-testing install libasan5
    

    五 参考

    1. 利用GCC编译选项快速定位内存错误
    2. Android native分析工具ASAN和HWASAN原理解析
    3. Address Sanitizer 用法
    4. AFL Fuzzing with ASAN
    5. KASAN实现原理
    6. Linux 下的 AddressSanitizer
      https://blog.csdn.net/hanlizhong85/article/details/78076668?utm_source=blogxgwz6
    7. https://blog.csdn.net/tq08g2z/article/details/90347700
    8. https://clang.llvm.org/docs/AddressSanitizer.html
    9. https://blog.csdn.net/wads23456/article/details/105141997?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase

    相关文章

      网友评论

          本文标题:查内存泄漏试试AScan

          本文链接:https://www.haomeiwen.com/subject/yboiahtx.html