valgrind 详解
一、概述
Valgrind 是一套 Linux 下,開放源代碼(GPL V2)的仿真調試工具的集合。
Valgrind 由內核(core)以及基于內核的其他調試工具組成。內核類似于一個框架(framework),它模擬了一個 CPU 環境,并提供服務給其他工具;而其他工具則類似于插件 (plug-in),利用內核提供的服務完成各種特定的內存調試任務。
Valgrind的體系結構如下圖所示:
二、包含的工具
Valgrind 的最新版是3.11.0,它一般包含下列工具:
1、Memcheck
最常用的工具,用來檢測程序中出現的內存問題,所有對內存的讀寫都會被檢測到,一切對malloc() / free() / new / delete 的調用都會被捕獲。所以,它能檢測以下問題:
- 對未初始化內存的使用;
- 讀/寫釋放后的內存塊;
- 讀/寫超出malloc分配的內存塊;
- 讀/寫不適當的棧中內存塊;
- 內存泄漏,指向一塊內存的指針永遠丟失;
- 不正確的malloc/free或new/delete匹配;
- memcpy()相關函數中的dst和src指針重疊。
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 只是為開發者展示如何創建一個工具。
三、原理
Memcheck 能夠檢測出內存問題,關鍵在于其建立了兩個全局表。
(1)Valid-Value 表
對于進程的整個地址空間中的每一個字節(byte),都有與之對應的 8 個 bits;對于 CPU 的每個寄存器,也有一個與之對應的 bit 向量。這些 bits 負責記錄該字節或者寄存器值是否具有有效的、已初始化的值。
(2)Valid-Address 表
對于進程整個地址空間中的每一個字節(byte),還有與之對應的 1 個 bit,負責記錄該地址是否能夠被讀寫。
檢測原理:
當要讀寫內存中某個字節時,首先檢查這個字節對應的 A bit。如果該 A bit顯示該位置是無效位置,memcheck 則報告讀寫錯誤。
內核(core)類似于一個虛擬的 CPU 環境,這樣當內存中的某個字節被加載到真實的 CPU 中時,該字節對應的 V bit 也被加載到虛擬的 CPU 環境中。一旦寄存器中的值,被用來產生內存地址,或者該值能夠影響程序輸出,則 memcheck 會檢查對應的V bits,如果該值尚未初始化,則會報告使用未初始化內存錯誤。
四、常用選項
(1)--help:顯示幫助信息;
(2)--version:顯示 valgrind 版本;
(3)--tool=<name>:運行 valgrind 中名為 toolname 的工具,默認 memcheck,還可以為cachegrid、drd、lackey、callgrind、helgrind、massif等;
(4)--quiet:安靜地運行,只打印錯誤信息;
(5)--verbose:更詳細的信息,增加錯誤數統計;
(6)--trace-childer=no | yes:跟蹤子線程;
(7)--track-fds=no | yes:跟蹤打開的文件描述;
(8)--time-stamp=no | yes:增加時間戳到 Log 信息;
(9)--log-fd=<number>:輸出Log信息到文件描述符;
(10)--log-file=<file>:輸出Log信息到指定的文件;
(11)--xml=yes:將錯誤信息以xml格式輸出,只有memcheck可用;
(12)--xml-file=<file>:XML輸出到指定文件;
(13)--error-limit=no | yes:如果錯誤太多,則停止顯示新錯誤;
(14)--error-exitcode=<number>:如果發現錯誤,則返回錯誤代碼;
(15)--leak-check=no | summary | full:對發現的內存泄露給出的信息級別,只有memcheck可用。(建議添加)
(16)--show-reachable=no | yes,用于控制是否檢測控制范圍之外的泄漏,比如全局指針、static指針等。
(17)–num-callers=(num):這個值默認是12,最高是50。表示顯示多少層的堆棧,設置越高會使Valgrind運行越慢而且使用更多的內存,但是在嵌套調用層次比較高的程序中非常實用。
(18)--trace-children=no | yes,是否跟入子進程。
五、栗子
#include <stdlib.h> #include <stdio.h> #include <memory.h>int main(void) {char *ptr = (char *)malloc(10);ptr[12] = 'a'; // 內存越界memcpy(ptr +1, ptr, 5); // 踩內存char a[10];a[12] = 'i'; // 數組越界free(ptr); // 重復釋放free(ptr);char *p1;*p1 = '1'; // 非法指針return 0;}gcc -o memleak main.cc -g
valgrind --tool=memcheck --leak-check=full ./memleak
結果:
10617== Memcheck, a memory error detector ==10617== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==10617== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info ==10617== Command: ./memleak ==10617== ==10617== Invalid write of size 1 // 踩了1個字節的內存。 ==10617== at 0x1091DA: main (main.cc:8) ==10617== Address 0x4a5804c is 2 bytes after a block of size 10 alloc'd ==10617== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) ==10617== by 0x1091CD: main (main.cc:7) ==10617== ==10617== Invalid write of size 1 // 踩了1個字節的內存。 ==10617== at 0x484043E: memcpy@GLIBC_2.2.5 (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) ==10617== by 0x1091F8: main (main.cc:9) ==10617== Address 0x4a5804a is 0 bytes after a block of size 10 alloc'd ==10617== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) ==10617== by 0x1091CD: main (main.cc:7) ==10617== ==10617== Invalid free() / delete / delete[] / realloc() // 重復釋放。 ==10617== at 0x483CA3F: free (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) ==10617== by 0x109214: main (main.cc:13) ==10617== Address 0x4a58040 is 0 bytes inside a block of size 10 free'd ==10617== at 0x483CA3F: free (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) ==10617== by 0x109208: main (main.cc:12) ==10617== Block was alloc'd at ==10617== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) ==10617== by 0x1091CD: main (main.cc:7) ==10617== ==10617== Use of uninitialised value of size 8 // 使用了未申請的內存(8bit) ==10617== at 0x109219: main (main.cc:15) ==10617== ==10617== Invalid write of size 1 // 在非法地址上寫了1個字節。 ==10617== at 0x109219: main (main.cc:15) ==10617== Address 0x0 is not stack'd, malloc'd or (recently) free'd ==10617== ==10617== // 非法指針,導致coredump ==10617== Process terminating with default action of signal 11 (SIGSEGV) ==10617== Access not within mapped region at address 0x0 ==10617== at 0x109219: main (main.cc:15) ==10617== If you believe this happened as a result of a stack ==10617== overflow in your program's main thread (unlikely but ==10617== possible), you can try to increase the size of the ==10617== main thread stack using the --main-stacksize= flag. ==10617== The main thread stack size used in this run was 8388608. ==10617== ==10617== HEAP SUMMARY: ==10617== in use at exit: 0 bytes in 0 blocks ==10617== total heap usage: 1 allocs, 2 frees, 10 bytes allocated ==10617== ==10617== All heap blocks were freed -- no leaks are possible ==10617== ==10617== Use --track-origins=yes to see where uninitialised values come from ==10617== For lists of detected and suppressed errors, rerun with: -s ==10617== ERROR SUMMARY: 5 errors from 5 contexts (suppressed: 0 from 0)六、堆棧分析器可視化
1、安裝
sudo apt install massif-visualizer
2、生成數據文件
valgrind --tool=massif ./memleak
3、可視化
massif-visualizer massif.out.8233
8233 為進程號
結果:
?4、簡單可視化
ms_print massif.out.8233
(SAW:Game Over!)
總結
以上是生活随笔為你收集整理的valgrind 详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 编译 / __attribute__(c
- 下一篇: linux ar 命令详解