0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

valgrind基本功能介绍、基础使用方法说明

嵌入式与Linux那些事 来源:嵌入式与Linux那些事 作者:嵌入式与Linux那些 2022-11-14 12:40 次阅读

1、Valgrind概述

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

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

85e2d876-63d2-11ed-8abf-dac502259ad0.png

2、工具下载安装

参考地址:https://www.valgrind.org/downloads/

安装:

tar–xfvalgrind-3.17.0.tar.bz2
cdvalgrind-3.17.0
./configure//运行配置脚本生成makefile文件,可以--help查看配置项,自行按需配置,比如修改编译工具、修改安装路径等
make
makeinstall//安装生成可执行文件,可执行文件的路径有参数--prefix指定,需要在PATH中添加环境变量;若不加参数--prefix指定,仅使用默认配置,则会自动关联

安装完后可以使用:

valgrind --help查看使用方法

3、使用基本选项

3.1 基本工具介绍

Memcheck。这是valgrind应用最广泛的工具,一个重量级的内存检查器,能够发现开发中绝大多数内存错误使用情况,比如:使用未初始化的内存,使用已经释放了的内存,内存访问越界等。这也是本文将重点介绍的部分。

Callgrind。它主要用来检查程序中函数调用过程中出现的问题。

Cachegrind。它主要用来检查程序中缓存使用出现的问题。

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

Massif。它主要用来检查程序中堆栈使用中出现的问题。

Extension。可以利用core提供的功能,自己编写特定的内存调试工具

3.2 常用选项

适用于所有Valgrind工具

–tool=< name >最常用的选项。运行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=< number >输出LOG到描述符文件[2=stderr]
–log-file=< file >将输出的信息写入到filename.PID的文件里,PID是运行程序的进行ID
–log-file-exactly=< file >输出LOG信息到file
–log-file-qualifier=< VAR >取得环境变量的值来做为输出信息的文件名。[none]
–log-socket=ipaddr:port输出LOG到socket,ipaddr:port

LOG信息输出

–xml=yes将信息以xml格式输出,只有memcheck可用
–num-callers=< number >show< numbe r>callersinstacktraces[12]
–error-limit=no|yes如果太多错误,则停止显示新错误?[yes]
–error-exitcode=< number >如果发现错误则返回错误代码[0=disable]
–db-attach=no|yes当出现错误,valgrind会自动启动调试器gdb。[no]
–db-command=< command >启动调试器的命令行选项[gdb-nw%f%p]

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

–leak-check=no|summary|full要求对leak给出详细信息?[summary]
–leak-resolution=low|med|highhowmuchbtmerginginleakcheck[low]
–show-reachable=no|yesshowreachableblocksinleakcheck?[no]

更详细的使用信息详见帮助文件、man手册或官网:http://valgrind.org/docs/manual/manual-core.html

注意

(1)valgrind不会自动的检查程序的每一行代码,只会检查运行到的代码分支,所以单元测试或功能测试用例很重要;

(2)可以把valgrind看成是一个sandbox,通过valgrind运行的程序实际上是运行在valgrind的sandbox中的,所以,不要测试性能,会让你失望的,建议只做功能测试

(3)编译代码时,建议增加-g -o0选项,不要使用-o1、-o2选项

3.3 常用选项示例

–tool=< name > : use the Valgrind tool named < name > [memcheck]–log-file=< file > : log messages to < file >

示例:

valgrind--tool=memcheck--log-file=log.txt--leak-check=yes./test

说明:使用memcheck工具对test程序进行包含内存泄漏的检查,并将日志保存到log.txt

4、Memcheck工具介绍

Memcheck是valgrind应用最广泛的工具,能够发现开发中绝大多数内存错误使用情况。此工具主要可检查以下错误

使用未初始化的内存(Use of uninitialised memory)

使用已经释放了的内存(Reading/writing memory after it has been free’d)

使用超过malloc分配的内存空间(Reading/writing off the end of malloc’d blocks)

对堆栈的非法访问(Reading/writing inappropriate areas on the stack)

申请的空间是否有释放(Memory leaks – where pointers to malloc’d blocks are lost forever)

malloc/free/new/delete申请和释放内存的匹配(Mismatched use of malloc/new/new [] vs free/delete/delete [])

