C++内存分析工具

C++ 内存分析工具

背景

​ 内存泄漏在c++中是一个常见的问题,有一个好用的内存分析工具就是很有必要的,下面来介绍两个简单好用的内存分析工具,Valgrind和Sanitizer;

Valgrind

valgrind是一种非侵入式的内存检测工具,在进行检查程序前不需要重新编译和连接;Memcheck会在错误发生时立即报告这些错误,给出发生错误的源行号,以及为到达该行而调用的函数的堆栈跟踪。Memcheck在字节级跟踪可寻址性,在位级跟踪值的初始化。因此,它可以检测单个未初始化位的使用,并且不会在位字段操作中报告虚假错误。Memcheck运行程序的速度比正常慢10-30倍。

工具的安装可以参考:Valgrind: Current Releases

主要包含一下集中标准的工具:

1. memcheck:内存错误检测器,检测内存问题,如内存泄漏,数组访问越界,非法指针等
2. callgrind:调用图缓存生成器,检测程序运行的时间和调用过程,用于分析性能,和Linux的perf工具类似
3. cachegrind:缓存和分支预测分析器;分析CPU缓存的命中率
4. heigrind:用于检测多线程中资源竞争问题;
5. massif:堆栈分析器,检测堆栈的hi使用使用情况;
主要介绍一下memcheck工具的的使用

原理:

通过维护两张表(valid-value和valid-address)来实现对运行程序的内存检查;

  • valid-value:记录整个进程地址空间的每个字节(每个字节对应8个bits位);CPU的每个寄存器同样有与之对应的bit向量;这些bit负责记录对应的字节或者寄存器中的值是否有效,是否已初始化;
  • valid-address:对整个进程地址空间的每个字节有一个与之对应的1bit,负责记该地址空间是否能够被读写;
  • 当要内存读写某个字节时,首先会检查valid-address表中的字节对应的那1bit值,判断字节所在地址时候有效,若发现时无效地址,memcheck会输出读写错误的报告;当内核要加载某个字节时,该字节对应在valid-value中的值也会加载到CPU内核中,当内核寄存器中的值被用来生产内存地址或者能影响程序的输出时,memcheck会检查该字节对应valid-value表中的值是否有效或者是否初始化,并给出对应的检测结果;

使用:

首先编译源代码时要加上 -g参数,保留调试信息,然后运行下面命令

valgrind --tool=memcheck --leak-check=full --show-reachable=yes ./demo
  • –tool=memcheck:表示使用memcheck工具
  • –leak-check=full:表示显示每个泄漏的细节
  • –log-file=val_report:可以增加该参数,将分析结果生成日志,放到当前目录下的val_report文件中便于后续分析
例1 内存泄漏
#include <iostream>
using namespace std;
int main()
{
    int *p = new int(10);
    cout << "*p:" << *p << endl;
    //delete p;
    return 0;
}
==9543== Memcheck, a memory error detector
==9543== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==9543== Using Valgrind-3.20.0 and LibVEX; rerun with -h for copyright info
==9543== Command: ./demo
==9543== Parent PID: 8780
==9543== 
==9543== 
==9543== HEAP SUMMARY:
==9543==     in use at exit: 4 bytes in 1 blocks
==9543==   total heap usage: 3 allocs, 2 frees, 73,732 bytes allocated
==9543== 
==9543== 4 bytes in 1 blocks are definitely lost in loss record 1 of 1
==9543==    at 0x4C2F87B: operator new(unsigned long) (vg_replace_malloc.c:434)
==9543==    by 0x4008E8: main (demo.cpp:5)
==9543== 
==9543== LEAK SUMMARY:
==9543==    definitely lost: 4 bytes in 1 blocks
==9543==    indirectly lost: 0 bytes in 0 blocks
==9543==      possibly lost: 0 bytes in 0 blocks
==9543==    still reachable: 0 bytes in 0 blocks
==9543==         suppressed: 0 bytes in 0 blocks
==9543== 
==9543== For lists of detected and suppressed errors, rerun with: -s
==9543== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

