Valgrind——memcheck内存泄漏分析

这篇具有很好参考价值的文章主要介绍了Valgrind——memcheck内存泄漏分析。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

概述

valgrind 官网 https://www.valgrind.org/

valgrind 是 Linux 业界主流且非常强大的内存泄漏检查工具。在其官网介绍中,内存检查(memcheck)只是其其中一个功能。valgrind 默认使用 memcheck 去检查内存问题。

valgrind 这个工具不能用于调试正在运行的程序,因为待分析的程序必须在它特定的环境中运行,它才能分析内存。

memcheck 检测内存问题的原理如下图所示:
Valgrind——memcheck内存泄漏分析
Memcheck 能够检测出内存问题,关键在于其建立了两个全局表。

valid-value map:
对于进程的整个地址空间中的每一个字节(byte),都有与之对应的 8 个 bits;对于 CPU 的每个寄存器,也有一个与之对应的 bit 向量。这些 bits 负责记录该字节或者寄存器值是否具有有效的、已初始化的值。

valid-address map
对于进程整个地址空间中的每一个字节(byte),还有与之对应的 1 个 bit,负责记录该地址是否能够被读写。


memcheck检测原理

当要读写内存中某个字节时,首先检查 valid-address map 中这个字节对应的 A bit。如果该A bit显示该位置是无效位置,memcheck 则报告读写错误。

内核(core)类似于一个虚拟的 CPU 环境,这样当内存中的某个字节被加载到真实的 CPU 中时,该字节对应的 V bit (在 valid-value map 中) 也被加载到虚拟的 CPU 环境中。一旦寄存器中的值,被用来产生内存地址,或者该值能够影响程序输出,则 memcheck 会检查对应的 V bits,如果该值尚未初始化,则会报告使用未初始化内存错误。


内存泄露分类

valgrind 将内存泄漏分为 4 类。

  • 明确泄漏(definitely lost):内存还没释放,但已经没有指针指向内存,内存已经不可访问
  • 间接泄漏(indirectly lost):泄漏的内存指针保存在明确泄漏的内存中,随着明确泄漏的内存不可访问,导致间接泄漏的内存也不可访问
  • 可能泄漏(possibly lost):指针并不指向内存头地址,而是指向内存内部的位置
  • 仍可访达(still reachable):指针一直存在且指向内存头部,直至程序退出时内存还没释放。

明确泄露

官方用户手册描述如下:

This means that no pointer to the block can be found. The block is
classified as “lost”, because the programmer could not possibly have
freed it at program exit, since no pointer to it exists. This is
likely a symptom of having lost the pointer at some earlier point in
the program. Such cases should be fixed by the programmer.

其实简单来说,就是内存还没释放,但已经没有任何指针指向这片内存,内存地址已经丢失。定义比较好理解,就不举例了。

valgrind 检查到明确泄漏时,会打印类似下面这样的日志:

19182 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
19182 at 0x1B8FF5CD: malloc (vg_replace_malloc.c:130)
19182 by 0x8048385: f (a.c:5)
19182 by 0x80483AB: main (a.c:11)

明确泄漏的内存是强烈建议修复的,这没啥好争辩的。


间接泄漏

官方用户手册描述如下:

This means that no pointer to the block can be found. The block is
classified as “lost”, because the programmer could not possibly have
freed it at program exit, since no pointer to it exists. This is
likely a symptom of having lost the pointer at some earlier point in
the program. Such cases should be fixed by the programmer.

间接泄漏就是指针并不直接丢失,但保存指针的内存地址丢失了。比较拗口,咱们看个例子:

struct list {
	struct list *next;
};

int main(int argc, char **argv)
{
	struct list *root;
	
	root = (struct list *)malloc(sizeof(struct list));
	root->next = (struct list *)malloc(sizeof(struct list));
	printf("root %p roop->next %p\n", root, root->next);
	root = NULL;
	return 0;
}

丢失的是 root 指针,导致 root 存储的 next 指针成为了间接泄漏。

valgrind 检查会打印如下日志:

