【Linux性能】Linux 下利用 Valgrind 进行内存调试

news/2025/1/14 20:53:11/文章来源:https://www.cnblogs.com/o-O-oO/p/18671692

一、概述

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

二、Valgrind 的使用

2.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 输出的日志文件的文件名。你可以将 替换为你想要的文件名或路径。

–leak-check=yes:这个选项告诉 Valgrind 在程序运行结束后检查内存泄漏。它将会列出程序中存在的任何未释放的内存。(还有一种写法:--leak-check=full,意思是一样的)

./your_app:这里应该是你要检查的可执行文件的路径。将 your_app 替换为你的程序的实际名称。

arg1 arg2…:这些是你的程序可能需要的命令行参数。用空格分隔,替换为你程序实际需要的参数。

2.2 Valgrind 工具集

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

Memcheck:这是 Valgrind 最常用的工具之一,用于检测程序中的内存错误,例如内存泄漏、未初始化的内存读取、非法内存访问等。

Cachegrind:用于模拟缓存和分支预测器的行为,帮助优化程序的缓存使用和执行路径。

Callgrind:用于程序性能分析,跟踪函数调用关系和执行次数,帮助找出程序中的性能瓶颈。

Helgrind:专门用于检测多线程程序中的并发错误,如数据竞争、死锁等问题。

Massif:用于分析程序的堆内存使用情况,包括堆分配、释放和堆内存的快照。

DHAT (Dynamic Heap Analysis Tool):用于深入分析程序的堆内存分配情况,帮助找出内存分配和使用方面的问题。

BBV(Basic Block Vectors):可用于收集程序中基本块的统计信息,帮助理解程序的执行路径和性能特征。

每个工具都有其特定的用途和优势,可以根据需要选择合适的工具来进行程序调试、性能优化或内存分析。

接下来主要是介绍 Memcheck 工具的使用。

2.3 Memcheck

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

  • 使用未初始化的内存
    
  • 内存泄漏
    
  • 在内存被释放后进行读/写
    
  • 内存块的尾部进行读/写
    

1、使用未初始化的内存

首先看一个例子:

#include <stdio.h>
#include <stdlib.h>int main(){char *p;char c = *p;printf("\n [%c]\n", c);return 0;
}

可以看出,这里访问了一个野指针。接下来编译:

$ gcc test

然后使用 Valgrind 工具分析:

$ valgrind --tool=memcheck ./a.out

报错信息和出现错误的位置都打印了出来:

2、内存泄漏

还是先看代码:

#include <stdio.h>
#include <stdlib.h>int main(){char *p = malloc(1);*p = 'a';char c = *p;printf("\n [%c]\n", c);return 0;
}

可以看到,这里为 p 指针申请了一个地址,不过最后没有 free 掉这个地址就 return 0 了,也就是会照成内存泄漏。先编译:

$ gcc test

然后使用 Valgrind 工具分析:

$ valgrind --tool=memcheck --leak-check=full ./a.out

结果如下,可以看到提示信息显示 alloc 了 2 次,但却只 free 1 次,所以发生了内存泄漏,再下面是内存泄漏的详细信息。

3、在内存被释放后进行读/写

示例代码如下:

#include <stdio.h>
#include <stdlib.h>int main(){char *p = malloc(1);*p = 'a';char c = *p;printf("\n [%c]\n", c);free(p);c = *p;return 0;
}

编译后,用 vallgrind 查看:

$ gcc test.c
$ valgrind --tool=memcheck ./a.out

上面的代码中,我们有一个释放了内存的指针 p,然后我们又尝试利用指针获取值。从下面的输出内容可以看到,Valgrind检测到了无效的读取操作然后输出了警告"Invalid read of size 1’.

4、内存块的尾部进行读/写

代码如下:

#include <stdio.h>
#include <stdlib.h>int main(){char *p = malloc(1);*p = 'a';char c = *(p+1);printf("\n [%c]\n", c);free(p);return 0;
}
$ gcc test.c
$ valgrind --tool=memcheck ./a.out

可以看到,这里依旧是非法的读,因为我们只申请了 1 个字节空间:

2.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搭配错误

三、分析内存泄漏的使用技巧

3.1 Valgrind 协调 GDB 工作

