MD小智

学习-思考-分享


  • 首页

  • 分类

  • 归档

  • 关于我

  • 搜索

内存调试工具Valgrind之Memcheck

时间: 2022-06-15   |   分类: 工具教程   | 字数: 7398 字 | 阅读: 15分钟 | 阅读次数:

介绍

Valgrind是一款用于内存调试、内存泄漏检测以及性能分析的软件开发工具。

交叉编译

开发主机Ubuntu20.04(x86_64)
目标主机ARM64(aarch64)
  1. 在这里下载valgrind-3.19.0.tar.bz2,执行指令tar xvf valgrind-3.17.0.tar.bz2解压源码,效果如下;

    ╭─ caeri@ubuntu  ~/lzp/GIT  INSERT                                                                                                                                                              2 ↵
    ╰─ tar xvf valgrind-3.17.0.tar.bz2
    valgrind-3.17.0/
    valgrind-3.17.0/docs/
    valgrind-3.17.0/docs/cg_merge.1
    
  2. 导出交叉编译工具链到当前环境;

     source /opt/dias/fsl-imx-xwayland/5.4-zeus/environment-setup-aarch64-poky-linux
    

    导出后,可以看到当前的编译器已经指向了交叉编译器

    ╭─ caeri@ubuntu  ~/lzp/GIT/valgrind-3.17.0  INSERT                                                                                                                                                ✔
    ╰─ echo $CC
    aarch64-poky-linux-gcc  -mcpu=cortex-a35+crc+crypto -fstack-protector-strong  -D_FORTIFY_SOURCE=2 -Wformat -Wformat-security -Werror=format-security --sysroot=/opt/dias/fsl-imx-xwayland/5.4-zeus/sysroots/aarch64-poky-linux
    
  3. 在开发主机新建一个安装文件夹,该文件夹路径应与以后在开发板上安装valgrind的路径保持一致,我们这里新建如下:

    make -p /caeridata/bin/lzp/valgrind
    

    这里说明,未来在目标板(开发板)上,要将valgrind安装在目录/caeridata/bin/lzp/valgrind下。

  4. 执行如下指令./configure --target=aarch64-poky-linux --host=aarch64-poky-linux --prefix=/caeridata/bin/lzp/valgrind,设置编译参数,执行后效果如下:

    ╭─ caeri@ubuntu  ~/lzp/GIT/valgrind-3.17.0  INSERT                                                                                                                                                ✔
    ╰─ ./configure --target=aarch64-poky-linux --host=aarch64-poky-linux --prefix=/caeridata/bin/lzp/valgrind
    configure: loading site script /opt/dias/fsl-imx-xwayland/5.4-zeus/site-config-aarch64-poky-linux
    checking for a BSD-compatible install... /usr/bin/install -c
    checking whether build environment is sane... yes
    checking for aarch64-poky-linux-strip... aarch64-poky-linux-strip
    checking for a thread-safe mkdir -p... /usr/bin/mkdir -p
    ......
    config.status: executing depfiles commands
    
             Maximum build arch: arm64
             Primary build arch: arm64
           Secondary build arch:
                       Build OS: linux
         Link Time Optimisation: no
           Primary build target: ARM64_LINUX
         Secondary build target:
               Platform variant: vanilla
          Primary -DVGPV string: -DVGPV_arm64_linux_vanilla=1
             Default supp files: xfree-3.supp xfree-4.supp glibc-2.X-drd.supp glibc-2.34567-NPTL-helgrind.supp glibc-2.X.supp
    

    这里的几个参数

    • target 目标主机
    • prefix 安装目录,注意要保证有写入权限;
  5. 执行make进行编译,执行效果如下:

    ╭─ caeri@ubuntu  ~/lzp/GIT/valgrind-3.17.0  INSERT                                                                                                                                                ✔
    ╰─ make
    echo "# This is a generated file, composed of the following suppression rules:" > default.supp
    echo "# " xfree-3.supp xfree-4.supp glibc-2.X-drd.supp glibc-2.34567-NPTL-helgrind.supp glibc-2.X.supp  >> default.supp
    ......
    make[2]: Leaving directory '/home/caeri/lzp/GIT/valgrind-3.17.0/mpi'
    Making all in solaris
    make[2]: Entering directory '/home/caeri/lzp/GIT/valgrind-3.17.0/solaris'
    make[2]: Nothing to be done for 'all'.
    make[2]: Leaving directory '/home/caeri/lzp/GIT/valgrind-3.17.0/solaris'
    Making all in docs
    make[2]: Entering directory '/home/caeri/lzp/GIT/valgrind-3.17.0/docs'
    make[2]: Nothing to be done for 'all'.
    make[2]: Leaving directory '/home/caeri/lzp/GIT/valgrind-3.17.0/docs'
    make[1]: Leaving directory '/home/caeri/lzp/GIT/valgrind-3.17.0'
    
  6. 编译完成后,需要进行make install,我这里因为使用了非root用户编译且安装目录在/caerdata…因此我这里安装的时候需要切换到root用户,且需要重新导出交叉编译工具链。这里就不进行演示了。

  7. 安装完成后,效果如下:

    root@ubuntu:/caeridata/bin/lzp/valgrind# ls
    bin  include  lib  libexec  share
    
  8. 接下来将整个文件夹打包,然后复制到开发板解压

    #在开发主机上,打包
    tar cvf valgrind.tar valgrind
    #在开发板上,解压到/caeridata/bin/lzp/
    tar -xvf valgrind.tar -C /caeridata/bin/lzp/
    
  9. 最后,在开发板上的效果如下:

    root@imx8qxpc0mek:/caeridata/bin/lzp/valgrind# ls
    bin      include  lib      libexec  share
    
  10. 到这里安装就完成了,为了方便使用,可以在开发板上/caeridata/bin/lzp/valgrind/bin这个路径导出到环境变量中,方便在其他目录使用。