# valgrind --tool=memcheck --leak-check=full --show-reachable=yes /data/demo-c
==10435== Memcheck, a memory error detector
==10435== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==10435== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info
==10435== Command: /data/demo-c
==10435==
root 0x4a33040 roop->next 0x4a33090
==10435==
==10435== HEAP SUMMARY:
==10435==     in use at exit: 16 bytes in 2 blocks
==10435==   total heap usage: 3 allocs, 1 frees, 1,040 bytes allocated
==10435==
==10435== 8 bytes in 1 blocks are indirectly lost in loss record 1 of 2
==10435==    at 0x4845084: malloc (vg_replace_malloc.c:380)
==10435==    by 0x4007BF: main (in /data/demo-c)
==10435==
==10435== 16 (8 direct, 8 indirect) bytes in 1 blocks are definitely lost in loss record 2 of 2
==10435==    at 0x4845084: malloc (vg_replace_malloc.c:380)
==10435==    by 0x4007B3: main (in /data/demo-c)
==10435==
==10435== LEAK SUMMARY:
==10435==    definitely lost: 8 bytes in 1 blocks
==10435==    indirectly lost: 8 bytes in 1 blocks
==10435==      possibly lost: 0 bytes in 0 blocks
==10435==    still reachable: 0 bytes in 0 blocks
==10435==         suppressed: 0 bytes in 0 blocks
==10435==
==10435== For lists of detected and suppressed errors, rerun with: -s
==10435== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

默认情况下,只会打印 明确泄漏 和 可能泄漏,如果需要同时打印间接泄漏,需要加上选项 --show-reachable=yes.

间接泄漏的内存肯定也要修复的,不过一般会随着明确泄漏的修复而修复


可能泄漏

官方用户手册描述如下:

This means that a chain of one or more pointers to the block has been found, but at least one of the pointers is an interior-pointer. This could just be a random value in memory that happens to point into a block, and so you shouldn’t consider this ok unless you know you have interior-pointers.

valgrind 之所以会怀疑可能泄漏,是因为指针已经偏移,并没有指向内存头,而是有内存偏移,指向内存内部的位置。

有些时候,这并不是泄漏,因为这些程序就是这么设计的,例如为了实现内存对齐,额外申请内存,返回对齐后的内存地址。但更多时候,是我们不小心 p++ 了。

可能泄漏的情况需要我们根据代码情况自己分析确认。


仍可访达

官方用户手册描述如下:

This covers cases 1 and 2 (for the BBB blocks) above. A start-pointer or chain of start-pointers to the block is found. Since the block is still pointed at, the programmer could, at least in principle, have freed it before program exit. “Still reachable” blocks are very common and arguably not a problem. So, by default, Memcheck won’t report such blocks individually.

仍可访达 表示在程序退出时,不管是正常退出还是异常退出,内存申请了没释放,都属于仍可访达的泄漏类型。

如果测试的程序是正常退出的,那么这些 仍可访达 的内存就是泄漏,最好修复了。如果测试是长期运行的程序,通过信号提前终止,那么这些内存就大概率并不会泄漏。

其他的内存错误检查类型

即使是 memcheck 一个工具,除了检查内存泄漏之外,还支持其他内存错误使用的检查。

  • 非法读/写内存(Illegal read / Illegal write errors)
  • 使用未初始化的变量(Use of uninitialised values)
  • 系统调用传递不可访问或未初始化内存(Use of uninitialised or unaddressable values in system calls)
  • 非法释放(Illegal frees)
  • 不对应的内存申请和释放(When a heap block is freed with an inappropriate
    deallocation function)
  • 源地址和目的地址重叠(Overlapping source and destination blocks)
  • 内存申请可疑大小(Fishy argument values)

memcheck工具的支持的错误类型可看官方文档:https://www.valgrind.org/docs/manual/mc-manual.html#mc-manual.errormsgs


本文翻译几个感兴趣的错误类型:

非法读/写内存

例子:

Invalid read of size 4
   at 0x40F6BBCC: (within /usr/lib/libpng.so.2.1.0.9)
   by 0x40F6B804: (within /usr/lib/libpng.so.2.1.0.9)
   by 0x40B07FF4: read_png_image(QImageIO *) (kernel/qpngio.cpp:326)
   by 0x40AC751B: QImageIO::read() (kernel/qimage.cpp:3621)
 Address 0xBFFFF0E0 is not stack'd, malloc'd or free'd

在你要操作的内存超出边界或者非法地址时,就会有这个错误提示。
常见的错误,例如访问数组越界:

int arr[4];
arr[4] = 10;

或者使用已经释放了的内存:

char *p = malloc(30);

free(p);

p[1] = ‘\0’;

如果发现这样的错误,最好也修复了。因为这些错误大概率会导致段错误


使用未初始化的变量

尤其出现在局部变量未赋值,却直接读取的情况。也包括申请了内存,没有赋值却直接读取,虽然这情况会读出 ‘\0’,不会导致异常,但更多时候是异常逻辑。

例子:

int main()
{
  int x;
  printf ("x = %d\n", x);
}