在 Linux 内存泄漏的排查过程中,Valgrind 和 GDB 的结合使用是一种强大的调试策略。Valgrind 能够帮助我们发现程序中的内存泄漏,而 GDB 则允许我们深入程序的执行,查看变量和内存状态,从而精确地定位问题。

Valgrind 提供了一个 --vgdb-error=0 的选项,允许我们在第一个错误发生时立即启动 GDB。这样,我们可以在程序执行到可能出现内存泄漏的地方时,立即进行检查。

下面是操作流程:

1、启动 Valgrind,带有 GDB调试支持:

$ valgrind --tool=memcheck --vgdb=yes --vgdb-error=0 ./a.out

2、当 Valgrind 报告内存错误时,它会暂停程序执行。

3、在另一个终端中,我们可以启动 GDB 并连接到 Valgrind:

$ gdb ./a.out
(gdb) target remote | vgdb

4、然后就可以使用 GDB 的调试命令了,我们可以检查导致错误的代码行,查看变量的值和内存的状态。

3.2 利用 /proc 定位问题

Linux 的 /proc 文件系统包含了系统运行时的各种信息,其中也包括了进程的内存映射情况。
通过分析 /proc/[pid]/maps 文件,我们可以得知进程的内存分配情况,这对于定位内存泄漏非常有用。

每个进程的 /proc/[pid]/maps 文件都记录了该进程的内存映射。我们可以通过以下命令查看特定进程的内存映射:

$ cat /proc/[pid]/maps

[pid]需要替换为我们怀疑存在内存泄漏的进程ID。通过分析这个文件,我们可以看到进程的内存分配情况,包括哪些库文件被加载,以及它们的内存地址范围。

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

地址范围:表示内存段的起始和结束地址。

权限:表示内存段的访问权限。

偏移量:表示从文件开始到映射区域开始的偏移。

设备:表示关联的设备。

节点:表示文件系统中的节点号。

路径:表示映射到的文件路径,如果是 [heap] 则表示堆内存区域。

如果发生内存泄漏,表格中的某些行会显示出异常的模式,特别是在堆或者可能的匿名映射(通常是堆或栈的扩展)区域。
以下是一些可能表明内存泄漏的情况:

堆内存增长:如果 [heap] 区域的地址范围随时间不断增长,这可能表明堆内存正在泄漏。

频繁的小块分配:大量小块内存分配并且没有对应的释放,可能会在表格中显示为许多小范围的内存映射。

匿名映射:大量的匿名映射(没有关联路径的映射)可能是动态分配内存未被释放的迹象。

比如,下列数据展示了可能的内存泄漏的情况:

02557000-03578000           rw-p 00000000 00:00 0          [heap]
...
7ff3c8c00000-7ff3c8e21000  rw-p 00000000 00:00  0

在这个例子中,我们看到:

[heap] 区域的大小异常,表明可能有大量的内存分配没有得到释放。

存在连续的 rw-p 权限的匿名映射,这些可能是由于内存分配(如 malloc 或 new)造成的,如果这些区域的大小不断增长,且没有相应的释放,那么很可能是内存泄漏的地方。

3.3 使用 top、ps 识别异常进程

1、 使用 top

$ top -o %MEM

这个命令会将进程按内存使用率进行排序,帮助我们更快地定位到内存使用异常的进程。

在使用 top 命令观察进程的内存使用情况时,我们需要关注的是内存使用量(RES)和虚拟内存使用量(VIRT)。
内存泄漏通常表现为随着时间的推移,这两个值会不断增加。

内存使用量(RES):进程实际使用的物理内存大小。如果一个进程存在内存泄漏,我们会看到 RES 值不断上升,即使在没有新的活动产生时也是如此。这是因为泄漏的内存没有被操作系统回收,从而导致物理内存的持续占用。

虚拟内存使用量(VIRT):包括进程使用的所有内存,不仅包括RES,还包括进程未使用但已分配的内存。内存泄漏会导致VIRT值不断增加,这是因为进程请求了更多的内存,但并未释放。

2、 使用 ps

我们可以使用 ps 命令的 -o 选项来自定义输出,以便专注于内存相关的信息。例如:

$ ps -eo pid,ppid,cmd,%mem,%cpu --sort=-%mem

这个命令将列出所有进程,并按内存使用率降序排列,显示每个进程的 PID、PPID、命令行、内存使用率和 CPU 使用率。

