用户登录
用户注册

分享至

【内存泄漏】- 5. 使用Valgrind工具检测Python内存泄漏

  • 作者: 小韦嘎嘎
  • 来源: 51数据库
  • 2021-10-27

?1. 什么是valgrind

? ? ? Valgrind是一套Linux下,开放源代码(GPL V2)的仿真调试工具的集合。Valgrind由内核(core)以及基于内核的其他调试工具组成。内核类似于一个框架(framework),它模拟了一个CPU环境,并提供服务给其他工具;而其他工具则类似于插件 (plug-in),利用内核提供的服务完成各种特定的内存调试任务。

Valgrind的体系结构如下图所示:

Valgrind包括如下一些工具:

  1. Memcheck。这是valgrind应用最广泛的工具,一个重量级的内存检查器,能够发现开发中绝大多数内存错误使用情况,比如:使用未初始化的内存,使用已经释放了的内存,内存访问越界等。这也是本文将重点介绍的部分。
  2. Callgrind。它主要用来检查程序中函数调用过程中出现的问题。
  3. Cachegrind。它主要用来检查程序中缓存使用出现的问题。
  4. Helgrind。它主要用来检查多线程程序中出现的竞争问题。
  5. Massif。它主要用来检查程序中堆栈使用中出现的问题。
  6. Extension。可以利用core提供的功能,自己编写特定的内存调试工具。

Linux 程序内存空间布局

要发现Linux下的内存问题,首先一定要知道在Linux下,内存是如何被分配的?下图展示了一个典型的Linux C程序内存空间布局:

一个典型的Linux C程序内存空间由如下几部分组成:

  • 代码段(.text)。这里存放的是CPU要执行的指令。代码段是可共享的,相同的代码在内存中只会有一个拷贝,同时这个段是只读的,防止程序由于错误而修改自身的指令。
  • 初始化数据段(.data)。这里存放的是程序中需要明确赋初始值的变量,例如位于所有函数之外的全局变量:int val="100"。需要强调的是,以上两段都是位于程序的可执行文件中,内核在调用exec函数启动该程序时从源程序文件中读入。
  • 未初始化数据段(.bss)。位于这一段中的数据,内核在执行该程序前,将其初始化为0或者null。例如出现在任何函数之外的全局变量:int sum;
  • 堆(Heap)。这个段用于在程序中进行动态内存申请,例如经常用到的malloc,new系列函数就是从这个段中申请内存。
  • 栈(Stack)。函数中的局部变量以及在函数调用过程中产生的临时变量都保存在此段中。

内存检查原理

Memcheck检测内存问题的原理如下图所示:

?

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

  1. Valid-Value?表:

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

  1. Valid-Address?表

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

检测原理:

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

Valgrind?使用

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

选项,适用于所有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 <number> callers in stack traces [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|high how much bt merging in leak check [low]
  • ???????-show-reachable=no|yes show reachable blocks in leak check? [no]

?

?2. 使用Valgrind?检测c语言内存泄露例子?

例子一:

下面是一段有问题的C程序代码test.c

#include <stdlib.h>
void f(void)
{
???int* x = malloc(10 * sizeof(int));
???x[10] = 0; ?//问题1: 数组下标越界
} ?????????????????//问题2: 内存没有释放

int main(void)
{
???f();
???return 0;
?}


1、 编译程序test.c

gcc -Wall test.c -g -o test


2、 使用Valgrind检查程序BUG

valgrind --tool=memcheck --leak-check=full ./test


3、 分析输出的调试信息

==3908== Memcheck, a memory error detector.
==3908== Copyright (C) 2002-2007, and GNU GPL'd, by Julian Seward et al.
==3908== Using LibVEX rev 1732, a library for dynamic binary translation.
==3908== Copyright (C) 2004-2007, and GNU GPL'd, by OpenWorks LLP.
==3908== Using valgrind-3.2.3, a dynamic binary instrumentation framework.
==3908== Copyright (C) 2000-2007, and GNU GPL'd, by Julian Seward et al.
==3908== For more details, rerun with: -v
==3908== 
--3908-- DWARF2 CFI reader: unhandled CFI instruction 0:50
--3908-- DWARF2 CFI reader: unhandled CFI instruction 0:50
/*数组越界错误*/
==3908== Invalid write of size 4 ?????
==3908== ???at 0x8048384: f (test.c:6)
==3908== ???by 0x80483AC: main (test.c:11)
==3908== ?Address 0x400C050 is 0 bytes after a block of size 40 alloc'd
==3908== ???at 0x40046F2: malloc (vg_replace_malloc.c:149)
==3908== ???by 0x8048377: f (test.c:5)
==3908== ???by 0x80483AC: main (test.c:11)
==3908== 
==3908== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 14 from 1)
==3908== malloc/free: in use at exit: 40 bytes in 1 blocks. 
==3908== malloc/free: 1 allocs, 0 frees, 40 bytes allocated.
==3908== For counts of detected errors, rerun with: -v
==3908== searching for pointers to 1 not-freed blocks.
==3908== checked 59,124 bytes.
==3908== 
==3908== 
/*有内存空间没有释放*/
==3908== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==3908== ???at 0x40046F2: malloc (vg_replace_malloc.c:149)
==3908== ???by 0x8048377: f (test.c:5)
==3908== ???by 0x80483AC: main (test.c:11)
==3908== 
==3908== LEAK SUMMARY:
==3908== ???definitely lost: 40 bytes in 1 blocks.
==3908== ?????possibly lost: 0 bytes in 0 blocks.
==3908== ???still reachable: 0 bytes in 0 blocks.
==3908== ????????suppressed: 0 bytes in 0 blocks.