如果要详细列出哪里申请的内存未初始化,需要使用参数 --track-origins=yes,但也会让运行慢很多。

错误显示是这样的:

Conditional jump or move depends on uninitialised value(s)
at 0x402DFA94: _IO_vfprintf (_itoa.h:49)
by 0x402E8476: _IO_printf (printf.c:36)
by 0x8048472: main (tests/manuel1.c:8)


系统调用传递不可访问或未初始化内存

memcheck 工具会检查所有系统调用的参数:

  • 参数是否有初始化
  • 如果是系统调用读取程序提供的buffer,会产检整个buffer是否可访问和已经初始化
  • 如果是系统调用要往用户的buffer写入数据,会检查buffer是否可访问

错误显示是这样的:

  Syscall param write(buf) points to uninitialised byte(s)
     at 0x25A48723: __write_nocancel (in /lib/tls/libc-2.3.3.so)
     by 0x259AFAD3: __libc_start_main (in /lib/tls/libc-2.3.3.so)
     by 0x8048348: (within /auto/homes/njn25/grind/head4/a.out)
   Address 0x25AB8028 is 0 bytes inside a block of size 10 alloc'd
     at 0x259852B0: malloc (vg_replace_malloc.c:130)
     by 0x80483F1: main (a.c:5)

  Syscall param exit(error_code) contains uninitialised byte(s)
     at 0x25A21B44: __GI__exit (in /lib/tls/libc-2.3.3.so)
     by 0x8048426: main (a.c:8)

不对应的内存申请和释放

检查逻辑如下:

  • malloc, calloc, realloc, valloc 申请的内存,必须用 free 释放。
  • new 申请的内存,必须用 delete 释放。
  • new[] 申请的内存,必须用 delete[] 释放。

错误显示是这样的:

Mismatched free() / delete / delete []
   at 0x40043249: free (vg_clientfuncs.c:171)
   by 0x4102BB4E: QGArray::~QGArray(void) (tools/qgarray.cpp:149)
   by 0x4C261C41: PptDoc::~PptDoc(void) (include/qmemarray.h:60)
   by 0x4C261F0E: PptXml::~PptXml(void) (pptxml.cc:44)
 Address 0x4BB292A8 is 0 bytes inside a block of size 64 alloc'd
   at 0x4004318C: operator new[](unsigned int) (vg_clientfuncs.c:152)
   by 0x4C21BC15: KLaola::readSBStream(int) const (klaola.cc:314)
   by 0x4C21C155: KLaola::stream(KLaola::OLENode const *) (klaola.cc:416)
   by 0x4C21788F: OLEFilter::convert(QCString const &) (olefilter.cc:272)

shell命令及参数

valgrind 的执行命令如下:

valgrind [valgrind_optons] myprog [myprog_arg1 …]

例如:

valgrind --leak-check=full ls -al

使用valgrind做内存检查,程序的执行效率会比平常慢大约20~30倍,以及用更多的内存。
valgrind 会在收到到 1000 个不同的错误,或者共计 10,000,000 个错误时自动停止继续收集错误信息。

此外,不建议直接通过 valgrind 来运行脚本,否则只会得到 shell 或者其他的解释器相关的错误报告。我们可以通过提供选项 --trace-children=yes 来强制解决这个问题,但是仍然有可能出现混淆。

valgrind 只有在进程退出时,才会一次性打印所有的分析结果。

参数

valgrind 有非常多的参数,可以自行通过 valgrind --help 查看大致说明

本文只对用到的几个参数进行详细说明。

--tool=<toolname>[default: memcheck]

valgrind支持不少检查工具,都有各种功能。但用的更多的还是他的内存检查(memcheck)。–tool= 用于选择你需要执行的工具,如果不指明则默认为 memcheck。

--log-file=<filename> And --log-fd=<number> [default: 2, stderr]

valgrind 打印日志转存到指定文件或者文件描述符。如果没有这个参数,valgrind 的日志会连同用户程序的日志一起输出,对于大多数使用者来说,会显得非常乱。

valgrind 还支持把错误日志重定向到 socket 中,由于没用过,就不展开了。

–leak-check=<no|summary|yes|full> [default: summary]
这个参数决定了输出泄漏结果时,输出的是结果内容。 no 没有输出,summary 只输出统计的结果,yes 和 full 输出详细内容。
常见的使用是:–leak-check=full