在输出的检测分析结果中,第1行已经指出存在一个内存检测错误;第8行给出具体的内存检测结果,共有4个字节的内存出现了泄漏;在demo.cpp文件的第5行出现该内存泄漏;第16行是将本次内存泄漏的所有情况进行汇总;主要专注前4类,具体含义如下:

  • definitely lost:确认存在内存泄漏;当程序结束时,存在动态申请的内存没有释放,且通过其他指针都无法正常访问该内存;
  • indirectly lost : 间接存在内存泄漏;当使用含有指针成员变量的类时可能会报该错误,一般都会与definitely lost同时存在,解决definitely lost问题,该问题随之消失;
  • possibly lost :可能存在内存泄漏;当程序结束时,存在动态申请的内存内有释放,且通过指针变量无法访问该内存的起始地址,但可以访问该内存中的后一部分数据;
  • still reachable:存在可以访问,未丢失但也未释放;若程序正常结束,虽然不会造成程序崩溃,但长时间的运行会消耗是系统内存资源,建议修复;若非正常结束,可忽略;
例2 使用已经释放的内存
#include <iostream>
using namespace std;
int main()
{
    int *p = new int(10);
    cout << "*p:" << *p << endl;
    delete p;
    int a = *p;
    return 0;
}

==10129== Memcheck, a memory error detector
==10129== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==10129== Using Valgrind-3.20.0 and LibVEX; rerun with -h for copyright info
==10129== Command: ./demo
==10129== 
*p:10
==10129== Invalid read of size 4
==10129==    at 0x40097F: main (demo.cpp:8)
==10129==  Address 0x5b1fc80 is 0 bytes inside a block of size 4 free'd
==10129==    at 0x4C31C86: operator delete(void*) (vg_replace_malloc.c:935)
==10129==    by 0x40097A: main (demo.cpp:7)
==10129==  Block was alloc'd at
==10129==    at 0x4C2F87B: operator new(unsigned long) (vg_replace_malloc.c:434)
==10129==    by 0x400938: main (demo.cpp:5)
==10129== 
==10129== 
==10129== HEAP SUMMARY:
==10129==     in use at exit: 0 bytes in 0 blocks
==10129==   total heap usage: 3 allocs, 3 frees, 73,732 bytes allocated
==10129== 
==10129== All heap blocks were freed -- no leaks are possible
==10129== 
==10129== For lists of detected and suppressed errors, rerun with: -s
==10129== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

结果分析的第7行指出,存在无效的读操作,发生在demo.cpp的第8行;该内存空间是在demo.cpp的第5行进行分配空间,在demo.cpp的第7行进行空间释放,在8行又去访问该内存;

例3 数组访问越界
#include <iostream>
#include <stdlib.h>
using namespace std;
int main()
{
    int *p1 = (int*)malloc(sizeof(int)*4);
    p1[4] = 10;
    free(p1);
    int *p2 = new int[4]{1,2,3,4};
    int c = p2[50];
    delete []p2;
    return 0;
}
==12822== Memcheck, a memory error detector
==12822== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==12822== Using Valgrind-3.20.0 and LibVEX; rerun with -h for copyright info
==12822== Command: ./demo
==12822== 
==12822== Invalid write of size 4
==12822==    at 0x4007F7: main (demo.cpp:7)
==12822==  Address 0x5b1fc90 is 0 bytes after a block of size 16 alloc'd
==12822==    at 0x4C2F15A: malloc (vg_replace_malloc.c:393)
==12822==    by 0x4007EA: main (demo.cpp:6)
==12822== 
==12822== Invalid read of size 4
==12822==    at 0x400845: main (demo.cpp:10)
==12822==  Address 0x5b1fd98 is 120 bytes inside an unallocated block of size 4,121,280 in arena "client"
==12822== 
==12822== 
==12822== HEAP SUMMARY:
==12822==     in use at exit: 0 bytes in 0 blocks
==12822==   total heap usage: 3 allocs, 3 frees, 72,736 bytes allocated
==12822== 
==12822== All heap blocks were freed -- no leaks are possible
==12822== 
==12822== For lists of detected and suppressed errors, rerun with: -s
==12822== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

检测结果第6行和第12行指出,出现无效的写和无效的读;在demo.cpp的第6行存在数组越界访问,无效的写操作;在demo.cpp的第10行出现数组访问越界,无效的读操作;

只能检测动态数组访问越界,静态数组无法检测;

