Memcheck:一个内存错误检测器
目錄
4.1。概觀4.2。來自Memcheck的錯誤消息說明要使用此工具,可以--tool=memcheck?在Valgrind命令行中指定。不過,由于Memcheck是默認工具,所以您不需要。
4.1。概觀
Memcheck是一個內存錯誤檢測器。它可以檢測C和C ++程序中常見的以下問題。
-
訪問內存您不應該,例如超越和低估堆塊,超出堆棧的頂部,以及在釋放后訪問內存。
-
使用未定義的值,即尚未初始化的值,或已從其他未定義值導出的值。
-
不正確的釋放堆內存,比如雙重釋放堆塊,或使用?malloc/?new/?new[]?對?free/?delete/delete[]
-
重疊src和?dst指針?memcpy以及相關功能。
-
將一個腥味(大概為負)值傳遞給?size一個內存分配功能的參數。
-
內存泄漏。
像這樣的問題可能難以通過其他方式找到,經常長時間不被發現,然后導致偶爾的,難以診斷的崩潰。
4.2。來自Memcheck的錯誤消息說明
Memcheck發出一系列錯誤消息。本節將簡要介紹哪些錯誤消息的意思。錯誤檢查機械的精確行為在Memcheck檢查機械的詳細信息中有所描述。
4.2.1。非法讀取/非法寫入錯誤
例如:
大小4讀取無效在0x40F6BBCC :(在/usr/lib/libpng.so.2.1.0.9中)由0x40F6B804 :(在/usr/lib/libpng.so.2.1.0.9中)by 0x40B07FF4:read_png_image(QImageIO *)(kernel / qpngio.cpp:326)by 0x40AC751B:QImageIO :: read()(kernel / qimage.cpp:3621)地址0xBFFFF0E0不是stack'd,malloc'd或free'd當您的程序在Memcheck認為不應該讀取或寫入內存時,會發生這種情況。在這個例子中,程序做了4個字節的地址處讀0xBFFFF0E0,內系統提供的庫libpng.so.2.1.0.9,將其從其他地方以相同的庫調用某處,從326行調用qpngio.cpp,等等上。
Memcheck嘗試確定非法地址可能涉及什么,因為這通常是有用的。所以,如果它指向一個已經被釋放的內存塊,那么你會被通知這個,而且塊被釋放。同樣,如果它應該是剛剛結束堆塊,這是數組下標中一個一個錯誤的常見結果,你會被告知這個事實,以及塊分配的地方。如果您使用--read-var-info選項Memcheck將運行更慢,但可能會更詳細地描述任何非法地址。
在這個例子中,Memcheck不能識別地址。實際上地址在堆棧上,但是由于某種原因,這不是有效的堆棧地址 - 它不在堆棧指針下面,這是不允許的。在這種特殊情況下,這可能是由GCC產生的無效代碼造成的,這是古代GCC的一個已知bug。
請注意,Memcheck僅告訴您,您的程序即將訪問非法地址的內存。它不能阻止訪問發生。所以,如果您的程序進行通常會導致分段錯誤的訪問,您的程序仍然會遇到相同的命運 - 但??是在此之前您將收到來自Memcheck的消息。在這個特定的例子中,在堆棧上讀取垃圾是非致命的,并且程序保持活著。
4.2.2。使用未初始化的值
例如:
有條件的跳躍或移動取決于未初始化的值在0x402DFA94:_IO_vfprintf(_itoa.h:49)by 0x402E8476:_IO_printf(printf.c:36)通過0x8048472:main(tests / manuel1.c:8)當程序使用尚未初始化的值(換句話說,未定義)時,將報告未初始化值使用錯誤。這里,未定義的值用于printfC庫機械的?某處。運行以下小程序時報告此錯誤:
int main() {int x;printf(“x =%d \ n”,x); }重要的是要了解,您的程序可以盡可能多地復制垃圾(未初始化)數據。Memcheck觀察這個并且跟蹤數據,但不會抱怨。只有當您的程序嘗試以可能影響程序外部可見行為的方式使用未初始化的數據時,才會發出投訴。在此示例中,x未初始化。Memcheck觀察到傳遞給的值_IO_printf,然后發送給_IO_vfprintf,但不發表任何評論。但是,?_IO_vfprintf必須檢查它的值,?x所以它可以把它變成相應的ASCII字符串,而在這一點Memcheck抱怨。
未初始化數據的來源往往是:
-
程序中的局部變量尚未初始化,如上例所示。
-
在你(或構造函數)之前的堆塊(分配給?malloc,new或類似的函數)的內容寫在那里。
要查看程序中未初始化數據源的信息,請使用該--track-origins=yes選項。這使得Memcheck運行速度更慢,但可以更容易地追蹤未初始化值錯誤的根本原因。
4.2.3。在系統調用中使用未初始化或不可尋址的值
Memcheck檢查系統調用的所有參數:
-
它會自動檢查所有直接參數,無論它們是初始化的。
-
此外,如果系統調用需要從程序提供的緩沖區中讀取,Memcheck會檢查整個緩沖區是否可尋址,并將其內容初始化。
-
此外,如果系統調用需要寫入用戶提供的緩沖區,Memcheck會檢查緩沖區是否可尋址。
系統調用后,Memcheck更新其跟蹤信息,以精確反映系統調用引起的內存狀態變化。
以下是兩個無效參數的系統調用示例:
#include <stdlib.h>#include <unistd.h>int main(void){char * arr = malloc(10);int * arr2 = malloc(sizeof(int));寫(1 / * stdout * /,arr,10);出口(ARR2 [0]);}你得到這些投訴...
Syscall param write(buf)指向未初始化的字節在0x25A48723:__write_nocancel(在/lib/tls/libc-2.3.3.so)by 0x259AFAD3:__libc_start_main(in /lib/tls/libc-2.3.3.so)由0x8048348 :(在/auto/homes/njn25/grind/head4/a.out中)地址0x25AB8028是大小為10的塊內的0個字節在0x259852B0:malloc(vg_replace_malloc.c:130)由0x80483F1:主(ac:5)Syscall param exit(error_code)包含未初始化的字節在0x25A21B44:__GI__exit(在/lib/tls/libc-2.3.3.so)通過0x8048426:main(ac:8)因為程序具有(a)從堆塊將未初始化的垃圾寫入標準輸出,并且(b)將未初始化的值傳遞給exit。請注意,第一個錯誤是指由buf(不是?buf本身)指向的內存?,但是第二個錯誤直接指向了exit參數?arr2[0]。
4.2.4。非法釋放
例如:
無效()在0x4004FFDF:free(vg_clientmalloc.c:577)通過0x80484C7:main(tests / doublefree.c:10)地址0x3807F7B4是一個大小為177的空閑塊內的0個字節在0x4004FFDF:free(vg_clientmalloc.c:577)通過0x80484C7:main(tests / doublefree.c:10)Memcheck使用malloc/?跟蹤程序分配的塊new,所以它可以準確地知道free/ /?的參數?delete是否合法。這里,這個測試程序已經釋放了同一個程序段兩次。與非法讀/寫錯誤一樣,Memcheck嘗試了解地址的釋放。如果在這里,地址是先前被釋放的地址,那么你會被告知 - 同一塊的重復釋放容易發現。如果您嘗試釋放不指向堆塊開始的指針,您還將收到此消息。
4.2.5。當一個堆塊被釋放不正當的解除分配功能
在以下示例中,分配的塊?new[]錯誤地被釋放?free:
free()/ delete / delete []在0x40043249:free(vg_clientfuncs.c:171)by 0x4102BB4E:QGArray ::?QGArray(void)(tools / qgarray.cpp:149)由0x4C261C41:PptDoc ::?PptDoc(void)(include / qmemarray.h:60)通過0x4C261F0E:PptXml ::?PptXml(void)(pptxml.cc:44)地址0x4BB292A8是一個大小為64個alloc'd的塊內的0個字節在0x4004318C:operator new [](unsigned int)(vg_clientfuncs.c:152)by 0x4C21BC15:KLaola :: readSBStream(int)const(klaola.cc:314)by 0x4C21C155:KLaola :: stream(KLaola :: OLENode const *)(klaola.cc:416)通過0x4C21788F:OLEFilter :: convert(QCString const&)(olefilter.cc:272)在C++它與它是如何分配兼容的方式來釋放內存是很重要的。交易是:
-
如果與分配?malloc,?calloc,?realloc,?valloc或者?memalign,您必須取消分配free。
-
如果分配new,你必須解除配置delete。
-
如果分配new[],你必須解除配置delete[]。
最糟糕的是,在Linux上顯然無論如何混合使用,但同樣的程序可能會在不同的平臺(例如Solaris)上崩潰。所以最好是妥善解決它。根據KDE的人“令人驚奇的是有多少C ++程序員不知道這個”。
要求背后的原因如下。在某些C ++實現中,delete[]必須使用分配的對象,new[]因為在指針實際返回之前,編譯器會將數組的大小和指針成員存儲到數組的內容的析構函數中。?delete不解決這個問題,會混淆,可能會破壞堆。
4.2.6。重疊的源和目標塊
下面的C庫函數從一個存儲器塊復制一些數據到另一個(或類似的東西): ,?memcpy,?strcpy,?strncpy,?。strcat?strncat由他們src和?dst指針指向的塊不允許重疊。POSIX標準具有“如果在重疊的對象之間進行復制,行為未定義”。因此,Memcheck檢查這一點。
例如:
== 27492 == memcpy中的源和目的地重疊(0xbffff294,0xbffff280,21) == 27492 == 0x40026CDC:memcpy(mc_replace_strmem.c:71) == 27492 == by 0x804865A:main(overlap.c:40)您不希望兩個塊重疊,因為其中一個塊可能被復制部分覆蓋。
你可能會認為,Memcheck在dst小于等于?的情況下被過度迂回地報告src。例如,明顯的實現方式memcpy是從第一個字節到最后一個字節的復制。但是,某些架構的優化指南建議從最后一個字節復制到第一個。而且,復制前的一些實現memcpy為零?dst,因為歸零目的地的高速緩存行可以提高性能。
故事的道德是:如果你想寫真正的便攜式代碼,不要對語言實現做任何假設。
4.2.7。有趣的參數值
所有內存分配函數都會使用一個參數來指定應該分配的內存塊的大小。顯然,請求的大小應該是非負值,通常不會過大。例如,在64位機器上,分配請求的大小超過2 ** 63個字節是非常不起眼的。更可能的是,這樣的值是錯誤大小計算的結果,并且實際上是負值(恰好恰好出現過大,因為位模式被解釋為無符號整數)。這樣的值被稱為“腥值”。所述size的分配如下功能參數被檢查為腥:?malloc,?calloc,?realloc,?memalign,?new,?new []。?__builtin_new,?__builtin_vec_new,calloc
例如:
== 32233 ==函數malloc的參數'size'有一個腥(可能為負)的值:-3 == 32233 ==在0x4C2CFA7:malloc(vg_replace_malloc.c:298) == 32233 == 0x400555:foo(fishy.c:15) == 32233 == 0x400583:main(fishy.c:23)在早期的Valgrind版本中,這些值被稱為“愚蠢的參數”,并且不包括后跟蹤。
4.2.8。內存泄漏檢測
Memcheck記錄所有響應于malloc/?etc的調用發出的堆塊?new。所以當程序退出時,它知道哪些塊沒有被釋放。
如果--leak-check適當設置,對于每個剩余的塊,Memcheck確定塊是否可以從根集中的指針到達。根組包括(a)所有線程的通用寄存器,以及(b)可訪問的客戶端存儲器中的初始化,對齊,指針大小的數據字,包括堆棧。
有兩種方式可以達到一個塊。第一個是使用“開始指針”,即指向塊開頭的指針。第二個是“內部指針”,即指向塊中間的指針。有幾種方式可以發現內部指針可以發生:
-
指針本來可能是一個開始指針,并被程序故意地(或不是故意地)移動。特別是,如果您的程序使用帶標記的指針,即如果它使用指針的底部,兩位或三位,由于對齊而通常總是為零,以便存儲額外的信息,則可能會發生這種情況。
-
這可能是內存中的隨機垃圾值,完全不相關,只是巧合。
-
它可能是指向C ++的內部char數組的指針?std::string。例如,一些編譯器在std :: string的開頭添加3個字,以便在包含字符數組的內存之前存儲長度,容量和引用計數。他們在這3個字之后返回一個指針,指向char數組。
-
一些代碼可能會分配一個內存塊,并使用前8個字節來存儲(塊大小-8)作為64位數。?sqlite3MemMalloc做這個。
-
它可能是一個指向分配給C ++對象(具有析構函數)的數組的指針new[]。在這種情況下,一些編譯器在分配的塊的開始處存儲包含數組長度的“魔術cookie”,并返回一個指向剛剛過去的魔術cookie(即內部指針)的指針。有關詳細信息,請參閱此頁面。
-
它可能是指向使用多重繼承的C ++對象的內部部分的指針。
您可以選擇激活啟發式檢漏過程中使用,以檢測對應的內部指針stdstring,?length64,?newarray?和multipleinheritance案件。如果啟發式檢測到內部指針對應于這種情況,則該塊將被認為是內部指針可達的。換句話說,內部指針將被視為起始指針。
考慮到這一點,考慮下面描述的九種可能的情況。
指針鏈AAA泄漏案BBB泄漏案------------- ------------- ------------- (1)RRR ------------> BBB DR (2)RRR ---> AAA ---> BBB DR IR (3)RRR BBB DL (4)RRR AAA ---> BBB DL IL (5)RRR ------?-----> BBB(y)DR,(n)DL (6)RRR ---> AAA - ? - > BBB DR(y)IR,(n)DL (7)RRR - → - > AAA ---> BBB(y)DR,(n)DL(y)IR,(n)IL (8)RRR-α - > AAA - → - > BBB(y)DR,(n)DL(y,y)IR,(n,y)IL,(_,n) (9)RRR AAA-β-> BBB DL(y)IL,(n)DL指針鏈傳說: - RRR:根集節點或DR塊 - AAA,BBB:堆塊 - --->:一個開始指針 - - ? - >:內部指針泄漏案例傳奇: - DR:直接可達 - IR:間接可達 - DL:直接丟失 - IL:間接丟失 - (y)XY:如果內部指針是真正的指針,則為XY - (n)XY:如果內部指針不是真正的指針,則為XY - (_)XY:在任一種情況下都是XY每個可能的情況都可以減少到上述九個之一。Memcheck在其輸出中合并了一些這種情況,導致以下四種泄漏。
-
“仍然可達”。這涵蓋上面的1和2(對于BBB塊)的情況。找到一個起始指針或鏈的起始指針。由于塊仍然指向,程序員至少原則上可以在程序退出之前釋放它。“仍然可達”的塊是非常普遍的,可以說是不成問題的。因此,默認情況下,Memcheck不會單獨報告此類塊。
-
“絕對迷失”。這涵蓋上面的案例3(對于BBB塊)。這意味著沒有找到指向塊的指針。該塊被分類為“丟失”,因為程序員不可能在程序退出時釋放它,因為沒有指向它的指針。這可能是程序中某個較早點丟失指針的癥狀。這種情況應由程序員修正。
-
“間接失蹤”。這涵蓋上面的案例4和9(對于BBB塊)。這意味著塊丟失,而不是因為沒有指針,而是因為所有指向它的塊都是丟失的。例如,如果您有一個二叉樹并且根節點丟失,則其所有子節點將被間接丟失。因為如果導致間接泄漏的絕對丟失塊是固定的,那么問題會消失,因此默認情況下Memcheck不會單獨報告這些塊。
-
“可能丟失”。這涵蓋上面的5-8(對于BBB塊)。這意味著已經找到了一個或多個指向塊的指針的鏈,但是指針中的至少一個是內部指針。這可能只是內存中的一個隨機值,恰好指向一個塊,所以你不應該考慮這個確定,除非你知道你有內部指針。
(注:將九種可能的情況映射到四種泄漏方式不一定是報告泄漏的最佳方法;特別是內部指針被不一致地處理,將來可能會改進分類。)
此外,如果塊的存在,則無論其屬于上述四種,都將被報告為“被抑制”。
以下是泄漏匯總示例。
泄漏摘要:絕對丟失:3個塊中的48個字節。間接丟失:32個字節在2個塊。可能丟失:6個塊中的96個字節。仍然可達:64個字節在4個塊。被禁止:0個字節,0個塊。如果使用啟發式方法來將某些塊視為可達到的,則漏洞摘要將詳細介紹每個啟發式啟發式啟發式可達的“仍可達到的”子集。在下面的例子中,95字節仍然可以達到,87字節(56 + 7 + 8 + 16)被認為是啟發式可達的。
泄漏摘要:絕對丟失:4個字節在1個塊間接丟失:0個字節,0個塊可能丟失:0個塊中的0個字節仍然可達:6個塊中有95個字節其中通過啟發式可達到:stdstring:56個字節,2個塊length64:16個字節,1個塊newarray:1個塊中有7個字節多個繼承:1個塊中8個字節被禁止:0個字節,0個塊如果--leak-check=full指定,Memcheck將給出每個絕對丟失或可能丟失的塊的詳細信息,包括分配的位置。(實際上,它將具有相同泄漏類型的所有塊的結果和足夠相似的堆棧跟蹤合并到一個“丟失記錄”中,?--leak-resolution允許您控制“足夠相似”的含義。)它不能告訴你什么時候或如何或為什么指向一個泄漏塊的指針丟失了;?你必須為自己工作。一般來說,您應該嘗試確保您的程序在出口時沒有任何明顯的丟失或可能丟失的塊。
例如:
1塊中的8個字節在14的損失記錄中絕對丟失在0x ........:malloc(vg_replace_malloc.c:...)by 0x ........:mk(leak-tree.c:11)by 0x ........:main(leak-tree.c:39)1個塊中的88個(8個直接,80個間接)字節在14的損失記錄13中絕對丟失在0x ........:malloc(vg_replace_malloc.c:...)by 0x ........:mk(leak-tree.c:11)by 0x ........:main(leak-tree.c:25)第一條消息描述了一個完全丟失的單個8字節塊的簡單情況。第二個例子提到另一個8字節塊已經絕對丟失了;?不同之處在于其他塊中的另外80個字節由于這個丟失塊而間接丟失。損失記錄不以任何顯著的順序呈現,因此損失記錄數字并不具體。損失記錄號可以在Valgrind gdbserver中使用,以列出泄露塊的地址和/或提供有關塊仍然可達的更多細節。
該選項--show-leak-kinds=<set>?控制一組泄漏種類以顯示何時--leak-check=full被指定。
該<set>泄漏種在下列方式之一指定的:
-
一個或多個逗號分隔的列表?definite indirect possible reachable。
-
all?指定完整集(所有泄漏種類)。
-
none?為空集。
泄漏種類的默認值為?--show-leak-kinds=definite,possible。
還可以顯示可達和間接丟失的塊,除了絕對可能丟失的塊,您可以使用--show-leak-kinds=all。只顯示可達和間接丟失的塊,使用?--show-leak-kinds=indirect,reachable。然后可以顯示可達和間接丟失的塊,如以下兩個示例所示。
4塊中的64字節仍然可以在4的損失記錄中達到在0x ........:malloc(vg_replace_malloc.c:177)by 0x ........:mk(leak-cases.c:52)by 0x ........:main(leak-cases.c:74)2個字節中的32個字節在4的損失記錄1中間接丟失在0x ........:malloc(vg_replace_malloc.c:177)by 0x ........:mk(leak-cases.c:52)by 0x ........:main(leak-cases.c:80)因為有不同種類的泄漏有不同的嚴重性,一個有趣的問題是:哪些泄漏應該算作真正的“錯誤”,哪些不應該?
這個問題的答案會影響打印ERROR SUMMARY在行中的數字,也影響--error-exitcode選項的效果。首先,如果指定了漏洞,則僅將其視為真實的“錯誤”?--leak-check=full。然后,該選項--errors-for-leak-kinds=<set>控制要作為錯誤考慮的一組泄漏種類。默認值為--errors-for-leak-kinds=definite,possible
4.3。Memcheck命令行選項
--leak-check=<no|summary|yes|full> [default: summary]啟用時,在客戶端程序完成時搜索內存泄漏。如果設置summary,它說發生了多少泄漏。如果設置為full或者?yes,每個單獨的泄漏將被詳細顯示和/或計數為錯誤,如選項?--show-leak-kinds和?--errors-for-leak-kinds。
在進行泄漏檢查時,確定Memcheck如何將不同的回溯視為相同的目的,以將多個泄漏合并到單個泄漏報告中。設置時low,只有前兩個條目需要匹配。何時med,四個條目必須匹配。何時high,所有條目都需要匹配。
對于硬核泄漏調試,您可能希望?--leak-resolution=high與--num-callers=40這些大數量一起使用?。
請注意,該--leak-resolution設置不會影響Memcheck查找泄漏的能力。它只會改變結果的呈現方式。
指定泄漏檢查中顯示的泄漏種類,full?方法如下:
-
一個或多個逗號分隔的列表?definite indirect possible reachable。
-
all指定完整集(所有泄漏種類)。相當于?--show-leak-kinds=definite,indirect,possible,reachable。
-
none?為空集。
指定泄漏種類作為full泄漏搜索中的錯誤?。這?<set>是相同的?--show-leak-kinds
指定在泄漏搜索期間使用的泄漏檢查啟發式集合。啟發式控制指向一個塊的內部指針使其被認為是可達到的。啟發式集合以下列方式之一指定:
-
一個或多個逗號分隔的列表?stdstring length64 newarray multipleinheritance。
-
all激活完整的啟發式集。相當于?--leak-check-heuristics=stdstring,length64,newarray,multipleinheritance。
-
none?為空集。
這些選項提供了一種替代方法來指定泄漏種類來顯示:
-
--show-reachable=no --show-possibly-lost=yes相當于?--show-leak-kinds=definite,possible。
-
--show-reachable=no --show-possibly-lost=no相當于?--show-leak-kinds=definite。
-
--show-reachable=yes相當于?--show-leak-kinds=all。
控制Memcheck報告是否使用未定義的值錯誤。no如果您不想看到未定義的值錯誤,請將其設置為?。它也有加速Memcheck的副作用。
控制Memcheck是否跟蹤未初始化值的來源。默認情況下,它不會,這意味著雖然可以告訴您未初始化的值正在以危險的方式使用,但它不能告訴您未初始化值的來源。這通常使得難以追溯根本問題。
設置時yes,Memcheck記錄所有未初始化值的起始位置。然后,當報告未初始化的值錯誤時,Memcheck將嘗試顯示值的原點。原點可以是以下四個位置之一:堆塊,堆棧分配,客戶端請求或其他其他來源(例如,調用brk)。
對于源自堆塊的未初始化值,Memcheck顯示塊分配的位置。對于來自堆棧分配的未初始化值,Memcheck可以告訴您分配了哪個值,但不超過該值 - 通常它會顯示函數的開始大括號的源位置。所以你應該仔細檢查所有函數的局部變量是否正確初始化。
性能開銷:原始跟蹤價格昂貴。它使Memcheck的速度減半,并將內存使用量提高至少100MB,甚至更多。然而,它可以大大減少確定未初始化價值錯誤的根本原因所需的努力,因此,盡管運行速度更慢,但通常也是程序員的生產力獲勝。
準確度:Memcheck相當準確地跟蹤起始點。為了避免非常大的空間和時間開銷,進行一些近似。盡管可能不大可能,Memcheck會報告錯誤的來源,或者無法識別任何來源。
注意組合?--track-origins=yes?并且--undef-value-errors=no是無意義的。Memcheck啟動時檢查并拒絕此組合。
控制Memcheck如何從一些字節可尋址的地址處理32位,64位,128位和256位自然對齊的負載,而其他字節不可尋址。何時yes,這樣的負載不會產生地址錯誤。相反,來自非法地址的加載字節被標記為未初始化,并且以正常方式處理與合法地址相對應的字節。
當no來自部分無效地址的加載處理與完全無效地址的加載相同時:發出非法地址錯誤,并將生成的字節標記為初始化。
請注意,以這種方式行為的代碼違反ISO C / C ++標準,應被視為破壞。如果可能,這些代碼應該是固定的。
控制在檢查值的定義時,Memcheck是否應該使用更精確但更昂貴(耗時)的算法。默認設置不是這樣做,它通常就足夠了。然而,對于高度優化的代碼,valgrind有時可能會錯誤地抱怨。調用valgrind?--expensive-definedness-checks=yes?有助于提高性能成本。已經觀察到25%的運行時間降級,但額外的成本在很大程度上取決于手頭的應用。
控制用于保持malloc'd和/或free'd塊的堆棧跟蹤。
使用alloc-then-free,在分配時間記錄堆棧跟蹤,并與塊相關聯。當塊被釋放時,記錄第二個堆棧跟蹤,這將替換分配堆棧跟蹤。因此,與此塊相關的任何“使用免費”錯誤只能顯示塊被釋放的位置的堆棧跟蹤。
對于alloc-and-free塊,存儲塊的分配和分配堆棧跟蹤。因此,“免費使用”錯誤將同時顯示,這可能會使錯誤更容易診斷。與alloc-then-free此相比,此設置稍微增加了Valgrind的內存使用,因為該塊包含兩個引用而不是一個引用。
有了alloc,只是分配棧跟蹤記錄(并報告)。隨著free,只有釋放堆棧跟蹤記錄(并報告)。這些值稍微減少了Valgrind的內存和CPU使用率。它們可以是有用的,具體取決于您正在搜索的錯誤類型以及需要分析的詳細程度。例如,如果您只對內存泄漏錯誤感興趣,則記錄分配堆棧跟蹤就足夠了。
隨著none,沒有堆棧跟蹤記錄的malloc和free操作。如果您的程序分配了許多塊和/或從許多不同的堆棧跟蹤分配/釋放,這可以顯著降低cpu和/或所需的內存。當然,與堆塊相關的錯誤將不會報告很少的細節。
請注意,一旦記錄了堆棧跟蹤,Valgrind將堆棧跟蹤保存在內存中,即使它沒有被任何塊引用。一些程序(例如,遞歸算法)可以產生大量堆棧跟蹤。如果Valgrind在這種情況下使用太多內存,可以減少選項所需的內存--keep-stacktraces?和/或使用較小的選項值--num-callers。
當客戶端程序使用free(in?C)或?delete?(C++)釋放內存時?,該內存不會立即可用于重新分配。相反,它被標記為不可訪問,并放置在自由塊的隊列中。目的是盡可能延長釋放記憶回流的時間。這增加了Memcheck在被釋放后的某個相當長的一段時間內能夠檢測到塊的無效訪問的機會。
此選項指定隊列中塊的最大總大小(以字節為單位)。默認值為二千萬字節。增加這會增加Memcheck使用的內存總量,但是可能會檢測到無效的釋放塊的使用,否則將無法檢測到。
當從可用于重新分配的釋放塊的隊列中制作塊時,Memcheck優先重新循環大小或等于的大小--freelist-big-blocks。這確保了釋放大塊(特別是釋放塊大于?--freelist-vol)不會立即導致自由列表中所有(或許多)小塊的重新流通。換句話說,即使大塊被釋放,這個選項增加了發現“小”塊的懸掛指針的可能性。
設置值為0表示所有塊都按FIFO順序重新循環。
啟用時,假設讀取和寫入堆棧指針之外的一些小距離是由于GCC 2.96中的錯誤,并且不會報告它們。“小距離”默認為256字節。請注意,GCC 2.96是一些古老的Linux發行版(RedHat 7.X)上的默認編譯器,因此您可能需要使用此選項。不要使用它,如果你不必要,因為它可以導致真正的錯誤被忽視。一個更好的選擇是使用更新的GCC修復這個錯誤。
在32位PowerPC Linux上使用GCC 3.X或4.X時,還可能需要使用此選項。這是因為GCC生成的代碼偶爾訪問堆棧指針以下,特別是對于浮點數到整數轉換。這違反了32位PowerPC ELF規范,這并不意味著堆棧指針下方的位置可以訪問。
此選項自版本3.12已棄用,可能會從將來的版本中刪除。您應該使用它?--ignore-range-below-sp來指定應忽略的堆棧指針下方的偏移量的精確范圍。一個合適的等價物是--ignore-range-below-sp=1024-1。
這是替代不推薦使用的?--workaround-gcc296-bugs選項。指定時,會導致Memcheck不會在堆棧指針下方的指定偏移量下報告訪問錯誤。兩個偏移量必須是正十進制數,并且有些是反直覺的 - 第一個必須更大,以便將非環繞地址范圍意味著忽略。例如,要忽略堆棧指針下方8192字節的4字節訪問,請使用--ignore-range-below-sp=8192-8189。只能指定一個范圍。
啟用時,Memcheck使用與分配功能匹配的功能來檢查堆塊是否被釋放。也就是說,它預計free將用于刪除根據所分配的塊malloc,delete供分配的塊new,并delete[]為塊的分配new[]。如果檢測到不匹配,則報告錯誤。這通常很重要,因為在某些環境中,釋放不匹配的功能可能導致崩潰。
但是有一種情況是不能避免這種錯配。那就是當用戶提供?new/?new[]該呼叫malloc和delete/或delete[]該呼叫的實現free時,這些功能是不對稱內聯的。例如,假設delete[]是內聯的,但new[]不是。結果是Memcheck將所有delete[]呼叫視為直接呼叫free,即使程序源不包含不匹配的呼叫。
這會導致很多混亂和不相關的錯誤報告。?--show-mismatched-frees=no禁用這些檢查。盡管如此,通常不建議您禁用它們,因為您可能會錯過實際錯誤。
Memcheck的可尋址性檢查將忽略此選項中列出的任何范圍(可以指定多個范圍,以逗號分隔)。
填補了分配的塊malloc,?new等等,但不calloc與指定的字節。這在嘗試擺脫晦澀的內存損壞問題時很有用。分配的區域仍然被Memcheck視為未定義 - 此選項僅影響其內容。請注意,--malloc-fill當用作客戶端請求VALGRIND_MEMPOOL_ALLOC或VALGRIND_MALLOCLIKE_BLOCK的參數時,不會影響內存塊。
填充由釋放的塊free,?delete等等,與指定的字節值。這在嘗試擺脫晦澀的內存損壞問題時很有用。Memcheck仍然將被釋放的區域視為訪問無效 - 此選項僅影響其內容。請注意,--free-fill當用作客戶端請求VALGRIND_MEMPOOL_FREE或VALGRIND_FREELIKE_BLOCK的參數時,不會影響內存塊。
4.4。編寫抑制文件
抑制錯誤中描述了基本抑制格式?。
抑制型(第二行)應具有以下格式:
MEMCHECK:suppression_typeMemcheck抑制類型如下:
-
Value1,?Value2,?Value4,?Value8,?Value16,使用的1,2,4,8或16字節的值時意一個未初始化的值錯誤。
-
Cond(或其舊名稱Value0),意思是使用未初始化的CPU條件代碼。
-
Addr1,?Addr2,?Addr4,?Addr8,?Addr16,分別是1,2,4,8或16字節的存儲器訪問期間意思的地址無效。
-
Jump,意味著跳轉到不可尋址的位置錯誤。
-
Param,意味著無效的系統調用參數錯誤。
-
Free,意思是無效或不匹配的。
-
Overlap,意思是?src/?dst重疊?memcpy或相似的功能。
-
Leak,意味著內存泄漏。
Param?此時,錯誤具有強制性的額外信息行,這是違規系統調用參數的名稱。
Leak?錯誤有一個可選的額外信息行,格式如下:
匹配泄漏種:<設定>其中<set>指定該抑制條目匹配的泄漏種類。?<set>以與選項相同的方式指定--show-leak-kinds,即以下之一:
- 一個或多個逗號分隔的列表?definite indirect possible reachable。
- all?指定完整集(所有泄漏種類)。
- none?為空集。
如果此可選附加行不存在,則抑制條目將匹配所有泄漏類型。
請注意,使用創建的泄漏抑制?--gen-suppressions將包含此可選額外的行,因此可能會比您預期的更少的泄漏。您可能需要在使用生成的壓縮之前刪除該行。
其他Memcheck錯誤種類沒有額外的行。
如果您給出-v選項,Valgrind將在執行結束時打印所使用的抑制列表。對于泄漏抑制,該輸出給出與抑制相匹配的不同丟失記錄的數量,以及抑制抑制的字節數和塊數。如果運行包含多個泄漏檢查,則在每次新的泄漏檢查之前,字節和塊的數量將重置為零。請注意,不同的丟失記錄的數量不會重置為零。
在下面的示例中,在最后一次泄漏搜索中,7個塊和96個字節已被抑制,名稱為?some_leak_suppression:
--21041-- used_suppression:10 some_other_leak_suppression s.supp:14被抑制:1個塊中的12,400個字節 --21041-- used_suppression:39 some_leak_suppression s.supp:2被抑制:7個塊中的96個字節對于ValueN和AddrN?錯誤,調用上下文的第一行是發生錯誤的函數的名稱,或者失敗的是.so包含錯誤位置的文件或可執行文件的完整路徑。對于Free錯誤,第一行是函數做的釋放(例如,名字?free,__builtin_vec_delete等等)。為Overlap錯誤,第一行是與重疊參數的函數的名稱(例如?memcpy,strcpy等)。
任何抑制的最后一部分指定需要匹配的調用上下文的其余部分。
4.5。Memcheck檢查機械的詳細信息
如果您想要詳細了解Memcheck正在檢查的內容,請閱讀本部分。
4.5.1。有效值(V)位
考慮到Memcheck實現與真實CPU相同的合成CPU是最簡單的,除了一個關鍵的細節。在實際CPU中處理,存儲和處理的數據的每一位(字面上)在合成CPU中具有相關聯的“有效值”位,其說明伴隨位是否具有合法值。在下面的討論中,該位被稱為V(有效值)位。
因此,系統中的每個字節都有一個8位,它隨處可見。例如,當CPU從存儲器加載一個字大小的項目(4字節)時,它也會從位圖中加載相應的32 V位,該位圖存儲過程'整個地址空間的V位。如果CPU應該將該值的全部或部分內容寫入不同地址的存儲器,則相關的V位將被存儲在V位位圖中。
簡而言之,系統中的每一位都有(概念上)一個關聯的V位,即使在CPU內部也隨處可見。是的,所有CPU的寄存器(整數,浮點,向量和條件寄存器)都有自己的V位向量。為了使這個工作,Memcheck使用大量的壓縮來緊密地表示V位。
復制值不會導致Memcheck檢查或報告錯誤。但是,當以可能影響程序的外部可見行為的方式使用值時,將立即檢查相關的V位。如果其中任何一個表示該值未定義(甚至部分),則會報告錯誤。
這是一個(絕對荒謬的)例子:
int i,j; int [10],b [10]; for(i = 0; i <10; i ++){j = a [i];b [i] = j; }Memcheck不會對此發出任何抱怨,因為它僅將未初始化的值復制a[]到?b[]并且不會以影響程序行為的方式使用它們。但是,如果循環更改為:
for(i = 0; i <10; i ++){j + = a [i]; } if(j == 77) printf(“hello there \ n”);那么Memcheck會抱怨說,?if條件取決于未初始化的值。請注意,它不會抱怨j += a[i];,因為在那個時候,未定義不是“可觀察的”。只有當printfMemcheck投訴時,必須做出是否做出您的程序的可觀察的操作的決定。
大多數低級操作(如增加)使Memcheck使用V位作為操作數來計算結果的V位。即使結果部分或全部未定義,也不會抱怨。
對定義的檢查僅發生在三個位置:當使用值來生成存儲器地址時,需要進行控制流決定時,以及檢測到系統調用時,Memcheck根據需要檢查參數的定義。
如果檢查應檢測未定義,則會發出錯誤消息。結果的值隨后被認為是明確的。否則會給出長鏈的錯誤消息。換句話說,一旦Memcheck報告了一個未定義的值錯誤,它會嘗試避免報告從相同的未定義值導出的進一步錯誤。
這聽起來過于復雜。為什么不檢查內存中的所有讀數,并抱怨如果未定義的值被加載到CPU寄存器中?那么這樣做并不好,因為完全合法的C程序通常會在內存中復制未初始化的值,我們不希望無限的抱怨。這是典型的例子。考慮一個這樣的結構:
struct S {int x; char c }; 結構S s1,s2; s1.x = 42; s1.c ='z'; s2 = s1;要問的問題是:struct S以字節為單位多大?一個int是4個字節和?char一個字節,所以也許struct S占用5個字節?錯誤。我們所知道的所有非玩具編譯器都將struct S整合大量的單詞,在這種情況下為8個字節。不這樣做會強制編譯器生成真正令人震驚的代碼來訪問?struct S某些架構上的數組。
所以s1占用8個字節,但只有5個字節被初始化。對于作業s2 = s1,GCC生成代碼,將所有8個字節批量復制到s2?不考慮其含義。如果Memcheck從內存中簡單地檢查了值,那么每當這樣的結構賦值發生時,它就會變得啰嗦。所以上述更復雜的行為是必要的。這讓GCC復制?s1到s2它喜歡的任何方式,如果未初始化值以后要使用的警告才會發出。
4.5.2。有效地址(A)位
請注意,上一小節描述了如何建立和維護值的有效性,而無需說明程序是否具有訪問任何特定內存位置的權限。我們現在考慮后一個問題。
如上所述,存儲器或CPU中的每一位具有相關聯的有效值(V)位。此外,內存中的所有字節,但不在CPU中,都具有相關的有效地址(A)位。這表示程序是否可以合法讀取或寫入該位置。它不表示在該位置的數據的有效性 - 這是V位的工作 - 只有位置是否可被訪問。
每次程序讀或寫內存時,Memcheck會檢查與地址相關的A位。如果其中任何一個表示無效的地址,則會發出錯誤。請注意,讀寫本身不改變A位,只能查閱它們。
那么A位如何設置/清除?喜歡這個:
-
當程序啟動時,所有的全局數據區被標記為可訪問。
-
當程序執行?malloc/?new時,用于準確分配區域的A位,而不是更多的字節被標記為可訪問。釋放該區域后,A位被更改以指示不可訪問。
-
堆棧指針寄存器(SP)向上或向下移動時,A位置1。規則是從SP棧到底的區域?被標記為可訪問,下面SP是不可訪問的。(如果這聽起來不合邏輯,請記住,幾乎所有的Unix系統(包括GNU / Linux),堆棧都在增長,而不是上升。)SP像這樣的跟蹤?具有一個有用的副作用,即本地功能的一部分堆棧變量等在函數輸入時自動標記可訪問,退出時無法訪問。
-
進行系統調用時,A位被適當地改變。例如,mmap?神奇地使文件出現在進程的地址空間中,因此如果mmap?成功,必須更新A位。
-
或者,您的程序可以使用上述客戶端請求機制明確地告訴Memcheck這些更改。
4.5.3。把它們放在一起
Memcheck的檢查機械可概括如下:
-
存儲器中的每個字節具有8個關聯的V(有效值)位,表示該字節是否具有定義的值,以及一個A(有效地址)位,表示該程序當前是否具有讀/寫那個地址。如上所述,大量使用壓縮意味著開銷通常在25%左右。
-
當讀取或寫入存儲器時,可以查詢相關的A位。如果它們指示無效的地址,Memcheck會發出無效的讀取或無效的寫入錯誤。
-
當內存讀入CPU的寄存器時,相關的V位將從存儲器中讀取并存儲在模擬CPU中。沒有咨詢。
-
當寄存器寫入存儲器時,該寄存器的V位也被寫回存儲器。
-
當CPU寄存器中的值用于生成存儲器地址或確定條件轉移的結果時,將檢查這些值的V位,如果其中任何一個未定義,則發出錯誤。
-
當CPU寄存器中的值用于任何其他目的時,Memcheck計算結果的V位,但不檢查它們。
-
一旦檢查了CPU中的值的V位,就將它們設置為指示有效性。這樣可以避免長鏈錯誤。
-
當從內存中加載值時,Memcheck會檢查該位置的A位,并在需要時發出非法地址警告。在這種情況下,加載的V位將被強制指示有效,盡管位置無效。
這顯然奇怪的選擇減少了向用戶呈現的令人困惑的信息量。它避免了從不可尋址并包含無效值的地方讀取存儲器的不愉快現象,因此,您不僅可以獲得無效地址(讀/寫)錯誤,還可以獲得一個潛在的大量未初始化值錯誤,每次使用該值時都會出現一個錯誤。
來自地址部分有效且部分無效的多字節加載有一個模糊的邊界情況。有關詳細信息,請參閱選項--partial-loads-ok的詳細信息。
MEMCHECK攔截來電malloc,?calloc,realloc,?valloc,memalign,?free,new,?new[],?delete和?delete[]。你得到的行為是:
-
malloc/?new/?new[]:返回存儲器被標記為可尋址的但不具有有效的值。這意味著你必須寫信才能閱讀它。
-
calloc:返回的內存標記為可尋址和有效,因為calloc將該區域清除為零。
-
realloc:如果新的大小大于舊的,新的部分可尋址但無效,如同?malloc。如果新尺寸較小,則下降部分被標記為不可尋址。你可能只傳給?realloc以前由malloc/?calloc/?發給你的指針?realloc。
-
free/?delete/?delete[]:你只能傳遞給這些函數一個指針,以前由相應的分配函數發給你。否則,Memcheck抱怨。如果指針確實有效,則Memcheck將其指向的整個區域標記為不可尋址,并將該塊置于自由塊隊列中。目的是推遲這個塊的重新分配。在這種情況下,所有嘗試訪問它將會引發無效的地址錯誤,就像您所希望的那樣。
4.6。Memcheck監視器命令
Memcheck工具提供由Valgrind內置的gdbserver?處理的監視器命令(請參閱Valgrind gdbserver的Monitor命令處理)。
-
xb <addr> [<len>]?顯示從<addr>開始的<len>(默認為1)字節的定義(V)位和值。對于每8個字節,輸出兩行。
第一行顯示8個字節的有效位。使用兩個十六進制數字給出范圍內每個字節的定義。這些十六進制數字對相應字節的每個位的有效性進行編碼,如果定義了該位,則使用0,如果該位未定義則為1。如果一個字節不可尋址,則其有效位被__(雙下劃線)替代。
第二行顯示低于相應有效位的字節值。用于顯示字節數據的格式與GDB命令'x / <len> xb <addr>'類似。不可尋址字節的值顯示為?(兩個問號)。
在下面的示例中,string10是一個10個字符的數組,其中偶數字節未定義。在下面的例子中,對應的字節string10[5]是不可尋址的。
(gdb)p&string10 $ 4 =(char(*)[10])0x804a2f0 (gdb)mo xb 0x804a2f0 10ff 00 ff 00 ff __ ff 00 0x804A2F0:0x3f 0x6e 0x3f 0x65 0x3f 0x ?? 0x3f 0x65ff 00 0x804A2F8:0x3f 0x00 地址0x804A2F0 len 10有1個字節不可尋址 (GDB)命令xb不能與寄存器一起使用。要獲取寄存器的有效位,您必須使用該選項啟動Valgrind?--vgdb-shadow-registers=yes。然后可以通過打印相應的“shadow 1”寄存器來獲得寄存器的有效位。在下面的x86示例中,寄存器eax具有未定義的所有位,而寄存器ebx被完全定義。
(gdb)p / x $ eaxs1 $ 9 = 0xffffffff (gdb)p / x $ ebxs1 $ 10 = 0x0 (GDB) -
get_vbits <addr> [<len>]?使用與xb命令相同的約定,顯示從<addr>開始的<len>(默認1)字節的定義(V)位?。get_vbits僅顯示V位(按4字節分組)。它不顯示值。如果要將V位與相應的字節值相關聯,則?xb命令將更容易使用,特別是在將未定義的整數部分與其V位值相關聯時,在小端計算機上。
下面的示例示出的結果get_vibts?對string10在所使用的?xb?命令的解釋。
(gdb)monitor get_vbits 0x804a2f0 10 ff00ff00 ff__ff00 ff00 地址0x804A2F0 len 10有1個字節不可尋址 (GDB) -
make_memory [noaccess|undefined|defined|Definedifaddressable] <addr> [<len>]將<addr>中的<len>(默認為1)字節的范圍標記為具有給定狀態。參數?noaccess將范圍標記為不可訪問,所以Memcheck會報告任何訪問錯誤。?undefined或defined將該區域標記為可訪問的,但是Memcheck分別將其中的字節視為具有未定義或定義的值。?Definedifaddressable標記為已定義的范圍內的字節,這些字節已經可以被尋址,但不能改變不可尋址范圍內的字節的狀態。請注意,第一個字母Definedifaddressable?是大寫D以避免混淆defined。
在下面的例子中,第一個字節?string10被標記為定義:
(gdb)monitor make_memory定義0x8049e28 1 (gdb)monitor get_vbits 0x8049e28 10 0000ff00 ff00ff00 ff00 (GDB) -
check_memory [addressable|defined] <addr> [<len>]檢查<addr>中<len>(默認為1)字節的范圍是否具有指定的可訪問性。然后輸出<addr>的描述。在以下示例中,可以使用詳細的描述,因為該選項--read-var-info=yes在Valgrind啟動時給出:
(gdb)monitor check_memory定義0x8049e28 1 地址0x8049E28 len 1定義 == 14698 ==位置0x8049e28是string10 [0]中的0個字節, == 14698 ==在prog.c中聲明:10,在線程1的幀#0中 (GDB) -
leak_check [full*|summary] [kinds <set>|reachable|possibleleak*|definiteleak] [heuristics heur1,heur2,...] [increased*|changed|any] [unlimited*|limited <max_loss_records_output>]?執行泄漏檢查。參數*中的參數表示默認值。
如果[full*|summary]是?summary,則僅提供泄漏搜索的摘要;?否則將生成完整的泄漏報告。完整的泄漏報告提供了每個泄漏的詳細信息:泄漏的塊被分配的堆棧跟蹤,泄露的塊的數量及其總大小。當請求完整的報告時,接下來的兩個參數進一步指定了什么樣的泄漏報告。如果匹配第二個參數和第三個參數,則會顯示一個泄漏的詳細信息。完整的泄漏報告可能會輸出許多泄漏的詳細信息。輸出信息的nr可以使用limited隨后的最大nr個泄漏記錄輸出的參數進行控制。如果達到此最大值,則泄漏搜索將輸出最大字節數的記錄。
該kinds參數控制了full泄漏搜索顯示哪些塊。可以使用<set>與命令行選項類似的方式指定要顯示的一組泄漏種類--show-leak-kinds。或者,該值definiteleak?相當于kinds definite,該值possibleleak相當于?kinds definite,possible:它還將顯示可能的泄漏塊,只有一個內部指針被找到。該值reachable將顯示所有塊類別(即等效于kinds all)。
該heuristics參數控制在泄漏搜索期間使用的啟發式。可以使用<set>類似于命令行選項來指定要使用的啟發式集合--leak-check-heuristics。heuristics參數?的默認值為heuristics none。
該[increased*|changed|any]參數控制對full泄漏搜索顯示哪些更改。該值increased指定僅顯示自上次泄漏檢查以來泄漏字節或塊數增加的塊分配堆棧。該值changed指定應顯示自上次泄漏檢查以來發生任何更改的分配堆棧。該值any指定應顯示所有泄漏條目,而不管任何增加或減少。當指定increased或changed指定時,泄漏報告條目將顯示相對于上一個泄漏報告的增量。
以下示例顯示leak_check了在memcheck/tests/leak-cases.c回歸測試中使用?monitor命令。第一個命令輸出一個具有增加的泄漏字節的條目。第二個命令與第一個命令相同,但使用GDB和Valgrind gdbserver接受的縮寫形式。它只輸出摘要信息,因為以前的泄漏搜索沒有增加。
(gdb)monitor leak_check full possibleleak增加 在1(+1)個塊中的== 19520 == 16(+16)個字節可能在12的損失記錄中丟失 == 19520 == at 0x40070B4:malloc(vg_replace_malloc.c:263) == 19520 == by 0x80484D5:mk(leak-cases.c:52) == 19520 == by 0x804855F:f(leak-cases.c:81) == 19520 == by 0x80488E0:main(leak-cases.c:107) == == 19520 == 19520 == LEAK SUMMARY: == 19520 ==絕對丟失:2(+0)個塊中的32(+0)個字節 == 19520 ==間接丟失:1(+0)個塊中的16(+0)個字節 == 19520 ==可能丟失:2(+1)塊中的32(+16)個字節 == 19520 ==仍然可達:6(+1)個塊中的96(+16)個字節 == 19520 == suppress:0(+0)個字節在0(+0)塊 == 19520 ==可以訪問的塊(找到指針的塊)未顯示。 == 19520 ==要查看它們,請將“可達到的任何”參數添加到leak_check == == 19520 (gdb)mo l == 19520 == LEAK SUMMARY: == 19520 ==絕對丟失:2(+0)個塊中的32(+0)個字節 == 19520 ==間接丟失:1(+0)個塊中的16(+0)個字節 == 19520 ==可能丟失:2(+0)個塊中的32(+0)個字節 == 19520 ==仍然可達:6(+0)個塊中的96(+0)個字節 == 19520 == suppress:0(+0)個字節在0(+0)塊 == 19520 ==可以訪問的塊(找到指針的塊)未顯示。 == 19520 ==要查看它們,請將“可達到的任何”參數添加到leak_check == == 19520 (GDB)請注意,當使用Valgrind的gdbserver時,無需重新運行--leak-check=full?--show-reachable=yes查看可訪問的塊。您可以通過使用GDB命令monitor leak_check full reachable any(或使用縮寫:)獲得相同的信息,而無需重新運行mo l f r a。
-
block_list <loss_record_nr>|<loss_record_nr_from>..<loss_record_nr_to> [unlimited*|limited <max_blocks>] [heuristics heur1,heur2,...]?顯示屬于<loss_record_nr>(或損失記錄范圍?<loss_record_nr_from>..<loss_record_nr_to>)的塊列表?。可以使用limited參數后跟最大nr個塊來輸出要打印?的塊的nr。如果給出一個或多個啟發式方法,則僅打印通過給定heur1,heur2,...?啟發式之一找到的損失記錄和塊。
泄漏搜索將丟失記錄中的已分配塊合并:丟失記錄重新分組具有相同狀態的所有塊(例如,絕對丟失)和相同的分配回溯。每個損失記錄在泄漏搜索結果中由損失記錄號識別。該block_list命令顯示損失記錄信息,后面跟隨損失記錄中合并的塊的地址和大小。如果使用啟發式方法找到一個塊,則塊大小后面是啟發式的。
如果直接丟失的塊導致一些其他塊間接丟失,則block_list命令還將顯示這些間接丟失的塊。根據直接丟失塊和間接丟失塊之間的間接級別,間接丟失的塊將被縮進。每個間接丟失的塊后面都是其損失記錄的引用。
只要在這種泄漏搜索之后沒有塊被釋放,就可以使用block_list命令對泄漏搜索結果:一旦程序釋放一個塊,在再次使用block_list之前需要新的泄漏搜索。
在下面的示例中,程序通過丟失指向塊A(樹頂部)的指針來泄漏樹結構。因此,塊A直接丟失,導致塊B到G的間接丟失。第一個block_list命令顯示A(一個絕對丟失的塊,地址為0x4028028,大小為16)的丟失記錄。塊A的間接丟失塊的地址和大小顯示在塊A的下方。第二個命令顯示第一個命令輸出的間接損失記錄之一的細節。
一個/ \公元前/ \ / \ DEFG (gdb)bt #0 main()在leak-tree.c:69 (gdb)monitor leak_check full any == 19552 == 112(16個直接,96個間接)1個塊中的字節在7的損失記錄中絕對丟失 == 19552 ==在0x40070B4:malloc(vg_replace_malloc.c:263) == 19552 == 0x80484D5:mk(leak-tree.c:28) == 19552 == by 0x80484FC:f(leak-tree.c:41) == 19552 == by 0x8048856:main(leak-tree.c:63) == == 19552 == 19552 == LEAK SUMMARY: == 19552 ==絕對丟失:1個塊中的16個字節 == 19552 ==間接丟失:6個塊中的96個字節 == 19552 ==可能丟失:0個字節,0個塊 == 19552 ==仍然可達:0個字節,0個塊 == 19552 == suppress:0個字節,0個塊 == == 19552 (gdb)monitor block_list 7 == 19552 == 112(16個直接,96個間接)1個塊中的字節在7的損失記錄中絕對丟失 == 19552 ==在0x40070B4:malloc(vg_replace_malloc.c:263) == 19552 == 0x80484D5:mk(leak-tree.c:28) == 19552 == by 0x80484FC:f(leak-tree.c:41) == 19552 == by 0x8048856:main(leak-tree.c:63) == 19552 == 0x4028028 [16] == 19552 == 0x4028068 [16]間接損失記錄1 == 19552 == 0x40280E8 [16]間接損失記錄3 == 19552 == 0x4028128 [16]間接損失記錄4 == 19552 == 0x40280A8 [16]間接損失記錄2 == 19552 == 0x4028168 [16]間接損失記錄5 == 19552 == 0x40281A8 [16]間接損失記錄6 (gdb)mo b 2 == 19552 == 1個塊中的16個字節在7的損失記錄中間接丟失 == 19552 ==在0x40070B4:malloc(vg_replace_malloc.c:263) == 19552 == 0x80484D5:mk(leak-tree.c:28) == 19552 == by 0x8048519:f(leak-tree.c:43) == 19552 == by 0x8048856:main(leak-tree.c:63) == 19552 == 0x40280A8 [16] == 19552 == 0x4028168 [16]間接損失記錄5 == 19552 == 0x40281A8 [16]間接損失記錄6 (GDB) -
who_points_at <addr> [<len>]?顯示找到指向addr的指針的所有位置。如果len等于1,則該命令僅顯示正好位于addr的位置(即“add指針”到addr)。如果len> 1,指示len第一個字節的“內部指針”也將顯示。
搜索的位置與泄漏搜索中使用的位置相同。所以,who_points_at可以用來顯示為什么泄漏搜索仍然可以到達一個塊,或者可以搜索懸浮指針到一個被釋放的塊。將描述指向addr的每個位置(或指向內部addr,如果正在搜索內部指針)。
在下面的示例中,block_list在樹被泄露之前顯示了指向“樹塊A”(參見命令中的示例)的指針。說明詳見--read-var-info=yes?Valgrind啟動時的選項。第二個調用顯示塊G的指針(起始和內部指針)。塊G(0x40281A8)可通過塊C(0x40280a8)和寄存器ECX為tid 1(tid為Valgrind線程ID)到達。通過注冊EBX“內部可達”。
(gdb)monitor who_points_at 0x4028028 == 20852 ==搜索指向0x4028028的指針 == 20852 == * 0x8049e20分在0x4028028 == 20852 ==位置0x8049e20是全局var“t”內的0個字節 == 20852 ==在leak-tree.c中聲明:35 (gdb)monitor who_points_at 0x40281A8 16 == 20852 ==搜索從0x40281a8指向16個字節的指針 == 20852 == * 0x40280ac指向0x40281a8 == 20852 ==地址0x40280ac是一個大小為16個alloc'd的塊內的4個字節 == 20852 == 0x40070B4:malloc(vg_replace_malloc.c:263) == 20852 == 0x80484D5:mk(leak-tree.c:28) == 20852 == 0x8048519:f(leak-tree.c:43) == 20852 == by 0x8048856:main(leak-tree.c:63) == 20852 == tid 1注冊ECX分數為0x40281a8 == 20852 == tid 1注冊EBX內部點在2個字節內部0x40281a8 (GDB)當who_points_at找到一個內部指針時,它會報告這個內部指針被認為是可達到的啟發式的。請注意,這是獨立于該選項的值完成的--leak-check-heuristics。在下面的示例中,丟失記錄6指示可能丟失的塊。who_points_at報告指出內部指針指向該塊,并且該塊可以被認為是可以使用啟發式的multipleinheritance。
(gdb)monitor block_list 6 == 3748 == 1個塊中的8個字節可能在7的損失記錄6中丟失 == 3748 == at 0x4007D77:operator new(unsigned int)(vg_replace_malloc.c:313) == 3748 == 0x8048954:main(leak_cpp_interior.cpp:43) == 3748 == 0x402A0E0 [8] (gdb)monitor who_points_at 0x402A0E0 8 == 3748 ==搜索指向0x402a0e0的8個字節的指針 == 3748 == * 0xbe8ee078內部點4個字節內部0x402a0e0 == 3748 ==地址0xbe8ee078在線程1的堆棧 == 3748 == block at 0x402a0e0認為可以通過ptr 0x402a0e4使用多重繼承啟發式 (GDB)
4.7。客戶端請求
下面定義了以下客戶端請求?memcheck.h。看到memcheck.h他們的參數的確切細節。
-
VALGRIND_MAKE_MEM_NOACCESS,?VALGRIND_MAKE_MEM_UNDEFINED和?VALGRIND_MAKE_MEM_DEFINED。這些標記地址范圍完全不可訪問,可訪問但包含未定義的數據,并且可訪問和包含定義的數據。在Valgrind運行時返回-1,否則返回0。
-
VALGRIND_MAKE_MEM_DEFINED_IF_ADDRESSABLE。這就像VALGRIND_MAKE_MEM_DEFINED只是影響那些已經可尋址的字節。
-
VALGRIND_CHECK_MEM_IS_ADDRESSABLE并且?VALGRIND_CHECK_MEM_IS_DEFINED:立即檢查給定的地址范圍是否具有相關屬性,如果沒有,請打印錯誤消息。此外,為了方便客戶,如果相關屬性成立,則返回零;?否則,返回的值是屬性不為真的第一個字節的地址。在Valgrind不運行時始終返回0。
-
VALGRIND_CHECK_VALUE_IS_DEFINED:一個快速簡便的方法來查明Valgrind是否認為一個特定的值(左值,準確)是可尋址和定義的。打印錯誤信息,如果沒有。它沒有返回值。
-
VALGRIND_DO_LEAK_CHECK:現在做一個完整的內存泄漏檢查(像--leak-check=full)。這對于逐步檢查程序執行中任意位置之間的泄漏是非常有用的。它沒有返回值。
-
VALGRIND_DO_ADDED_LEAK_CHECK:與?VALGRIND_DO_LEAK_CHECK以前的泄漏搜索相同?但僅顯示泄漏字節增加或塊數泄漏的條目。它沒有返回值。
-
VALGRIND_DO_CHANGED_LEAK_CHECK:與VALGRIND_DO_LEAK_CHECK以前的泄漏搜索相同?但僅顯示泄露字節增加或減少的條目或泄漏的塊數。它沒有返回值。
-
VALGRIND_DO_QUICK_LEAK_CHECK:喜歡?VALGRIND_DO_LEAK_CHECK,除了它只產生泄漏匯總(如--leak-check=summary)。它沒有返回值。
-
VALGRIND_COUNT_LEAKS:填寫四個參數,其中先前泄漏檢查發現的內存字節數(即直接泄漏和間接泄漏的總和),可疑,可達到并被抑制。這在調用VALGRIND_DO_LEAK_CHECK或?之后的測試線索代碼中很有用VALGRIND_DO_QUICK_LEAK_CHECK。
-
VALGRIND_COUNT_LEAK_BLOCKS:?VALGRIND_COUNT_LEAKS除了它返回塊數而不是每個類別中的字節數除外。
-
VALGRIND_GET_VBITS并且?VALGRIND_SET_VBITS:允許您獲取并設置地址范圍的V(有效性)位。你應該只設置你所擁有的V位?VALGRIND_GET_VBITS。只有那些真正了解他們在做什么的人才會。
-
VALGRIND_CREATE_BLOCK和?VALGRIND_DISCARD。?VALGRIND_CREATE_BLOCK?取一個地址,一個字節和一個字符串。然后指定的地址范圍與該字符串相關聯。當Memcheck報告對該范圍內的地址的無效訪問時,它將根據該塊而不是根據其知道的任何其他塊來描述它。請注意,這個宏的使用實際上并不會以任何方式改變內存的狀態 - 它只是給出范圍的名稱。
在某些時候,您可能希望Memcheck停止報告錯誤VALGRIND_CREATE_BLOCK。為了實現這一點,VALGRIND_CREATE_BLOCK返回一個“塊句柄”,它是一個C?int值。您可以將此塊句柄傳遞給VALGRIND_DISCARD。在這樣做之后,Valgrind將不再將指定范圍內的尋址錯誤與塊相關聯。傳遞無效手柄?VALGRIND_DISCARD是無害的。
4.8。內存池:描述和使用自定義分配器
有些程序通常使用自定義內存分配器,出于性能原因。留給自己,Memcheck無法理解自定義分配方案的行為,并且它了解標準分配器,因此可能會錯過您的程序中的錯誤和泄漏。本節描述的是一種給予Memcheck足夠的自定義分配器的描述的方法,它可以使至少有一些感覺發生了什么。
有許多不同種類的自定義分配器,所以Memcheck嘗試使用松散的抽象模型來推理它們。描述自定義分配系統時,我們使用以下術語:
-
自定義分配涉及一組獨立的“內存池”。
-
Memcheck對內存池的概念由一個“錨地址”和一組與錨地址相關聯的非重疊“塊”組成。
-
通常,池的錨地址是保存“標題”結構的地址。
-
典型地,該池的塊被從通過該系統獲得的連續的“超級塊”拉伸?malloc或?mmap。
請記住,上面的最后兩點表示“通常”:Valgrind mempool客戶端請求API對于mempool的確切結構有意識地模糊。沒有具體提及標題或超級塊。然而,以下圖片可能有助于闡明API中術語的意圖:
“池子”(錨地址)|v+ -------- + --- +| 標題| o |+ -------- + - | - +|v超級塊+ ------ + --- + -------------- + --- + ------------------ +| | RZB | 分配| rzB | |+ ------ + --- + -------------- + --- + ------------------ +^ ^| |“addr”“addr”+“size”注意,報頭和超級塊可以是連續的或不連續的,并且可能存在與單個報頭相關聯的多個超級塊;?這種變化對于Memcheck是不透明的。API只要求您的分配方案可以顯示“pool”,“addr”和“size”的合理值。
通常,在發出與mempools相關的客戶端請求之前,客戶端程序將為其mempool分配這樣的頭和超級塊,并使用VALGRIND_MAKE_MEM_NOACCESS客戶端請求標記超級塊NOACCESS?。
在處理mempools時,目標是保持一個特定的不變條件:Memcheck相信池的超級塊(包括redzones)的未分配部分是NOACCESS。為了維護這個不變量,客戶端程序必須確保超級塊在那個狀態下開始;?Memcheck不能這樣做,因為Memcheck從來沒有明確地了解一個池的超級塊,只有池中已分配的塊。
一旦池的頭和超級塊被建立并被正確標記,有許多客戶端請求程序可以用來通知Memcheck關于對mempool的狀態的改變:
-
VALGRIND_CREATE_MEMPOOL(pool, rzB, is_zeroed):該請求將地址pool注冊為內存池的錨地址。它還提供了一個大小rzB,指定從池分配的塊周圍放置的redzones?的大小?。最后,它提供了一個?is_zeroed參數,指定在分配時池的塊是否為零(更精確地:定義)。
完成此請求后,不會與池相關聯。該請求簡單地告訴Memcheck池存在,以便后續調用可以將其稱為池。
-
VALGRIND_CREATE_MEMPOOL_EXT(pool, rzB, is_zeroed, flags):創建具有一些標志的內存池(可以一起OR-ed)指定擴展行為。當標志為零時,行為是相同的?VALGRIND_CREATE_MEMPOOL。
-
該標志VALGRIND_MEMPOOL_METAPOOL?指定與使用的池相關聯的內存塊VALGRIND_MEMPOOL_ALLOC將被應用程序用作超級塊以使用MALLOC_LIKE塊VALGRIND_MALLOCLIKE_BLOCK。換句話說,元池是一個“2級”池:第一級是由...描述的塊VALGRIND_MEMPOOL_ALLOC。使用第二級塊進行描述VALGRIND_MALLOCLIKE_BLOCK。請注意,池和第二級塊之間的關聯是隱式的:第二級塊將位于第一級塊內。有必要VALGRIND_MEMPOOL_METAPOOL對這樣的2級池使用標志,否則valgrind將檢測到重疊的內存塊,并將中止執行(例如
-
VALGRIND_MEMPOOL_AUTO_FREE。這樣的元池也可以使用標志來標記為“自動免費”池,該標志VALGRIND_MEMPOOL_AUTO_FREE必須與...一起使用VALGRIND_MEMPOOL_METAPOOL。對于“自動免費”池,VALGRIND_MEMPOOL_FREE?將自動釋放第一個級別塊中包含的第二級別塊VALGRIND_MEMPOOL_FREE。換句話說,調用VALGRIND_MEMPOOL_FREE將對VALGRIND_FREELIKE_BLOCK包含在第一級塊中的所有第二級塊產生隱式調用。注意:使用VALGRIND_MEMPOOL_AUTO_FREE沒有VALGRIND_MEMPOOL_METAPOOL標志的標志?是一個錯誤。
-
-
VALGRIND_DESTROY_MEMPOOL(pool):此請求告訴Memcheck池已被拆除。Memcheck然后刪除與池相關聯的所有塊的記錄,以及其存儲池的記錄。在破壞其記憶的記錄的同時,Memcheck將池中任何活塊的redzones重置為NOACCESS。
-
VALGRIND_MEMPOOL_ALLOC(pool, addr, size):此請求通知Memcheck?size已經分配了一個字節塊addr,并將該組合與指定的塊相關聯?pool。如果池是用非零rzB紅色區域創建的?,Memcheck會將rzB塊之前和之后的字節標記?為NOACCESS。如果使用is_zeroed參數集創建池,Memcheck會將該塊標記為DEFINED,否則Memcheck會將該塊標記為UNDEFINED。
-
VALGRIND_MEMPOOL_FREE(pool, addr):此請求通知Memcheck,該塊addr?不應被視為已分配。Memcheck將標記與NOACCESS相關聯的塊addr,并刪除其塊的存在記錄。
-
VALGRIND_MEMPOOL_TRIM(pool, addr, size):此請求修剪與之相關聯的塊pool。該請求僅對與之相關的塊進行操作?pool。修剪正式定義為:
-
整個范圍內的所有塊?addr..(addr+size-1)都被保留。
-
完全超出范圍的所有塊?addr..(addr+size-1)都被丟棄,就像?VALGRIND_MEMPOOL_FREE被稱為它們一樣。
-
所有其他塊必須與范圍相交?addr..(addr+size-1);?十字路口以外的區域被標記為“非空”,就像它們已被獨立釋放一樣?VALGRIND_MEMPOOL_FREE。
這是一個很少見的請求,但可以在實現常規LIFO分配器中常見的無大規模操作的類型中有用。
-
-
VALGRIND_MOVE_MEMPOOL(poolA, poolB):此請求通知Memcheck以前錨定在地址的池poolA已移動到錨地址?poolB。這是一個罕見的請求,通常只有當您realloc的回憶標題時才需要。
此請求不會更改內存狀態位。
-
VALGRIND_MEMPOOL_CHANGE(pool, addrA, addrB, size):該請求通知MEMCHECK先前在地址分配的組塊addrA內的?pool已被移動和/或調整大小,應加以改變,以覆蓋該區域addrB..(addrB+size-1)。這是一個罕見的請求,通常只有realloc在超級塊或希望擴展塊而不改變其內存狀態位時才需要?。
此請求不會更改內存狀態位。
-
VALGRIND_MEMPOOL_EXISTS(pool):該請求通知呼叫者Memcheck是否正在錨定地址處追蹤回憶pool。當與該地址相關聯的mempool時,它評估為1,否則為0。這是一個罕見的請求,僅在客戶端代碼可能丟失了一組活動mempools的情況下才有用。
4.9。使用Valgrind調試MPI并行程序
Memcheck支持使用MPI消息傳遞標準的分布式內存應用程序的調試。該支持包括用于該PMPI_*接口的包裝函數庫?。當加入到應用程序的地址空間中,無論是直接鏈接或通過?LD_PRELOAD,該包裝攔截來電PMPI_Send,?PMPI_Recv等他們再使用客戶端的請求,通知所造成的功能被包裝內存狀態變化MEMCHECK。這減少了Memcheck通常為MPI應用報告的誤報數量。
包裝器還利用機會仔細檢查作為參數傳遞給MPI函數的緩沖區的大小和定義,從而檢測錯誤,例如將未定義的數據傳遞到?PMPI_Send或將數據接收到太小的緩沖區中。
與大多數其他Valgrind不同,包裝庫受BSD風格許可,因此您可以將其鏈接到任何您喜歡的代碼庫。查看mpi/libmpiwrap.c?許可證詳細信息的頂部。
4.9.1。建造和安裝包裝紙
如果可能,將自動構建包裝庫。Valgrind的配置腳本將尋找適合?mpicc于構建它。這必須與mpicc您用來構建您要調試的MPI應用程序相同。默認情況下,Valgrind嘗試?mpicc,但您可以使用configure-time選項指定一個不同的?--with-mpicc。目前,封裝器只能使用mpicc基于GNU GCC或Intel C ++編譯器的s來構建?。
檢查配置腳本是否打印如下所示的行:
檢查可用的MPI2兼容mpicc和mpi.h ...是的,mpicc如果說... no,您的?mpicc編譯和鏈接測試MPI2程序失敗。
如果配置測試成功,繼續與通常的方式?make和make install。最后的安裝樹應該包含?libmpiwrap-<platform>.so。
匯編測試MPI程序(例如,MPI hello-world),并嘗試:
LD_PRELOAD = $ prefix / lib / valgrind / libmpiwrap- <platform> .so \mpirun [args] $ prefix / bin / valgrind ./hello你應該看到類似于以下內容
valgrind MPI包裝紙31901:適用于pid 31901 valgrind MPI包裝器31901:嘗試MPIWRAP_DEBUG =幫助可能的選項對于組中的每個過程重復。如果沒有看到這些,有一種構建/安裝問題。
要包裝的MPI函數被假定為具有soname匹配的ELF共享對象?libmpi.so*。至少對于Open MPI和Quadrics MPI來說,這是正確的,如果需要,可以輕松更改。
4.9.2。入門
像往常一樣編譯MPI應用程序,注意使用與mpicc您的Valgrind構建配置相同的鏈接。
使用以下基本方案在Valgrind上運行您的應用程序:
MPIWRAP_DEBUG = [wrapper-args] \LD_PRELOAD = $ prefix / lib / valgrind / libmpiwrap- <platform> .so \mpirun [mpirun-args] \$ prefix / bin / valgrind [valgrind-args] \[應用程序] [app-args]作為一種替代?LD_PRELOADING?libmpiwrap-<platform>.so,你可以簡單地把它如果需要鏈接到您的應用程序。這不應該以任何方式打擾您的應用程序的本機行為。
4.9.3。控制包裝庫
環境變量?MPIWRAP_DEBUG在啟動時被查閱。默認行為是打印起始橫幅
valgrind MPI包裝器16386:適用于pid 16386 valgrind MPI包裝器16386:嘗試MPIWRAP_DEBUG =幫助可能的選項然后比較安靜。
您可以在其中列出逗號分隔的選項?MPIWRAP_DEBUG。這些是
-
verbose:顯示所有包裝的條目/出口。還顯示額外的調試信息,如優秀的狀態?MPI_Request期從半拉造成MPI_Irecv秒。
-
quiet:相反verbose,當打包機想要報告檢測到的編程錯誤,或者在包裝器發生災難性故障的情況下,只打印任何東西。
-
warn:默認情況下,缺少適當的包裝器的功能沒有評論,只是默默地忽略。這將導致每個使用的展開功能打印一個警告,每個功能最多可以有三個警告。
-
strict:如果使用缺少包裝器的功能,則打印錯誤消息并中止程序。
如果要使用Valgrind的XML輸出工具(--xml=yes),您應該傳入?quiet,?MPIWRAP_DEBUG以便從包裝器中除去任何無關的打印。
4.9.4。功能
所有MPI2功能除外?MPI_Wtick,?MPI_Wtime并?MPI_Pcontrol有包裝。前兩個沒有包裝,因為它們返回一個?double,Valgrind的函數包裝機制無法處理(但是可以很容易地擴展)。?MPI_Pcontrol不可包裹,因為它具有可變特性:?int MPI_Pcontrol(const int level, ...)
大多數函數都使用一個默認的包裝器來打包,除非是根據MPIWRAP_DEBUG上面列出的設置調用,否則它不會執行任何命令,也不會中止它。以下功能具有“真實”,有用的包裝器:
PMPI_Send PMPI_Bsend PMPI_Ssend PMPI_RsendPMPI_Recv PMPI_Get_countPMPI_Isend PMPI_Ibsend PMPI_Issend PMPI_IrsendPMPI_Irecv PMPI_Wait PMPI_Waitall PMPI_Test PMPI_TestallPMPI_Iprobe PMPI_ProbePMPI_CancelPMPI_SendrecvPMPI_Type_commit PMPI_Type_freePMPI_Pack PMPI_UnpackPMPI_Bcast PMPI_Gather PMPI_Scatter PMPI_Alltoall PMPI_Reduce PMPI_Allreduce PMPI_Op_createPMPI_Comm_create PMPI_Comm_dup PMPI_Comm_free PMPI_Comm_rank PMPI_Comm_sizePMPI_Error_string PMPI_Init PMPI_Initialized PMPI_FinalizePMPI_Address列出的?幾個功能?HAS_NO_WRAPPER。他們根本沒有包裝,因為沒有什么值得檢查,給出一個無操作的包裝器會降低性能沒有任何理由。
請注意,包裝庫本身本身可以產生大量對MPI實現的調用,特別是在步行復雜類型時。所謂的最常用的功能是?PMPI_Extent,?PMPI_Type_get_envelope,?PMPI_Type_get_contents,和?PMPI_Type_free。
4.9.5。類型
支持MPI-1.1結構化類型,并正確運行。目前支持的組合是?MPI_COMBINER_NAMED,?MPI_COMBINER_CONTIGUOUS,?MPI_COMBINER_VECTOR,?MPI_COMBINER_HVECTOR?MPI_COMBINER_INDEXED,?MPI_COMBINER_HINDEXED和?MPI_COMBINER_STRUCT。這應該涵蓋所有MPI-1.1類型。該機制(功能?walk_type)應易于擴展以覆蓋MPI2組合器。
MPI定義了一些命名結構化類型(MPI_FLOAT_INT,?MPI_DOUBLE_INT,?MPI_LONG_INT,?MPI_2INT,?MPI_SHORT_INT,?MPI_LONG_DOUBLE_INT),其是對一些基本類型和C的int。不幸的是,MPI規范使得不可能查看這些類型并查看字段在哪里。因此,這些包裝器假設類型被布置為struct { float val; int loc; }(for?MPI_FLOAT_INT)等,并且相應地進行操作。至少對于Open MPI 1.0.2和Quadrics MPI來說,這似乎是正確的。
如果strict是指定的選項MPIWRAP_DEBUG,如果遇到未處理類型,應用程序將中止。否則,應用程序將打印一條警告消息并繼續。
做了一些努力來在單次通過中標記/檢查對應于數組數組的存儲器范圍。這對于性能很重要,因為Valgrind要求標記/檢查任何范圍,無論多么小,承載相當大的恒定成本。這種優化被施加到原始類型的陣列(double,?float,?int,?long,long long,short,?char,和long double上平臺其中sizeof(long double) == 8)。對于所有其他類型的數組,包裝器單獨處理每個元素,因此可能會有非常大的性能成本。
4.9.6。寫新的包裝紙
在大多數情況下,包裝很簡單。唯一重要的復雜性是出現在非阻塞接收。
問題是MPI_Irecv?聲明recv緩沖區并立即返回,為事務提供一個handle(MPI_Request)。稍后用戶將必須輪詢完成?MPI_Wait等等,并且當事務成功完成時,包裝器必須繪制recv緩沖區。但是recv緩沖區細節沒有呈現給?MPI_Wait- 只有句柄是。因此,庫保持一個影子表,它將未完成的MPI_Requests與相應的緩沖區地址/計數/類型相關聯。操作完成后,將搜索表格中相關聯的地址/計數/類型信息,并相應地標記內存。
訪問表受到(POSIX pthreads)鎖的保護,以使庫線程安全。
該表被分配?malloc并且從不?freed,所以它將顯示在泄漏檢查中。
寫新的包裝器應該是相當容易的。源文件是?mpi/libmpiwrap.c。如果可能,找到一個與要包裝的行為類似行為的函數的現有包裝,并將其用作起點。包裝器按照與MPI 1.1規范相同的順序進行組織,以輔助導航。添加包裝器時,請記住在文件底部的長列表中注釋掉默認包裝器的定義(不要刪除它,只是將其注釋掉)。
4.9.7。使用包裝器時期待什么?
包裝器應該減少Memcheck在MPI應用程序上的錯誤錯誤率。因為封裝是在MPI接口完成的,所以接口下面的MPI實現中仍然可能會有大量的錯誤報告。你能做的最好的就是試圖壓制他們。
您還可以發現輸入端(緩沖區長度/定義)檢查在您的MPI使用中發現錯誤,例如傳遞太短的緩沖區?MPI_Recv。
未包裝的功能可能會增加錯誤率。一種可能的方法是與運行?MPI_DEBUG含有?warn。這將顯示您缺少正確的包裝器但仍然使用的功能。然后你可以為他們寫封裝。
PMPI_Reduce當使用自定義(用戶定義的)縮減功能時,一個已知的潛在的錯誤錯誤源?就是功能系列。在縮減操作中,每個節點都意圖將數據發送到使用指定的縮減功能的“中心點”,將數據項合并成單個項目。因此,通常,數據在節點之間傳遞并饋送到縮減功能,但是包裝庫不能將傳輸的數據標記為初始化之前被遞送到縮減功能,因為所有這些都發生在“內部”?PMPI_Reduce。因此,您的縮減功能可能會顯示誤報。
總結
以上是生活随笔為你收集整理的Memcheck:一个内存错误检测器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 表格 列合并
- 下一篇: 【Java学习笔记】字符串和Date的转