1. 概述

Valgrind 是一款用于内存调试、内存泄漏检测以及性能分析的软件开发工具。

Valgrind 发行版目前包括七个生产质量工具:一个内存错误检测器、两个线程错误检测器、一个缓存和分支预测分析器、一个调用图生成缓存和分支预测分析器,以及两个不同的堆分析器。它还包括一个实验性的 SimPoint 基本块矢量生成器。它可在以下平台上运行:X86/Linux、AMD64/Linux、ARM/Linux、ARM64/Linux、PPC32/Linux、PPC64/Linux、PPC64LE/Linux、S390X/Linux、MIPS32/Linux、MIPS64/Linux、X86/Solaris , AMD64/Solaris, ARM/Android (2.3.x 及更高版本), ARM64/Android, X86/Android (4.0 及更高版本), MIPS32/Android, X86/FreeBSD, AMD64/FreeBSD, X86/Darwin 和 AMD64/Darwin (Mac OS X 10.12)

1.1 工具

它一般包含下列工具:

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 只是为开发者展示如何创建一个工具。

1.2 原理

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

Valid-Value 表

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

Valid-Address 表

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

检测原理

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

2. 安装使用

2.1 下载

去官网valgrind.org / 下载最新版本

centos 下载:

  1. yum install valgrind

Ubuntu 下载

  1. sudo apt-get install valgrind

2.2 使用

用法: valgrind[options] prog-and-args [options]:

常用选项,适用于所有 Valgrind 工具

-tool= 最常用的选项。运行 valgrind 中名为 toolname 的工具。默认 memcheck。

h –help 显示帮助信息。

-version 显示 valgrind 内核的版本,每个工具都有各自的版本。

q –quiet 安静地运行,只打印错误信息。

v –verbose 更详细的信息, 增加错误数统计。

-trace-children=no|yes 跟踪子线程? [no]

-track-fds=no|yes 跟踪打开的文件描述?[no]

-time-stamp=no|yes 增加时间戳到 LOG 信息? [no]

-log-fd= 输出 LOG 到描述符文件 [2=stderr]

-log-file= 将输出的信息写入到 filename.PID 的文件里,PID 是运行程序的进行 ID

-log-file-exactly= 输出 LOG 信息到 file

-log-file-qualifier= 取得环境变量的值来做为输出信息的文件名。 [none]

-log-socket=ipaddr:port 输出 LOG 到 socket ,ipaddr:port

LOG 信息输出:

-xml=yes 将信息以 xml 格式输出,只有 memcheck 可用

-num-callers= show callers in stack traces [12]

-error-limit=no|yes 如果太多错误,则停止显示新错误? [yes]

-error-exitcode= 如果发现错误则返回错误代码 [0=disable]

-db-attach=no|yes 当出现错误,valgrind 会自动启动调试器 gdb。[no]

-db-command= 启动调试器的命令行选项[gdb -nw %f %p]

适用于 Memcheck 工具的相关选项:

-leak-check=no|summary|full 要求对 leak 给出详细信息? [summary]

-leak-resolution=low|med|high how much bt merging in leak check [low]

-show-reachable=no|yes show reachable blocks in leak check? [no]

3. 应用例子

3.1 数组越界

malloc1.c

  1. #include <stdio.h>
  2. int main(int argc, char **argv) {
  3. int *x = malloc(8 * sizeof(int));
  4. x[9] = 0; // 数组下标越界
  5. free(x);
  6. return 0;
  7. }

编译:

  1. gcc -Wall malloc1.c -g -o malloc1

使用 Valgrind 检查程序 BUG:

  1. # --leak-check=full 所有泄露检查
  2. valgrind --tool=memcheck --leak-check=full ./malloc1

