文章目录
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实例
-
用户态程序使用已释放内存
-
源文件:注意用户态程序要想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 }
-
将上述代码用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
-
-
用户态程序内存堆右越界访问
-
源文件
// 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; }
-
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工具由两部分组成:
-
运行时库
程序编译时启用ASAN后,会在程序可执行文件中增加libasan.so.x,它将在运行时加载。这些库会接管malloc和free函数。malloc执行完后,已分配内存的前后(称为“红区”)会被标记为"中毒"状态(off-limits),而释放的内存则会被隔离起来(暂时不会分配出去)且也会被标记为"中毒"状态。
-
编译器插桩模块
加了ASAN相关的编译选项后,代码中的每一次内存访问操作都会被编译器修改为如下方式:
-
编译前
*address = ...; // or: ... = *address;
-
编译后
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工具的交叉编译
-
源码下载:https://www.valgrind.org/downloads/current.html#current
-
解压压缩包进入源码目录
-
修该源码目录下的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" ;;
-
执行如下命令:
./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
-
编译
make make install
-
运行:将编译生成的整个可执行文件目录拷贝到单板上并在单板上执行下列命令:
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的调用都会被捕获。所以,它能检测以下问题:
- 对未初始化内存的使用;
- 读/写释放后的内存块;
- 读/写超出malloc分配的内存块;
- 读/写不适当的栈中内存块;
- 内存泄漏,指向一块内存的指针永远丢失;
- 不正确的malloc/free或new/delete匹配;
- 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;
}
-
执行下面命令生成可执行文件memTest.out
gcc -Wall -o memTest.out memTest.c
-
然后在单板上用Valgrind的memcheck工具对测试测序的内存进行检测。
valgrind --tool=memcheck --leak-check=full ./memTest.out
-
执行上述命令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使用实例: