[用户态内存] linux用户态内存检测技术

1 ASAN

ASAN(Address Sanitizer)是一款面向C/C++语言的内存错误问题检查工具,这些工具只能检测用户空间的内存问题。ASAN可以对用户态程序的如下内存问题进行检测:

  • 使用已释放内存(野指针)
  • 堆内存越界(读写)
  • 栈内存越界(读写)
  • 全局变量越界(读写)
  • 函数返回局部变量

ASAN使用方法和实例

ASAN使用方法

在程序编译时通过加入相关参数来启动Address Sanitizer

  • clang: -fsanitize=address,例如

    g++ -O -g -fsanitize=address use-after-free.c 
    
  • swiftc: -sanitize=address

  • xcodebuild: -enableAddressSanitizer YES

ASAN实例

  1. 用户态程序使用已释放内存

    1. 源文件:注意用户态程序要想asan工具能实时检测到它的内存错误,该程序在编译时要加入指定选项比如:

      g++ -O -g -fsanitize=address use-after-free.c 
      
      // To compile: g++ -O -g -fsanitize=address use-after-free.c 
      int main(int argc, char **argv) 
      { 
          int *array = new int[10]; 
          delete [] array; 
          //此出访问已经释放的array
          return array[argc]; // BOOM 
      }
      
    2. 将上述代码用c++编译器编译成.out程序,在配置了kasan的linux系统上运行.out程序,程序在运行过程中会实时生成kasan日志文件:

      #错误日志文件::;;dasfasan.log.a.out.1615451568.1209
      =================================================================
      #程序使用已经释放了的堆内存(on address 0x60400000dfd4)
      ==a.out==1209==ERROR: AddressSanitizer: heap-use-after-free on address 0x60400000dfd4 at pc 0x00000040079d bp 0x7ffc0c18a210 sp 0x7ffc0c18a208
      #清除打印出use-after-free.c问下的第5行使用了已经释放的内存
      READ of size 4 at 0x60400000dfd4 thread T0
          #0 0x40079c in main /workspace/taojing/myhouse/buildspace/test/use-after-free.c:5
          #1 0x3c3a220690 in __libc_start_main (/lib/libc.so.6+0x3c3a220690)
          #2 0x400679 in _start (/a.out+0x400679)
      
      0x60400000dfd4 is located 4 bytes inside of 40-byte region [0x60400000dfd0,0x60400000dff8)
      #程序释放点在文件use-after-free.c的第4行
      freed by thread T0 here:
          #0 0x7f440bbc50b0 in operator delete[](void*) (/lib/libasan.so.3+0xcf0b0)
          #1 0x400767 in main /workspace/taojing/myhouse/buildspace/test/use-after-free.c:4
      
      previously allocated by thread T0 here:
      SUMMARY(0000-00-00-08:32:49): AddressSanitizer: heap-use-after-free /workspace/taojing/myhouse/buildspace/test/use-after-free.c:5 in main
      Shadow bytes around the buggy address:
        0x0c087fff9ba0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
        0x0c087fff9bb0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
        0x0c087fff9bc0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
        0x0c087fff9bd0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
        0x0c087fff9be0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
      =>0x0c087fff9bf0: fa fa fa fa fa fa fa fa fa fa[fd]fd fd fd fd fa
        0x0c087fff9c00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
        0x0c087fff9c10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
        0x0c087fff9c20: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
        0x0c087fff9c30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
        0x0c087fff9c40: 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
        Heap right redzone:      fb
        Freed heap region:       fd
        Stack left redzone:      f1
        Stack mid redzone:       f2
        Stack right redzone:     f3
        Stack partial redzone:   f4
        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
      ==a.out==1209==ABORTING
      
  2. 用户态程序内存堆右越界访问

    1. 源文件

      // To compile: g++ -O -g -fsanitize=address out_of_heapBund_right.c 
      int main(int argc, char **argv)
      {
          int *array = new int[10];
          array[10] = 22;
          delete [] array;
          return 0;
      }
      
      
    2. kasan错误日志报告:

      =================================================================
      ==a.out==1276==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60400000dff8 at pc 0x00000040079a bp 0x7ffd3d40f7b0 sp 0x7ffd3d40f7a8
      WRITE of size 4 at 0x60400000dff8 thread T0
          #0 0x400799 in main /workspace/taojing/myhouse/buildspace/test/out_of_heapBund_right.c:4
          #1 0x3c3a220690 in __libc_start_main (/lib/libc.so.6+0x3c3a220690)
          #2 0x400679 in _start (/a.out+0x400679)
      
      0x60400000dff8 is located 0 bytes to the right of 40-byte region [0x60400000dfd0,0x60400000dff8)
      allocated by thread T0 here:
      SUMMARY(0000-00-00-09:02:16): AddressSanitizer: heap-buffer-overflow /workspace/taojing/myhouse/buildspace/test/out_of_heapBund_right.c:4 in main
      Shadow bytes around the buggy address:
        0x0c087fff9ba0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
        0x0c087fff9bb0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
        0x0c087fff9bc0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
        0x0c087fff9bd0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
        0x0c087fff9be0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
      =>0x0c087fff9bf0: fa fa fa fa fa fa fa fa fa fa 00 00 00 00 00[fa]
        0x0c087fff9c00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
        0x0c087fff9c10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
        0x0c087fff9c20: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
        0x0c087fff9c30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
        0x0c087fff9c40: 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
        Heap right redzone:      fb
        Freed heap region:       fd
        Stack left redzone:      f1
        Stack mid redzone:       f2
        Stack right redzone:     f3
        Stack partial redzone:   f4
        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
      ==a.out==1276==ABORTING
      
      