Valgrind-Memcheck说明

memcheck是valgrind的默认工具,所以不必须使用--tool=memcheck来指定。

memcheck将内存错误分为六种:

  • 访问禁止访问的内存;
  • 使用没有被定义的变量。例如变量数据没有被初始化,或者其他变量衍生的未初始化数据;
  • 不正确的释放堆内存。例如两次free一个堆块,或者没有成对的使用malloc/free、new/delete;
  • memcpy相关函数的src和dst有重叠;
  • 将可疑值(负值)传递给申请内存(malloc等)函数的大小参数;
  • 内存泄漏;

Memcheck检测结果说明

Memcheck会返回一系列的错误消息,下边我们针对各种消息进行说明。

非法读写Illegal read / Illegal write errors

示例

Invalid read of size 4
at 0x40F6BBCC: (within /usr/lib/libpng.so.2.1.0.9)
by 0x40F6B804: (within /usr/lib/libpng.so.2.1.0.9)
by 0x40B07FF4: read_png_image(QImageIO *) (kernel/qpngio.cpp:326)
by 0x40AC751B: QImageIO::read() (kernel/qimage.cpp:3621)
Address 0xBFFFF0E0 is not stack'd, malloc'd or free'd

这个发生在当你的程序读/写memcheck猜测你不应该读写的内存。

Memcheck会试图确定出错的具体位置。例如它指向一块已经被free的内存,那么Memcheck会指出free的位置。同样的,指针刚好在一个array数组的末尾,这是数组越界访问的常见错误,Memcheck将会告知这一信息,以及出错的位置。如果使用参数--read-var-info,Memcheck会告知更详细的信息,当然,这会导致软件运行更慢。

在上边这个示例中,Memcheck不能识别出这个地址。实际上,这个地址是在栈上,因为一些原因,这并不是一个有效的栈地址—它位于堆栈指针下方,这是不允许的。在这种特殊情况下,它可能是由GCC生成无效代码引起,这是一些古版GCC中的已知错误。

要注意的是,Memcheck只会告知你的程序即将访问一个非法地址,它并不会阻止程序的访问行为。因此,如果你的程序正常情况会导致segmentation fault这件事情依然会发生。但是你可以通过Memcheck马上返回一个消息。

使用未初始化数据 Use of uninitialised values

示例

Conditional jump or move depends on uninitialised value(s)
at 0x402DFA94: _IO_vfprintf (_itoa.h:49)
by 0x402E8476: _IO_printf (printf.c:36)
by 0x8048472: main (tests/manuel1.c:8)

当程序使用了一个未初始化的变量时,会返回该错误。在这里,未定义的变量,被C库的printf函数调用。这个错误在我们运行如下代码的时候可以复现。

int main()
{
	int x;
	printf ("x = %d\n", x);
}

Memcheck只监视变量,不提示任何警告,直到它在程序中被使用,Memcheck才开始报告。上述示例中,Memcheck观察传递给_IO_printf没有报告,然后传递给_IO_vfprintf,当_IO_vfprintf必须检查x的值以便于将其转换成ASCII码,此时才会报告错误。

未初始化的值往往来自2个地方:

  • 本地变量未被初始化,例如上边的示例;
  • 指针在malloc等函数给指针赋值之前,往指针写值;

在程序中观察未初始化信息,使用参数--track-origins=yes。

