valgrind 详解

一、概述

Valgrind 是一套 Linux 下,开放源代码(GPL V2)的仿真调试工具的集合。

Valgrind 由内核(core)以及基于内核的其他调试工具组成。内核类似于一个框架(framework),它模拟了一个 CPU 环境,并提供服务给其他工具;而其他工具则类似于插件 (plug-in),利用内核提供的服务完成各种特定的内存调试任务。

Valgrind的体系结构如下图所示:

二、包含的工具

Valgrind 的最新版是3.11.0,它一般包含下列工具:

1、Memcheck

最常用的工具,用来检测程序中出现的内存问题,所有对内存的读写都会被检测到,一切对malloc() / free() / new / delete 的调用都会被捕获。所以,它能检测以下问题:

  • 对未初始化内存的使用;
  • 读/写释放后的内存块;
  • 读/写超出malloc分配的内存块;
  • 读/写不适当的栈中内存块;
  • 内存泄漏,指向一块内存的指针永远丢失;
  • 不正确的malloc/free或new/delete匹配;
  • memcpy()相关函数中的dst和src指针重叠。

2、Callgrind

和 gprof 类似的分析工具,但它对程序的运行观察更是入微,能给我们提供更多的信息。和 gprof 不同,它不需要在编译源代码时附加特殊选项,但加上调试选项是推荐的。Callgrind 收集程序运行时的一些数据,建立函数调用关系图,还可以有选择地进行 cache 模拟。在运行结束时,它会把分析数据写入一个文件。callgrind_annotate 可以把这个文件的内容转化成可读的形式。

3、Cachegrind

Cache 分析器,它模拟 CPU 中的一级缓存 I1,Dl 和二级缓存,能够精确地指出程序中 cache 的丢失和命中。如果需要,它还能够为我们提供 cache 丢失次数,内存引用次数,以及每行代码,每个函数,每个模块,整个程序产生的指令数。这对优化程序有很大的帮助。

4、Helgrind

它主要用来检查多线程程序中出现的竞争问题。

Helgrind 寻找内存中被多个线程访问,而又没有一贯加锁的区域,这些区域往往是线程之间失去同步的地方,而且会导致难以发掘的错误。Helgrind 实现了名为“Eraser”的竞争检测算法,并做了进一步改进,减少了报告错误的次数。不过,Helgrind 仍然处于实验阶段。

5、Massif

堆栈分析器,它能测量程序在堆栈中使用了多少内存,告诉我们堆块,堆管理块和栈的大小

Massif 能帮助我们减少内存的使用,在带有虚拟内存的现代系统中,它还能够加速我们程序的运行,减少程序停留在交换区中的几率。

此外,lackey 和 nulgrind 也会提供。Lackey 是小型工具,很少用到;Nulgrind 只是为开发者展示如何创建一个工具。

三、原理

Memcheck 能够检测出内存问题,关键在于其建立了两个全局表。

(1)Valid-Value 表

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

(2)Valid-Address 表

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

检测原理:

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

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

四、常用选项

(1)--help:显示帮助信息;

(2)--version:显示 valgrind 版本;

(3)--tool=<name>:运行 valgrind 中名为 toolname 的工具,默认 memcheck,还可以为cachegrid、drd、lackey、callgrind、helgrind、massif等;

(4)--quiet:安静地运行,只打印错误信息;

(5)--verbose:更详细的信息,增加错误数统计;

(6)--trace-childer=no | yes:跟踪子线程;

(7)--track-fds=no | yes:跟踪打开的文件描述;

(8)--time-stamp=no | yes:增加时间戳到 Log 信息;

(9)--log-fd=<number>:输出Log信息到文件描述符;

(10)--log-file=<file>:输出Log信息到指定的文件;

(11)--xml=yes:将错误信息以xml格式输出,只有memcheck可用;

(12)--xml-file=<file>:XML输出到指定文件;

(13)--error-limit=no | yes:如果错误太多,则停止显示新错误;

(14)--error-exitcode=<number>:如果发现错误,则返回错误代码;