–show-leak-kinds= [default: definite,possible]
valgrind 有4种泄漏类型,这个参数决定显示哪些类型泄漏。definite indirect possible reachable 这4种可以设置多个,以逗号相隔,也可以用 all 表示全部类型,none 表示啥都不显示。
大多数情况,我们直接用 --show-reachable=yes 而不是 --show-leak-kinds=…,见下文。

–show-reachable=<yes | no> , --show-possibly-lost=<yes | no>
–show-reachable=no --show-possibly-lost=yes 等效于 --show-leak-kinds=definite,possible。
–show-reachable=no --show-possibly-lost=no 等效于 --show-leak-kinds=definite。
–show-reachable=yes 等效于 --show-leak-kinds=all。 需要注意的是,在使能 --show-reachable=yes 时,–show-possibly-lost=no 会无效。

常见的,这个参数这么使用:–show-reachable=yes

–trace-children=<yes | no> [default: no]
是否跟踪子进程?看自己需求,如果是多进程的程序,则建议使用这个功能。不过单进程使能了也不会有多大影响。

–keep-stacktraces=alloc | free | alloc-and-free | alloc-then-free | none [default: alloc-and-free]
内存泄漏不外乎申请和释放不配对,函数调用栈是只在申请时记录,还是在申请释放时都记录,还是其他?如果我们只关注内存泄漏,其实完全没必要申请释放都记录,因为这会占用非常多的额外内存和更多的 CPU 损耗,让本来就执行慢的程序雪上加霜。
因此,建议这么使用:–keep-stacktraces=alloc

–track-fds=<yes | no | all> [default: no]
是否跟踪文件打开和关闭?很多时候,文件打开后没关闭也是一个明显的泄漏。

–track-origins=<yes | no> [default: no]
对使用非初始化的变量的异常,是否跟踪其来源。
在确定要分析使用未初始化内存错误时使能即可,平时使能这个会导致程序执行非常慢。

–keep-debuginfo=<yes | no> [default: no]
如果程序有使用动态加载库(dlopen),在动态库卸载时(dlclose),debug信息都会被清除。使能这个选项后,即使动态库被卸载,也会保留调用栈信息。


代码实例

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

//内存泄漏1:malloc()不free()
static void mem_leak1(void)
{
        char  *p = malloc(1);
}