系统调用中未初始化或不可寻址的数据Use of uninitialised or unaddressable values insystem calls

Memcheck检测所有系统调用的参数:

  • 检测所有的直接参数是否被初始化;
  • 如果系统调用需要从应用程序提供的buffer中读取数据,Memcheck会检测buffer入口的地址是否正确,并检查buffer内容是否被初始化;
  • 如果系统调用需要写数据到应用程序提供的buffer,则Memcheck会检测buffer的入口地址;

系统调用后,Memcheck 会更新其跟踪的信息,以精确反映由系统调用引起内存状态的任何更改。

这里有一个系统调用参数无效的示例:

#include <stdlib.h>
#include <unistd.h>
int main( void )
{
	char* arr = malloc(10);
	int* arr2 = malloc(sizeof(int));
	write( 1 /* stdout */, arr, 10 );
	exit(arr2[0]);
}

可以得到:

Syscall param write(buf) points to uninitialised byte(s)
at 0x25A48723: __write_nocancel (in /lib/tls/libc-2.3.3.so)
by 0x259AFAD3: __libc_start_main (in /lib/tls/libc-2.3.3.so)
by 0x8048348: (within /auto/homes/njn25/grind/head4/a.out)
Address 0x25AB8028 is 0 bytes inside a block of size 10 alloc'd
at 0x259852B0: malloc (vg_replace_malloc.c:130)
by 0x80483F1: main (a.c:5)
Syscall param exit(error_code) contains uninitialised byte(s)
at 0x25A21B44: __GI__exit (in /lib/tls/libc-2.3.3.so)
by 0x8048426: main (a.c:8)

非法释放Illegal frees

示例

Invalid free()
at 0x4004FFDF: free (vg_clientmalloc.c:577)
by 0x80484C7: main (tests/doublefree.c:10)
Address 0x3807F7B4 is 0 bytes inside a block of size 177 free'd
at 0x4004FFDF: free (vg_clientmalloc.c:577)
by 0x80484C7: main (tests/doublefree.c:10)

Memcheck跟踪malloc/new申请的内存,因此它知道是否正确的free/delete释放内存。这这个测试程序中,同一个malloc的block,被free了两次。

申请内存被错误的释放

​ 在下边的示例中,用new[]申请的内存被错误的使用free释放。

Mismatched free() / delete / delete []
at 0x40043249: free (vg_clientfuncs.c:171)
by 0x4102BB4E: QGArray::~QGArray(void) (tools/qgarray.cpp:149)
by 0x4C261C41: PptDoc::~PptDoc(void) (include/qmemarray.h:60)
by 0x4C261F0E: PptXml::~PptXml(void) (pptxml.cc:44)
Address 0x4BB292A8 is 0 bytes inside a block of size 64 alloc'd
at 0x4004318C: operator new[](unsigned int) (vg_clientfuncs.c:152)
by 0x4C21BC15: KLaola::readSBStream(int) const (klaola.cc:314)
by 0x4C21C155: KLaola::stream(KLaola::OLENode const *) (klaola.cc:416)
by 0x4C21788F: OLEFilter::convert(QCString const &) (olefilter.cc:272)

在C++中,内存的申请方式与释放方式兼容非常重要。处理原则:

  • 如果使用malloc, calloc, realloc, valloc 或者 memalign申请的内存,必须使用free释放;
  • 如果使用new申请的内存,必须使用delete释放;
  • 如果使用new[]申请的内存,必须使用delete[]释放;

源地址和目标地址存在重叠

Overlapping source and destination blocks。

在C库中的memcpy,strcpy, strncpy, strcat, strncat等这一类函数。操作的源地址和目的地址被要求不能出现重叠。POSIX中有定义“如果copy的两个地址存在重叠,则这种行为未定义”。Memcheck可以监测这个问题。

例如:

==27492== Source and destination overlap in memcpy(0xbffff294, 0xbffff280, 21)
==27492== at 0x40026CDC: memcpy (mc_replace_strmem.c:71)
==27492== by 0x804865A: main (overlap.c:40)

可疑参数

Fishy argument values

所有的内存申请函数都有一个参数用来指明需要申请内存的大小。这个参数要求是非负数且通常不会过大。例如,在一个64位的机器上,很(不喜欢)少去申请一个2^63Byte大小的内存。这样的大的值通常可能会是一个错误的值,只是由于符号位未被解析,则展示出是一个巨大的非负值。这种数就叫做"fishy value",可疑数值。以下函数将被检查:malloc, calloc,realloc, memalign, new, new []. __builtin_new, __builtin_vec_new,calloc的两个参数都会被检查。

示例:

==32233== Argument 'size' of function malloc has a fishy (possibly negative) value: -3
==32233== at 0x4C2CFA7: malloc (vg_replace_malloc.c:298)
==32233== by 0x400555: foo (fishy.c:15)
==32233== by 0x400583: main (fishy.c:23)

内存泄漏

Memcheck会追踪所有 malloc/new申请的内存,当进程退出的时候,它知道所有没有被释放的内存块。

如果--leak-check参数被配置,对于每一个未被释放的block,Memcheck会确定该块是否属于可以访问 root-set的指针,rootk-set由所有线程的通用寄存器和可访问内存(包括栈)中指针大小的对齐数据字。

有两种方式可以操作到一个block,第一种是使用“start-pointer”,开始指针,第二种是使用block内部的指针。我们知道的有这几种方式访问一个block:

  • 指针开始可能是一个起始指针,会被程序有意(或者无意)移动。特别是,使用另一个tag来指向这个起始指针;
  • 它可能是一个内存中的一个垃圾数据(被错误的当成指针),数据整体是没有关联的,完全是一个巧合;
  • 它可能是 C++ std::string内部char数组的指针,在编译器中,申请数组会返回一个指向数组的指针;
  • 某些代码分配一个内存块,会使用前8个字节来存储指针,例如sqlite3MemMalloc就是这么做的;
  • 它可能是指向分配了 new[] 的 C++ 对象(具有析构函数)数组的指针;
  • 它可能是指向使用多重继承的C++对象内部部分的指针;

可以选择配置在泄漏搜索期间检测stdstring, length64, newarray和multipleinheritance对应的情况。如果启发式检测到内部指针,对应于这种情况,块将被视为可通过内部指针访问。换句话说,内部指针将被视为起始指针。

考虑到这一点,请考虑下图中描述的九种可能的情况。

		Pointer chain 			AAA Leak Case 			BBB Leak Case
		------------- 			------------- 			-------------
(1) 	RRR ------------> BBB							DR
(2)		RRR ---> AAA ---> BBB	DR						IR
(3)		RRR				  BBB							DL
(4)		RRR		 AAA ---> BBB	DL						IL
(5)		RRR ------?-----> BBB							(y)DR, (n)DL
(6)		RRR ---> AAA -?-> BBB	DR						(y)IR, (n)DL
(7)		RRR -?-> AAA ---> BBB	(y)DR, (n)DL			(y)IR, (n)IL
(8)		RRR -?-> AAA -?-> BBB	(y)DR, (n)DL			(y,y)IR, (n,y)IL, (_,n)DL
(9)		RRR		 AAA -?-> BBB	DL						(y)IL, (n)DL
Pointer chain legend:
- RRR: a root set node or DR block
- AAA, BBB: heap blocks
- --->: a start-pointer
- -?->: an interior-pointer
Leak Case legend:
- DR: 直接可达
- IR: 间接可达
- DL: 直接丢失
- IL: 间接丢失
- (y)XY: 如果内部指针是真正的指针
- (n)XY: 如果内部指针不是真正的指针
- (_)XY: 两种情况都不是

每种内存泄漏的情况都属于上边九种中的一种。Memcheck合并了一些情况进行输出,最后会输出4中结果:

  • 仍可以访问,包括上述的1、2。意味着BBB内存块仍然是可访问的,有可能在未来某个时刻被释放。
  • 绝对丢失,包括上述第3种情况。BBB无法再被访问,确认泄漏。
  • 间接丢失,包括上述第4、9种情况。由于AAA无法被访问,AAA和BBB都无法被正常释放。
  • 可能丢失,包括上述第5-8中情况。

在存在内存泄漏的情况下,Memcheck会反馈一张表:

LEAK SUMMARY:
	definitely lost: 48 bytes in 3 blocks.
	indirectly lost: 32 bytes in 2 blocks.
	possibly lost: 96 bytes in 6 blocks.
	still reachable: 64 bytes in 4 blocks.
	suppressed: 0 bytes in 0 blocks.	

如果指定了–leak-check = full,则Memcheck将提供每个绝对丢失或可能丢失的块的详细信息,包括分配位置。(实际上,它会将泄漏类型相同且堆栈跟踪充分相似的所有块的结果合并到单个“丢失记录”中。–leak-resolution使您可以控制“充分相似”的含义。)它无法告诉你何时,如何或为什么丢失指向泄漏块的指针;您必须自己解决。通常,你应该尝试确保你的程序在退出时没有任何绝对丢失或可能丢失的块。

