学做网站用到哪些知识,医院网站,长春好的做网站公司有哪些,网站建设新闻概述
Asan是Google专门为C/C开发的内存错误探测工具#xff0c;其具有如下功能
使用已释放内存#xff08;野指针#xff09;√堆内存越界#xff08;读写#xff09;√栈内存越界#xff08;读写#xff09;√全局变量越界#xff08;读写#xff09;函数返回局部变…概述
Asan是Google专门为C/C开发的内存错误探测工具其具有如下功能
使用已释放内存野指针√堆内存越界读写√栈内存越界读写√全局变量越界读写×函数返回局部变量 ×内存泄漏 √
注本文中测试使用的系统为Ubuntu 16.04GCC版本为5.4.0上面功能打√表示该版本GCC支持的功能打×的表示该版本GCC不支持的功能。
Asan 运行十分的快被测程序的运行速度一般只会降低2倍相比于valgrind动辄10~20倍的性能下降来说已经非常快了。
Asan包括编译器指令模块和运行时库两部分组成 编译器插桩模块 在程序编译时加入控制指令用于监测程序的所有内存使用行为代码中每一次的内存访问操作都会被编译器修改如下方式 原始代码 *address ...; // or: ... *address;编译后代码 if (IsPoisoned(address)) {ReportError(address, kAccessSize, kIsWrite);
}
*address ...; // or: ... *address;-* 运行时库* 用于替换glibc库的malloc/free函数实现内存的分配和释放操作。malloc执行完后已分配内存的前后称为“红区”会被标记为“中毒”状态而释放的内存则会被隔离起来暂时不会分配出去且也会被标记为“中毒”状态。
Asan起初作为LLVM的一部分后来其被集成到了4.8版本的GCC中其可以运行在X86、ARM、MIPS、PowerPC等平台操作系统支持Linux、OS X、iOS、Android、FreeBSD。
编译配置
GCC编译选项 -fsanitizeaddress开启内存越界检测 -fsanitizeleak开启内存泄漏检测 -fsanitize-recoveraddress一般后台程序为保证稳定性不能遇到错误就简单退出而是继续运行采用该选项支持内存出错之后程序继续运行需要叠加设置环境变量ASAN_ OPTIONShalt_on_error0才会生效若未设置此选项则内存出错即报错退出 -fno-stack-protector去使能栈溢出保护 -fno-omit-frame-pointer去使能栈溢出保护 -fno-var-tracking默认选项为-fvar-tracking会导致运行非常慢 -g1表示最小调试信息通常debug版本用-g即-g2
在Makefile中可以通过设置类似于下面的编译选项控制GCC的关于Asan的选项。
ASAN_CFLAGS -fsanitizeaddress -fsanitize-recoveraddressAsan运行选项
ASAN_OPTIONS是Address-Sanitizier的运行选项环境变量。
halt_on_error0检测内存错误后继续运行detect_leaks1:使能内存泄露检测malloc_context_size15内存错误发生时显示的调用栈层数为15log_path/var/log/asan.log:内存检查问题日志存放文件路径suppressions$SUPP_FILE:屏蔽打印某些内存错误
下面是设置ASAN_OPTIONS环境变量:
export ASAN_OPTIONShalt_on_error0:use_sigaltstack0:detect_leaks1:malloc_context_size15:log_path/var/log/asan.log:suppressions$SUPP_FILELSAN_OPTIONS是内存泄漏检测模块LeakSanitizier的环境变量常用的运行选项如下 exitcode0设置内存泄露退出码为0默认情况内存泄露退出码0x16 use_unaligned44字节对齐
下面是设置LSAN_OPTIONS环境变量:
export LSAN_OPTIONSexitcode0:use_unaligned4Asan运行库配置
用于Asan接管了应用程序内存的申请和释放操作其替换了原有malloc和free的操作比如glibc库所以在程序运行前需要使用LD_PRELOAD指定下libasan.so的位置。
示例
Asan除了内存泄漏之外默认情况下其监测到内存问题后程序就会立即退出并且打印出相关的内存问题日志。对于内存泄漏问题当程序正常退出时才会检测程序是否存在内存泄漏这里说的正常退出对于C/C程序有几种情况:
main函数return退出调用了exit函数退出
如果进程由于信号而退出的话Asan不能检测是否存在内存泄漏。
堆访问越界(heap-buffer-overflow)
#include stdlib.h
#include unistd.h
int main(int argc, char **argv)
{int *array malloc(sizeof (int) * 100);array[0] 0;int res array[1 100]; //array访问越界free(array);pause();//程序等待不退出return 0;
}编译命令
gcc heapOOB.c -o heapOOB -g -fsanitizeaddress -fsanitizeleak执行情况
3653ERROR: AddressSanitizer: heap-buffer-overflow on address 0x61400000ffd4 at pc 0x000000400871 bp 0x7ffe50cde9c0 sp 0x7ffe50cde9b0
READ of size 4 at 0x61400000ffd4 thread T0
#0 0x400870 in main /home/jetpack/work/4G/test/asan/heapOOB.c:7
#1 0x7f30b337a83f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.60x2083f)
#2 0x400708 in _start (/home/jetpack/work/4G/test/asan/heapOOB0x400708)
0x61400000ffd4 is located 4 bytes to the right of 400-byte region [0x61400000fe40,0x61400000ffd0)
allocated by thread T0 here:
#0 0x7f30b37bc602 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.20x98602)
#1 0x4007ee in main /home/jetpack/work/4G/test/asan/heapOOB.c:5
#2 0x7f30b337a83f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.60x2083f)栈访问越界(stack-buffer-overflow)
#include stdlib.h
#include unistd.h
int main(int argc, char **argv)
{int stack_array[100];stack_array[100] 0;//栈访问越界pause();return 0;
}编译命令
gcc stackOOB.c -o stackOOB -g -fsanitizeaddress -fsanitizeleak执行结果
3952ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fffdb72e830 at pc 0x0000004008ef bp 0x7fffdb72e660 sp 0x7fffdb72e650
WRITE of size 4 at 0x7fffdb72e830 thread T0
#0 0x4008ee in main /home/jetpack/work/4G/test/asan/stackOOB.c:6
#1 0x7f0c47a8e83f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.60x2083f)
#2 0x400748 in _start (/home/jetpack/work/4G/test/asan/stackOOB0x400748)
Address 0x7fffdb72e830 is located in stack of thread T0 at offset 432 in frame
#0 0x400825 in main /home/jetpack/work/4G/test/asan/stackOOB.c:4
This frame has 1 object(s):
[32, 432) stack_array Memory access at offset 432 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)使用已经释放的内存UseAfterFree
#include stdlib.h
#include unistd.h
int main(int argc, char **argv)
{int *array malloc(sizeof (int) * 100);array[0] 0;free(array);int res array[0]; //使用已经释放的内存pause();return 0;
}编译命令
gcc heapUAF.c -o heapUAF -g -fsanitizeaddress -fsanitizeleak执行结果:
4385ERROR: AddressSanitizer: heap-use-after-free on address 0x61400000fe40 at pc 0x000000400877 bp 0x7ffdc9019c20 sp 0x7ffdc9019c10
READ of size 4 at 0x61400000fe40 thread T0
#0 0x400876 in main /home/jetpack/work/4G/test/asan/heapUAF.c:8
#1 0x7f93f8d5683f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.60x2083f)
#2 0x400708 in _start (/home/jetpack/work/4G/test/asan/heapUAF0x400708)
0x61400000fe40 is located 0 bytes inside of 400-byte region [0x61400000fe40,0x61400000ffd0)
freed by thread T0 here:
#0 0x7f93f91982ca in __interceptor_free (/usr/lib/x86_64-linux-gnu/libasan.so.20x982ca)
#1 0x40083f in main /home/jetpack/work/4G/test/asan/heapUAF.c:7
#2 0x7f93f8d5683f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.60x2083f)
previously allocated by thread T0 here:
#0 0x7f93f9198602 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.20x98602)
#1 0x4007ee in main /home/jetpack/work/4G/test/asan/heapUAF.c:5
#2 0x7f93f8d5683f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.60x2083f)内存泄漏 (Memory Leak)
#include string.h
#include stdlib.h
#include unistd.hint main(int argc, char **argv)
{int *array malloc(sizeof (int) * 100);memset(array, 0, 100 * 4);return 0;
}编译命令
gcc heapLeak.c -o heapLeak -g -fsanitizeaddress -fsanitizeleak执行结果
3120ERROR: LeakSanitizer: detected memory leaksDirect leak of 400 byte(s) in 1 object(s) allocated from:
#0 0x7f412d5b7602 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.20x98602)
#1 0x40073e in main /home/jetpack/work/4G/test/asan/heapLeak.c:7
#2 0x7f412d17583f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.60x2083f)SUMMARY: AddressSanitizer: 400 byte(s) leaked in 1 allocation(s).
实际应用中特别是嵌入式设备程序会一直运行直到程序遇到问题比如内存访问越界导致段错误。所以如果想随时使程序正常退出检测程序是否存在内存泄漏的话可以通过向进程发送特定的信号 进程接收到信号后主动退出程序从而触发Asan内存泄漏检测。
//heapLeak.c#include string.h
#include stdlib.h
#include unistd.h
#include signal.h
#include stdio.hstatic int is_exit 0;void sig_exit(int num)
{if (num SIGUSR1) {//接收到SIGUSR1后设置is_exit为1printf(SIGKILL\n);is_exit 1;}
}int main(int argc, char **argv)
{struct sigaction action;sigemptyset(action.sa_mask);action.sa_flags 0;action.sa_handler sig_exit;sigaction(SIGUSR1, action, NULL);int *array malloc(sizeof (int) * 100);memset(array, 0, 100 * 4); while(1) { if (is_exit) {//如果is_exit为1退出循环从使程序正常退出break;} sleep(1);} return 0;
}编译命令
gcc heapLeak.c -o heapLeak -g -fsanitizeaddress -fsanitizeleak测试过程运行heapLeak
./heapLeak //等待接收SIGUSR1//查询进程id
pidof heapLeak
4347//发送SIGUSR1信号
kill -SIGUSR1 4347heapLeak收到SIGUSR1之后退出并输出内存泄漏统计信息
4347ERROR: LeakSanitizer: detected memory leaksDirect leak of 400 byte(s) in 1 object(s) allocated from:
#0 0x7fbcd578d602 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.20x98602)
#1 0x400b31 in main /home/jetpack/work/4G/test/asan/heapLeak.c:26
#2 0x7fbcd534b83f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.60x2083f)SUMMARY: AddressSanitizer: 400 byte(s) leaked in 1 allocation(s).
函数返回的局部变量访问
int *ptr;
void usr_func(void)
{ //申请栈内存int local[100] {0};ptr local;
} int main(void)
{ //使用函数栈返回的内存*ptr 0;return 0;
}全局变量访问越界
int global_array[100] {0}; int main(int argc, char **argv)
{//全局变量global_array访问越界 global_array[101];return 0;
}
注全局变量访问越界(GlobalOutOfBounds)和函数返回的局部变量访问UseAfterReturngcc-5.4.0暂时不支持所以暂时不能测试。
日志记录内存问题
如果出现问题时不是直接退出程序而是继续运行并且把内存问题输出到文件中的话可以通过使用-fsanitize-recoveraddress编译选项配合ASAN_OPTIONS变量实现运行程序之前声明该变量即可。
编译时加上-fsanitize-recoveraddress选项:
gcc heapOOB.c -o heapOOB -g -fsanitizeaddress -fsanitizeleak -fsanitize-recoveraddress运行时先声明ASAN_OPTIONS再运行程序
export ASAN_OPTIONShalt_on_error0:use_sigaltstack0:detect_leaks1:malloc_context_size15:log_path/var/log/asan.log:suppressions$SUPP_FILE如果gcc不支持-fsanitize-recoveraddress编译选项比如我现在使用的gcc-5.4.0那么程序在遇到问题的时候还是自动退出但日志会输出到log_path指定的目录比如
asan.log.5089 asan.log.5090 asan.log.5348 //其中文件后面的数字表示程序的进程号番外篇
说一个最近使用Asan的经历过程比较心酸但是也许有些意义拿来和大家分享下。
其实之前我是从来没有用Asan的某天看到后觉得这厮功能十分的强大可以拿来玩玩。于是写了个几个常见的内存使用问题发现确实可以在第一时间就能把问题揪出来。正巧我当时正在做一个嵌入式Linux的项目程序规模属于中等基本功能当时开发完了想着正好可以使用Asan测试下这个项目是不是存在内存问题。记得当时还查阅了一些资料分析了如何在项目里面使用Asan。之后就把Asan用上了当时确实找出来一个错误然后就没有再出现啥问题了(现在想想当时最大的疏忽就是没有看使用Asan工具会给应用带来多大额外的内存耗损就不会后来一直冤枉人家应用程序存在内存泄漏了;))。
这样相安无事的过了一段时间后有一天在我发现系统的内存剩余很少了第一反应就是应用出现了内存泄漏通过长时间的监测确定应用程序内存使用量存在不断增长的问题进一步印证了内存泄漏的猜想。之后通过各种分析甚至使用了valgrind工具都没有检测到内存泄漏但应用内存使用量确实在不断增长最后搞的十分的郁闷。
后来网络上有说glibc自带的内存分配器ptmalloc存在问题很容易造成内存碎片导致很多内存即使被free了也只能被进程占着不能返还给OS。我觉得这不是正和我遇到的问题一样嘛于是乎我查阅了大量的资料学习glibc ptmalloc的实现机制以及如何造成内存碎片多核、多线程场景下性能如何低的讨论甚至分析了另外两种内存分配器jemallocFacebook家的、tcmallocGoogle家的的性能如何如何厉害。
最后实在是走投无路了干脆直接把ptmalloc换了正好buildroot支持jemalloc编译顺其自然的我就把jemalloc换上了。不换不知道一换吓一跳应用虚拟内存使用直接从500M降到了60M当时直接给jemalloc跪了。
后来才知道虚拟内存大小降了那么多不是jemalloc的功能而是由于使用了Asan虚拟内存使用量才上来的也正是由于Asan需要实时监测内存使用情况才最终导致了内存的不断增长。
哈哈彻底理解了程序猿经常为了体验新的技术自己挖坑自己跳自己埋的故事了。
总结几点经验:
项目开发过程中新技术、新工具要慎用用之前要充分了解过程中要做好使用记录待问题出现时可以反过来找问题的可能原因。对于新技术、新工具要有一点免疫力不要头脑发热看到它鼓吹的功能、性能就恨不得马上应用到项目里这样其实往往是在给自己挖坑最后自己怎么死的都不知道。最后新技术、新工具不是不学不用了反而要时刻保持一颗积极探索的心态碰到好的东西要积极学习等待想法成熟之后再应用到实际工作中去。
当然从这次事件中也学到了很多比如大概了解了常用的几款内存分配器的原理和使用方式了解了Asan和valgrind的基本原理和使用方式这些对以后的工作都是大有用处的算是因祸得福 吧哈哈。