valgrind 基本功能介紹、基礎使用方法說明
1、Valgrind 概述
Valgrind 是一套 Linux 下,開放源代碼(GPL V2)的仿真調試工具的集合。
Valgrind 由內核(core)以及基於內核的其他調試工具組成。內核類似於一個框架(framework),它模擬了一個 CPU 環境,並提供服務給其他工具;而其他工具則類似於插件 (plug-in),利用內核提供的服務完成各種特定的內存調試任務。
2、工具下載安裝
參考地址:https://www.valgrind.org/downloads/
安裝:
tar –xf valgrind-3.17.0.tar.bz2
cd valgrind-3.17.0
./configure // 運行配置腳本生成makefile文件,可以--help查看配置項,自行按需配置,比如修改編譯工具、修改安裝路徑等
make
make install //安裝生成可執行文件,可執行文件的路徑有參數--prefix指定,需要在PATH中添加環境變量;若不加參數--prefix指定,僅使用默認配置,則會自動關聯
安裝完後可以使用:
valgrind --help 查看使用方法
3、使用基本選項
3.1 基本工具介紹
-
Memcheck。這是 valgrind 應用最廣泛的工具,一個重量級的內存檢查器,能夠發現開發中絕大多數內存錯誤使用情況,比如:使用未初始化的內存,使用已經釋放了的內存,內存訪問越界等。這也是本文將重點介紹的部分。
-
Callgrind。它主要用來檢查程序中函數調用過程中出現的問題。
-
Cachegrind。它主要用來檢查程序中緩存使用出現的問題。
-
Helgrind。它主要用來檢查多線程程序中出現的競爭問題。
-
Massif。它主要用來檢查程序中堆棧使用中出現的問題。
-
Extension。可以利用 core 提供的功能,自己編寫特定的內存調試工具
3.2 常用選項
- 適用於所有 Valgrind 工具
–tool=< name > 最常用的選項。運行 valgrind中名爲toolname的工具。默認memcheck。
-h --help 顯示幫助信息。
–version 顯示valgrind內核的版本,每個工具都有各自的版本。
-q --quiet 安靜地運行,只打印錯誤信息。
-v --verbose 更詳細的信息, 增加錯誤數統計。
–trace-children=no|yes 跟蹤子線程? [no]
–track-fds=no|yes 跟蹤打開的文件描述?[no]
–time-stamp=no|yes 增加時間戳到LOG信息? [no]
–log-fd=< number > 輸出LOG到描述符文件 [2=stderr]
–log-file=< file > 將輸出的信息寫入到filename.PID的文件裏,PID是運行程序的進行ID
–log-file-exactly=< file > 輸出LOG信息到 file
–log-file-qualifier=< VAR > 取得環境變量的值來做爲輸出信息的文件名。 [none]
–log-socket=ipaddr:port 輸出LOG到socket ,ipaddr:port
- LOG 信息輸出
–xml=yes 將信息以xml格式輸出,只有memcheck可用
–num-callers=< number > show < numbe r> callers in stack traces [12]
–error-limit=no|yes 如果太多錯誤,則停止顯示新錯誤? [yes]
–error-exitcode=< number > 如果發現錯誤則返回錯誤代碼 [0=disable]
–db-attach=no|yes 當出現錯誤,valgrind會自動啓動調試器gdb。[no]
–db-command=< command > 啓動調試器的命令行選項[gdb -nw %f %p]
- 適用於 Memcheck 工具的相關選項:
–leak-check=no|summary|full 要求對leak給出詳細信息? [summary]
–leak-resolution=low|med|high how much bt merging in leak check [low]
–show-reachable=no|yes show reachable blocks in leak check? [no]
更詳細的使用信息詳見幫助文件、man 手冊或官網:http://valgrind.org/docs/manual/manual-core.html
- 注意
(1)valgrind 不會自動的檢查程序的每一行代碼,只會檢查運行到的代碼分支,所以單元測試或功能測試用例很重要;
(2)可以把 valgrind 看成是一個 sandbox,通過 valgrind 運行的程序實際上是運行在 valgrind 的 sandbox 中的,所以,不要測試性能,會讓你失望的,建議只做功能測試
(3)編譯代碼時,建議增加 - g -o0 選項,不要使用 - o1、-o2 選項
3.3 常用選項示例
–tool= : use the Valgrind tool named < name > [memcheck] –log-file=< file > : log messages to < file >
示例:
valgrind --tool=memcheck --log-file=log.txt --leak-check=yes ./test
說明:使用 memcheck 工具對 test 程序進行包含內存泄漏的檢查,並將日誌保存到 log.txt
4、Memcheck 工具介紹
Memcheck 是 valgrind 應用最廣泛的工具,能夠發現開發中絕大多數內存錯誤使用情況。此工具主要可檢查以下錯誤
-
使用未初始化的內存 (Use of uninitialised memory)
-
使用已經釋放了的內存 (Reading/writing memory after it has been free’d)
-
使用超過 malloc 分配的內存空間 (Reading/writing off the end of malloc’d blocks)
-
對堆棧的非法訪問 (Reading/writing inappropriate areas on the stack)
-
申請的空間是否有釋放 (Memory leaks – where pointers to malloc’d blocks are lost forever)
-
malloc/free/new/delete 申請和釋放內存的匹配 (Mismatched use of malloc/new/new [] vs free/delete/delete [])
-
src 和 dst 的重疊 (Overlapping src and dst pointers in memcpy() and related functions)
#include<iostream>
int main()
{
int *pInt;
std::cout<<"使用未初始化的內存";
int a=*pInt; //使用未初始化的內存
}
#include<iostream>
int main()
{
int *pArray=(int *)malloc(sizeof(int) *5);
std::cout<<"使用已經釋放了的內存";
free(pArray);
pArray[0]=0; //使用已經釋放了的內存
}
#include<iostream>
int main()
{
int *pArray=(int *)malloc(sizeof(int) *5);
std::cout<<"使用超過malloc分配的內存空間";
pArray[5]=5; //使用超過malloc分配的內存空間
free(pArray);
}
#include<iostream>
int main()
{
int *pArray=(int *)malloc(sizeof(int) *5);
std::cout<<"malloc缺少free";
}
#include<iostream>
int main()
{
char a[10];
for (char c=0; c < sizeof(a); c++)
{
a[c]=c;
}
std::cout<<"拷貝的src和dst存在重疊";
memcpy(&a[4],&a[0],6);
}
注:程序有時會申請很多常駐節點,這些未釋放的節點不應視爲問題;
一般隨着程序的運行,導致節點單向增加的 malloc 或 new 操作,視爲內存泄漏
4.1 示例 1
源碼:
#include<iostream>
int main()
{
int *pArray=(int *)malloc(sizeof(int) *5);
std::cout<<"使用超過malloc分配的內存空間";
pArray[5]=5; //使用超過malloc分配的內存空間
free(pArray);
}
12345678
編譯:
g++ test1.cpp -g -o test1_g //-g:讓 memcheck 工具可以取到出錯的具體行號
調試:
valgrind --leak-check=yes --log-file=1_g ./test1_g
生成日誌文件 1_g:
(1)當前程序(./test1_g) 的進程號
(2)valgrind memcheck 工具的 license 說明
(3)加載程序的運行方式
(4)父進程號,當前終端的進程
(5)檢測到的錯誤信息
(6)堆棧摘要、小結,該例子中總共兩次 alloc、兩次 free,沒有內存泄漏
(7) 檢測到的錯誤數量,這裏提示 1 個
4.2 示例 2
#include<iostream>
int main()
{
int *pArray=(int *)malloc(sizeof(int) *5);
std::cout<<"使用已經釋放了的內存";
free(pArray);
pArray[0]=0; //使用已經釋放了的內存
}
編譯:
g++ test7.cpp -g -o test7_g //-g:讓 memcheck 工具可以取到出錯的具體行號
調試:
valgrind --leak-check=yes --log-file=7_g ./test7_g
生成日誌文件 7_g:
(1)因爲還是使用同一個終端,所以父進程還是 8248
(2) 有兩個非法的讀、寫錯誤
編譯:
g++ test7.cpp -g -o test2_g_O2 -O2
調試:
valgrind --leak-check=yes --log-file=7_g_O2 ./test7_g_O2
生成日誌文件 7_g_O2:
可以看到同樣的程序,在加上 - O2 之後,pArray[0]=0; 語句被優化掉了,所以沒有被檢測出來。
爲了做到更嚴格的檢測,編譯時需要保證編譯器沒有做優化,即優化等級爲 - O0,gcc、g++ 默認就是採用 - O0 的,但是大部分實際設計都會在 Makefile 中添加 - O1 或者 - O2 參數,所以最好還是檢查下。
4.3 Memcheck 報告輸出文檔整體格式總結
-
copyright 版權聲明
-
異常讀寫報告 2.1 主線程異常讀寫
線程 A 異常讀寫報告 線程 B 異常讀寫報告
-
其他線程
-
堆內存泄露報告 4.1 堆內存使用情況概述 (HEAP SUMMARY)
4.2 確信的內存泄露報告 (definitely lost)
4.3 可疑內存操作報告 (show-reachable=no 關閉,打開:–show-reachable=yes)
4.4 泄露情況概述 (LEAK SUMMARY)
4.4 Memcheck 日誌報告的基本格式
{問題描述}
at {地址、函數名、模塊或代碼行}
by {地址、函數名、代碼行}
by …{逐層依次顯示調用堆棧}
Address 0x??? {描述地址的相對關係}
4.5 memcheck 包含的 7 類錯誤
-
illegal read/illegal write errors 提示信息:[invalid read of size 4]
-
use of uninitialised values 提示信息:[Conditional jump or move depends on uninitialised value]
-
use of uninitialised or unaddressable values in system calls 提示信息:[syscall param write(buf) points to uninitilaised bytes]
-
illegal frees 提示信息:[invalid free()]
-
when a heap block is freed with an inappropriate deallocation function 提示信息:[Mismatched free()/delete/delete[]]
-
overlapping source and destination blocks 提示信息:[source and destination overlap in memcpy(,)]
-
memory leak detection ① still reachable 內存指針還在還有機會使用或釋放,指針指向的動態內存還沒有被釋放就退出了
② definitely lost 確定的內存泄露,已經不能訪問這塊內存
③ indirectly lost 指向該內存的指針都位於內存泄露處
④ possibly lost 可能的內存泄露,仍然存在某個指針能夠快速訪問某塊內存,但該指針指向的已經不是內存首位置
4.6 memcheck 工具原理
Memcheck 實現了一個仿真的 CPU,被監控的程序被這個仿真 CPU 解釋執行,該仿真 CPU 可以在所有的內存讀寫指令發生時,檢測地址的合法性和讀操作的合法性。
Memcheck 能夠檢測出內存問題,關鍵在於其建立了兩個全局表。
-
Valid-Value 表:
對於進程的整個地址空間中的每一個字節 (byte),都有與之對應的 8 個 bits;對於 CPU 的每個寄存器,也有一個與之對應的 bit 向量。這些 bits 負責記錄該字節或者寄存器值是否具有有效的、已初始化的值。
-
Valid-Address 表
對於進程整個地址空間中的每一個字節 (byte),還有與之對應的 1 個 bit,負責記錄該地址是否能夠被讀寫。
檢測原理:
當要讀寫內存中某個字節時,首先檢查這個字節對應的 A bit。如果該 A bit 顯示該位置是無效位置,memcheck 則報告讀寫錯誤。
內核(core) 類似於一個虛擬的 CPU 環境,這樣當內存中的某個字節被加載到真實的 CPU 中時,該字節對應的 V bit 也被加載到虛擬的
CPU 環境中。一旦寄存器中的值,被用來產生內存地址,或者該值能夠影響程序輸出,則 memcheck 會檢查對應的 V bits,如果該值
尚未初始化,則會報告使用未初始化內存錯誤。
簡單來說
-
如何知道那些地址是合法的(內存已分配)? 維護一張合法地址表(Valid-address (A) bits),當前所有可以合法讀寫(已分配)的地址在其中有對應的表項。該表通過以下措施維護:
① 全局數據 (data, bss section)–在程序啓動的時候標記爲合法地址
② 局部變量–監控 sp(stack pointer) 的變化,動態維護
③動態分配的內存–截獲 分配 / 釋放 內存的調用:malloc, calloc, realloc, valloc, memalign, free, new, new[], delete and delete[]
④ 系統調用–截獲 mmap 映射的地址
⑤ 其他–可以顯示知會 memcheck 某地字段是合法的
-
如何知道某內存是否已經被賦值?
①維護一張合法值表(Valid-value (V) bits),指示對應的 bit 是否已經被賦值。因爲虛擬 CPU 可以捕獲所有對內存的寫指令,所以這張表很容易維護。
5、Callgrind 工具介紹
Callgrind 性能分析工具,它不需要在編譯源碼時附加特殊選項。Callgrind 使用 cachegrind 的統計信息 Ir(I cache reads,即一條指令執行的次數)來統計程序中函數的調用情況,建立函數調用關係圖,還可以有選擇地進行 cache 模擬。在運行結束時,它會把分析數據寫入一個文件,callgrind_annotate 可以把這個文件的內容轉化成可讀的形式。
5.1 Callgrind 文本分析基本操作
示例:
(1)
cd linux/bin
valgrind --tool=callgrind ./Devtest
生成一個文件:callgrind.out.27439
或者
valgrind --tool=callgrind --separate-threads=yes ./Devtest
生成三個文件:callgrind.out.1234(爲空),callgrind.out.1234-01(線程1),callgrind.out.1234-02(線程2)
(2)
callgrind_annotate callgrind.out.27439 > log
callgrind_annotate 是可以將 callgrind.out.pid 文件的內容轉化爲可讀的形式,並重定向到 log 文件,分別打開 callgrind.out.pid、log 文件,你會發現它們的不同(callgrind.out.pid 是人類不便於直接理解的格式,callgrind_annotate 相當於一個翻譯,將 callgrind.out.pid 按照我們喜歡的方式展現出來)。
callgrind_annotate 解析 callgrind.out.pid 而生成的 log 文件,打開後內容如下:
可以看到每個函數所屬的動態庫,該函數調用所耗費的指令數,默認是從大到小排序的。
callgrind_annotate 還有幾個可選參數:
-
--inclusive=yes:不但分別統計每個語句的執行次數,還把調用關係計算進入,比如函數 foo 調用了 bar,那麼 foo 的代價中會加入 bar 的代價。
-
--tree=both:顯示調用關係。
-
--auto=yes:會自動將統計信息和源碼關聯。就是會顯示每個函數的源碼,並且在前面顯示每條語句的運行代價
(3)可以對單獨的文件進行關聯:
callgrind_annotate callgrind.out.9441 main.c | grep -v “???”
注:“???” 前綴的調用,都是系統庫底層調用,不重要,可用 grep -v 過濾掉
5.2 Callgrind 流程圖分析基本操作
以工程右圖 “官網提供的示例代碼” 爲例,會比較直觀:
gcc –g test.c -o test
valgrind --tool=callgrind ./test
生成一個文件:callgrind.out.pid
python gprof2dot.py -f callgrind -n10 -s callgrind.out.[pid] > valgrind.dot
dot -Tpng valgrind.dot -o valgrind.png
打開圖片打開,據說能一目瞭然的知道運行時間消耗的分佈
6、Cachegrind 工具介紹
6.1 基本介紹
-
Cachegrind 基於 Valgrind 的剖析器(profiler)計算機系統變得越來越複雜,剖析存儲系統往往是系統瓶頸,需要剖析 Cache
-
功能
-
模擬 L1、L2 Cache
-
剖析 Cache 行爲,執行次數、失效率等
-
按照文件、函數、代碼行、彙編指令剖析
- 作用
-
詳細 Cache 剖析,發現程序瓶頸
-
指令改進程序,提高執行效率
-
Trace 驅動的 Cache 模擬器
- 優點
-
容易使用,不需要重新編譯
-
剖析所有執行的代碼,包括庫
-
不限定語言
-
速度相對較快
-
靈活,模擬不同配置的 Cache
6.2 使用步驟
valgrind --tool=cachegrind ./test
同時生成文件 cachegrind.out.pid
callgrind_annotate cachegrind.out.4599 | grep -v “???”
和 callgrind 一樣,也可以通過 callgrind_annotate 翻譯爲可讀信息。從中可以看到 I1 cache(指令緩存)、D1 cache(數據緩存)、LL cache(公共的二級緩存) 的命中情況。
7、Massif 工具介紹
Massif 是一個內存剖析工具。通過不斷的取程序堆的快照來達到監視程序內存分配的目的。
7.1 示例
g++ test.cc -o test
valgrind --tool=massif ./test
就得到一個 massif 文件:massif.out.pid
使用 ms_print 來解析這個輸出文件:
ms_print massif.out.pid
通過圖形快照看出堆棧的內存變化情況:
8、Helgrind 工具介紹
Helgrind 是 Valgrind 的一個重點功能 本節主要針對與多線程基本安全問題進行檢測。
-
資源不安全訪問
-
死鎖問題
-
POSIX pthreads API 的錯誤使用
-
在前面幾個基礎上都能安全無誤的情況下 多於多線程程序就是要能夠能好將同步塊儘量縮到最小
8.1 Helgrind 資源不安全訪問
解決問題:
問題 1: 調用 Helgrind 能夠很好的解決掉,以右邊基本程序爲例
#include <pthread.h>
int var = 0;
void* child_fn (void* arg)
{
var++;
return NULL;
}
int main (void)
{
pthread_t child;
pthread_t child2;
pthread_create(&child,NULL, child_fn, NULL);
pthread_create(&child2,NULL,child_fn,NULL);
pthread_join(child,NULL);
pthread_join(child2,NULL);
return 0;
}
123456789101112131415161718192021
明顯 var 是共享的 不安全訪問,調用 Helgrind 看看怎麼能夠檢測出來:
gcc -g test.c -o test –lpthread
valgrind --tool=helgrind ./test
運行 helgrind 之後會生成如下結果,從信息提示中可以看到有兩個錯誤,對 val 全局變量的搶佔使用
問題 2:
死鎖問題是儘量避免,helgrind 可以檢測出加鎖解鎖順序出現問題導致的死鎖問題,這個問題我們可以好好看下:https://blog.csdn.net/sfwtoms11/article/details/38438253
再看下連續加 2 次鎖的情況
#include <pthread.h>
pthread_mutex_t mut_thread;
int var = 0;
void* child_fn ( void* arg )
{
pthread_mutex_lock(&mut_thread);
var++;
pthread_mutex_lock(&mut_thread);
return NULL;
}
int main ( void )
{
pthread_t child;
pthread_t child2;
pthread_mutex_init(&mut_thread,NULL);
pthread_create(&child,NULL, child_fn, NULL);
pthread_create(&child2,NULL,child_fn,NULL);
pthread_join(child,NULL);
pthread_join(child2,NULL);
return 0;
}
1234567891011121314151617181920212223
mutex 加解鎖順序導致的問題:
#include <pthread.h>
pthread_mutex_t mut_thread;
pthread_mutex_t mut_thread1;
int var = 0;
void* child_fn ( void* arg ) {
pthread_mutex_lock(&mut_thread);
pthread_mutex_lock(&mut_thread1);
var++;
pthread_mutex_unlock(&mut_thread);
pthread_mutex_unlock(&mut_thread1);
return NULL;
}
void* child_fn1(void *arg)
{
pthread_mutex_lock(&mut_thread1);
pthread_mutex_lock(&mut_thread);
var++;
pthread_mutex_unlock(&mut_thread1);
pthread_mutex_unlock(&mut_thread);
return NULL;
}
int main ( void ) {
pthread_t child;
pthread_t child2;
pthread_mutex_init(&mut_thread,NULL);
pthread_mutex_init(&mut_thread1,NULL);
pthread_create(&child,NULL, child_fn, NULL);
pthread_create(&child2,NULL,child_fn1,NULL);
pthread_join(child,NULL);
pthread_join(child2,NULL);
return 0;
}
文章推薦
https://valgrind.org/docs/manual/manual.html
https://www.jianshu.com/p/ad297253cb97
https://www.jianshu.com/p/5a31d9aa1be2
https://www.linuxidc.com/Linux/2014-10/108087.htm
https://blog.csdn.net/weixin_45518728/article/details/119865117
https://www.cnblogs.com/ranxf/p/11413735.html
原文鏈接:https://blog.csdn.net/qq_16933601/article/details/127834883?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22127834883%22%2C%22source%22%3A%22qq_16933601%22%7D 版權歸原作者所有,本文僅限分享使用。如有侵權,請聯繫作者刪除。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/7m20dncp9qm1jHQjq8xRdg