src和dst的重叠(Overlapping src and dst pointers in memcpy() and related functions)

#include
intmain()
{
int*pInt;
std::cout<<"使用未初始化的内存";
 int a=*pInt;    //使用未初始化的内存
}

#include
intmain()
{
int*pArray=(int*)malloc(sizeof(int)*5);
std::cout<<"使用已经释放了的内存";
 free(pArray);
 pArray[0]=0;    //使用已经释放了的内存
}

#include
intmain()
{
int*pArray=(int*)malloc(sizeof(int)*5);
std::cout<<"使用超过malloc分配的内存空间";
 pArray[5]=5;    //使用超过malloc分配的内存空间
 free(pArray);
}

#include
intmain()
{
int*pArray=(int*)malloc(sizeof(int)*5);
std::cout<<"malloc缺少free";
}

#include
intmain()
{
chara[10];
for(charc=0;c< sizeof(a); c++)
 {
  a[c]=c;
 }
 std::cout<<"拷贝的src和dst存在重叠";
 memcpy(&a[4],&a[0],6);
}

注:程序有时会申请很多常驻节点,这些未释放的节点不应视为问题;

一般随着程序的运行,导致节点单向增加的malloc或new操作,视为内存泄漏

4.1 示例1

源码:

#include
intmain()
{
int*pArray=(int*)malloc(sizeof(int)*5);
std::cout<<"使用超过malloc分配的内存空间";
 pArray[5]=5;    //使用超过malloc分配的内存空间
 free(pArray);
}
12345678

编译:

g++test1.cpp-g-otest1_g//-g:让memcheck工具可以取到出错的具体行号

调试:

valgrind--leak-check=yes--log-file=1_g./test1_g

生成日志文件1_g:

85f7dbea-63d2-11ed-8abf-dac502259ad0.png

(1)当前程序(./test1_g)的进程号

(2)valgrind memcheck工具的license说明

(3)加载程序的运行方式

(4)父进程号,当前终端的进程

(5)检测到的错误信息

(6)堆栈摘要、小结,该例子中总共两次alloc、两次free,没有内存泄漏

(7) 检测到的错误数量,这里提示1个

4.2 示例2

#include
intmain()
{
int*pArray=(int*)malloc(sizeof(int)*5);
std::cout<<"使用已经释放了的内存";
 free(pArray);
 pArray[0]=0;    //使用已经释放了的内存
}

编译:

g++test7.cpp-g-otest7_g//-g:让memcheck工具可以取到出错的具体行号

调试:

valgrind--leak-check=yes--log-file=7_g./test7_g

生成日志文件7_g:

86286526-63d2-11ed-8abf-dac502259ad0.png

(1)因为还是使用同一个终端,所以父进程还是8248

(2) 有两个非法的读、写错误

编译:

g++test7.cpp-g-otest2_g_O2-O2

调试:

valgrind--leak-check=yes--log-file=7_g_O2./test7_g_O2

生成日志文件7_g_O2:

867a9666-63d2-11ed-8abf-dac502259ad0.png

可以看到同样的程序,在加上-O2之后,pArray[0]=0;语句被优化掉了,所以没有被检测出来。

为了做到更严格的检测,编译时需要保证编译器没有做优化,即优化等级为-O0,gcc、g++默认就是采用-O0的,但是大部分实际设计都会在Makefile中添加-O1或者-O2参数,所以最好还是检查下。

4.3 Memcheck 报告输出文档整体格式总结

86286526-63d2-11ed-8abf-dac502259ad0.png

copyright 版权声明

异常读写报告2.1 主线程异常读写

线程A异常读写报告线程B异常读写报告

其他线程

堆内存泄露报告4.1 堆内存使用情况概述(HEAP SUMMARY)

4.2 确信的内存泄露报告(definitely lost)

4.3 可疑内存操作报告 (show-reachable=no关闭,打开:–show-reachable=yes)

4.4 泄露情况概述(LEAK SUMMARY)

4.4 Memcheck 日志报告的基本格式

86e1ba26-63d2-11ed-8abf-dac502259ad0.png

{问题描述}
at{地址、函数名、模块或代码行}
by{地址、函数名、代码行}
by…{逐层依次显示调用堆栈}
Address0x???{描述地址的相对关系}

4.5 memcheck包含的7类错误

illegal read/illegal write errors提示信息:[invalid read of size 4]

use of uninitialised values提示信息:[Conditional jump or move depends on uninitialised value]

use of uninitialised or unaddressable values in system calls提示信息:[syscall param write(buf) points to uninitilaised bytes]

illegal frees提示信息:[invalid free()]

when a heap block is freed with an inappropriate deallocation function提示信息:[Mismatched free()/delete/delete[]]

overlapping source and destination blocks提示信息:[source and destination overlap in memcpy(,)]

memory leak detection① still reachable内存指针还在还有机会使用或释放,指针指向的动态内存还没有被释放就退出了

② definitely lost确定的内存泄露,已经不能访问这块内存

③ indirectly lost指向该内存的指针都位于内存泄露处

④ possibly lost可能的内存泄露,仍然存在某个指针能够快速访问某块内存,但该指针指向的已经不是内存首位置

4.6 memcheck工具原理

Memcheck实现了一个仿真的CPU,被监控的程序被这个仿真CPU解释执行,该仿真CPU可以在所有的内存读写指令发生时,检测地址的合法性和读操作的合法性。

86f9b87e-63d2-11ed-8abf-dac502259ad0.png

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,如果该值

尚未初始化,则会报告使用未初始化内存错误。

简单来说

如何知道那些地址是合法的(内存已分配)?维护一张合法地址表(Valid-address (A) bits),当前所有可以合法读写(已分配)的地址在其中有对应的表项。该表通过以下措施维护:

① 全局数据(data, bss section)–在程序启动的时候标记为合法地址

② 局部变量–监控sp(stack pointer)的变化,动态维护

③动态分配的内存–截获 分配/释放 内存的调用:malloc, calloc, realloc, valloc, memalign, free, new, new[], delete and delete[]

④ 系统调用–截获mmap映射的地址

⑤ 其他–可以显示知会memcheck某地字段是合法的

如何知道某内存是否已经被赋值?

①维护一张合法值表(Valid-value (V) bits),指示对应的bit是否已经被赋值。因为虚拟CPU可以捕获所有对内存的写指令,所以这张表很容易维护。

5、Callgrind工具介绍

Callgrind性能分析工具,它不需要在编译源码时附加特殊选项。Callgrind使用cachegrind的统计信息Ir(I cache reads,即一条指令执行的次数)来统计程序中函数的调用情况,建立函数调用关系图,还可以有选择地进行cache模拟。在运行结束时,它会把分析数据写入一个文件,callgrind_annotate可以把这个文件的内容转化成可读的形式。

5.1 Callgrind文本分析基本操作

示例:

(1)

cdlinux/bin
valgrind--tool=callgrind./Devtest

生成一个文件:callgrind.out.27439

或者

valgrind--tool=callgrind--separate-threads=yes./Devtest

生成三个文件:callgrind.out.1234(为空),callgrind.out.1234-01(线程1),callgrind.out.1234-02(线程2)

(2)

callgrind_annotatecallgrind.out.27439>log

callgrind_annotate是可以将callgrind.out.pid文件的内容转化为可读的形式,并重定向到log文件,分别打开callgrind.out.pid、log文件,你会发现它们的不同(callgrind.out.pid是人类不便于直接理解的格式,callgrind_annotate相当于一个翻译,将callgrind.out.pid按照我们喜欢的方式展现出来)。

callgrind_annotate解析callgrind.out.pid而生成的log文件,打开后内容如下:

87200b50-63d2-11ed-8abf-dac502259ad0.png

可以看到每个函数所属的动态库,该函数调用所耗费的指令数,默认是从大到小排序的。

callgrind_annotate 还有几个可选参数:

--inclusive=yes:不但分别统计每个语句的执行次数,还把调用关系计算进入,比如函数foo调用了bar,那么foo的代价中会加入bar的代价。

--tree=both:显示调用关系。

--auto=yes:会自动将统计信息和源码关联。就是会显示每个函数的源码,并且在前面显示每条语句的运行代价

(3)可以对单独的文件进行关联:

callgrind_annotatecallgrind.out.9441main.c|grep-v“???”

注:“???”前缀的调用,都是系统库底层调用,不重要,可用grep -v过滤掉

5.2 Callgrind流程图分析基本操作

以工程右图“官网提供的示例代码”为例,会比较直观:

gcc–gtest.c-otest
valgrind--tool=callgrind./test

生成一个文件:callgrind.out.pid

pythongprof2dot.py-fcallgrind-n10-scallgrind.out.[pid]>valgrind.dot
dot-Tpngvalgrind.dot-ovalgrind.png

打开图片打开,据说能一目了然的知道运行时间消耗的分布

876a4b66-63d2-11ed-8abf-dac502259ad0.png8783d766-63d2-11ed-8abf-dac502259ad0.png

6、Cachegrind工具介绍

6.1 基本介绍

Cachegrind基于Valgrind的剖析器(profiler)计算机系统变得越来越复杂,剖析存储系统往往是系统瓶颈,需要剖析Cache

功能

模拟L1、L2 Cache

剖析Cache行为,执行次数、失效率等

按照文件、函数、代码行、汇编指令剖析

作用

详细Cache剖析,发现程序瓶颈

指令改进程序,提高执行效率

Trace驱动的Cache模拟器

优点

容易使用,不需要重新编译

剖析所有执行的代码,包括库

不限定语言

速度相对较快

灵活,模拟不同配置的Cache

6.2 使用步骤

valgrind--tool=cachegrind./test
879b19e4-63d2-11ed-8abf-dac502259ad0.png

同时生成文件cachegrind.out.pid

callgrind_annotatecachegrind.out.4599|grep-v“???”
87c0f33a-63d2-11ed-8abf-dac502259ad0.png在这里插入图片描述

和callgrind一样,也可以通过callgrind_annotate翻译为可读信息。从中可以看到I1 cache(指令缓存)、D1 cache(数据缓存)、LL cache(公共的二级缓存)的命中情况。

7、Massif工具介绍

Massif是一个内存剖析工具。通过不断的取程序堆的快照来达到监视程序内存分配的目的。

7.1 示例

g++test.cc-otest
valgrind--tool=massif./test

就得到一个massif文件:massif.out.pid

使用ms_print来解析这个输出文件:

ms_printmassif.out.pid

通过图形快照看出堆栈的内存变化情况:

87e60b66-63d2-11ed-8abf-dac502259ad0.png8807f79e-63d2-11ed-8abf-dac502259ad0.png

8、Helgrind工具介绍

Helgrind是Valgrind的一个重点功能 本节主要针对与多线程基本安全问题进行检测。

资源不安全访问

死锁问题

POSIX pthreads API的错误使用

在前面几个基础上都能安全无误的情况下 多于多线程程序就是要能够能好将同步块尽量缩到最小

8.1 Helgrind 资源不安全访问

解决问题:

问题1: 调用Helgrind能够很好的解决掉,以右边基本程序为例

#include

intvar=0;
void*child_fn(void*arg)
{
var++;
returnNULL;
}
intmain(void)
{
pthread_tchild;
pthread_tchild2;

pthread_create(&child,NULL,child_fn,NULL);
pthread_create(&child2,NULL,child_fn,NULL);

pthread_join(child,NULL);
pthread_join(child2,NULL);

return0;
}
123456789101112131415161718192021

明显var是共享的 不安全访问,调用Helgrind看看怎么能够检测出来:

gcc-gtest.c-otest–lpthread
valgrind--tool=helgrind./test

运行helgrind之后会生成如下结果,从信息提示中可以看到有两个错误,对val全局变量的抢占使用

889e93fc-63d2-11ed-8abf-dac502259ad0.png88c93e54-63d2-11ed-8abf-dac502259ad0.png

问题2:

死锁问题是尽量避免,helgrind可以检测出加锁解锁顺序出现问题导致的死锁问题,这个问题我们可以好好看下:https://blog.csdn.net/sfwtoms11/article/details/38438253

再看下连续加2次锁的情况

#include

pthread_mutex_tmut_thread;
intvar=0;
void*child_fn(void*arg)
{
pthread_mutex_lock(&mut_thread);
var++;
pthread_mutex_lock(&mut_thread);
returnNULL;
}

intmain(void)
{
pthread_tchild;
pthread_tchild2;
pthread_mutex_init(&mut_thread,NULL);
pthread_create(&child,NULL,child_fn,NULL);
pthread_create(&child2,NULL,child_fn,NULL);
pthread_join(child,NULL);
pthread_join(child2,NULL);
return0;
}
1234567891011121314151617181920212223

