Linux 下使用 Valgrind 举行内存调试

打印 上一主题 下一主题

主题 845|帖子 845|积分 2535


一、概述

Valgrind 是一个开源的内存调试和性能分析工具,用于帮助开发者找出程序中的内存错误,如内存走漏、使用未初始化的内存、非法内存访问等问题。它在 Linux 平台上广泛使用,而且支持下多种处置惩罚器架构。
二、Valgrind 的使用

1、基本格式

  1. valgrind --tool=memcheck -–gen-suppressions=all -–show-leak-kinds=all --log-file=<filename> --leak-check=yes ./your_app arg1 arg2...
复制代码


  • valgrind:这是一个内存调试工具集,其中的 memcheck 是其中的一个工具,它用于检查内存相干的错误。
  • -–gen-suppressions=all:误报是内存走漏排查中的常见现象。使用该参数,我们可以标志那些误报,生成抑制规则,让 Valgrind 在后续的检查中忽略这些特定的情况。
  • –show-leak-kinds=all:显示全部的内存走漏信息。
  • –log-file=<filename>:这是一个选项,用于指定 Valgrind 输出的日志文件的文件名。你可以将 <filename> 替换为你想要的文件名或路径。
  • –leak-check=yes:这个选项告诉 Valgrind 在程序运行结束后检查内存走漏。它将会列出程序中存在的任何未开释的内存。(尚有一种写法:--leak-check=full,意思是一样的)
  • ./your_app:这里应该是你要检查的可实行文件的路径。将 your_app 替换为你的程序的实际名称。
  • arg1 arg2…:这些是你的程序大概需要的下令行参数。用空格分隔,替换为你程序实际需要的参数。
2、Valgrind 工具集

Valgrind 工具集包含多个工具,每个工具都针对不同的调试、分析和性能优化使命。以下是 Valgrind 工具会合一些常用的工具:

  • Memcheck:这是 Valgrind 最常用的工具之一,用于检测程序中的内存错误,例如内存走漏、未初始化的内存读取、非法内存访问等。
  • Cachegrind:用于模拟缓存和分支预测器的活动,帮助优化程序的缓存使用和实行路径。
  • Callgrind:用于程序性能分析,跟踪函数调用关系和实行次数,帮助找出程序中的性能瓶颈。
  • Helgrind:专门用于检测多线程程序中的并发错误,如数据竞争、死锁等问题。
  • Massif:用于分析程序的堆内存使用情况,包罗堆分配、开释和堆内存的快照。
  • DHAT (Dynamic Heap Analysis Tool):用于深入分析程序的堆内存分配情况,帮助找出内存分配和使用方面的问题。
  • BBV(Basic Block Vectors):可用于收集程序中基本块的统计信息,帮助理解程序的实行路径和性能特征。
每个工具都有其特定的用途和上风,可以根据需要选择符合的工具来举行程序调试、性能优化或内存分析。
接下来主要是介绍 Memcheck 工具的使用。
3、Memcheck

Valgrind 在内存检测方面主要有四个使用场景:

  • 使用未初始化的内存
  • 内存走漏
  • 在内存被开释后举行读/写
  • 内存块的尾部举行读/写
3.1 使用未初始化的内存

首先看一个例子:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. int main(){
  4.     char *p;
  5.     char c = *p;
  6.     printf("\n [%c]\n", c);
  7.     return 0;
  8. }
复制代码
可以看出,这里访问了一个野指针。接下来编译:
  1. $ gcc test
复制代码
然后使用 Valgrind 工具分析:
  1. $ valgrind --tool=memcheck ./a.out
复制代码
报错信息和出现错误的位置都打印了出来:

3.2 内存走漏

照旧先看代码:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. int main(){
  4.     char *p = malloc(1);
  5.     *p = 'a';
  6.     char c = *p;
  7.     printf("\n [%c]\n", c);
  8.     return 0;
  9. }
复制代码
可以看到,这里为 p 指针申请了一个地址,不过末了没有 free 掉这个地址就 return 0 了,也就是会照成内存走漏。先编译:
  1. $ gcc test