8 bytes in 1 blocks are definitely lost in loss record 1 of 14
at 0x........: malloc (vg_replace_malloc.c:...)
by 0x........: mk (leak-tree.c:11)
by 0x........: main (leak-tree.c:39)
88 (8 direct, 80 indirect) bytes in 1 blocks are definitely lost in loss record 13 of 14
at 0x........: malloc (vg_replace_malloc.c:...)
by 0x........: mk (leak-tree.c:11)
by 0x........: main (leak-tree.c:25)

内存检测实验

以C语言demo,展示可能会导致上述Memcheck报错的情况。

非法读写

概述

非法读写是

实验代码

#include <stdio.h>

int main(int argc, char **argv)
{
    int *ptr = NULL;

    printf("read ptr: %d\n",*ptr);
    return 0;
}

实验过程

  1. 编译程序,然后运行

    gcc memcheck.c -o memcheck
    valgrind ./memcheck  
    
  2. 这个程序因为指针没有初始化绑定,因此一定会产生一个“segmentation fault”,我们这里执行完,memcheck结果如下:

    ╭─ caeri@ubuntu  ~/lzp/program_C/valgrind  INSERT                                                                                                                                             127 ↵
    ╰─ valgrind ./memcheck
    ==5660== Memcheck, a memory error detector
    ==5660== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
    ==5660== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
    ==5660== Command: ./memcheck
    ==5660==
    ==5660== Invalid read of size 4
    ==5660==    at 0x109168: main (in /home/caeri/lzp/program_C/valgrind/memcheck)
    ==5660==  Address 0x0 is not stack'd, malloc'd or (recently) free'd
    ==5660==
    ==5660==
    ==5660== Process terminating with default action of signal 11 (SIGSEGV)
    ==5660==  Access not within mapped region at address 0x0
    ==5660==    at 0x109168: main (in /home/caeri/lzp/program_C/valgrind/memcheck)
    ==5660==  If you believe this happened as a result of a stack
    ==5660==  overflow in your program's main thread (unlikely but
    ==5660==  possible), you can try to increase the size of the
    ==5660==  main thread stack using the --main-stacksize= flag.
    ==5660==  The main thread stack size used in this run was 8388608.
    ==5660==
    ==5660== HEAP SUMMARY:
    ==5660==     in use at exit: 0 bytes in 0 blocks
    ==5660==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
    ==5660==
    ==5660== All heap blocks were freed -- no leaks are possible
    ==5660==
    ==5660== For lists of detected and suppressed errors, rerun with: -s
    ==5660== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
    
  3. 这里首先可以看到在输出8-10行中,出现的非法读取提示,提示地址”0x0不属于栈(当然不属于),malloc申请的内存,或者是已经被释放掉了“,其实这里的指针直接是NULL,明显指针有问题。

    ==5660== Invalid read of size 4
    ==5660==    at 0x109168: main (in /home/caeri/lzp/program_C/valgrind/memcheck)
    ==5660==  Address 0x0 is not stack'd, malloc'd or (recently) free'd
    

结论

  • 指针指向的地址非法会导致“Invalid read of size 4”错误;

使用未初始化数据

概述

实验代码

#include <stdio.h>

int main(int argc, char **argv)
{
    int val;

    printf("read ptr: %d\n",val);
    return 0;
}