mutex加解锁顺序导致的问题:

#include

pthread_mutex_tmut_thread;
pthread_mutex_tmut_thread1;
intvar=0;
void*child_fn(void*arg){
pthread_mutex_lock(&mut_thread);
pthread_mutex_lock(&mut_thread1);
var++;
pthread_mutex_unlock(&mut_thread);
pthread_mutex_unlock(&mut_thread1);
returnNULL;
}
void*child_fn1(void*arg)
{
pthread_mutex_lock(&mut_thread1);
pthread_mutex_lock(&mut_thread);
var++;
pthread_mutex_unlock(&mut_thread1);
pthread_mutex_unlock(&mut_thread);
returnNULL;
}

intmain(void){
pthread_tchild;
pthread_tchild2;
pthread_mutex_init(&mut_thread,NULL);
pthread_mutex_init(&mut_thread1,NULL);
pthread_create(&child,NULL,child_fn,NULL);
pthread_create(&child2,NULL,child_fn1,NULL);
pthread_join(child,NULL);
pthread_join(child2,NULL);
return0;
}

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    11296

    浏览量

    209358
  • Valgrind
    +关注

    关注

    0

    文章

    9

    浏览量

    6807

原文标题:valgrind基本功能介绍、基础使用方法说明

文章出处:【微信号:嵌入式与Linux那些事,微信公众号:嵌入式与Linux那些事】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    物联网的最基本功能是什么?

    万物互联大时代,物联网的最基本功能是什么?
    发表于 06-16 11:33

    BMS的基本功能和硬件拓扑

    BMS基本功能BMS 的硬件拓扑BMS的状态估算及均衡控制电池内短路的快速识别
    发表于 12-16 06:30

    MCU的基本功能及程序编写

    MCU的基本功能MCU程序的编写
    发表于 01-12 07:53

    MCU的基本功能和程序编写

    MCU的基本功能MCU程序的编写
    发表于 01-14 07:42

    MCU的基本功能及程序编写

    MCU的基本功能MCU程序的编写
    发表于 02-02 06:05

    MCU的基本功能

    MCU的基本功能MCU程序的编写
    发表于 02-05 06:37

    智能合约的基本功能是什么

    合约的基本功能是通过价值交换约定各方责任。由于DLT(分布式账簿)技术的出现,智能合约让合约的编写和执行变得更加高效且自动化。然而,如今的智能合约还存在诸多缺陷,因为它们无法与真实世界联通。预言机
    发表于 07-12 09:00

    晶体管测量模块的基本功能有哪些

    晶体管测量模块的基本特性有哪些?晶体管测量模块的基本功能有哪些?
    发表于 09-24 07:37

    MCU的基本功能有哪些呢

    以下来自Atmel Mega128的说明手册:微控制器(微处理器)Microcontroller(MCU)的四个基本功能为:1.access memory,2.perform calculation
    发表于 11-03 06:50

    STM32CUBEMX基本功能如何配置?

    STM32CUBEMX基本功能如何配置?
    发表于 11-23 06:12

    串口的基本配置和基本功能是什么?

    串口功能有哪些?串口的基本配置和基本功能是什么?
    发表于 12-10 07:19

    reertos基本功能包括什么

    本文介绍嵌入式实时操作系统FreeRTO的常用API,freertos基本功能包括 任务调度、内存管理、中断管理、定时器管理、消息队列、信号量、互斥锁等。1. FreeRTOS 任务相关API
    发表于 12-27 06:36

    介绍一下解决USART通信的最基本功能实现的编程思路

    作为初学stm32的小白,下面我来介绍一下解决USART通信的最基本功能实现的编程思路。1.
    发表于 01-20 07:17

    介绍操作系统的基本功能以及UCOSII的移植方法

    文章向大家介绍操作系统的基本功能,以及UCOSII的移植方法。RTOS:Real time Operation SystemKeil RTX 是免版税的确定性实时操作系统,适用于 ARM 和 Cortex-M 设备。RTOS可以
    发表于 02-18 06:59

    RK3328的软件调试操作使用方法是什么

    RK3328的基本功能特点有哪些?如何对RK3328的多功能硬件进行配置?RK3328的软件调试操作使用方法是什么?
    发表于 03-09 06:14