valgrind 的使用

2019-05-11  本文已影响0人  liualiu

Valgrind

Valgrind 原理

valgrind 是一个提供了一些 debug 和优化的工具的工具箱,可以使得你的程序减少内存泄漏或者错误访问.
valgrind 默认使用 memcheck 去检查内存问题.

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


valgrind.jpg

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

  1. valid-value map:
    对于进程的整个地址空间中的每一个字节(byte),都有与之对应的 8 个 bits;对于 CPU 的每个寄存器,也有一个与之对应的 bit 向量。这些 bits 负责记录该字节或者寄存器值是否具有有效的、已初始化的值。
  2. valid-address map
    对于进程整个地址空间中的每一个字节(byte),还有与之对应的 1 个 bit,负责记录该地址是否能够被读写。

检测原理:

Quick start

使用valgrind 很简单, 首先编译好要测试的程序 (为了使valgrind发现的错误更精确,如能够定位到源代码行,建议在编译时加上-g参数,编译优化选项请选择O0,虽然这会降低程序的执行效率。), 假设运行这个程序的命令是

./a.out arg1 arg2

那么要使用 valgrind 的话只需要运行

valgrind --leak-check=yes ./a.out arg1 arg2

就可以了.

valgrind的输出也很好看得懂, 例如下面这个 C 程序

  #include <stdlib.h>

  void f(void)
  {
     int* x = malloc(10 * sizeof(int));
     x[10] = 0;        // problem 1: heap block overrun
  }                    // problem 2: memory leak -- x not freed

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

valgrind 的输出为

liu@liu ~> valgrind --leak-check=yes ./a.out 
==4372== Memcheck, a memory error detector
==4372== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==4372== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==4372== Command: ./a.out
==4372== 
==4372== Invalid write of size 4
==4372==    at 0x400504: f (in /home/liu/a.out)
==4372==    by 0x400523: main (in /home/liu/a.out)
==4372==  Address 0x51fa068 is 0 bytes after a block of size 40 alloc'd
==4372==    at 0x4C2EBAB: malloc (vg_replace_malloc.c:299)
==4372==    by 0x4004F7: f (in /home/liu/a.out)
==4372==    by 0x400523: main (in /home/liu/a.out)
==4372== 
==4372== 
==4372== HEAP SUMMARY:
==4372==     in use at exit: 40 bytes in 1 blocks
==4372==   total heap usage: 1 allocs, 0 frees, 40 bytes allocated
==4372== 
==4372== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==4372==    at 0x4C2EBAB: malloc (vg_replace_malloc.c:299)
==4372==    by 0x4004F7: f (in /home/liu/a.out)
==4372==    by 0x400523: main (in /home/liu/a.out)
==4372== 
==4372== LEAK SUMMARY:
==4372==    definitely lost: 40 bytes in 1 blocks
==4372==    indirectly lost: 0 bytes in 0 blocks
==4372==      possibly lost: 0 bytes in 0 blocks
==4372==    still reachable: 0 bytes in 0 blocks
==4372==         suppressed: 0 bytes in 0 blocks
==4372== 
==4372== For counts of detected and suppressed errors, rerun with: -v
==4372== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

Valgrind 分析常见的内存问题

Used in TM

在 debug 的过程中,gdb 和程序崩溃的时候显示的信息都具有一定的误导性,但是valgrind 对于查找bug的帮助很大, 按照程序的逻辑,每秒种将 struct 结构序列化成 json 格式并保存到文件中去(保存一次打印一个 tick ),然后十秒之后终止。但是我们可以看到,这里第三次打印的时候就崩溃了。系统提示是(address boundary error)。

[图片上传中...(Selection_001.png-27236b-1557573570699-0)]

讲道理,我们现在应该使用 gdb 来看一下崩溃处的错误吧

Selection_002.png

然后可以发现是 malloc 函数里面崩溃了,这看不出什么东西,再继续查看一下函数调用的栈

Selection_003.png

然后我们就发现了其实是 jansson 库的 new 的时候的 bug,然后 debug 就自然而然的跑偏了

在这个时候就可以使用 valgrind 了:

valgrind --leak-check=yes ./p2p/test/addressbook_test
Selection_004.png

在这个打印出来的信息我们可以看到,json_decref invalid read 了内存了,说明 json_decref 的参数(一个 json_t 的对象)已经提前释放了。
然后根据提示,找到了 pear_address_book.c:470 行的代码:

pr_save_json_to_file(json, file_path);
json_decref(json);
g_ptr_array_unref(addr_book_json->addrs);

然后进去 pr_save_json_to_file(json, file_path) 里面看了一下,里面已经 对 json 对象 调用了 json_decref 了,所以有两次释放。

valgrind 的一些问题

对于下列的代码,valgrind 就会报出内存内存泄漏, 但是将代码 g_ptr_array_free(b,0); 改成 g_ptr_array_unref(b); 就完全没问题,虽然没有设定释放函数,也没有释放。

#include <glib.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
        GPtrArray* a = g_ptr_array_new_with_free_func(g_free);
        GPtrArray* b = g_ptr_array_new();
        for(int i=0;i<5;i++){
                int * t = g_new(int,1);
                g_ptr_array_add(a, t);
                g_ptr_array_add(b, t);
        }
        g_ptr_array_unref(a);
        g_ptr_array_free(b,0);
        return 0;
}

Selection_005.png
上一篇下一篇

猜你喜欢

热点阅读