运行结果:

  1. [root@hackett valgrind]# valgrind --tool=memcheck --leak-check=full ./malloc1
  2. ==550168== Memcheck, a memory error detector
  3. ==550168== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
  4. ==550168== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info
  5. ==550168== Command: ./malloc1
  6. ==550168==
  7. ==550168== Invalid write of size 4
  8. ==550168== at 0x4005FB: main (malloc1.c:7)
  9. ==550168== Address 0x520b064 is 4 bytes after a block of size 32 alloc'd
  10. ==550168== at 0x4C360A5: malloc (vg_replace_malloc.c:380)
  11. ==550168== by 0x4005EE: main (malloc1.c:5)
  12. ==550168==
  13. ==550168==
  14. ==550168== HEAP SUMMARY:
  15. ==550168== in use at exit: 0 bytes in 0 blocks
  16. ==550168== total heap usage: 1 allocs, 1 frees, 32 bytes allocated
  17. ==550168==
  18. ==550168== All heap blocks were freed -- no leaks are possible
  19. ==550168==
  20. ==550168== For lists of detected and suppressed errors, rerun with: -s
  21. ==550168== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

分析:

1、每一行开头的数字代表是进程 ID,这里的进程 ID 为 550168

2、提示
==550168== Invalid write of size 4
==550168== at 0x4005FB: main (malloc1.c:7)
这两行显示的是错误出现的位置,就是 x 分配了 32 byte 的空间,但是向第 36 个 byte 写数据, 所以就会显示 Invalid write 的错误,代码在 malloc1.c 的第 7 行

3.2 内存释放后进行读写

malloc2.c

  1. #include <stdio.h>
  2. int main(int argc, char **argv) {
  3. char *p = malloc(1);
  4. *p = 'a';
  5. char c = *p;
  6. printf("[%c]\n", c);
  7. free(p); // 释放
  8. c = *p; //取值 读
  9. return 0;
  10. }

编译:

  1. gcc -Wall malloc2.c -g -o malloc2

使用 Valgrind 检查程序 BUG:

  1. # --leak-check=full 所有泄露检查
  2. valgrind --tool=memcheck --leak-check=full ./malloc2

运行结果:

  1. [root@hackett valgrind]# valgrind --tool=memcheck --leak-check=full ./malloc2
  2. ==550063== Memcheck, a memory error detector
  3. ==550063== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
  4. ==550063== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info
  5. ==550063== Command: ./malloc2
  6. ==550063==
  7. [a]
  8. ==550063== Invalid read of size 1
  9. ==550063== at 0x400679: main (malloc2.c:15)
  10. ==550063== Address 0x520b040 is 0 bytes inside a block of size 1 free'd
  11. ==550063== at 0x4C38A03: free (vg_replace_malloc.c:755)
  12. ==550063== by 0x400674: main (malloc2.c:13)
  13. ==550063== Block was alloc'd at
  14. ==550063== at 0x4C360A5: malloc (vg_replace_malloc.c:380)
  15. ==550063== by 0x40063E: main (malloc2.c:5)
  16. ==550063==
  17. ==550063==
  18. ==550063== HEAP SUMMARY:
  19. ==550063== in use at exit: 0 bytes in 0 blocks
  20. ==550063== total heap usage: 2 allocs, 2 frees, 1,025 bytes allocated
  21. ==550063==
  22. ==550063== All heap blocks were freed -- no leaks are possible
  23. ==550063==
  24. ==550063== For lists of detected and suppressed errors, rerun with: -s
  25. ==550063== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
  26. 复制代码

分析:

1、每一行开头的数字代表是进程 ID,这里的进程 ID 为 550063

2、提示
==550063== Invalid read of size 1
==550063== at 0x400679: main (malloc2.c:15)
==550063== Address 0x520b040 is 0 bytes inside a block of size 1 free’d
==550063== at 0x4C38A03: free (vg_replace_malloc.c:755)
==550063== by 0x400674: main (malloc2.c:13)
这五行显示的是错误出现的位置,就是 p 指向的内存已经释放了,还对其进行读操作,所以就会显示 Invalid read 的错误,代码在 malloc2.c 的第 15 行

3.3 无效读写

malloc3.c

  1. #include <stdio.h>
  2. int main(int argc, char **argv) {
  3. char *p = malloc(1);
  4. *p = 'a';
  5. char c = *(p+1); // 地址加1 无效读
  6. printf("[%c]\n",c);
  7. free(p); // 释放
  8. return 0;
  9. }

编译:

  1. gcc -Wall malloc3.c -g -o malloc3

使用 Valgrind 检查程序 BUG:

  1. # --leak-check=full 所有泄露检查
  2. valgrind --tool=memcheck --leak-check=full ./malloc3

