介绍
Valgrind是一款用于内存调试、内存泄漏检测以及性能分析的软件开发工具。
交叉编译
开发主机 | Ubuntu20.04(x86_64) |
---|---|
目标主机 | ARM64(aarch64) |
在这里下载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
导出交叉编译工具链到当前环境;
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
在开发主机新建一个安装文件夹,该文件夹路径应与以后在开发板上安装valgrind的路径保持一致,我们这里新建如下:
make -p /caeridata/bin/lzp/valgrind
这里说明,未来在目标板(开发板)上,要将valgrind安装在目录
/caeridata/bin/lzp/valgrind
下。执行如下指令
./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 安装目录,注意要保证有写入权限;
执行
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'
编译完成后,需要进行
make install
,我这里因为使用了非root用户编译且安装目录在/caerdata…
因此我这里安装的时候需要切换到root用户,且需要重新导出交叉编译工具链。这里就不进行演示了。安装完成后,效果如下:
root@ubuntu:/caeridata/bin/lzp/valgrind# ls bin include lib libexec share
接下来将整个文件夹打包,然后复制到开发板解压
#在开发主机上,打包 tar cvf valgrind.tar valgrind #在开发板上,解压到/caeridata/bin/lzp/ tar -xvf valgrind.tar -C /caeridata/bin/lzp/
最后,在开发板上的效果如下:
root@imx8qxpc0mek:/caeridata/bin/lzp/valgrind# ls bin include lib libexec share
到这里安装就完成了,为了方便使用,可以在开发板上
/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;
}
实验过程
编译程序,然后运行
gcc memcheck.c -o memcheck valgrind ./memcheck
这个程序因为指针没有初始化绑定,因此一定会产生一个“
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)
这里首先可以看到在输出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;
}
实验过程
编译程序,然后运行
gcc memcheck.c -o memcheck valgrind ./memcheck
这里我们的局部变量
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)
首先,这里打印“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)
接下来,会提示“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;
}
实验过程
编译,执行后结果如下
╭─ 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)
这里我们向
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;
}
实验过程
编译后,执行结果如下:
╭─ 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;
}
实验过程
编译程序
gcc -o memcheck memcheck.c
程序直接执行结果如下:
明显可以看出来,在buf复制过程中,出现了重叠部分,即前3个字节被重复复制。
before: This is a overlapping. after: ThiThis is a overlappin
我们再继续使用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)
很遗憾的是,不清楚哪儿出了问题,Valgrind将错误认为是“Conditional jump or move depends on uninitialised value(s)”。而这个结果一般是因为变量未初始化。
到这里,这个失败的实验先告一段落。后续碰到触发了重叠的警告的时候,再回来补充。
结论
- 无
可疑参数
概述
这里的可疑参数,指的是在申请内存的时候,传入的内存大小为负数导致的错误。
实验代码
#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;
}
实验过程
编译程序,然后运行
gcc -o memcheck memcheck.c
这里我们传入参数为-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)
可以在结果中看到,这里提示传入的参数为
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
;