例子二:

没有内存泄漏
编译C++ -“Hello kiccleaf!”

#include <iostream.h>

int main()
{
?cout << "Hello kiccleaf!/n" << endl;
?return 0;
}

用g++编译C++程序

g++ Hello.cpp -o hello

检测及结果:

[root@xuanAS4 LNMP]# valgrind --tool=memcheck --leak-check=full ./hello
==8926== Memcheck, a memory error detector.
==8926== Copyright (C) 2002-2008, and GNU GPL'd, by Julian Seward et al.
==8926== Using LibVEX rev 1884, a library for dynamic binary translation.
==8926== Copyright (C) 2004-2008, and GNU GPL'd, by OpenWorks LLP.
==8926== Using valgrind-3.4.1, a dynamic binary instrumentation framework.
==8926== Copyright (C) 2000-2008, and GNU GPL'd, by Julian Seward et al.
==8926== For more details, rerun with: -v
==8926== 
Hello kiccleaf!


==8926== 
==8926== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 15 from 1)
==8926== malloc/free: in use at exit: 0 bytes in 0 blocks.
==8926== malloc/free: 0 allocs, 0 frees, 0 bytes allocated.
==8926== For counts of detected errors, rerun with: -v
==8926== All heap blocks were freed -- no leaks are possible

3. 使用valgrind工具检测python内存泄露

在检测之前,需要一些准备工作,需要对python源码一些进行设置,否则在检测过程中会报错。

我这里的环境是:arm开发板、python2.7

因为是交叉编译,所以需要下载valgrind源码进行交叉编译,并且对python2.7源码修改后,也需要进行交叉编译才能放在到arm板内进行检测。如果直接在机器上运行,不需要交叉编译的,请忽略这一步。

3.1 编译PC版本的python

Python源码可以到python官网下载或者从我的csdn下载:

https://download.csdn.net/download/sishuihuahua/10899217

下载到本地后在linux下使用以下命令进行编译:

1、tar -xvf Python-2.7.3.tar.xz

2、./configure?

3、make python Parser/pgen

4、mv python hostpython

5、mv Parser/pgen Parser/hostpgen

6、make distclean

3.2 修改相关的文件:

Objects/obmalloc.c ??----+704:

#define Py_USING_MEMORY_DEBUGGER

Misc/valgrind-python.supp ?----+127:
{
???ADDRESS_IN_RANGE/Invalid read of size 4
???Memcheck:Addr4
???fun:PyObject_Free
}


{
???ADDRESS_IN_RANGE/Invalid read of size 4
???Memcheck:Value4
???fun:PyObject_Free
}


{
???ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value
???Memcheck:Cond
???fun:PyObject_Free
}


{
???ADDRESS_IN_RANGE/Invalid read of size 4
???Memcheck:Addr4
???fun:PyObject_Realloc
}


{
???ADDRESS_IN_RANGE/Invalid read of size 4
???Memcheck:Value4
???fun:PyObject_Realloc
}


{
???ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value
???Memcheck:Cond
???fun:PyObject_Realloc

}

3.3 打补丁

补丁下载地址:

https://download.csdn.net/download/sishuihuahua/10899271

patch -p1 < ../Python-2.7.3-xcompile.patch

3.4 配置交叉编译

下载valgrind源码:

https://download.csdn.net/download/sishuihuahua/10899241

配置交叉编译:

echo ac_cv_file__dev_ptmx=no > config.site
echo ac_cv_file__dev_ptc=no >> config.site
export CONFIG_SITE=config.site

./configure CC=arm-linux-gnueabihf-gcc CXX=arm-linux-gnueabihf-g++ AR=arm-linux-gnueabihf-ar RANLIB=arm-linux-gnueabihf-ranlib LD=arm-linux-gnueabihf-ld NM=arm-linux-gnueabihf-nm --host=arm-linux-gnueabihf --build=x86_64-linux --disable-ipv6 --without-pymalloc ?--with-valgrind --with-wide-unicode --enable-unicode=ucs4

make HOSTPYTHON=./hostpython HOSTPGEN=./Parser/hostpgen BLDSHARED="arm-linux-gnueabihf-gcc -shared" CROSS_COMPILE=arm-linux-gnueabihf- CROSS_COMPILE_TARGET=yes HOSTARCH=arm-linux BUILDARCH=x86_64-linux

make install HOSTPYTHON=./hostpython BLDSHARED="arm-linux-gnueabihf-gcc -shared" CROSS_COMPILE=arm-linux-gnueabihf- CROSS_COMPILE_TARGET=yes prefix=$PWD/_install

3.5 将生成的文件全部复制到板子上(/usr/)

?

3.6 设置环境变量

在arm板命令行上执行如下命令,更加具体的python程序添加其他的export:

export PYTHONPATH=/usr/lib/python2.7/dist-packages

3.7 测试

valgrind python2.7 test_main.py

执行结果个上文类似,分析的方法是一致的。

?

参考链接:https://blog.csdn.net/destina/article/details/6198443

至此,内存泄露的全部总结到此告一段落,算是一个总结,也是一个开始,终于是从0到1的进步。

软件
前端设计
程序设计
Java相关