一、valgrind简介
Valgrind是一款用于内存调试、内存泄漏检测以及性能分析、检测线程错误的软件开发工具。
Valgrind一般包含下列工具:
1.Memcheck
最常用的工具,用来检测程序中出现的内存问题,所有对内存的读写都会被检测到,一切对malloc()/free()/new/delete的调用都会被捕获。所以,它能检测以下问题:
1.对未初始化内存的使用;
2.读/写释放后的内存块;
3.读/写超出malloc分配的内存块;
4.读/写不适当的栈中内存块;
5.内存泄漏,指向一块内存的指针永远丢失;
6.不正确的malloc/free或new/delete匹配;
7,memcpy()相关函数中的dst和src指针重叠。
这些问题往往是C/C++程序员最头疼的问题,Memcheck在这里帮上了大忙。
memcheck 工具的常用选型
1、leak-check
–leak-check=<no|summary|yes|full> [default: summary]
用于控制内存泄漏检测力度。
no,不检测内存泄漏;
summary,仅报告总共泄漏的数量,不报告具体泄漏位置;
yes/full,报告泄漏总数、泄漏的具体位置。
2、show-reachable
–show-reachable=<yes|no> [default: no]
用于控制是否检测控制范围之外的泄漏,比如全局指针、static指针等。
3、undef-value-errors
–undef-value-errors=<yes|no> [default: yes]
用于控制是否检测代码中使用未初始化变量的情况。
4、其他选项
–log-file=filename 将结果输出到文件。
–log-socket=192.168.0.1:12345 输出到网络。
–trace-children=<yes|no> [default: no]
–track-fds=<yes|no> [default: no]
–log-fd=<number> [default: 2, stderr]
–xml=<yes|no> [default: no]
–num-callers=<number> [default: 12]
–show-below-main=<yes|no> [default: no]
内存检查选项:
–leak-check=<no|summary|yes|full> [default: summary]
当这个选项打开时,当客户程序结束时查找内存泄漏。内存泄漏意味着有用malloc分配内存块,但是没有用free释放,而且没有指针指向这块内存。这样的内存块永远不能被程序释放,因为没有指针指向它们。如果设置为summary,Valgrind会报告有多少内存泄漏发生了。如果设置为full或yes,Valgrind给出每一个独立的泄漏的详细信息。
–show-reachable=<yes|no> [default: no]
当这个选项关闭时,内存泄漏检测器只显示没有指针指向的内存块,或者只能找到指向块中间的指针。当这个选项打开时,内存泄漏检测器还报告有指针指向的内存块。这些块是最有可能出现内存泄漏的地方。你的程序可能,至少在原则上,应该在退出前释放这些内存块。这些有指针指向的内存块和没有指针指向的内存块,或者只有内部指针指向的块,都可能产生内存泄漏,因为实际上没有一个指向块起始的指针可以拿来释放,即使你想去释放它。
–leak-resolution=<low|med|high> [default: low]
在做内存泄漏检查时,确定memcheck将怎么样考虑不同的栈是相同的情况。当设置为low时,只需要前两层栈匹配就认为是相同的情况;当设置为med,必须要四层栈匹配,当设置为high时,所有层次的栈都必须匹配。对于hardcore内存泄漏检查,你很可能需要使用–leak-resolution=high和–num-callers=40或者更大的数字。注意这将产生巨量的信息,这就是为什么默认选项是四个调用者匹配和低分辨率的匹配。注意–leak-resolution= 设置并不影响memcheck查找内存泄漏的能力。它只是改变了结果如何输出。
–freelist-vol=<number> [default: 5000000]
当客户程序使用free(C中)或者delete(C++)释放内存时,这些内存并不是马上就可以用来再分配的。这些内存将被标记为不可访问的,并被放到一个已释放内存的队列中。这样做的目的是,使释放的内存再次被利用的点尽可能的晚。这有利于memcheck在内存块释放后这段重要的时间检查对块不合法的访问。这个选项指定了队列所能容纳的内存总容量,以字节为单位。默认的值是5000000字节。增大这个数目会增加memcheck使用的内存,但同时也增加了对已释放内存的非法使用的检测概率。
–workaround-gcc296-bugs=<yes|no> [default: no]
当这个选项打开时,假定读写栈指针以下的一小段距离是gcc 2.96的bug,并且不报告为错误。距离默认为256字节。注意gcc 2.96是一些比较老的Linux发行版(RedHat 7.X)的默认编译器,所以你可能需要使用这个选项。如果不是必要请不要使用这个选项,它可能会使一些真正的错误溜掉。一个更好的解决办法是使用较新的,修正了这个bug的gcc/g++版本。
–partial-loads-ok=<yes|no> [default: no]
控制memcheck如何处理从地址读取时字长度,字对齐,因此哪些字节是可以寻址的,哪些是不可以寻址的。当设置为yes是,这样的读取并不抛出一个寻址错误。而是从非法地址读取的V字节显示为未定义,访问合法地址仍然是像平常一样映射到内存。设置为no时,从部分错误的地址读取与从完全错误的地址读取同样处理:抛出一个非法地址错误,结果的V字节显示为合法数据。注意这种代码行为是违背ISO C/C++标准,应该被认为是有问题的。如果可能,这种代码应该修正。这个选项应该只是做为一个最后考虑的方法。
–undef-value-errors=<yes|no> [default: yes]
控制memcheck是否检查未定义值的危险使用。当设为yes时,Memcheck的行为像Addrcheck, 一个轻量级的内存检查工具,是Valgrind的一个部分,它并不检查未定义值的错误。使用这个选项,如果你不希望看到未定义值错误。
2.Callgrind
和gprof类似的分析工具,但它对程序的运行观察更是入微,能给我们提供更多的信息。和gprof不同,它不需要在编译源代码时附加特殊选项,但加上调试选项是推荐的。Callgrind收集程序运行时的一些数据,建立函数调用关系图,还可以有选择地进行cache模拟。在运行结束时,它会把分析数据写入一个文件。callgrind_annotate可以把这个文件的内容转化成可读的形式。
3.Cachegrind
Cache分析器,它模拟CPU中的一级缓存I1,Dl和二级缓存,能够精确地指出程序中cache的丢失和命中。如果需要,它还能够为我们提供cache丢失次数,内存引用次数,以及每行代码,每个函数,每个模块,整个程序产生的指令数。这对优化程序有很大的帮助。
4.Helgrind
它主要用来检查多线程程序中出现的竞争问题。Helgrind寻找内存中被多个线程访问,而又没有一贯加锁的区域,这些区域往往是线程之间失去同步的地方,而且会导致难以发掘的错误。Helgrind实现了名为“Eraser”的竞争检测算法,并做了进一步改进,减少了报告错误的次数。不过,Helgrind仍然处于实验阶段。
5. Massif
堆栈分析器,它能测量程序在堆栈中使用了多少内存,告诉我们堆块,堆管理块和栈的大小。Massif能帮助我们减少内存的使用,在带有虚拟内存的现代系统中,它还能够加速我们程序的运行,减少程序停留在交换区中的几率。
此外,lackey和nulgrind也会提供。Lackey是小型工具,很少用到;Nulgrind只是为开发者展示如何创建一个工具。
二、安装valgrind
wget https://sourceware.org/pub/valgrind/valgrind-3.15.0.tar.bz2
:~/Download$ tar -xjvf valgrind-3.15.0.tar.bz2
:~/Download$ cd valgrind-3.15.0/
因为valgrind支持多个平台,根据当前主机配置valgrind
:~/Download/valgrind-3.15.0$ ./configure
./configure之后就有makefile出现,接着就是make编译,安装
:~/Download/valgrind-3.15.0$ make
:~/Download/valgrind-3.15.0$ sudo make install
查看一下版本看看是否安装好
:~/Download/valgrind-3.15.0$ valgrind –version
valgrind-3.15.0
三、valgrind使用
1、内存泄漏
#include <stdio.h>
#include <stdlib.h>
void fun(void)
{
int *x = malloc(10*sizeof(int));
x [10] = 0; //问题1:堆块溢出
} //问题2:内存泄漏 - x未释放
int main(int argc, char **argv)
{
fun() ;
return 0 ;
}
编译程序-g以包含调试信息,以便Memcheck的错误消息包含确切的行号。
:~/c_code$ gcc -g valgrind_test.c -o valgrind_test
正常运行
:~/c_code$ ./valgrind_test
Memcheck是默认工具。–leak-check 选项打开详细的内存泄漏检测器。程序运行速度会比正常情况慢很多(例如20到30倍),并且会占用更多内存。Memcheck将发出有关内存错误和检测到的泄漏的消息。
:~/c_code$ valgrind –leak-check=yes ./valgrind_test
一开始是valgrind信息“==62414==”表示进程号
==62414== Memcheck, a memory error detector
==62414== Copyright (C) 2002-2017, and GNU GPL’d, by Julian Seward et al.
==62414== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==62414== Command: ./valgrind_test
程序访问非法地址的内存,无效写入
==62414== Invalid write of size 4
==62414== at 0x40054B: fun (valgrind_test.c:21)
==62414== by 0x400566: main (valgrind_test.c:28)
==62414== Address 0x5201068 is 0 bytes after a block of size 40 alloc’d
==62414== at 0x4C2AEC3: malloc (vg_replace_malloc.c:309)
==62414== by 0x40053E: fun (valgrind_test.c:20)
==62414== by 0x400566: main (valgrind_test.c:28)
==62414==
堆区情况:
==62414== HEAP SUMMARY:
==62414== in use at exit: 40 bytes in 1 blocks
==62414== total heap usage: 1 allocs, 0 frees, 40 bytes allocated
内存泄漏消息如下所示:
==62414== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==62414== at 0x4C2AEC3: malloc (vg_replace_malloc.c:309)
==62414== by 0x40053E: fun (valgrind_test.c:20)
==62414== by 0x400566: main (valgrind_test.c:28)
有几种泄漏; 两个最重要的类别是,肯定泄露(definitely lost),可能已经泄露(possibly lost)
==62414== LEAK SUMMARY:
==62414== definitely lost: 40 bytes in 1 blocks
==62414== indirectly lost: 0 bytes in 0 blocks
==62414== possibly lost: 0 bytes in 0 blocks
==62414== still reachable: 0 bytes in 0 blocks
==62414== suppressed: 0 bytes in 0 blocks
==62414==
==62414== For lists of detected and suppressed errors, rerun with: -s
==62414== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
2线程锁
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
void * thread_worker1(void *args) ;
typedef struct worker_ctx_s
{
int shared_var ;
pthread_mutex_t lock ;
} worker_ctx_t ;
void *thread_worker1(void *args)
{
worker_ctx_t *ctx = (worker_ctx_t *)args ;
if(!args)
{
printf("%s() get invalid arguments\n", __FUNCTION__) ; // __FUNCTION__ get function name
pthread_exit(NULL) ;
}
printf("Thread worker1 [%ld] start running...\n", pthread_self()) ;
/* 两次上锁导致死锁 */
pthread_mutex_lock(&(ctx->lock)) ;
pthread_mutex_lock(&(ctx->lock)) ;
pthread_mutex_unlock( &ctx->lock ) ;
sleep(1) ;
printf("Thread worker1 exit...\n") ;
pthread_exit(NULL) ;
return NULL ;
}
int main(int argc, char **argv)
{
worker_ctx_t worker_ctx ;
pthread_t tid ;
pthread_attr_t thread_attr ;
worker_ctx.shared_var = 1000 ;
pthread_mutex_init(&worker_ctx.lock, NULL) ;
if ( pthread_attr_init(&thread_attr) != 0 )
{
printf("pthread_attr_init() failed: %s\n", strerror(errno)) ;
return -1 ;
}
if ( pthread_attr_setstacksize( &thread_attr, 1024*120) != 0 )
{
printf("pthread_setstacksize() failure: %s\n", strerror(errno) ) ;
return -2 ;
}
if ( pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED) != 0 )
{
printf("pthread_attr_setdetachstate() error: %s\n", strerror(errno)) ;
return -3 ;
}
/* Create thread */
pthread_create(&tid, &thread_attr, thread_worker1, &worker_ctx);
if ( pthread_attr_destroy(&thread_attr) != 0 )
{
printf("pthread_attr_destroy() failed :%s\n", strerror(errno)) ;
return -4 ;
}
sleep(5) ;
return 0 ;
}
:~/c_code$ gcc -g valgrind_test_helgrand.c -o valgrind_test_helgrand -lpthread
:~/c_code$ valgrind –tool=helgrind ./valgrind_test_helgrand
==63432== Helgrind, a thread error detector
==63432== Copyright (C) 2007-2017, and GNU GPL’d, by OpenWorks LLP et al.
==63432== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==63432== Command: ./valgrind_test_helgrand
程序打印的消息
Thread worker1 [67376896] start running…
==63432== —Thread-Announcement——————————————
两个线程被创建
==63432== Thread #2 was created
==63432== at 0x5158FFE: clone (clone.S:74)
==63432== by 0x4E44199: do_clone.constprop.3 (createthread.c:75)
==63432== by 0x4E458BA: create_thread (createthread.c:245)
==63432== by 0x4E458BA: pthread_create@@GLIBC_2.2.5 (pthread_create.c:611)
==63432== by 0x4C31CBA: pthread_create_WRK (hg_intercepts.c:427)
==63432== by 0x4C32D98: pthread_create@* (hg_intercepts.c:460)
==63432== by 0x400CA2: main (valgrind_test_helgrand.c:84)
==63432==
==63432== —————————————————————-
尝试重新锁定本身已持有的非递归锁
==63432== Thread #2: Attempt to re-lock a non-recursive lock I already hold
==63432== at 0x4C2F259: mutex_lock_WRK (hg_intercepts.c:899)
==63432== by 0x4C3317D: pthread_mutex_lock (hg_intercepts.c:925)
==63432== by 0x400B5C: thread_worker1 (valgrind_test_helgrand.c:44)
==63432== by 0x4C31EAE: mythread_wrapper (hg_intercepts.c:389)
==63432== by 0x4E45183: start_thread (pthread_create.c:312)
==63432== by 0x515903C: clone (clone.S:111)
锁之前已经获取过
==63432== Lock was previously acquired
==63432== at 0x4C2F320: mutex_lock_WRK (hg_intercepts.c:909)
==63432== by 0x4C3317D: pthread_mutex_lock (hg_intercepts.c:925)
==63432== by 0x400B4C: thread_worker1 (valgrind_test_helgrand.c:43)
==63432== by 0x4C31EAE: mythread_wrapper (hg_intercepts.c:389)
==63432== by 0x4E45183: start_thread (pthread_create.c:312)
==63432== by 0x515903C: clone (clone.S:111)
==63432==
==63432== —————————————————————-
退出线程时任保持一个锁
==63432== Thread #2: Exiting thread still holds 1 lock
==63432== at 0x4E4BF1C: __lll_lock_wait (lowlevellock.S:135)
==63432== by 0x4E47648: _L_lock_909 (pthread_mutex_lock.c:151)
==63432== by 0x4E4746E: pthread_mutex_lock (pthread_mutex_lock.c:79)
==63432== by 0x4C2F2BB: mutex_lock_WRK (hg_intercepts.c:902)
==63432== by 0x4C3317D: pthread_mutex_lock (hg_intercepts.c:925)
==63432== by 0x400B5C: thread_worker1 (valgrind_test_helgrand.c:44)
==63432== by 0x4C31EAE: mythread_wrapper (hg_intercepts.c:389)
==63432== by 0x4E45183: start_thread (pthread_create.c:312)
==63432== by 0x515903C: clone (clone.S:111)
==63432==
==63432==
==63432== Use –history-level=approx or =none to gain increased speed, at
==63432== the cost of reduced accuracy of conflicting-access information
==63432== For lists of detected and suppressed errors, rerun with: -s
==63432== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 26 from 25)