查看原文
其他

【小白课程】openKylin上内存分析小帮手(Valgrind--Memcheck)使用教程

小K openKylin
2024-08-23
开源聚力,共创未来点击蓝字,关注openKylin~

01Valgrind介绍

Valgrind是一个功能强大的开源工具集,用于帮助开发人员进行内存调试、性能分析和程序修复。它提供了一系列工具,可以在各种平台上分析和调试C、C++和其他语言的应用程序。

Valgrind的主要功能之一是内存调试。它可以检测应用程序中的内存错误,如内存泄漏、非法读写和使用已释放内存等。同时,Valgrind还提供了准确的内存使用情况报告,帮助开发人员识别和解决内存管理问题。

除了内存调试,Valgrind还提供了性能分析功能。它可以跟踪应用程序的执行情况,提供详细的函数调用图、时间分布图、内存分配图等,帮助开发人员找到性能瓶颈和优化机会。Valgrind还能够测量应用程序的缓存命中率、分支预测准确性等硬件性能指标,帮助优化程序的性能。


1.Memecheck

Memcheck是Valgrind工具集中的一个主要组件,也是最常用的工具之一。它用于分析和调试应用程序的内存使用情况,并检测内存错误,如内存泄漏、非法读写和使用已释放内存等。

Memcheck通过运行时在应用程序的内存分配和访问上进行跟踪和分析来捕获潜在的内存错误。它使用了一种称为"影子内存"的技术,将应用程序运行在一个虚拟的环境中,将每个字节的内存状态进行跟踪记录。这使得Memcheck能够检测到各种内存错误,即使是微小的错误也能够被发现。

下面是Memcheck主要能够检测的内存错误类型:

  • 未初始化的读取:当应用程序尝试读取未初始化的内存时,Memcheck会发出警告。这种错误可能导致程序的不确定行为和崩溃。

  • 未释放的内存:当应用程序分配了内存但没有正确释放时,Memcheck会检测到内存泄漏。它会跟踪每次内存分配和释放,并在程序结束时报告未释放的内存块。

  • 重复释放的内存:如果应用程序多次释放同一个内存块,或者释放已经释放的内存,Memcheck会发出警告。

  • 内存越界访问:当应用程序访问超出分配内存范围的区域时,Memcheck会检测到内存越界错误。它能够捕获对栈、堆和全局变量的无效访问。

  • 内存错误(例如:使用已释放内存):如果应用程序使用了已经释放的内存块,或者对无效指针进行访问,Memcheck会发出警告。这些错误可能导致不可预测的行为,如程序崩溃或数据损坏。

除了检测内存错误,Memcheck还提供了详细的报告和诊断信息,以帮助开发人员定位和修复问题。报告中包含了错误发生的位置、调用栈信息以及有关内存分配和使用的详细信息。这些信息有助于开发人员追踪和理解内存错误的原因,从而进行修复。02如何在openKylin上使用Valgrind

1.安装

使用下述代码安装Valgrind:

sudo apt install valgrind

2.检查

需要进行分析的代码,未编译时,需要进行编译。

gcc -g myfile.c -o your_program

3.执行

  • 简易分析数据获取

valgrind --tool=callgrind ./your_program

特点:Valgrind 将自动启动 Callgrind 工具,并执行你的程序。程序的执行过程将被 Valgrind 记录下来以进行后续分析。

  •  指定分析数据获取(常用)

valgrind --tool=memcheck [options] executable [args]

举例:

valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all --log-file=./valgrind-peony.txt peony

特点:可以定制分析指定内容,并配置各项输出结果等。

常用参数说明:

-leak-check=<参数>:控制内存泄漏检查的级别。

  • no:不进行内存泄漏检查。

  • summary:输出内存泄漏的摘要信息。

  • full:输出详细的内存泄漏信息,包括泄漏的内存块列表。

  • yes(默认值):等同于–leak-check=full。


-show-leak-kinds=<参数>:显示所有类型的内存泄漏。

  • definite:只显示确定性的内存泄漏。

  • indirect:只显示间接内存泄漏。其他泄漏引起。

  • possible:显示可能的内存泄漏。包括可能的直接和间接泄漏。

  • all(默认值):显示所有类型的内存泄漏。


-log-file=<文件名>:将Valgrind的输出结果保存到指定的日志文件中,而不是直接打印到终端上。

  • <文件名>:指定要保存Valgrind输出结果的文件名,可以是相对路径或绝对路径。