例4 多次内存释放
#include <iostream>
using namespace std;
int main()
{
    int *p2 = new int[4]{1,2,3,4};
    delete []p2;
    delete []p2;
    return 0;
}
==13064== Memcheck, a memory error detector
==13064== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==13064== Using Valgrind-3.20.0 and LibVEX; rerun with -h for copyright info
==13064== Command: ./demo
==13064== 
==13064== Invalid free() / delete / delete[] / realloc()
==13064==    at 0x4C32BC2: operator delete[](void*) (vg_replace_malloc.c:1115)
==13064==    by 0x4007AB: main (demo.cpp:7)
==13064==  Address 0x5b1fc80 is 0 bytes inside a block of size 16 free'd
==13064==    at 0x4C32BC2: operator delete[](void*) (vg_replace_malloc.c:1115)
==13064==    by 0x400798: main (demo.cpp:6)
==13064==  Block was alloc'd at
==13064==    at 0x4C30989: operator new[](unsigned long) (vg_replace_malloc.c:652)
==13064==    by 0x400757: main (demo.cpp:5
==13064== 
==13064== 
==13064== HEAP SUMMARY:
==13064==     in use at exit: 0 bytes in 0 blocks
==13064==   total heap usage: 2 allocs, 3 frees, 72,720 bytes allocated
==13064== 
==13064== All heap blocks were freed -- no leaks are possible
==13064== 
==13064== For lists of detected and suppressed errors, rerun with: -s
==13064== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

检测结果第6行指出,出现无效的内存释放操作;该内存在demo.cpp文件的第5行申请,demo.cpp的第6行释放,demo.cpp在第7行进行二次释放,错误操作;在结果的第19行也指出,2个内存块申请,3分内存块释放操作,存在错误的内存释放操作;

例5 申请和释放操作不匹配
#include <iostream>
#include <stdlib.h>
int main()
{
    int *p1 = (int*)malloc(sizeof(int)*4);
    delete p1;
    return 0;
}

==13194== Memcheck, a memory error detector
==13194== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==13194== Using Valgrind-3.20.0 and LibVEX; rerun with -h for copyright info
==13194== Command: ./demo
==13194== 
==13194== Mismatched free() / delete / delete []
==13194==    at 0x4C31C86: operator delete(void*) (vg_replace_malloc.c:935)
==13194==    by 0x40076A: main (demo.cpp:6)
==13194==  Address 0x5b1fc80 is 0 bytes inside a block of size 16 alloc'd
==13194==    at 0x4C2F15A: malloc (vg_replace_malloc.c:393)
==13194==    by 0x40075A: main (demo.cpp:5)
==13194== 
==13194== 
==13194== HEAP SUMMARY:
==13194==     in use at exit: 0 bytes in 0 blocks
==13194==   total heap usage: 2 allocs, 2 frees, 72,720 bytes allocated
==13194== 
==13194== All heap blocks were freed -- no leaks are possible
==13194== 
==13194== For lists of detected and suppressed errors, rerun with: -s
==13194== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

检测结果第6行指出,存在内存申请和释放的操作不匹配;在demo.cpp文件的第6行使用了malloc申请内存,在demo.cpp文件的第6行使用了delete操作,内存申请和释放的操作不匹配;

具体使用手册参考:Valgrind

官网:Valgrind Home

Sanitizer
简介

​ Address Aanitizer是一个快速检测内存错误的工具,它只将程序运行的速度拖慢了两倍,相比valgrand的10~20倍来说性能已经非常好了;他主要包含一个编译器instrumentation模块和一个替换malloc()/free()函数的运行时动态库;

GCC从4.8开始,AddressSanitizer就成为其一部分,一般建使用gcc4.9以上的版本;

原理

​ ASAN(AddressSanitizer)运行时,将malloc()/free() 进行了替换;malloc()分配缓存前后的空间以及被free()释放的空间都标记位poisoned,替换情况如下:

//替换前
*address = ...;
//替换后
if (IsPoisoned(addrss))
{
    ReportError(address, kAccessSize, kIsWrite);
}
//访问之前检查地址是否可以访问
*address = ...;
使用步骤
  1. 编译时需要添加 -fsanitize=address 编译选项
  2. 添加 -fno-omit-frame-pointer 编译选项,可得到更详细的stack trace
  3. 选择 -O1 或者更高级别的编译优化选项
gcc -fsanitize=address -fno-omit-frame-pointer -O1 -g demo.cpp -o demo
错误类型
  • 堆(heap)use after free 释放后使用

    int main ()
    {
        int* p = new int(10);
        delete p;
        return *p;
    }
    
  • heap buffer overflow 堆缓存访问溢出

    int main()
    {
        int* arr[10] = new int[10];
        int a = arr[10];
        return 0;
    }
    
  • stack buffer overflow 栈缓存访问益处

    int main()
    {
        int arr[10] = {10};
        int a = arr[10];
        return 0;
    }
    
  • global buffer overflow 全局缓存访问益处

    int arr[10] = {10};
    int main()
    {
        int a = arr[10];
        return 0;
    }
    
  • memory leaks 内存泄漏

    int main()
    {
        int* a = new int(10);
        return 0;
    }
    
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值