1、Valgrind概述
Valgrind是一套Linux下,開放源代碼(GPL V2)的仿真調(diào)試工具的集合。
Valgrind由內(nèi)核(core)以及基于內(nèi)核的其他調(diào)試工具組成。內(nèi)核類似于一個框架(framework),它模擬了一個CPU環(huán)境,并提供服務給其他工具;而其他工具則類似于插件 (plug-in),利用內(nèi)核提供的服務完成各種特定的內(nèi)存調(diào)試任務。
2、工具下載安裝
參考地址:https://www.valgrind.org/downloads/
安裝:
tar–xfvalgrind-3.17.0.tar.bz2 cdvalgrind-3.17.0 ./configure//運行配置腳本生成makefile文件,可以--help查看配置項,自行按需配置,比如修改編譯工具、修改安裝路徑等 make makeinstall//安裝生成可執(zhí)行文件,可執(zhí)行文件的路徑有參數(shù)--prefix指定,需要在PATH中添加環(huán)境變量;若不加參數(shù)--prefix指定,僅使用默認配置,則會自動關聯(lián)
安裝完后可以使用:
valgrind --help查看使用方法
3、使用基本選項
3.1 基本工具介紹
Memcheck。這是valgrind應用最廣泛的工具,一個重量級的內(nèi)存檢查器,能夠發(fā)現(xiàn)開發(fā)中絕大多數(shù)內(nèi)存錯誤使用情況,比如:使用未初始化的內(nèi)存,使用已經(jīng)釋放了的內(nèi)存,內(nèi)存訪問越界等。這也是本文將重點介紹的部分。
Callgrind。它主要用來檢查程序中函數(shù)調(diào)用過程中出現(xiàn)的問題。
Cachegrind。它主要用來檢查程序中緩存使用出現(xiàn)的問題。
Helgrind。它主要用來檢查多線程程序中出現(xiàn)的競爭問題。
Massif。它主要用來檢查程序中堆棧使用中出現(xiàn)的問題。
Extension。可以利用core提供的功能,自己編寫特定的內(nèi)存調(diào)試工具
3.2 常用選項
適用于所有Valgrind工具
–tool=最常用的選項。運行valgrind中名為toolname的工具。默認memcheck。 -h--help顯示幫助信息。 –version顯示valgrind內(nèi)核的版本,每個工具都有各自的版本。 -q--quiet安靜地運行,只打印錯誤信息。 -v--verbose更詳細的信息,增加錯誤數(shù)統(tǒng)計。 –trace-children=no|yes跟蹤子線程?[no] –track-fds=no|yes跟蹤打開的文件描述?[no] –time-stamp=no|yes增加時間戳到LOG信息?[no] –log-fd=輸出LOG到描述符文件[2=stderr] –log-file=將輸出的信息寫入到filename.PID的文件里,PID是運行程序的進行ID –log-file-exactly=輸出LOG信息到file –log-file-qualifier=取得環(huán)境變量的值來做為輸出信息的文件名。[none] –log-socket=ipaddr:port輸出LOG到socket,ipaddr:port
LOG信息輸出
–xml=yes將信息以xml格式輸出,只有memcheck可用 –num-callers=showcallersinstacktraces[12] –error-limit=no|yes如果太多錯誤,則停止顯示新錯誤?[yes] –error-exitcode=如果發(fā)現(xiàn)錯誤則返回錯誤代碼[0=disable] –db-attach=no|yes當出現(xiàn)錯誤,valgrind會自動啟動調(diào)試器gdb。[no] –db-command=啟動調(diào)試器的命令行選項[gdb-nw%f%p]
適用于Memcheck工具的相關選項:
–leak-check=no|summary|full要求對leak給出詳細信息?[summary] –leak-resolution=low|med|highhowmuchbtmerginginleakcheck[low] –show-reachable=no|yesshowreachableblocksinleakcheck?[no]
更詳細的使用信息詳見幫助文件、man手冊或官網(wǎng):http://valgrind.org/docs/manual/manual-core.html
注意
(1)valgrind不會自動的檢查程序的每一行代碼,只會檢查運行到的代碼分支,所以單元測試或功能測試用例很重要;
(2)可以把valgrind看成是一個sandbox,通過valgrind運行的程序?qū)嶋H上是運行在valgrind的sandbox中的,所以,不要測試性能,會讓你失望的,建議只做功能測試
(3)編譯代碼時,建議增加-g -o0選項,不要使用-o1、-o2選項
3.3 常用選項示例
–tool=< name > : 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程序進行包含內(nèi)存泄漏的檢查,并將日志保存到log.txt
4、Memcheck工具介紹
Memcheck是valgrind應用最廣泛的工具,能夠發(fā)現(xiàn)開發(fā)中絕大多數(shù)內(nèi)存錯誤使用情況。此工具主要可檢查以下錯誤
使用未初始化的內(nèi)存(Use of uninitialised memory)
使用已經(jīng)釋放了的內(nèi)存(Reading/writing memory after it has been free’d)
使用超過malloc分配的內(nèi)存空間(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申請和釋放內(nèi)存的匹配(Mismatched use of malloc/new/new [] vs free/delete/delete [])
src和dst的重疊(Overlapping src and dst pointers in memcpy() and related functions)
#includeintmain() { int*pInt; std::cout<<"使用未初始化的內(nèi)存"; ?int?a=*pInt;????//使用未初始化的內(nèi)存 } #include intmain() { int*pArray=(int*)malloc(sizeof(int)*5); std::cout<<"使用已經(jīng)釋放了的內(nèi)存"; ?free(pArray); ?pArray[0]=0;????//使用已經(jīng)釋放了的內(nèi)存 } #include intmain() { int*pArray=(int*)malloc(sizeof(int)*5); std::cout<<"使用超過malloc分配的內(nèi)存空間"; ?pArray[5]=5;????//使用超過malloc分配的內(nèi)存空間 ?free(pArray); } #include intmain() { int*pArray=(int*)malloc(sizeof(int)*5); std::cout<<"malloc缺少free"; } #include intmain() { chara[10]; for(charc=0;c
注:程序有時會申請很多常駐節(jié)點,這些未釋放的節(jié)點不應視為問題;
一般隨著程序的運行,導致節(jié)點單向增加的malloc或new操作,視為內(nèi)存泄漏
4.1 示例1
源碼:
#includeintmain() { int*pArray=(int*)malloc(sizeof(int)*5); std::cout<<"使用超過malloc分配的內(nèi)存空間"; ?pArray[5]=5;????//使用超過malloc分配的內(nèi)存空間 ?free(pArray); } 12345678
編譯:
g++test1.cpp-g-otest1_g//-g:讓memcheck工具可以取到出錯的具體行號
調(diào)試:
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,沒有內(nèi)存泄漏
(7) 檢測到的錯誤數(shù)量,這里提示1個
4.2 示例2
#includeintmain() { int*pArray=(int*)malloc(sizeof(int)*5); std::cout<<"使用已經(jīng)釋放了的內(nèi)存"; ?free(pArray); ?pArray[0]=0;????//使用已經(jīng)釋放了的內(nèi)存 }
編譯:
g++test7.cpp-g-otest7_g//-g:讓memcheck工具可以取到出錯的具體行號
調(diào)試:
valgrind--leak-check=yes--log-file=7_g./test7_g
生成日志文件7_g:
(1)因為還是使用同一個終端,所以父進程還是8248
(2) 有兩個非法的讀、寫錯誤
編譯:
g++test7.cpp-g-otest2_g_O2-O2
調(diào)試:
valgrind--leak-check=yes--log-file=7_g_O2./test7_g_O2
生成日志文件7_g_O2:
可以看到同樣的程序,在加上-O2之后,pArray[0]=0;語句被優(yōu)化掉了,所以沒有被檢測出來。
為了做到更嚴格的檢測,編譯時需要保證編譯器沒有做優(yōu)化,即優(yōu)化等級為-O0,gcc、g++默認就是采用-O0的,但是大部分實際設計都會在Makefile中添加-O1或者-O2參數(shù),所以最好還是檢查下。
4.3 Memcheck 報告輸出文檔整體格式總結
copyright 版權聲明
異常讀寫報告2.1 主線程異常讀寫
線程A異常讀寫報告線程B異常讀寫報告
其他線程
堆內(nèi)存泄露報告4.1 堆內(nèi)存使用情況概述(HEAP SUMMARY)
4.2 確信的內(nèi)存泄露報告(definitely lost)
4.3 可疑內(nèi)存操作報告 (show-reachable=no關閉,打開:–show-reachable=yes)
4.4 泄露情況概述(LEAK SUMMARY)
4.4 Memcheck 日志報告的基本格式
{問題描述} at{地址、函數(shù)名、模塊或代碼行} by{地址、函數(shù)名、代碼行} by…{逐層依次顯示調(diào)用堆棧} Address0x???{描述地址的相對關系}
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內(nèi)存指針還在還有機會使用或釋放,指針指向的動態(tài)內(nèi)存還沒有被釋放就退出了
② definitely lost確定的內(nèi)存泄露,已經(jīng)不能訪問這塊內(nèi)存
③ indirectly lost指向該內(nèi)存的指針都位于內(nèi)存泄露處
④ possibly lost可能的內(nèi)存泄露,仍然存在某個指針能夠快速訪問某塊內(nèi)存,但該指針指向的已經(jīng)不是內(nèi)存首位置
4.6 memcheck工具原理
Memcheck實現(xiàn)了一個仿真的CPU,被監(jiān)控的程序被這個仿真CPU解釋執(zhí)行,該仿真CPU可以在所有的內(nèi)存讀寫指令發(fā)生時,檢測地址的合法性和讀操作的合法性。
Memcheck 能夠檢測出內(nèi)存問題,關鍵在于其建立了兩個全局表。
Valid-Value 表:
對于進程的整個地址空間中的每一個字節(jié)(byte),都有與之對應的8 個bits;對于CPU 的每個寄存器,也有一個與之對應的bit 向量。這些bits 負責記錄該字節(jié)或者寄存器值是否具有有效的、已初始化的值。
Valid-Address 表
對于進程整個地址空間中的每一個字節(jié)(byte),還有與之對應的1 個bit,負責記錄該地址是否能夠被讀寫。
檢測原理:
當要讀寫內(nèi)存中某個字節(jié)時,首先檢查這個字節(jié)對應的A bit。如果該A bit顯示該位置是無效位置,memcheck 則報告讀寫錯誤。
內(nèi)核(core)類似于一個虛擬的CPU 環(huán)境,這樣當內(nèi)存中的某個字節(jié)被加載到真實的CPU 中時,該字節(jié)對應的V bit 也被加載到虛擬的
CPU 環(huán)境中。一旦寄存器中的值,被用來產(chǎn)生內(nèi)存地址,或者該值能夠影響程序輸出,則memcheck 會檢查對應的V bits,如果該值
尚未初始化,則會報告使用未初始化內(nèi)存錯誤。
簡單來說
如何知道那些地址是合法的(內(nèi)存已分配)?維護一張合法地址表(Valid-address (A) bits),當前所有可以合法讀寫(已分配)的地址在其中有對應的表項。該表通過以下措施維護:
① 全局數(shù)據(jù)(data, bss section)–在程序啟動的時候標記為合法地址
② 局部變量–監(jiān)控sp(stack pointer)的變化,動態(tài)維護
③動態(tài)分配的內(nèi)存–截獲 分配/釋放 內(nèi)存的調(diào)用:malloc, calloc, realloc, valloc, memalign, free, new, new[], delete and delete[]
④ 系統(tǒng)調(diào)用–截獲mmap映射的地址
⑤ 其他–可以顯示知會memcheck某地字段是合法的
如何知道某內(nèi)存是否已經(jīng)被賦值?
①維護一張合法值表(Valid-value (V) bits),指示對應的bit是否已經(jīng)被賦值。因為虛擬CPU可以捕獲所有對內(nèi)存的寫指令,所以這張表很容易維護。
5、Callgrind工具介紹
Callgrind性能分析工具,它不需要在編譯源碼時附加特殊選項。Callgrind使用cachegrind的統(tǒng)計信息Ir(I cache reads,即一條指令執(zhí)行的次數(shù))來統(tǒng)計程序中函數(shù)的調(diào)用情況,建立函數(shù)調(diào)用關系圖,還可以有選擇地進行cache模擬。在運行結束時,它會把分析數(shù)據(jù)寫入一個文件,callgrind_annotate可以把這個文件的內(nèi)容轉(zhuǎn)化成可讀的形式。
5.1 Callgrind文本分析基本操作
示例:
(1)
cdlinux/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_annotatecallgrind.out.27439>log
callgrind_annotate是可以將callgrind.out.pid文件的內(nèi)容轉(zhuǎn)化為可讀的形式,并重定向到log文件,分別打開callgrind.out.pid、log文件,你會發(fā)現(xiàn)它們的不同(callgrind.out.pid是人類不便于直接理解的格式,callgrind_annotate相當于一個翻譯,將callgrind.out.pid按照我們喜歡的方式展現(xiàn)出來)。
callgrind_annotate解析callgrind.out.pid而生成的log文件,打開后內(nèi)容如下:
可以看到每個函數(shù)所屬的動態(tài)庫,該函數(shù)調(diào)用所耗費的指令數(shù),默認是從大到小排序的。
callgrind_annotate 還有幾個可選參數(shù):
--inclusive=yes:不但分別統(tǒng)計每個語句的執(zhí)行次數(shù),還把調(diào)用關系計算進入,比如函數(shù)foo調(diào)用了bar,那么foo的代價中會加入bar的代價。
--tree=both:顯示調(diào)用關系。
--auto=yes:會自動將統(tǒng)計信息和源碼關聯(lián)。就是會顯示每個函數(shù)的源碼,并且在前面顯示每條語句的運行代價
(3)可以對單獨的文件進行關聯(lián):
callgrind_annotatecallgrind.out.9441main.c|grep-v“???”
注:“???”前綴的調(diào)用,都是系統(tǒng)庫底層調(diào)用,不重要,可用grep -v過濾掉
5.2 Callgrind流程圖分析基本操作
以工程右圖“官網(wǎng)提供的示例代碼”為例,會比較直觀:
gcc–gtest.c-otest valgrind--tool=callgrind./test
生成一個文件:callgrind.out.pid
pythongprof2dot.py-fcallgrind-n10-scallgrind.out.[pid]>valgrind.dot dot-Tpngvalgrind.dot-ovalgrind.png
打開圖片打開,據(jù)說能一目了然的知道運行時間消耗的分布
6、Cachegrind工具介紹
6.1 基本介紹
Cachegrind基于Valgrind的剖析器(profiler)計算機系統(tǒng)變得越來越復雜,剖析存儲系統(tǒng)往往是系統(tǒng)瓶頸,需要剖析Cache
功能
模擬L1、L2 Cache
剖析Cache行為,執(zhí)行次數(shù)、失效率等
按照文件、函數(shù)、代碼行、匯編指令剖析
作用
詳細Cache剖析,發(fā)現(xiàn)程序瓶頸
指令改進程序,提高執(zhí)行效率
Trace驅(qū)動的Cache模擬器
優(yōu)點
容易使用,不需要重新編譯
剖析所有執(zhí)行的代碼,包括庫
不限定語言
速度相對較快
靈活,模擬不同配置的Cache
6.2 使用步驟
valgrind--tool=cachegrind./test
同時生成文件cachegrind.out.pid
callgrind_annotatecachegrind.out.4599|grep-v“???”在這里插入圖片描述
和callgrind一樣,也可以通過callgrind_annotate翻譯為可讀信息。從中可以看到I1 cache(指令緩存)、D1 cache(數(shù)據(jù)緩存)、LL cache(公共的二級緩存)的命中情況。
7、Massif工具介紹
Massif是一個內(nèi)存剖析工具。通過不斷的取程序堆的快照來達到監(jiān)視程序內(nèi)存分配的目的。
7.1 示例
g++test.cc-otest valgrind--tool=massif./test
就得到一個massif文件:massif.out.pid
使用ms_print來解析這個輸出文件:
ms_printmassif.out.pid
通過圖形快照看出堆棧的內(nèi)存變化情況:
8、Helgrind工具介紹
Helgrind是Valgrind的一個重點功能 本節(jié)主要針對與多線程基本安全問題進行檢測。
資源不安全訪問
死鎖問題
POSIX pthreads API的錯誤使用
在前面幾個基礎上都能安全無誤的情況下 多于多線程程序就是要能夠能好將同步塊盡量縮到最小
8.1 Helgrind 資源不安全訪問
解決問題:
問題1: 調(diào)用Helgrind能夠很好的解決掉,以右邊基本程序為例
#includeintvar=0; void*child_fn(void*arg) { var++; returnNULL; } intmain(void) { pthread_tchild; pthread_tchild2; pthread_create(&child,NULL,child_fn,NULL); pthread_create(&child2,NULL,child_fn,NULL); pthread_join(child,NULL); pthread_join(child2,NULL); return0; } 123456789101112131415161718192021
明顯var是共享的 不安全訪問,調(diào)用Helgrind看看怎么能夠檢測出來:
gcc-gtest.c-otest–lpthread valgrind--tool=helgrind./test
運行helgrind之后會生成如下結果,從信息提示中可以看到有兩個錯誤,對val全局變量的搶占使用
問題2:
死鎖問題是盡量避免,helgrind可以檢測出加鎖解鎖順序出現(xiàn)問題導致的死鎖問題,這個問題我們可以好好看下:https://blog.csdn.net/sfwtoms11/article/details/38438253
再看下連續(xù)加2次鎖的情況
#includepthread_mutex_tmut_thread; intvar=0; void*child_fn(void*arg) { pthread_mutex_lock(&mut_thread); var++; pthread_mutex_lock(&mut_thread); returnNULL; } intmain(void) { pthread_tchild; pthread_tchild2; 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); return0; } 1234567891011121314151617181920212223
mutex加解鎖順序?qū)е碌膯栴}:
#includepthread_mutex_tmut_thread; pthread_mutex_tmut_thread1; intvar=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); returnNULL; } 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); returnNULL; } intmain(void){ pthread_tchild; pthread_tchild2; 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); return0; }
審核編輯:湯梓紅
-
Linux
+關注
關注
87文章
11123瀏覽量
207905 -
Valgrind
+關注
關注
0文章
9瀏覽量
6779
原文標題:valgrind基本功能介紹、基礎使用方法說明
文章出處:【微信號:嵌入式與Linux那些事,微信公眾號:嵌入式與Linux那些事】歡迎添加關注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關推薦
評論