运行结果:

  1. [root@iZwz97bu0gr8vx0j8l6kkzZ valgrind]# valgrind --tool=memcheck --leak-check=full ./malloc3
  2. ==550135== Memcheck, a memory error detector
  3. ==550135== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
  4. ==550135== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info
  5. ==550135== Command: ./malloc3
  6. ==550135==
  7. ==550135== Invalid read of size 1
  8. ==550135== at 0x40064E: main (malloc3.c:9)
  9. ==550135== Address 0x520b041 is 0 bytes after a block of size 1 alloc'd
  10. ==550135== at 0x4C360A5: malloc (vg_replace_malloc.c:380)
  11. ==550135== by 0x40063E: main (malloc3.c:5)
  12. ==550135==
  13. []
  14. ==550135==
  15. ==550135== HEAP SUMMARY:
  16. ==550135== in use at exit: 0 bytes in 0 blocks
  17. ==550135== total heap usage: 2 allocs, 2 frees, 1,025 bytes allocated
  18. ==550135==
  19. ==550135== All heap blocks were freed -- no leaks are possible
  20. ==550135==
  21. ==550135== For lists of detected and suppressed errors, rerun with: -s
  22. ==550135== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

分析:

1、每一行开头的数字代表是进程 ID,这里的进程 ID 为 550135

2、提示
==550135== Invalid read of size 1
==550135== at 0x40064E: main (malloc3.c:9)
这五行显示的是错误出现的位置,就是对未分配内存的空间进行读取,所以就会显示 Invalid read 的错误,代码在 malloc3.c 的第 9 行

3.4 内存泄漏

malloc4.c

  1. #include <stdio.h>
  2. int main(int argc, char **argv) {
  3. char *p = malloc(1);
  4. *p = 'a';
  5. char c = *p;
  6. printf("[%c]\n",c); // 申请后没有释放p 内存泄漏
  7. return 0;
  8. }

编译:

  1. gcc -Wall malloc4.c -g -o malloc4

使用 Valgrind 检查程序 BUG:

  1. # --leak-check=full 所有泄露检查
  2. valgrind --tool=memcheck --leak-check=full ./malloc4

运行结果:

  1. [root@hackett valgrind]# valgrind --tool=memcheck --leak-check=full ./malloc4
  2. ==550195== Memcheck, a memory error detector
  3. ==550195== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
  4. ==550195== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info
  5. ==550195== Command: ./malloc4
  6. ==550195==
  7. [a]
  8. ==550195==
  9. ==550195== HEAP SUMMARY:
  10. ==550195== in use at exit: 1 bytes in 1 blocks
  11. ==550195== total heap usage: 2 allocs, 1 frees, 1,025 bytes allocated
  12. ==550195==
  13. ==550195== 1 bytes in 1 blocks are definitely lost in loss record 1 of 1
  14. ==550195== at 0x4C360A5: malloc (vg_replace_malloc.c:380)
  15. ==550195== by 0x4005EE: main (malloc4.c:5)
  16. ==550195==
  17. ==550195== LEAK SUMMARY:
  18. ==550195== definitely lost: 1 bytes in 1 blocks
  19. ==550195== indirectly lost: 0 bytes in 0 blocks
  20. ==550195== possibly lost: 0 bytes in 0 blocks
  21. ==550195== still reachable: 0 bytes in 0 blocks
  22. ==550195== suppressed: 0 bytes in 0 blocks
  23. ==550195==
  24. ==550195== For lists of detected and suppressed errors, rerun with: -s
  25. ==550195== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

分析:

1、每一行开头的数字代表是进程 ID,这里的进程 ID 为 550195

2、提示
==550195== 1 bytes in 1 blocks are definitely lost in loss record 1 of 1
==550195== at 0x4C360A5: malloc (vg_replace_malloc.c:380)
==550195== by 0x4005EE: main (malloc4.c:5)
这三行显示的是错误出现的位置,是 stack trace 内存泄漏,代码在 malloc1.c 的第 5 行,申请了内存没有进行对应的 free

3.5 内存多次释放

