开发程序时,运用动态存储变量,必然要处理内存管理的难题。程序动态分配的存储区域,运行结束后必须予以解除。未释放动态分配的存储区域导致内存泄漏,是运用动态存储变量时最突出的麻烦。通常情况下,程序员会频繁调用系统内置的内存操作函数,比如malloc、realloc、calloc、free等,以此实现动态内存的申请与回收。然而,如果在开发过程中大量使用动态变量,并且频繁执行函数调用,就很容易出现内存管理方面的问题。
2. 如何排查内存泄漏
我们在日常构建过程中难免遭遇内存占用异常现象,此乃普遍状况。一旦出现内存占用异常,就必须着手分析内存占用异常的成因。大家应该经常运用以下几种分析内存占用异常的辅助手段,具体包括:
今天木荣没有讲解先前的检测工具,而是向听众展示了另一种内存泄漏检测工具:AddressSanitizer(ASan)。这个工具适用于多个操作系统平台,包括Linux、OS和Android等。它不仅能够发现内存泄漏,还具备检测多种常见内存异常的功能。
常见的内存问题检测:
3. AddressSanitizer(ASan)工具
地址检查器是一种高效的内存问题识别软件,运行速度极快,仅使程序运行速度减慢大约两倍,明显优于Valgrind。该工具由两部分构成,分别是编译时插入的监测组件,以及能够替换malloc()和free()函数的执行时支持库。从gcc 4.8版本起,AddressSanitizer被整合进了gcc。若想获得更佳的使用感受,建议选用4.9或更新的版本,原因是gcc 4.8中的AddressSanitizer功能尚不成熟,其最突出的不足之处在于缺少符号信息。
使用方法:
使用地址检查功能编译main.c文件,生成可执行文件main,并保留调试信息
内存泄漏
#include
void Fun()
{
char *pM = malloc(10);
}
启动主程序时接受参数数量和参数字符串数组,然后执行程序主体部分
{
Fun();
return 0;
}
编译输出
内存越界
#include
#include
void Fun()
{
char *pM = malloc(10);
}
int main(int argc, char *argv[])
{
//Fun();
指针array通过malloc分配了十整型大小的内存空间
if(array)
{
将数组全部置零,共十个整数元素,每个元素大小为整型字节长度
}
int res = array[10];
free(array);
return 0;
}
运行输出
在 ubuntu 用户的工作空间目录下,使用 gcc 编译器,启用地址 sanitization 功能,将 main.c 源文件编译成可执行文件 main,同时生成调试信息 当前目录下文件清单如下,请稍等,正在列出所有内容,包括隐藏文件,完成后会显示详细信息,请耐心等待。 main main.c ubuntu用户在home目录下的workspace_ex/Linux/ASan文件夹中运行main程序 那个东西看起来非常奇怪,让人难以理解,它静静地躺在那里,周围没有任何动静,仿佛时间都停滞了,这种景象让人感到十分不安,却又无法解释它的存在,真是令人费解。 ==3234==出现错误信息:地址检查器发现堆缓冲区溢出问题发生在内存地址0x60400000dff8处,当前处理器指令指针位于0x000000400854,调用栈底部指针为0x7ffccc9253d0,栈指针为0x7ffccc9253c0 读取长度为四的数据,位于地址0x60400000dff8,属于线程T0 内存地址0x400853位于main函数中,具体文件为/home/ubuntu/workspace_ex/Linux/ASan/main.c,该文件第16行代码处 #1 0x7fafc87a883f 在 __libc_start_main 处发生异常 /lib/x86_64-linux-gnu/libc.so.6 的 0x2083f 位置 第2个位置0x4006f8出现在启动函数中,具体位于/home/ubuntu/workspace_ex/Linux/ASan/main文件,偏移量是0x4006f8。 0x60400000dff8位于0x60400000dfd0起始地址右侧40字节范围内,具体偏移量为0字节,该地址属于[0x60400000dfd0,0x60400000dff8)这个区间内 allocated by thread T0 here: 内存分配出错,发生在地址0x7fafc8bea602,属于malloc函数,该函数位于/usr/lib/x86_64-linux-gnu/libasan.so.2的0x98602偏移处 内存地址0x4007f7出现在main函数中,具体位置是/home/ubuntu/workspace_ex/Linux/ASan/main.c文件的第11行 #2 0x7fafc87a883f 在 __libc_start_main 处发生异常 /lib/x86_64-linux-gnu/libc.so.6 的 0x2083f 位置 摘要:内存检查器:堆缓冲区溢出错误发生在文件 Linux/ASan/main.c 的第16行 main函数中 暗影字节环绕着车辆编号 十六进制地址0x0c087fff9ba0处显示的一连串fa字符 十六进制地址0x0c087fff9bb0处显示的一连串fa字符 0x0c087fff9bc0: 十二个 fa 连续出现 0x0c087fff9bd0: 那个地址的内容是一长串 fa 字符 0x0c087fff9be0: 那个数字序列代表一个特定的内存地址, 其中包含若干个连续的十六进制字符, 这些字符看起来像是某种编码或标识符 内存地址0x0c087fff9bf0处,数据呈现为fa fa fa fa fa fa fa fa fa,后面跟着四个连续的00,最后以fa结束 十六进制地址0x0c087fff9c00处显示的一连串fa字符 0x0c087fff9c10: 十二个圆圈,一个接一个,排列着 十六进制地址0x0c087fff9c20处显示一长串连续的fa字符 0x0c087fff9c30: 那个标记由十二个相同的字符组成,每个字符都是fa 0x0c087fff9c40: 那个地址里面存储着若干个零值字节, 这些字节连在一起形成了特定的数据模式 暗影字节传奇,一个暗影字节相当于八应用字节 Addressable: 00 部分可寻址:第一项,第二项,第三项,第四项,第五项,第六项,第七项。 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 ==3234==ABORTING
#include
#include
int g_nArray[10] = {0};
void Fun()
{
char *pM = malloc(10);
}
int main(int argc, char *argv[])
{
//Fun();
/*
int *array = malloc(10*sizeof(int));
if(array)
{
memset(array, 0, 10*sizeof(int));
}
int res = array[10];
free(array);
*/
索引值nIndex等于数组g_nArray的第十位元素的内容
return 0;
}
运行输出
当前工作目录位于用户的工作空间中,具体路径为Linux系统下的ASan文件夹,此目录属于当前登录用户ubuntu,系统显示为ubuntu系统环境,具体路径为用户家目录下的workspace_ex文件夹,内部包含Linux相关内容gcc -fsanitize=address -o main -g main.c ubuntu在ubuntu的workspace_ex/Linux/ASan目录下 ls main main.c 运行main程序,在ASan环境中,使用ubuntu系统,工作目录位于workspace_ex/Linux文件夹下 ================================================================= 出现严重问题,检测到全局缓冲区溢出,地址位于0x000000601128,当前程序计数器值为0x00000040083e,调用栈底部指针为0x7ffc8a332540,栈指针为0x7ffc8a332530 读取大小为四的数据,位于地址0x000000601128,这个操作是在线程T0中执行的 具体行数为第 26 行 内存地址0x7fda216a583f出现在函数__libc_start_main中,该函数位于文件/lib/x86_64-linux-gnu/libc.so.6的偏移位置0x2083f处 内存地址0x400718在_start函数中发生异常,该函数位于路径/home/ubuntu/workspace_ex/Linux/ASan/main,具体位置从0x400718开始 0x000000601128在右侧0字节处,紧邻全局变量'g_nArray',该变量定义于'main.c:4:5'(0x601100),其大小为40摘要:内存检查器:全局缓冲区溢出 错误位置:/home/ubuntu/workspace_ex/Linux/ASan/main.c第26行 主函数 Shadow bytes around the buggy address: 十六进制地址0x0000800b81d0处,后续一连串都是零字节,共计三十二个零字节连续出现 内存地址0x0000800b81e0处,存放着十二个连续的零字节,每个字节都为零,没有任何有效数据 内存地址0x0000800b81f0处的内容全部为零,共计十六个字节,每个字节值都是零 内存地址0x0000800b8200处,内容全部为零,共十六个字节,每个字节值均为零 内存地址0x0000800b8210处存储的值为全零,共计十六个字节,每个字节均为零,没有任何有效数据 内存地址0x0000800b8220处数据为十六进制0,数据以字节为单位依次为0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 内存地址0x0000800b8230处存储的值为全零,共占据十六个字节,每个字节均未赋值 内存地址0x0000800b8240处的内容全部为零,连续出现了若干个零字节,具体数值为十六进制00,且该数值重复了多次,占据了较大的空间范围 内存地址0x0000800b8250处,存放着十二个连续的零字节,每个字节都是零值,没有数据内容 内存地址0x0000800b8260处存储的数据全部为零,共计十六个字节,每个字节的具体数值均为零 内存地址0x0000800b8270处,存放着一系列连续的零,总共有十六个零,每个零占据一个字节的空间,这些零构成了一个完整的字节序列。 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 ==4196==ABORTING ubuntu@ubuntu:~/workspace_ex/Linux/ASan$
使用释放的内存
#include
#include
#include
int g_nArray[10] = {0};
int *p;
void Fun()
{
char *pM = malloc(10);
}
void Fun1()
{
int nVar = 0;
p = &nVar;
}
int main(int argc, char *argv[])
{
//Fun();
int *array = malloc(10*sizeof(int));
if(array)
{
memset(array, 0, 10*sizeof(int));
}
int res = array[10];
free(array);
array[0] = 1;
return 0;
}
运行输出
ubuntu@ubuntu:~/workspace_ex/Linux/ASan$ gcc -fsanitize=address -o main -g main.c ubuntu@ubuntu:~/workspace_ex/Linux/ASan$ ./main ================================================================= 内存检查错误:发生堆缓冲区溢出,地址为0x60400000dff8,程序计数器位于0x000000400b68,调用栈指针在0x7ffefbe3b170,栈指针为0x7ffefbe3b160 READ of size 4 at 0x60400000dff8 thread T0 内存地址0x400b67出现在main函数中,位于/home/ubuntu/workspace_ex/Linux/ASan/main.c文件的第28行 地址0x7f1bbb78983f出现在函数__libc_start_main中,该函数位于文件/lib/x86_64-linux-gnu/libc.so.6的偏移量0x2083f处 第2处 0x400928位于_start函数内 位置为/home/ubuntu/workspace_ex/Linux/ASan/main+0x400928 0x60400000dff8 is located 0 bytes to the right of 40-byte region [0x60400000dfd0,0x60400000dff8) allocated by thread T0 here: 内存分配错误发生在地址0x7f1bbbbcb602处,相关函数为malloc,文件路径为/usr/lib/x86_64-linux-gnu/libasan.so.2,偏移量为0x98602 内存地址0x400b0b出现在main函数中,具体位置是/home/ubuntu/workspace_ex/Linux/ASan/main.c文件的第23行 #2 0x7f1bbb78983f 在 __libc_start_main 处发生异常 /lib/x86_64-linux-gnu/libc.so.6 的 0x2083f 位置 摘要:内存检查工具:堆缓冲区溢出错误发生在文件 Linux/ASan/main.c 的第28行 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 ==4954==ABORTING ubuntu@ubuntu:~/workspace_ex/Linux/ASan$
AddressSanitizer可以识别的异常种类异常种类异常说明
(heap) Use after free
访问堆上已被释放的内存
Heap buffer overflow
堆上缓冲区访问溢出
Stack buffer overflow
栈上缓冲区访问溢出
Global buffer overflow
全局缓冲区访问溢出
Use after return
访问栈上已被释放的内存
Use after scope
栈对象使用超过定义范围
Initialization order bugs
初始化命令错误
Memory leaks
内存泄漏
详细内容请参考谷歌的官方资料库:https://github.com/google/sanitizers/wiki/AddressSanitizer,里面提供了相关信息。
结束语
ASan是一种出色的内存错误检测手段,无需事先设置环境,操作起来也相当便捷,编译期间仅需加上-fsanitize=address -g参数即可,执行程序时可通过设定ASAN_OPTIONS环境变量来发现各类内存异常。倘若存在疑问,不妨查阅官方文档,非常欢迎大家一起探讨进步。