ASAN实现原理

ASAN工具由两部分组成:

  1. 运行时库

    程序编译时启用ASAN后,会在程序可执行文件中增加libasan.so.x,它将在运行时加载。这些库会接管malloc和free函数。malloc执行完后,已分配内存的前后(称为“红区”)会被标记为"中毒"状态(off-limits),而释放的内存则会被隔离起来(暂时不会分配出去)且也会被标记为"中毒"状态。

  2. 编译器插桩模块

    加了ASAN相关的编译选项后,代码中的每一次内存访问操作都会被编译器修改为如下方式:

    1. 编译前

      *address = ...;  // or: ... = *address;
      
    2. 编译后

      if (IsMarkedAsOffLimits(address)) {
        ReportError(address);
      }
      *address = ...;  // or: ... = *address;
      

      当访问到被标记为"中毒"状态(off-limits)的内存时,Address Sanitizer就会报告异常

具体的ASAN实现可参考:http://blog.binpang.me/2017/07/26/AddressSanitizer/

ASAN性能影响

开启ASan,将使代码执行效率降低2-20倍,内存使用增加5-10倍。可以通过设置-O1优化级别来提高内存利用率。

2 Valgrind

valgrind是运行在Linux上一套基于仿真技术的程序调试和分析工具,它包含一个内核──一个软件合成的CPU,和一系列的小工具,每个工具都可以完成一项任务──调试,分析,或测试等。本章只介绍Valgrind工具在用户态程序内存检测方面的应用。

Valgrind可以检测内存泄漏和内存违例,还可以分析cache的使用等,灵活轻巧而又强大,能直穿程序错误的心脏,真可谓是程序员的瑞士军刀。

Valgrind工具的交叉编译

  1. 源码下载:https://www.valgrind.org/downloads/current.html#current

  2. 解压压缩包进入源码目录

  3. 修该源码目录下的host名字,否则会出现:configure: error: Unsupported host architecture. Sorry错误提示,修该方式:若你编译的是x86_64位的os,在configure文件中找到x86_64并将其修该为x86_64*| x86*.

         x86_64)
            { $as_echo "$as_me:${as_lineno-$LINENO}: result: ok (${host_cpu})" >&5
    $as_echo "ok (${host_cpu})" >&6; }
            ARCH_MAX="amd64"
            ;;
    
    

    修该为

    x86_64*|x86*)
            { $as_echo "$as_me:${as_lineno-$LINENO}: result: ok (${host_cpu})" >&5
    $as_echo "ok (${host_cpu})" >&6; }
            ARCH_MAX="amd64"
            ;;
    
  4. 执行如下命令:

    ./configure --host=x86_64-linux 
    			#CC、CPP、g++指定为交叉编译的X86_64版本编译器
    			CC=x86_64-pc-linux-gnu-gcc 
    			CPP=x86_64-pc-linux-gnu-cpp 
    			CXX=x86_64-pc-linux-gnu-g++
    			#可执行文件存放目录(绝对路径)
    			--prefix=/home/taojing/valgrind-3.16.1
    
  5. 编译

    make
    make install
    
  6. 运行:将编译生成的整个可执行文件目录拷贝到单板上并在单板上执行下列命令:

    export VALGRIND_LIB=/valgrind-3.15.1/lib/valgrind
    
    export PATH=/valgrind-3.16.1/bin:$PATH
    

    最后就可以单板上执行valgrind程序

    valgrind --tool=memcheck --leak-check=full ./xxx(需要监控的程序)
    