malloc5.c

  1. #include <stdio.h>
  2. int main(int argc, char **argv) {
  3. char *p = malloc(1);
  4. *p = 'a';
  5. char c = *p; // 地址加1
  6. printf("[%c]\n",c);
  7. free(p);
  8. free(p);// 内存多次释放
  9. free(p);// 内存多次释放
  10. return 0;
  11. }

编译:

  1. gcc -Wall malloc5.c -g -o malloc5

使用 Valgrind 检查程序 BUG:

  1. # --leak-check=full 所有泄露检查
  2. valgrind --tool=memcheck --leak-check=full ./malloc5

运行结果:

  1. [root@hackett valgrind]# valgrind --tool=memcheck --leak-check=full ./malloc5
  2. ==550227== Memcheck, a memory error detector
  3. ==550227== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
  4. ==550227== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info
  5. ==550227== Command: ./malloc5
  6. ==550227==
  7. [a]
  8. ==550227== Invalid free() / delete / delete[] / realloc()
  9. ==550227== at 0x4C38A03: free (vg_replace_malloc.c:755)
  10. ==550227== by 0x400680: main (malloc5.c:14)
  11. ==550227== Address 0x520b040 is 0 bytes inside a block of size 1 free'd
  12. ==550227== at 0x4C38A03: free (vg_replace_malloc.c:755)
  13. ==550227== by 0x400674: main (malloc5.c:13)
  14. ==550227== Block was alloc'd at
  15. ==550227== at 0x4C360A5: malloc (vg_replace_malloc.c:380)
  16. ==550227== by 0x40063E: main (malloc5.c:5)
  17. ==550227==
  18. ==550227== Invalid free() / delete / delete[] / realloc()
  19. ==550227== at 0x4C38A03: free (vg_replace_malloc.c:755)
  20. ==550227== by 0x40068C: main (malloc5.c:15)
  21. ==550227== Address 0x520b040 is 0 bytes inside a block of size 1 free'd
  22. ==550227== at 0x4C38A03: free (vg_replace_malloc.c:755)
  23. ==550227== by 0x400674: main (malloc5.c:13)
  24. ==550227== Block was alloc'd at
  25. ==550227== at 0x4C360A5: malloc (vg_replace_malloc.c:380)
  26. ==550227== by 0x40063E: main (malloc5.c:5)
  27. ==550227==
  28. ==550227==
  29. ==550227== HEAP SUMMARY:
  30. ==550227== in use at exit: 0 bytes in 0 blocks
  31. ==550227== total heap usage: 2 allocs, 4 frees, 1,025 bytes allocated
  32. ==550227==
  33. ==550227== All heap blocks were freed -- no leaks are possible
  34. ==550227==
  35. ==550227== For lists of detected and suppressed errors, rerun with: -s
  36. ==550227== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

分析:

1、每一行开头的数字代表是进程 ID,这里的进程 ID 为 550227

2、
==550227== Invalid free() / delete / delete[] / realloc()
==550227== at 0x4C38A03: free (vg_replace_malloc.c:755)
==550227== by 0x400680: main (malloc5.c:14)
这三行显示的是第一个错误出现的位置,就是多次释放内存问题, 所以就会显示 Invalid free() / delete / delete[] / realloc() 的错误,代码在 malloc5.c 的第 14 行

3、
==550227== Invalid free() / delete / delete[] / realloc()
==550227== at 0x4C38A03: free (vg_replace_malloc.c:755)
==550227== by 0x40068C: main (malloc5.c:15)
这三行显示的是第二个错误出现的位置,就是多次释放内存问题, 所以就会显示 Invalid free() / delete / delete[] / realloc() 的错误,代码在 malloc5.c 的第 15 行

3.6 动态内存管理

常见的内存分配方式分三种:静态存储,栈上分配,堆上分配。全局变量属于静态存储,它们是在编译时就被分配了存储空间,函数内的局部变量属于栈上分配,而最灵活的内存使用方式当属堆上分配,也叫做内存动态分配了。常用的内存动态分配函数包括:malloc, alloc, realloc, new 等,动态释放函数包括 free, delete。

一旦成功申请了动态内存,我们就需要自己对其进行内存管理,而这又是最容易犯错误的。