结合历史数据,我们可以分析进程的内存使用趋势。通过定期记录 ps 命令的输出,我们可以创建一个内存使用的时间序列,这有助于我们识别内存泄漏的长期趋势。

#! /bin/sh
while true; do ps -eo pid,cmd,%mem,%cpu --sort=-%mem | head -n 10 >> memory_usage.log; sleep 60; 
done

这段脚本会每分钟记录内存使用最高的 10 个进程,并将结果追加到 memory_usage.log 文件中。

原创 Linux开发架构之路

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/869247.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C#/.NET/.NET Core技术前沿周刊 | 第 21 期(2025年1.6-1.12)

前言 C#/.NET/.NET Core技术前沿周刊,你的每周技术指南针!记录、追踪C#/.NET/.NET Core领域、生态的每周最新、最实用、最有价值的技术文章、社区动态、优质项目和学习资源等。让你时刻站在技术前沿,助力技术成长与视野拓宽。欢迎投稿、推荐或自荐优质文章、项目、学习资源等…

26. 文件操作

一、数据读写在 PySide6 中对文件和文件夹的进行操作时,主要使用 QFile 类、QFileInfo 类和 QDir 类。我们可以在终端中使用 pip 安装 pyside6 模块。 pip install pyside6在 PySide6 窗口程序对文件或者文件夹进行操作时,不强制要求必须使用 PySide6 中提供的 QFile、QDir 等…

MySQL基础函数使用

DQL中的函数 # 官方函数链接 https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_date-format4.1 单行函数函数都是数据库提前给我们准备好的,所以我们可以直接调用,使用函数可以让指定的列计算出我们需要的数据 单行函数 : 指的是操作一行数据…

【模拟电子技术】06-双极晶体管的结构与放大原理

【模拟电子技术】06-双极晶体管的结构与放大原理图(b)中我们可以看到三个区的分类,发射区之所以为发射区是因为掺杂浓度高,才能发射电子出去。而集电区掺杂浓度低,就好比我们想让一个房间当作仓库,那么它的空间肯定要大,里面原本不能是放了很多东西。下图NPN晶体管中发射…

地平线Vision Mamba:超越ViT,最具潜力的下一代通用视觉主干网络

• Vision Mamba 论文链接: https://arxiv.org/abs/2401.09417 • 项目主页: https://github.com/hustvl/Vim 简介 本文的工作Vision Mamba[1]发表在ICML 2024。研究的问题是如何设计新型神经网络来实现高效的视觉表示学习。该任务要求神经网络模型能够在处理高分辨率图像时既…

1.11日报

今天确定了外包杯的队友,但还没想好主题。阅读了开学测试的内容 一开始还没什么头绪

蔡磊:刘强东一直在为我兜底,纵使不胜也绝不屈服

时钟滑到了2025年,蔡磊患病的第五年。自从2024年5月那次感冒把他送进了重症监护室,他的身体机能开始断崖式下降。 但他仍在与生命抢时间,每天从早晨9点到晚上11点排满工作,并制定了2025年规划,虽然一切结果未知,但希望总是在一点点增加。 把冰洞凿得更大一些 如果不是蔡磊…

【专题】2025年节日营销趋势洞察报告汇总PDF洞察(附原数据表)

原文链接: https://tecdat.cn/?p=38813 在当今复杂多变且竞争激烈的消费市场环境下,节日营销已成为企业获取市场份额、提升品牌影响力的关键战略时机。我们深知深入洞察节日营销趋势对于企业决策的重要性。 本报告汇总基于对 2024 年多个关键消费节点及消费者行为的深度调研…

FreeFileSync:开源文件夹同步备份神器

在工作中我们常遇到U盘丢失或损坏。或者文件由于备份不及时,导致文件异常。同时备份的资料越多,人工手动复制就越容易出错或遗漏,最终的备份又乱又不可靠。 怎样解决此问题呢? FreeFileSync是一款功能强大的文件同步和备份软件,适用于Windows操作系统。它支持多种语言,界…

Centos7.9离线部署ceph(octopus)

原创文档编写不易,未经许可请勿转载,目前仅发布于博客园,其他平台均为非法转载。文档中有疑问的可以邮件联系我文章。 邮箱:yinwanit@163.com 说明 文档适用于cenots7.9操作系统下部署ceph octopus版本分布式存储使用。 离线部署的本质是先基于在线环境如vmware workstatio…