复制代码
然后使用 Valgrind 工具分析:
  1. $ valgrind --tool=memcheck --leak-check=full ./a.out
复制代码
效果如下,可以看到提示信息显示 alloc 了 2 次,但却只 free 1 次,以是发生了内存走漏,再下面是内存走漏的详细信息。

3.3 在内存被开释后举行读/写

示例代码如下:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. int main(){
  4.     char *p = malloc(1);
  5.     *p = 'a';
  6.     char c = *p;
  7.     printf("\n [%c]\n", c);
  8.     free(p);
  9.     c = *p;
  10.     return 0;
  11. }
复制代码
编译后,用 vallgrind 查察:
  1. $ gcc test
  2. .c$ valgrind --tool=memcheck ./a.out
复制代码
上面的代码中,我们有一个开释了内存的指针 p,然后我们又实验使用指针获取值。从下面的输出内容可以看到,Valgrind 检测到了无效的读取操作然后输出了告诫"Invalid read of size 1’.

3.4 内存块的尾部举行读/写

代码如下:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. int main(){
  4.     char *p = malloc(1);
  5.     *p = 'a';
  6.     char c = *(p+1);
  7.     printf("\n [%c]\n", c);
  8.     free(p);
  9.     return 0;
  10. }
复制代码
  1. $ gcc test
  2. .c$ valgrind --tool=memcheck ./a.out
复制代码
可以看到,这里仍旧是非法的读,因为我们只申请了 1 个字节空间:

4、常见错误

下面是一些日志打印中常见的错误:


  • malloc/free: in use at exit :内存在退出前没有开释
  • invalid write of size:非法写入内存,一般为数组越界
  • invalid read of size:非法读内存:一般为数组越界
  • definitely lost /possibly lost /still reachable in loss record:内存未开释

    • definitely :确认丢失。程序中存在内存泄露,应尽快修复。
    • indirectly:间接丢失。当使用了含有指针成员的类或结构时大概会报这个错误 。
    • possibly:大概丢失。大多数情况下应视为与"definitely lost"一样需要尽快修复。
    • still reachable:可以访问,未丢失但也未开释。如果程序是正常结束的,那么它大概不会造成程序崩溃,但长时间运行有大概耗尽体系资源。
    • suppressed:已被办理。出现了内存泄露但体系自动处置惩罚了。可以无视这类错误。

  • invalid free()/delete/delete[]:同一指针被多次开释
  • source and destination overlay:一般是使用strncpy,memcpy引起
  • syscall param contains uninitialized byte:调用体系函数时传入了未初始化的变量
  • conditional jump or move depends on uninitialized value :条件判断时使用了未初始化的变量
  • access not with mapped region/stack overflow:栈溢出
  • mismatch free()/delete/delete[]/new:delete/malloc/free搭配错误
三、分析内存走漏的使用本领

1、Valgrind 调和 GDB 工作

在 Linux 内存走漏的排查过程中,Valgrind 和 GDB 的联合使用是一种强大的调试策略。Valgrind 可以或许帮助我们发现程序中的内存走漏,而 GDB 则允许我们深入程序的实行,查察变量和内存状态,从而精确地定位问题。
Valgrind 提供了一个 --vgdb-error=0 的选项,允许我们在第一个错误发生时立即启动 GDB。如许,我们可以在程序实行到大概出现内存走漏的地方时,立即举行检查。
下面是操作流程:

  • 启动 Valgrind,带有 GDB调试支持:
  1. $ valgrind --tool=memcheck --vgdb=yes --vgdb-error=0 ./a.out
复制代码

  • 当 Valgrind 报告内存错误时,它会暂停程序实行。
  • 在另一个终端中,我们可以启动 GDB 并连接到 Valgrind:
  1. $ gdb ./a.out
  2. (gdb) target remote | vgdb
复制代码

  • 然后就可以使用 GDB 的调试下令了,我们可以检查导致错误的代码行,查察变量的值和内存的状态。
2、使用 /proc 定位问题