malloc6.c

  1. #include <stdio.h>
  2. // 内存动态管理
  3. int main(int argc, char **argv) {
  4. int i;
  5. char *p = (char *)malloc(10);
  6. char *pt = p;
  7. for(i = 0; i < 10; i++) {
  8. p[i] = 'A'+i;
  9. }
  10. free(p);
  11. pt[1] = 'x';
  12. free(pt);
  13. return 0;
  14. }

编译:

  1. gcc -Wall malloc6.c -g -o malloc6

使用 Valgrind 检查程序 BUG:

  1. # --leak-check=full 所有泄露检查
  2. valgrind --tool=memcheck --leak-check=full ./malloc6

运行结果:

  1. [root@hackett valgrind]# valgrind --tool=memcheck --leak-check=full ./malloc6
  2. ==550239== Memcheck, a memory error detector
  3. ==550239== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
  4. ==550239== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info
  5. ==550239== Command: ./malloc6
  6. ==550239==
  7. ==550239== Invalid write of size 1
  8. ==550239== at 0x400639: main (malloc6.c:16)
  9. ==550239== Address 0x520b041 is 1 bytes inside a block of size 10 free'd
  10. ==550239== at 0x4C38A03: free (vg_replace_malloc.c:755)
  11. ==550239== by 0x400630: main (malloc6.c:14)
  12. ==550239== Block was alloc'd at
  13. ==550239== at 0x4C360A5: malloc (vg_replace_malloc.c:380)
  14. ==550239== by 0x4005EE: main (malloc6.c:7)
  15. ==550239==
  16. ==550239== Invalid free() / delete / delete[] / realloc()
  17. ==550239== at 0x4C38A03: free (vg_replace_malloc.c:755)
  18. ==550239== by 0x400647: main (malloc6.c:18)
  19. ==550239== Address 0x520b040 is 0 bytes inside a block of size 10 free'd
  20. ==550239== at 0x4C38A03: free (vg_replace_malloc.c:755)
  21. ==550239== by 0x400630: main (malloc6.c:14)
  22. ==550239== Block was alloc'd at
  23. ==550239== at 0x4C360A5: malloc (vg_replace_malloc.c:380)
  24. ==550239== by 0x4005EE: main (malloc6.c:7)
  25. ==550239==
  26. ==550239==
  27. ==550239== HEAP SUMMARY:
  28. ==550239== in use at exit: 0 bytes in 0 blocks
  29. ==550239== total heap usage: 1 allocs, 2 frees, 10 bytes allocated
  30. ==550239==
  31. ==550239== All heap blocks were freed -- no leaks are possible
  32. ==550239==
  33. ==550239== For lists of detected and suppressed errors, rerun with: -s
  34. ==550239== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

分析:

1、每一行开头的数字代表是进程 ID,这里的进程 ID 为 550239

2、
==550239== Invalid write of size 1
==550239== at 0x400639: main (malloc6.c:16)
这一行显示这里有一个错误,就是发生非法写操作, 所以就会显示 Invalid write 的错误,代码在 malloc6.c 的第 16 行

3、
==550239== Invalid free() / delete / delete[] / realloc()
==550239== at 0x4C38A03: free (vg_replace_malloc.c:755)
==550239== by 0x400647: main (malloc6.c:18)
==550239== Address 0x520b040 is 0 bytes inside a block of size 10 free’d
==550239== at 0x4C38A03: free (vg_replace_malloc.c:755)
==550239== by 0x400630: main (malloc6.c:14)
这五行显示的是第二个错误出现的位置,指针 p 和 pt 指向的是同一块内存,却被先后释放两次。系统会在堆上维护一个动态内存链表,如果被释放,就意味着该块内存可以继续被分配给其他部分,如果内存被释放后再访问,就可能覆盖其他部分的信息,这是一种严重的错误,上述程序第 16 行中就在释放后仍然写这块内存。

总结

  1. 申请内存在使用完成后就要释放。如果没有释放,或少释放了就是内存泄露;多释放也会产生问题。
  2. 注意数组的大小,别越界读写访问非法内存
  3. malloc 和 new 要对应 free 和 delete 使用。

原文:https://juejin.cn/post/7050740899529097253