Valgrind工具使用方式

Valgrind的使用非常简单,valgrind命令的格式如下:

valgrind [valgrind-options] your-prog [your-prog options]

一些常用的选项如下:

选项 		  				  作用
-h --help 					显示帮助信息。
--version					显示valgrind内核的版本,每个工具都有各自的版本。
-q --quiet					安静地运行,只打印错误信息。
-v --verbose				打印更详细的信息。
--tool= [default: memcheck]	最常用的选项。运行valgrind中名为toolname的工具。如果省略工具名,默认运行memcheck。
--db-attach= [default: no]	绑定到调试器上,便于调试错误。

一些常用的选项如下:
选项
作用
-h --help
显示帮助信息。
–version
显示valgrind内核的版本,每个工具都有各自的版本。
-q --quiet
安静地运行,只打印错误信息。
-v --verbose
打印更详细的信息。
–tool= [default: memcheck]
最常用的选项。运行valgrind中名为toolname的工具。如果省略工具名,默认运行memcheck。
–db-attach= [default: no]
绑定到调试器上,便于调试错误。

Valgrind内存检测功能

Valgrind包含一系列工具其中Memcheck是最常用的工具,用来检测程序中出现的内存问题,所有对内存的读写都会被检测到,一切对malloc()/free()/new/delete的调用都会被捕获。所以,它能检测以下问题:

  1. 对未初始化内存的使用;
  2. 读/写释放后的内存块;
  3. 读/写超出malloc分配的内存块;
  4. 读/写不适当的栈中内存块;
  5. 内存泄漏,指向一块内存的指针永远丢失;
  6. 不正确的malloc/free或new/delete匹配;
  7. memcpy()相关函数中的dst和src指针重叠。

Valgrind工具内存检测实例

内存越界访问和内存泄露检测实例

测试源程序memTest.c

#include<stdio.h>
#include<stdlib.h>
void fun()
{
         int *x = (int *)malloc(10 * sizeof(int));
         x[10] = 0;
}
int main()
{
         fun();
         return 0;
}
  1. 执行下面命令生成可执行文件memTest.out

    gcc -Wall -o  memTest.out memTest.c
    
  2. 然后在单板上用Valgrind的memcheck工具对测试测序的内存进行检测。

    valgrind --tool=memcheck --leak-check=full ./memTest.out
    
  3. 执行上述命令shell终端会打印出程序执行过程中内存检测报告

    #275表示memTest.out执行时的进程pid
    ==275== Memcheck, a memory error detector
    ==275== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
    ==275== Using Valgrind-3.16.1 and LibVEX; rerun with -h for copyright info
    ==275== Command: ./memTest.out
    ==275==
    #此处告诉我们在main函数调用的fun函数里发生了4字节的非法写入
    ==275== Invalid write of size 4
    ==275==    at 0x400514: fun (in /memTest.out)
    ==275==    by 0x40052A: main (in /memTest.out)
    ==275==  Address 0x4c17068 is 0 bytes after a block of size 40 alloc'd
    ==275==    at 0x4A07F3D: malloc (vg_replace_malloc.c:307)
    ==275==    by 0x400507: fun (in /memTest.out)
    ==275==    by 0x40052A: main (in /memTest.out)
    ==275==
    ==275==
    #堆上内存情况汇总,1次alloc,0次free,说明存在内存泄露
    ==275== HEAP SUMMARY:
    ==275==     in use at exit: 40 bytes in 1 blocks
    ==275==   total heap usage: 1 allocs, 0 frees, 40 bytes allocated
    ==275==
    #40字节在1个block上被分配但是未进行回收,导致内存泄露
    ==275== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
    ==275==    at 0x4A07F3D: malloc (vg_replace_malloc.c:307)
    ==275==    by 0x400507: fun (in /memTest.out)
    ==275==    by 0x40052A: main (in /memTest.out)
    ==275==
    #泄露汇总,一个block上的40字节内存被泄露
    ==275== LEAK SUMMARY:
    ==275==    definitely lost: 40 bytes in 1 blocks
    ==275==    indirectly lost: 0 bytes in 0 blocks
    ==275==      possibly lost: 0 bytes in 0 blocks
    ==275==    still reachable: 0 bytes in 0 blocks
    ==275==         suppressed: 0 bytes in 0 blocks
    ==275==
    ==275== For lists of detected and suppressed errors, rerun with: -s
    ==275== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
    
    

其他valgrind使用实例:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值