实验过程

  1. 编译程序,然后运行

    gcc memcheck.c -o memcheck
    valgrind ./memcheck  
    
  2. 这里我们的局部变量val没有初始化,Memcheck结果如下:

    ╭─ caeri@ubuntu  ~/lzp/program_C/valgrind  INSERT                                                                                                                                     SIGSEGV(11) ↵
    ╰─ valgrind --read-var-info=yes ./memcheck
    ==6920== Memcheck, a memory error detector
    ==6920== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
    ==6920== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
    ==6920== Command: ./memcheck
    ==6920==
    ==6920== Conditional jump or move depends on uninitialised value(s)
    ==6920==    at 0x48D9958: __vfprintf_internal (vfprintf-internal.c:1687)
    ==6920==    by 0x48C3D3E: printf (printf.c:33)
    ==6920==    by 0x109171: main (in /home/caeri/lzp/program_C/valgrind/memcheck)
    ==6920==
    ==6920== Use of uninitialised value of size 8
    ==6920==    at 0x48BD69B: _itoa_word (_itoa.c:179)
    ==6920==    by 0x48D9574: __vfprintf_internal (vfprintf-internal.c:1687)
    ==6920==    by 0x48C3D3E: printf (printf.c:33)
    ==6920==    by 0x109171: main (in /home/caeri/lzp/program_C/valgrind/memcheck)
    ==6920==
    ==6920== Conditional jump or move depends on uninitialised value(s)
    ==6920==    at 0x48BD6AD: _itoa_word (_itoa.c:179)
    ==6920==    by 0x48D9574: __vfprintf_internal (vfprintf-internal.c:1687)
    ==6920==    by 0x48C3D3E: printf (printf.c:33)
    ==6920==    by 0x109171: main (in /home/caeri/lzp/program_C/valgrind/memcheck)
    ==6920==
    ==6920== Conditional jump or move depends on uninitialised value(s)
    ==6920==    at 0x48DA228: __vfprintf_internal (vfprintf-internal.c:1687)
    ==6920==    by 0x48C3D3E: printf (printf.c:33)
    ==6920==    by 0x109171: main (in /home/caeri/lzp/program_C/valgrind/memcheck)
    ==6920==
    ==6920== Conditional jump or move depends on uninitialised value(s)
    ==6920==    at 0x48D96EE: __vfprintf_internal (vfprintf-internal.c:1687)
    ==6920==    by 0x48C3D3E: printf (printf.c:33)
    ==6920==    by 0x109171: main (in /home/caeri/lzp/program_C/valgrind/memcheck)
    ==6920==
    read ptr: 0
    ==6920==
    ==6920== HEAP SUMMARY:
    ==6920==     in use at exit: 0 bytes in 0 blocks
    ==6920==   total heap usage: 1 allocs, 1 frees, 1,024 bytes allocated
    ==6920==
    ==6920== All heap blocks were freed -- no leaks are possible
    ==6920==
    ==6920== Use --track-origins=yes to see where uninitialised values come from
    ==6920== For lists of detected and suppressed errors, rerun with: -s
    ==6920== ERROR SUMMARY: 5 errors from 5 contexts (suppressed: 0 from 0)
    
  3. 首先,这里打印“Conditional jump or move depends on uninitialised value(s)”,可以看到在调用printf之后,调用了__vfprintf_internal,这里会检测参数的值,因此在这里会报错。

    ==6920== Conditional jump or move depends on uninitialised value(s)
    ==6920==    at 0x48D9958: __vfprintf_internal (vfprintf-internal.c:1687)
    ==6920==    by 0x48C3D3E: printf (printf.c:33)
    ==6920==    by 0x109171: main (in /home/caeri/lzp/program_C/valgrind/memcheck)
    
  4. 接下来,会提示“Use of uninitialised value of size 8”,这是因为我们未初始化变量val导致的。

    ==6920== Use of uninitialised value of size 8
    ==6920==    at 0x48BD69B: _itoa_word (_itoa.c:179)
    ==6920==    by 0x48D9574: __vfprintf_internal (vfprintf-internal.c:1687)
    ==6920==    by 0x48C3D3E: printf (printf.c:33)
    ==6920==    by 0x109171: main (in /home/caeri/lzp/program_C/valgrind/memcheck)
    

结论

  • 使用未初始化的局部变量会导致“Use of uninitialised value of size 8”;
  • 使用未初始化的全局变量不会导致上述问题。这里因为局部变量在栈,不会被初始化,而全局变量会被自动初始化为0,所以这里的val如果定义在main函数外边,就不会报错。

系统调用中未初始化或不可寻址的数据

概述

使用系统调用时,写入方向的参数使用了未初始化的变量。例如write写入的内容为未初始化。

实验代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    int fd = 1;
    char * buf = malloc(10);
    write(fd, buf, 10);
    free(buf);
    return 0;
}

实验过程

  1. 编译,执行后结果如下

    ╭─ caeri@ubuntu  ~/lzp/program_C/valgrind  INSERT                                                                                                                                                 ✔
    ╰─ valgrind --read-var-info=yes ./memcheck
    ==9476== Memcheck, a memory error detector
    ==9476== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
    ==9476== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
    ==9476== Command: ./memcheck
    ==9476==
    ==9476== Syscall param write(buf) points to uninitialised byte(s)
    ==9476==    at 0x4970077: write (write.c:26)
    ==9476==    by 0x1091C6: main (in /home/caeri/lzp/program_C/valgrind/memcheck)
    ==9476==  Address 0x4a57040 is 0 bytes inside a block of size 10 alloc'd
    ==9476==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
    ==9476==    by 0x1091AC: main (in /home/caeri/lzp/program_C/valgrind/memcheck)
    ==9476==
    ==9476==
    ==9476== HEAP SUMMARY:
    ==9476==     in use at exit: 0 bytes in 0 blocks
    ==9476==   total heap usage: 1 allocs, 1 frees, 10 bytes allocated
    ==9476==
    ==9476== All heap blocks were freed -- no leaks are possible
    ==9476==
    ==9476== Use --track-origins=yes to see where uninitialised values come from
    ==9476== For lists of detected and suppressed errors, rerun with: -s
    ==9476== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
    
  2. 这里我们向stdout写入了一个malloc回来的buf,显然这时候buf里的值是随机的,因此会触发这个错误。

    ==9476== Syscall param write(buf) points to uninitialised byte(s)
    ==9476==    at 0x4970077: write (write.c:26)
    ==9476==    by 0x1091C6: main (in /home/caeri/lzp/program_C/valgrind/memcheck)
    ==9476==  Address 0x4a57040 is 0 bytes inside a block of size 10 alloc'd
    ==9476==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
    ==9476==    by 0x1091AC: main (in /home/caeri/lzp/program_C/valgrind/memcheck)
    