Linux 的 /proc 文件体系包含了体系运行时的各种信息,其中也包罗了历程的内存映射情况。通太过析 /proc/[pid]/maps 文件,我们可以得知历程的内存分配情况,这对于定位内存走漏非常有用。
每个历程的 /proc/[pid]/maps 文件都记录了该历程的内存映射。我们可以通过以下下令查察特定历程的内存映射:
  1. $ cat /proc/[pid]/maps
复制代码
[pid] 需要替换为我们怀疑存在内存走漏的历程ID。通太过析这个文件,我们可以看到历程的内存分配情况,包罗哪些库文件被加载,以及它们的内存地址范围。

从左向右的六列数据的含义如下:


  • 地址范围:表示内存段的起始和结束地址。
  • 权限:表示内存段的访问权限。
  • 偏移量:表示从文件开始到映射区域开始的偏移。
  • 装备:表示关联的装备。
  • 节点:表示文件体系中的节点号。
  • 路径:表示映射到的文件路径,如果是 [heap] 则表示堆内存区域。
如果发生内存走漏,表格中的某些行会显示出异常的模式,特殊是在堆大概大概的匿名映射(通常是堆或栈的扩展)区域。以下是一些大概表明内存走漏的情况:

  • 堆内存增长:如果 [heap] 区域的地址范围随时间不断增长,这大概表明堆内存正在走漏。
  • 频繁的小块分配:大量小块内存分配而且没有对应的开释,大概会在表格中显示为很多小范围的内存映射。
  • 匿名映射:大量的匿名映射(没有关联路径的映射)大概是动态分配内存未被开释的迹象。
好比,下列数据展示了大概的内存走漏的情况:
  1. 02557000-03578000           rw-p 00000000 00:00 0                [heap]
  2. ...
  3. 7ff3c8c00000-7ff3c8e21000        rw-p 00000000 00:00        0       
复制代码
在这个例子中,我们看到:


  • [heap] 区域的大小异常,表明大概有大量的内存分配没有得到开释。
  • 存在连续的 rw-p 权限的匿名映射,这些大概是由于内存分配(如 mallocnew)造成的,如果这些区域的大小不断增长,且没有相应的开释,那么很大概是内存走漏的地方。
3、使用 top、ps 识别异常历程

3.1 使用 top

  1. $ top -o %MEM
复制代码
这个下令会将历程按内存使用率举行排序,帮助我们更快地定位到内存使用异常的历程。
在使用 top 下令观察历程的内存使用情况时,我们需要关注的是内存使用量(RES)和虚拟内存使用量(VIRT)。内存走漏通常表现为随着时间的推移,这两个值会不断增长。


  • 内存使用量(RES):历程实际使用的物理内存大小。如果一个历程存在内存走漏,我们会看到 RES 值不断上升,纵然在没有新的活动产生时也是如此。这是因为走漏的内存没有被操作体系回收,从而导致物理内存的连续占用。
  • 虚拟内存使用量(VIRT):包罗历程使用的全部内存,不仅包罗RES,还包罗历程未使用但已分配的内存。内存走漏会导致VIRT值不断增长,这是因为历程请求了更多的内存,但并未开释。
3.2 使用 ps

我们可以使用 ps 下令的 -o 选项来自定义输出,以便专注于内存相干的信息。例如:
  1. $ ps -eo pid,ppid,cmd,%mem,%cpu --sort=-%mem
复制代码
这个下令将列出全部历程,并按内存使用率降序分列,显示每个历程的 PID、PPID、下令行、内存使用率和 CPU 使用率。
联合历史数据,我们可以分析历程的内存使用趋势。通过定期记录 ps 下令的输出,我们可以创建一个内存使用的时间序列,这有助于我们识别内存走漏的长期趋势。
  1. #! /bin/sh
  2. while true; do
  3.         ps -eo pid,cmd,%mem,%cpu --sort=-%mem | head -n 10 >> memory_usage.log;
  4.         sleep 60;
  5. done
复制代码
这段脚本会每分钟记录内存使用最高的 10 个历程,并将效果追加到 memory_usage.log 文件中。


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

道家人

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表