注意:确保指定的文件名是有效和可写的,以免出现保存输出结果失败的情况。


-show-reachable=<yes|no>:显示可访问但未释放的内存块,默认值为no,当设置为yes时,会显示可访问但未释放的内存块的详细信息,主要用于查找潜在的内存泄漏。


-track-origins=<yes|no>:追踪未初始化的内存块的来源默认值为no,当设置为yes时,会将未初始化的内存块的内容标记为来源未知(origin unknown),主要用于检测未初始化值的错误。


4.分析

SUMMARY

SUMMARY部分是Valgrind输出的总结信息,提供了检测到的内存错误的统计和摘要。它包括了内存泄漏的数量、错误的数量以及性能相关的信息。泄漏类型如下:


01“Definitely lost”(绝对泄漏)

表示程序分配的内存块没有被释放,并且没有任何指针指向它们,导致无法访问和释放这些内存,可能是由于分配后忘记释放、指针丢失或错误的内存管理引起的。


02“Possibly lost”(可能泄漏)

表示程序分配的内存块没有被释放,但仍存在相关指针指向它们,可能由于指针丢失或错误的内存管理引起,但存在可能指针仍可访问相关内存的情况。


03“Indirectly lost”(间接泄漏)

表示由于前面的内存泄漏导致的其他内存泄漏,前面的错误泄漏了内存块,而这些被泄漏的内存块又是其他内存块的分配者。


04“Reachable”(可访问的)

表示程序结束时仍然有一些未释放的内存块,但它们仍然可(有指针指向)不被视为内存泄漏,可能是由于程序设计需要保留这些内存块,比如全局变量等。


05“suppressed”(受抑制的)

当Valgrind检测到某个特定错误或警告时,可能会在输出结果中显示“suppressed”来指示已抑制相关信息,抑制的信息通常与先前报告的错误或警告相关联,可能是相同的错误或警告,但由于某种原因被抑制,不会再次输出抑制,通常发生在相关的错误或警告已经被准确地报告过,并且由于大量的重复发生,Valgrind选择抑制该信息。

ERROR/MEMORY LEAK DETAILS

列出每个错误和内存泄漏的相关信息。这些信息包括错误类型、错误位置、错误堆栈跟踪、泄漏的内存块的地址等。

ERROR TYPES

  • Invalid read/write:非法的读取或写入操作

  • Uninitialized value:使用未初始化的值

  • Invalid free/delete:释放了无效的内存

  • Mismatched free/delete:释放内存时不匹配

  • Memory leak:内存泄漏问题

  • Use of unaddressable memory:使用了不可寻址的内存

  • Incorrect order of deallocation:释放内存的顺序错误

  • Overlapping source and destination blocks:源和目标内存块重叠

MEMORY LEAK DETAILS

检测到内存泄漏,提供有关泄漏的详细信息。包括泄漏的内存块的大小、泄漏的位置、内存块的地址等。

STACK TRACE

在某些错误中,Valgrind会提供错误发生时的堆栈跟踪信息。


5.处理

针对valgrind给出的相关信息可以从以下步骤进行处理:

  • 定位泄漏源

使用Valgrind提供的堆栈跟踪信息,找到导致泄漏的代码路径和分配位置。

  • 确认正确释放

检查相应的代码,在合适的位置释放这些未释放的内存块。

  • 验证修复效果

重新运行程序并使用Valgrind验证是否解决了绝对泄漏问题。

  • 注意细节

确保在释放内存之前,所有相关指针都已经被正确处理和释放。

  • 调整代码逻辑

如果发现了不必要的可访问内存块,进行必要的代码调整和释放内存的操作。


6.分析说明举例

此处以一个存在绝对漏洞的分析结果进行分析说明:

首先,运行按照指令打开文件管理器,运行及常规操作完成后,关闭文件管理器,可以得到一个valgrind-peony.txt的分析结果,打开分析文本。

找到一条Definitely lost的记录,并进行分析,将记录结果列举如下:

==295098== 8,408 (1,392 direct, 7,016 indirect) bytes in 1 blocks are definitely lost in loss record 2,355 of 2,361==295098== at 0x483BE63: operator new(unsigned long) (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)==295098== by 0x105A25AD: Qt5UKUIStyle::Qt5UKUIStyle(bool, bool, QString) (in /usr/lib/x86_64-linux-gnu/qt5/plugins/styles/libqt5-style-ukui.so)==295098== by 0x105A07F4: Qt5UKUIStylePlugin::create(QString const&) (in /usr/lib/x86_64-linux-gnu/qt5/plugins/styles/libqt5-style-ukui.so)==295098== by 0x5553BF9: QStyleFactory::create(QString const&) (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.12.12)==295098== by 0x557B2DF: QProxyStyle::QProxyStyle(QString const&) (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.12.12)==295098== by 0x1041E58C: UKUI::ProxyStyle::ProxyStyle(QString const&) (in /usr/lib/x86_64-linux-gnu/qt5/plugins/styles/libukui-proxy-style.so)==295098== by 0x1041FD3A: UKUI::ProxyStylePlugin::create(QString const&) (in /usr/lib/x86_64-linux-gnu/qt5/plugins/styles/libukui-proxy-style.so)==295098== by 0x5553BF9: QStyleFactory::create(QString const&) (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.12.12)==295098== by 0x54E6041: QApplication::style() (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.12.12)==295098== by 0x54E639C: QApplicationPrivate::initialize() (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.12.12)==295098== by 0x54E63F7: QApplicationPrivate::init() (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.12.12)==295098== by 0x13EC10: SingleApplication::SingleApplication(int&, char**, char const*, bool, QFlagsSingleApplication::Mode, int) (singleapplication.cpp:40)

上述反馈结果可以得出以下分析:

根据Valgrind输出,可以看到loss record 2,355 of 2,361中有8,408字节的内存在1个堆块中被明确地丢失。具体的丢失堆块的分配和释放情况如下所示:

  • 在函数Qt5UKUIStyle::Qt5UKUIStyle(bool, bool, QString)中,通过operator new(unsigned long)进行了内存分配,但没有相应的释放操作。

  • 这个失去的堆块在libqt5-style-ukui.so库中的Qt5UKUIStylePlugin::create(QString const&)函数被创建。

  • 接着由libqt5-style-ukui.so库通过QStyleFactory::create(QString const&)函数创建了QProxyStyle对象。

  • QProxyStyle对象进一步由libukui-proxy-style.so库UKUI::ProxyStyle::ProxyStyle (QString const&)函数创建。

  • 在此之后,又通过libukui-proxy-style.so库中的UKUI::ProxyStylePlugin::create(QString const&)函数通过QStyleFactory::create(QString const&)函数的调用创建了QProxyStyle对象。

在调用栈的最后,这段丢失的内存是在单独应用程序的构造函数SingleApplication::SingleApplication(int&,char**,char const*, bool, QFlags<SingleApplication::Mode>, int)中出现的。

Valgrind的输出表明,在这些函数的执行过程中,没有释放掉先前分配的内存。这样的内存泄漏可能会导致程序运行时内存使用过多,最终可能会导致性能下降或崩溃。

最后进一步修改代码,将未释放的内存进行释放。然后再次进行分析验证。
03使用建议
  • 初次使用时,不进行任何操作,待程序启动完成后直接关闭程序。用于分析初始化存在的内存泄漏;

  • 静态分析,启动程序后不进行任何操作,待2-3小时或足够长的时候后关闭程序。用于分析静态程序存在的内存泄漏;

  • 指定分析,启动程序后,执行相关操作后关闭程序。用于分析功能内存泄漏。


openKylin(开放麒麟)社区旨在以“共创”为核心,在开源、自愿、平等、协作的基础上,通过开源、开放的方式与企业构建合作伙伴生态体系,共同打造开源操作系统顶级社区,推动Linux开源技术及其软硬件生态繁荣发展。

社区理事成员单位包括麒麟软件、普华基础软件、中科方德、麒麟信安、凝思软件、一铭软件、中兴新支点、元心科技、中国电科32所、技德系统、北京麟卓、先进操作系统创新中心、飞腾、兆芯、龙芯中科、景美、京东科技、平头哥、申泰信息、海光等21家产业同仁和行业机构。


来源:李卓安
审核:openKylin

往期推荐:







国赛来袭!中国研究生操作系统开源创新大赛报名开启,十万奖金等你来~

给开发者更好的选择!支持多种AI框架的openKylin 2.0 alpha版本来啦!

openKylin城市用户组招募啦,超多权益等你来!

openKylin 1.0.2版本正式发布!支持Intel最新CPU平台
继续滑动看下一个
openKylin
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存