结论

  • 在使用系统调用接口时,传入了未初始化的变量,会导致“Syscall param write(buf) points to uninitialised byte(s)”

非法释放

概述

申请的内存被错误(重复)释放。

实验代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    int fd = 1;
    char * buf = malloc(10);
    write(fd, buf, 10);
    free(buf);
    free(buf);
    return 0;
}

实验过程

  1. 编译后,执行结果如下:

    ╭─ caeri@ubuntu  ~/lzp/program_C/valgrind  INSERT                                                                                                                                                 ✔
    ╰─ valgrind --read-var-info=yes ./memcheck
    ==9848== Memcheck, a memory error detector
    ==9848== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
    ==9848== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
    ==9848== Command: ./memcheck
    ==9848==
    ==9848== Invalid free() / delete / delete[] / realloc()
    ==9848==    at 0x483CA3F: free (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
    ==9848==    by 0x1091A8: main (in /home/caeri/lzp/program_C/valgrind/memcheck)
    ==9848==  Address 0x4a57040 is 0 bytes inside a block of size 10 free'd
    ==9848==    at 0x483CA3F: free (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
    ==9848==    by 0x10919C: main (in /home/caeri/lzp/program_C/valgrind/memcheck)
    ==9848==  Block was alloc'd at
    ==9848==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
    ==9848==    by 0x10918C: main (in /home/caeri/lzp/program_C/valgrind/memcheck)
    ==9848==
    ==9848==
    ==9848== HEAP SUMMARY:
    ==9848==     in use at exit: 0 bytes in 0 blocks
    ==9848==   total heap usage: 1 allocs, 2 frees, 10 bytes allocated
    ==9848==
    ==9848== All heap blocks were freed -- no leaks are possible
    ==9848==
    ==9848== For lists of detected and suppressed errors, rerun with: -s
    ==9848== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
    

结论

  • 重复free同一个block会导致“Invalid free() / delete / delete[] / realloc()”;

  • 释放无效的block也会导致“Invalid free() / delete / delete[] / realloc()”,例如

    char buf[1] = {0};
    free(buf);
    

申请内存被错误释放

概述

这个主要是针对在C++中,不同的申请内存的方式new/new[]需要不同的delete/delete[]的方式进行释放。

源地址与目标地址重叠

概述

用来调试在代码中,出现的memcpy等类似函数的源与目标重叠的情况。

但是,很抱歉的是,在实际测试中,即使我的代码出现了重叠情况,但是valgrind没有按照预想的那样扑捉到。以下是这次失败的实验。

实验代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char **argv)
{
    char buf[] = "This is a overlapping.";
    printf("before: %s\n", buf);
    memcpy(buf+3, buf, 20);
    printf("after: %s\n", buf);
    return 0;
}

实验过程

  1. 编译程序

    gcc -o memcheck memcheck.c
    
  2. 程序直接执行结果如下:

    明显可以看出来,在buf复制过程中,出现了重叠部分,即前3个字节被重复复制。

    before: This is a overlapping.
    after: ThiThis is a overlappin
    
  3. 我们再继续使用Valgrind,我们期望在这里看到相关“Source and destination overlap in memcpy”这样子的打印。执行结果如下:

    ╭─ caeri@ubuntu  ~/lzp/program_C/valgrind  INSERT                                                                                                                                                 ✔
    ╰─ valgrind ./memcheck
    ==13227== Memcheck, a memory error detector
    ==13227== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
    ==13227== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
    ==13227== Command: ./memcheck
    ==13227==
    before: This is a overlapping.
    ==13227== Conditional jump or move depends on uninitialised value(s)
    ==13227==    at 0x483EF58: strlen (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
    ==13227==    by 0x48DAD14: __vfprintf_internal (vfprintf-internal.c:1688)
    ==13227==    by 0x48C3D3E: printf (printf.c:33)
    ==13227==    by 0x109223: main (in /home/caeri/lzp/program_C/valgrind/memcheck)
    ==13227==
    after: ThiThis is a overlappin
    ==13227==
    ==13227== HEAP SUMMARY:
    ==13227==     in use at exit: 0 bytes in 0 blocks
    ==13227==   total heap usage: 1 allocs, 1 frees, 1,024 bytes allocated
    ==13227==
    ==13227== All heap blocks were freed -- no leaks are possible
    ==13227==
    ==13227== Use --track-origins=yes to see where uninitialised values come from
    ==13227== For lists of detected and suppressed errors, rerun with: -s
    ==13227== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
    
  4. 很遗憾的是,不清楚哪儿出了问题,Valgrind将错误认为是“Conditional jump or move depends on uninitialised value(s)”。而这个结果一般是因为变量未初始化。

  5. 到这里,这个失败的实验先告一段落。后续碰到触发了重叠的警告的时候,再回来补充。

结论

  • 无

可疑参数

概述

这里的可疑参数,指的是在申请内存的时候,传入的内存大小为负数导致的错误。

实验代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char **argv)
{
    char *buf = malloc(atoi(argv[1]));
    free(buf);
    return 0;
}

实验过程

  1. 编译程序,然后运行

    gcc -o memcheck memcheck.c
    
  2. 这里我们传入参数为-1,以触发valgrind报警。运行结果如下:

    ╭─ caeri@ubuntu  ~/lzp/program_C/valgrind  INSERT                                                                                                                                       SIGINT(2) ↵
    ╰─ valgrind ./memcheck -1
    ==19332== Memcheck, a memory error detector
    ==19332== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
    ==19332== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
    ==19332== Command: ./memcheck -1
    ==19332==
    ==19332== Argument 'size' of function malloc has a fishy (possibly negative) value: -1
    ==19332==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
    ==19332==    by 0x1091B8: main (in /home/caeri/lzp/program_C/valgrind/memcheck)
    ==19332==
    ==19332==
    ==19332== HEAP SUMMARY:
    ==19332==     in use at exit: 0 bytes in 0 blocks
    ==19332==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
    ==19332==
    ==19332== All heap blocks were freed -- no leaks are possible
    ==19332==
    ==19332== For lists of detected and suppressed errors, rerun with: -s
    ==19332== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
    
  3. 可以在结果中看到,这里提示传入的参数为fishy value;

    ==19332== Argument 'size' of function malloc has a fishy (possibly negative) value: -1
    ==19332==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
    ==19332==    by 0x1091B8: main (in /home/caeri/lzp/program_C/valgrind/memcheck)
    

结论

  • malloc、calloc等申请内存的接口传入负数参数时,会触发fishy value;

内存泄漏

#内存泄漏# #Valgrind#
微信扫一扫关注

声明:内存调试工具Valgrind之Memcheck

链接:https://mdxz2048.github.io/post/study_valgrind_memcheck/

作者:MD小智

声明: 本博客文章除特别声明外,均采用 CC BY-NC-SA 3.0许可协议,转载请注明出处!

创作实属不易,如有帮助,那就打赏博主些许茶钱吧 ^_^
WeChat Pay

微信打赏

Alipay

支付宝打赏

自上而下理解内核网络(五)---网络层(IP协议)数据的接收与发送
ASN1C使用手册
  • 文章目录
  • 站点概览
MD小智

MD小智

我们会高估自己所拥有的一切!

39 日志
5 分类
48 标签
GitHub
标签云
  • 编码
  • 自上而下理解内核网络
  • 文件同步
  • 12306bypass
  • Asn.1
  • Asn1c编译器
  • Chatgpt
  • D触发器
  • Ean13
  • Gdb交叉编译
  • 介绍
  • 交叉编译
  • Valgrind-Memcheck说明
    • 非法读写Illegal read / Illegal write errors
    • 使用未初始化数据 Use of uninitialised values
    • 系统调用中未初始化或不可寻址的数据Use of uninitialised or unaddressable values insystem calls
    • 非法释放Illegal frees
    • 申请内存被错误的释放
    • 在C++中,内存的申请方式与释放方式兼容非常重要。处理原则:
    • 源地址和目标地址存在重叠
    • 可疑参数
    • 内存泄漏
  • 内存检测实验
    • 非法读写
    • 使用未初始化数据
    • 系统调用中未初始化或不可寻址的数据
    • 非法释放
    • 申请内存被错误释放
    • 源地址与目标地址重叠
    • 可疑参数
    • 内存泄漏
© 2010 - 2024 MD小智
Powered by - Hugo v0.96.0 / Theme by - NexT
/
Storage by GitHub / MD小智
0%