(15)--leak-check=no | summary | full:对发现的内存泄露给出的信息级别,只有memcheck可用。(建议添加

(16)--show-reachable=no | yes,用于控制是否检测控制范围之外的泄漏,比如全局指针、static指针等。

(17)–num-callers=(num):这个值默认是12,最高是50。表示显示多少层的堆栈,设置越高会使Valgrind运行越慢而且使用更多的内存,但是在嵌套调用层次比较高的程序中非常实用。

(18)--trace-children=no | yes,是否跟入子进程。

五、栗子

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

int main(void)
{
    char *ptr = (char *)malloc(10);
    ptr[12] = 'a'; // 内存越界
    memcpy(ptr +1, ptr, 5); // 踩内存
    char a[10];
    a[12] = 'i'; // 数组越界
    free(ptr); // 重复释放
    free(ptr);
    char *p1;
    *p1 = '1'; // 非法指针

    return 0;

}

gcc -o memleak main.cc -g

valgrind --tool=memcheck --leak-check=full ./memleak

结果:

10617== Memcheck, a memory error detector
==10617== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==10617== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==10617== Command: ./memleak
==10617== 
==10617== Invalid write of size 1    // 踩了1个字节的内存。
==10617==    at 0x1091DA: main (main.cc:8)
==10617==  Address 0x4a5804c is 2 bytes after a block of size 10 alloc'd
==10617==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==10617==    by 0x1091CD: main (main.cc:7)
==10617== 
==10617== Invalid write of size 1    // 踩了1个字节的内存。
==10617==    at 0x484043E: memcpy@GLIBC_2.2.5 (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==10617==    by 0x1091F8: main (main.cc:9)
==10617==  Address 0x4a5804a is 0 bytes after a block of size 10 alloc'd
==10617==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==10617==    by 0x1091CD: main (main.cc:7)
==10617== 
==10617== Invalid free() / delete / delete[] / realloc()    // 重复释放。
==10617==    at 0x483CA3F: free (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==10617==    by 0x109214: main (main.cc:13)
==10617==  Address 0x4a58040 is 0 bytes inside a block of size 10 free'd
==10617==    at 0x483CA3F: free (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==10617==    by 0x109208: main (main.cc:12)
==10617==  Block was alloc'd at
==10617==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==10617==    by 0x1091CD: main (main.cc:7)
==10617== 
==10617== Use of uninitialised value of size 8    // 使用了未申请的内存(8bit)
==10617==    at 0x109219: main (main.cc:15)
==10617== 
==10617== Invalid write of size 1    // 在非法地址上写了1个字节。
==10617==    at 0x109219: main (main.cc:15)
==10617==  Address 0x0 is not stack'd, malloc'd or (recently) free'd
==10617== 
==10617==  // 非法指针,导致coredump
==10617== Process terminating with default action of signal 11 (SIGSEGV)
==10617==  Access not within mapped region at address 0x0
==10617==    at 0x109219: main (main.cc:15)
==10617==  If you believe this happened as a result of a stack
==10617==  overflow in your program's main thread (unlikely but
==10617==  possible), you can try to increase the size of the
==10617==  main thread stack using the --main-stacksize= flag.
==10617==  The main thread stack size used in this run was 8388608.
==10617== 
==10617== HEAP SUMMARY:
==10617==     in use at exit: 0 bytes in 0 blocks
==10617==   total heap usage: 1 allocs, 2 frees, 10 bytes allocated
==10617== 
==10617== All heap blocks were freed -- no leaks are possible
==10617== 
==10617== Use --track-origins=yes to see where uninitialised values come from
==10617== For lists of detected and suppressed errors, rerun with: -s
==10617== ERROR SUMMARY: 5 errors from 5 contexts (suppressed: 0 from 0)

六、堆栈分析器可视

1、安装

sudo apt install massif-visualizer

2、生成数据文件

valgrind --tool=massif ./memleak

3、可视化

massif-visualizer massif.out.8233

8233 为进程号

结果:

 4、简单可视化

ms_print massif.out.8233

(SAW:Game Over!)

  • 19
    点赞
  • 87
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值