//创建文件描述符不关闭
static void mem_leak2(void)
{
    int fd1 = open("text1.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
    int fd2 = open("text2.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
    int fd3 = open("text3.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
}

//溢出:越界访问(指针)
static void mem_overrun1(void)
{
        char  *p = malloc(1);
        *(short *)p = 2;
        
        free(p);
}

//溢出:越界访问(数组)
static void mem_overrun2(void)
{
        char  array[4];
        strcpy(array, "hello");
}

//分配一次,释放两次
static void mem_double_free(void)
{
        char  *p = malloc(1);
        free(p);
        free(p);
}

//使用野指针
static void mem_use_wild_pointer(void)
{
        char  *p;
        *p = 1;
}

//释放野指针
static void mem_free_wild_pointer(void)
{
        char  *p;
        free(p);
}

这里一共列出了七种常见的内存问题:
1、动态内存泄露
2、资源泄露,这里以文件描述符为例;
3、动态内存越界;
4、数组内存越界;
5、动态内存double free;
6、使用野指针,即未初始化的指针;
7、释放野指针,即未初始化的指针;

其中由于本示例代码过于简单,第6种情况,使用野指针会直接导致程序崩溃,所以在main中,并没有真正的调用那个示例代码。
由于valgrind只能检测执行到的代码,所以在后面的报告中,不会报告第6种错误情况。
但是,在大型的项目中,有可能使用野指针并不会导致程序崩溃。另外上面的7中情况,有些情况严格的说,实际上可以归为一类。

看执行后的报告:
Valgrind——memcheck内存泄漏分析
1、这里检测到了动态内存越界,提示Invaild write
Valgrind——memcheck内存泄漏分析
2、这里检测到了文件描述符的泄露(打开未关闭)Valgrind——memcheck内存泄漏分析
3、这里检测到了double free的问题,提示Invalid Free(如下图)
4、这里检测到了非法释放,释放野指针、以及double free
Valgrind——memcheck内存泄漏分析
5、这里检测到了未初始化变量
Valgrind——memcheck内存泄漏分析
堆的使用信息:
一共用了3次alloc,4次free。

Valgrind——memcheck内存泄漏分析
6、检测到一个字节的内存泄露
Valgrind——memcheck内存泄漏分析
内存泄露的总结:
Valgrind——memcheck内存泄漏分析


参考博客:
https://www.cnblogs.com/gmpy/p/14778243.html
https://www.jianshu.com/p/5a31d9aa1be2文章来源地址https://www.toymoban.com/news/detail-431444.html

到了这里,关于Valgrind——memcheck内存泄漏分析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 聊一聊 Valgrind 监视非托管内存泄露和崩溃

    只要是程序总会出现各种莫名其妙的问题,比如:非托管内存泄露,程序崩溃,在 Windows 平台上一般用微软自家的官方工具 App Verifier 就可以洞察,那问题出在 Linux 上怎么办呢?由于 Linux 崇尚自由,需要在各种牛鬼蛇神写的非官方开源软件中寻找一个比较靠谱的,比如本篇所

    2024年02月02日
    浏览(73)
  • faac内存开销较大,为方便嵌入式设备使用进行优化(valgrind使用)

    faac内存开销较大,为方便嵌入式设备使用进行优化,在github上提了issues但是没人理我,所以就搞一份代码自己玩吧。 基于faac_1_30版本,原工程https://github.com/knik0/faac faac内存优化: faac内存开销较大,为方便嵌入式设备使用进行优化,在github上提了issues但是没人理我,所以就搞

    2024年02月14日
    浏览(37)
  • valgrind基本功能介绍、基础使用方法说明 valgrind基本功能介绍、基础使用方法说明

    valgrind基本功能介绍、基础使用方法说明_valgrind使用方法_HNU Latecomer的博客-CSDN博客 拷贝效果不好,请看原文。 1、Valgrind概述 Valgrind是一套Linux下,开放源代码(GPL V2)的仿真调试工具的集合。 Valgrind由内核(core)以及基于内核的其他调试工具组成。内核类似于一个框架(f

    2024年02月07日
    浏览(50)
  • valgrind的使用

    一. 简述valgrind是什么,为何能进行内存泄露 valgrind是一个程序调试及性能分析的工具集,涵盖memcheck, cachegrind,helgrind,callgrind,启动valgrind时通过–tool来指定具体要调用的工具。不论使用哪个工具,通过valgrind来启动程序时都会取得对程序的控制权,从关联库中读取调试信息。

    2024年02月11日
    浏览(24)
  • linux编译源码,安装valgrind

    目录 1 下载源码 2 在虚拟机上解压 3 进入解压的目录,执行make 4 安装 5 检查安装是否成功 本文参考了内存检查工具valgrind介绍、安装与使用-CSDN博客 我到Valgrind: Current Releases 下载了valgrind 3.22.0源码 我使用的虚拟机是银河麒麟

    2024年01月22日
    浏览(35)
  • valgrind being installed on Arm platform

    valgrind安装: 2.在ARM的板子上运行valgrind, 程序出现valgrind Fatal error at startup: a function redirection的错误提示。查找了下,发现是因为libc或ld.so库进行过strip操作。 直接安装一个debug版本的库就可以了。

    2024年01月21日
    浏览(31)
  • 一生一芯9——ubuntu22.04安装valgrind

    这里安装的valgrind版本是3.19.0 在选定的目录下打开终端,输入以下指令 直至下载完成 输入下面指令解压安装包 注: 先前在输入指令 会出现如下错误,目前尚未解决,但得知-xvf可以自动检测格式进行解压后,果断采用-xvf进行解压 而下载安装包 使用命令行查看文件类型时,

    2024年02月11日
    浏览(47)
  • RK3399平台开发系列讲解(内核调试篇)Valgrind使用案例

    🚀 返回专栏总目录 沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 Valgrind 是一个开源的内存调试和性能分析工具,用于帮助开发者找出程序中的内存错误,如内存泄漏、使用未初始化的内存、非法内存访问等问题。它在 Linux 平台上广泛使用,并且支持多种处理器

    2024年02月12日
    浏览(28)
  • ThreadLocal引发的内存泄漏分析

    Object o = new Object(); 这个o,我们可以称之为对象引用,而new Object()我们可以称之为在内存中产生了一个对象实例。 当写下  o=null 时,只是表示o不再指向堆中object的对象实例,不代表这个对象实例不存在了。 强引用:  就是指在程序代码之中普遍存在的,类似“Object obj=new Ob

    2024年02月09日
    浏览(46)
  • LeakCanary内存泄漏检测框架分析。

    一、什么叫内存泄漏、内存溢出? 内存溢出(out of memory):是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个10M的Bitmap,但系统分配给APP的连续内存不足10M,就会导致内存溢出。 内存泄漏(memory leak):是指程序在申请内存后,无法释放已申

    2024年02月15日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包