日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

使用和了解Valgrind核心:高级主题

發布時間:2025/3/15 编程问答 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 使用和了解Valgrind核心:高级主题 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

目錄

3.1。客戶端請求機制3.2。使用Valgrind gdbserver和GDB調試程序
3.2.1。快速入門:3步調試3.2.2。Valgrind gdbserver整體組織3.2.3。將GDB連接到Valgrind gdbserver3.2.4。連接到Android gdbserver3.2.5。監視Valgrind gdbserver的命令處理3.2.6。Valgrind gdbserver線程信息3.2.7。檢查和修改Valgrind影子寄存器3.2.8。Valgrind gdbserver的限制3.2.9。vgdb命令行選項3.2.10。Valgrind監視器命令
3.3。功能包裝
3.3.1。一個簡單的例子3.3.2。包裝規格3.3.3。包裝語義3.3.4。調試3.3.5。限制 - 控制流程3.3.6。限制 - 原始功能簽名3.3.7。例子

本章介紹了Valgrind核心服務的高級方面,主要是為希望以某些有用的方式自定義和修改Valgrind的默認行為的用戶感興趣。涵蓋的主題是:

  • “客戶請求”機制

  • 使用Valgrind的gdbserver和GDB調試程序

  • 功能包裝

3.1。客戶端請求機制

Valgrind具有一個陷門機制,通過該機制,客戶端程序可以將所有方式的請求和查詢傳遞給Valgrind和當前工具。在內部,這廣泛用于使各種事情發揮作用,盡管從外部看不到。

為了方便起見,提供了這些所謂的客戶端請求的一部分,以便您可以告訴Valgrind有關程序行為的事實,也可以進行查詢。特別是,您的程序可以告訴Valgrind有關其他方面不會知道的事情,從而獲得更好的結果。

客戶端需要包含頭文件才能使其工作。哪個頭文件取決于您使用的客戶端請求。一些客戶端請求由內核處理,并在頭文件中定義valgrind/valgrind.h。工具特定的頭文件以工具命名,例如?valgrind/memcheck.h。每個特定于工具的頭文件都包含在內valgrind/valgrind.h,因此如果您包含特定于工具的頭,則不需要將其包含在客戶端中。所有頭文件可以在安裝include/valgrindValgrind的目錄中找到。

這些頭文件中的宏具有魔術屬性,它們可以生成Valgrind可以發現的代碼。但是,代碼在Valgrind上運行時不執行任何操作,因此您不會因為使用此文件中的宏而被迫在Valgrind下運行程序。此外,您不需要將程序與任何額外的支持庫鏈接。

添加到您的二進制代碼的代碼可以忽略不計的性能影響:在x86,amd64,ppc32,ppc64和ARM上,開銷是6個簡單的整數指令,除非是緊密循環,否則可能無法檢測。但是,如果您真的希望編譯客戶端請求,可以使用-DNVALGRIND(類似于?-DNDEBUG“效果”?assert)進行編譯。

我們鼓勵您將標題復制valgrind/*.h到項目的包含目錄中,這樣您的程序就沒有編譯時依賴于正在安裝的Valgrind。Valgrind標題與其他代碼大部分不同,是BSD風格的許可證,所以您可以不必擔心許可證不兼容。

以下是對可用宏的簡要說明?valgrind.h,可以使用多種工具(請參閱特定于工具的文檔以了解工具特定的宏)。

RUNNING_ON_VALGRIND

如果在Valgrind上運行,則返回1,如果在實際CPU上運行則返回0。如果您運行Valgrind本身,則返回您運行的Valgrind仿真層數。

VALGRIND_DISCARD_TRANSLATIONS:

丟棄指定地址范圍內的代碼翻譯。如果您正在調試JIT編譯器或其他動態代碼生成系統,則很有用。在此調用之后,嘗試在無效地址范圍內執行代碼將導致Valgrind對該代碼進行新的翻譯,這可能是您想要的語義。請注意,代碼無效是昂貴的,因為快速找到所有相關的翻譯是非常困難的,所以盡量不要經常打電話。請注意,您可以很聰明:只有當以前包含代碼的區域被新的代碼覆蓋時,才需要調用它。您可以選擇將代碼寫入新鮮的內存,并且偶爾調用這些代碼一次丟棄大量的舊代碼。

或者,對于ppc32?--smc-check=all/ Linux,ppc64 / Linux或ARM / Linux?的透明自修改代碼支持,使用或運行。

VALGRIND_COUNT_ERRORS:

返回Valgrind到目前為止發現的錯誤數。與--log-fd=-1選項結合使用時,可用于測試工具代碼?;?這個默認運行Valgrind,但客戶端程序可以檢測何時發生錯誤。僅對報告錯誤的工具有用,例如它對Memcheck有用,但對于Cachegrind,它總是返回零,因為Cachegrind不報告錯誤。

VALGRIND_MALLOCLIKE_BLOCK:

如果您的程序管理自己的內存,而不是使用標準的malloc/?new/?new[],則跟蹤有關堆塊的信息的工具不會做得很好。例如,Memcheck將不會檢測到幾乎相同的錯誤,并且錯誤消息將不會提供信息。為了改善這種情況,在您的自定義分配器分配一些新內存之后使用此宏。有關valgrind.h如何使用它的信息,請參閱注釋?。

VALGRIND_FREELIKE_BLOCK:

這應該與之配合使用?VALGRIND_MALLOCLIKE_BLOCK。再次看看valgrind.h有關如何使用它的信息。

VALGRIND_RESIZEINPLACE_BLOCK:

通知Valgrind工具,分配的塊的大小已被修改,但不是其地址。查看valgrind.h有關如何使用它的更多信息。

VALGRIND_CREATE_MEMPOOL,?VALGRIND_DESTROY_MEMPOOL,?VALGRIND_MEMPOOL_ALLOC,?VALGRIND_MEMPOOL_FREE,?VALGRIND_MOVE_MEMPOOL,?VALGRIND_MEMPOOL_CHANGE,?VALGRIND_MEMPOOL_EXISTS

這些類似于?VALGRIND_MALLOCLIKE_BLOCK和?VALGRIND_FREELIKE_BLOCK?,但偏向于使用內存池的代碼定制。有關詳細說明,請參閱?內存池。

VALGRIND_NON_SIMD_CALL[0123]:

在客戶端程序中執行實際?CPU?上的功能?,而不是Valgrind正常運行代碼的虛擬CPU。該函數必須采用一個整數(保持線程ID)作為第一個參數,然后將0,1,2或3個參數(取決于使用哪個客戶端請求)。這些在Valgrind內部以各種方式使用。它們可能對客戶端程序有用。

警告:只有使用這些,如果你?真的知道你在做什么。它們并不完全可靠,可能導致Valgrind崩潰。查看?valgrind.h更多詳情。

VALGRIND_PRINTF(format, ...):

將Printf樣式的消息打印到Valgrind日志文件。該消息以一對**標記之間的PID為前綴?。(與所有客戶端請求一樣,如果客戶端程序未在Valgrind下運行,則不會輸出任何內容。)直到遇到換行符或后續的Valgrind輸出打印時才會產生輸出;?這允許您通過多個呼叫建立單行輸出。返回輸出的字符數,不包括PID前綴。

VALGRIND_PRINTF_BACKTRACE(format, ...):

喜歡VALGRIND_PRINTF(特別是返回值是相同的),但是之后立即打印一個堆棧回溯。

VALGRIND_MONITOR_COMMAND(command):

執行給定的monitor命令(一個字符串)。如果識別到命令,則返回0。如果無法識別命令,則返回1。請注意,某些監視器命令提供對通過特定客戶端請求可訪問的功能的訪問。例如,可以使用VALGRIND_DO_LEAK_CHECK或通過監視器命令“leak_search”從客戶端程序請求memcheck泄漏搜索。請注意,命令字符串的語法僅在運行時驗證。因此,如果存在,最好使用特定的客戶端請求對參數進行更好的編譯時驗證。

VALGRIND_STACK_REGISTER(start, end):

注冊一個新的堆棧。通知Valgrind,start和end之間的內存范圍是唯一的堆棧。返回可與其他VALGRIND_STACK_*調用一起使用的堆棧標識符?。

Valgrind將使用此信息來確定堆棧指針的更改是否是推送到堆棧上的項目或更改為新堆棧。如果您正在使用用戶級線程包,并注意到堆棧跟蹤記錄中的崩潰或Valgrind關于未初始化內存讀取的虛假錯誤,請使用此方法。

警告:不幸的是,這個客戶端請求是不可靠的,最好避免。

VALGRIND_STACK_DEREGISTER(id):

取消注冊以前注冊的堆棧。通知Valgrind以前注冊的堆棧ID的內存范圍?id不再是堆棧。

警告:不幸的是,這個客戶端請求是不可靠的,最好避免。

VALGRIND_STACK_CHANGE(id, start, end):

更改以前注冊的堆棧。通知Valgrind,堆棧id的先前注冊的堆棧?id已更改其起始值和結束值。如果您的用戶級線程包實現堆棧增長,請使用此選項。

警告:不幸的是,這個客戶端請求是不可靠的,最好避免。

3.2。使用Valgrind gdbserver和GDB調試程序

在Valgrind下運行的程序不會直接由CPU執行。而是運行在Valgrind提供的合成CPU上。這就是為什么調試器在Valgrind上運行時無法調試程序的原因。

本節介紹GDB如何與Valgrind gdbserver進行交互,以在Valgrind下提供完全可調試的程序。以這種方式使用,GDB還提供了Valgrind核心或工具功能的交互式使用,包括Memcheck和按需Massif快照生產的增量泄漏搜索。

3.2.1。快速入門:3步調試

開始使用最簡單的方法是使用標志運行Valgrind?--vgdb-error=0。然后按照屏幕上的方向進行操作,這樣您就可以獲得啟動GDB并將其連接到程序所需的精確命令。

否則,這里有一個更詳細的概述。

如果要在使用Memcheck工具時使用GDB調試程序,請啟動Valgrind,如下所示:

valgrind --vgdb = yes --vgdb-error = 0 prog

在另一個shell中,啟動GDB:

gdb程序

然后給GDB發送以下命令:

(gdb)target remote | vgdb

您現在可以調試程序,例如插入斷點,然后使用GDB?continue?命令。

這個快速啟動信息足以基本使用Valgrind gdbserver。以下部分描述了Valgrind和GDB組合提供的更高級的功能。請注意,--vgdb=yes可以省略命令行標志,因為這是默認值。

3.2.2。Valgrind gdbserver整體組織

GNU GDB調試器通常用于調試在同一臺機器上運行的進程。在這種模式下,GDB使用系統調用來控制和查詢被調試的程序。這樣做很好,但只允許GDB調試在同一臺計算機上運行的程序。

GDB還可以調試在不同計算機上運行的進程。為了實現這一點,GDB定義了一個協議(即一組查詢和回復數據包),有助于獲取內存或寄存器的值,設置斷點等。gdbserver是“GDB遠程調試”協議的實現。要調試在遠程計算機上運行的進程,gdbserver(有時稱為GDB存根)必須在遠程計算機端運行。

Valgrind內核提供了一個內置的gdbserver實現,它使用--vgdb=yes?或激活--vgdb=full。該gdbserver允許在Valgrind的合成CPU上運行的進程遠程調試。GDB向Valgrind嵌入式gdbserver發送協議查詢數據包(如“獲取寄存器內容”)。gdbserver執行查詢(例如,它將獲得合成CPU的寄存器值),并將結果返回給GDB。

GDB可以使用各種通道(TCP / IP,串行線等)與gdbserver進行通信。在Valgrind的gdbserver的情況下,通過一個管道和一個名為vgdb的小幫助程序完成通信,該程序充當中間人。如果沒有使用GDB,vgdb也可以用于從shell命令行將監控命令發送到Valgrind gdbserver。

3.2.3。將GDB連接到Valgrind gdbserver

要調試程序“?prog” Valgrind的下運行,你必須確保Valgrind的gdbserver的是通過指定激活--vgdb=yes?或--vgdb=full。輔助命令行選項,?--vgdb-error=number可以用于告知gdbserver在顯示指定數量的錯誤后才會變為活動狀態。因此,值為零將導致gdbserver在啟動時變為活動狀態,這允許您在開始運行之前插入斷點。例如:

valgrind --tool = memcheck --vgdb = yes --vgdb-error = 0 ./prog

Valgrind gdbserver在啟動時被調用,并指示它正在等待來自GDB的連接:

== 2418 == Memcheck,一個內存錯誤檢測器 == 2418 ==版權所有(C)2002-2010和GNU GPL'd,由Julian Seward等人 == 2418 ==使用Valgrind-3.7.0.SVN和LibVEX; 用-h重新運行版權信息 == 2418 ==命令:./prog == == 2418 == 2418 ==(啟動時的動作)vgdb me ...

然后可以將GDB(在另一個shell中)連接到Valgrind gdbserver。為此,必須在程序上啟動GDB?prog:

gdb ./prog

然后,您向GDB指出您要調試遠程目標:

(gdb)target remote | vgdb

GDB然后啟動一個vgdb中繼應用程序與Valgrind嵌入式gdbserver進行通信:

(gdb)target remote | vgdb 遠程調試使用| vgdb 在gdb和進程2418之間中繼數據 從/lib/ld-linux.so.2...done讀取符號。 從/usr/lib/debug/lib/ld-2.11.2.so.debug...done讀取符號。 /lib/ld-linux.so.2的加載符號 [切換到線程2418] 來自/lib/ld-linux.so.2的_start()中的0x001f2850 (GDB)

請注意,vgdb是作為Valgrind分發的一部分提供的。您不需要單獨安裝。

如果vgdb檢測到可以連接多個Valgrind gdbservers,它將列出所有這些服務器及其PID,然后退出。然后,您可以重新發行GDB“目標”命令,但指定要調試的進程的PID:

(gdb)target remote | vgdb 遠程調試使用| vgdb 沒有--pid = arg給定和多個valgrind pids找到: 使用--pid = 2479 for valgrind --tool = memcheck --vgdb = yes --vgdb-error = 0 ./prog 使用--pid = 2481 for valgrind --tool = memcheck --vgdb = yes --vgdb-error = 0 ./prog 使用--pid = 2483 for valgrind --vgdb = yes --vgdb-error = 0 ./another_prog 遠程通信錯誤:資源暫時不可用。 (gdb)target remote | vgdb --pid = 2479 遠程調試使用| vgdb --pid = 2479 在gdb和進程2479之間中繼數據 從/lib/ld-linux.so.2...done讀取符號。 從/usr/lib/debug/lib/ld-2.11.2.so.debug...done讀取符號。 /lib/ld-linux.so.2的加載符號 [切換到線程2479] 來自/lib/ld-linux.so.2的_start()中的0x001f2850 (GDB)

一旦GDB連接到Valgrind gdbserver,它就可以像你在本機調試程序一樣使用:

  • 斷點可插入或刪除。

  • 可以檢查或修改變量和寄存器值。

  • 可以配置信號處理(打印,忽略)。

  • 執行可以被控制(繼續,步驟,下一步,stepi等)。

  • 程序執行可以使用Control-C中斷。

等等。有關GDB功能的完整說明,請參閱GDB用戶手冊。

3.2.4。連接到Android gdbserver

開發Android應用程序時,通常會使用開發系統(安裝了Android NDK)來編譯應用程序。將使用Android目標系統或仿真器來運行應用程序。在此設置中,Valgrind和vgdb將在Android系統上運行,而GDB將在開發系統上運行。GDB將使用Android NDK'adb forward'應用程序連接到在Android系統上運行的vgdb。

示例:在Android系統上,執行以下操作:

valgrind --vgdb-error = 0 --vgdb = yes prog 然后在另一個shell中運行: vgdb --port = 1234

在開發系統中,執行以下命令:

adb forward tcp:1234 tcp:1234 gdb程序 (gdb)target remote:1234

GDB將使用本地tcp / ip連接來連接到Android adb轉發器。Adb將在主機系統和Android目標系統之間建立中繼連接。確保使用Android NDK系統中提供的GDB(通常是arm-linux-androideabi-gdb),因為主機GDB可能無法調試Android手臂應用程序。請注意,本地端口nr(由GDB使用)不一定等于vgdb使用的端口號:adb可以在不同端口號之間轉發tcp / ip。

在當前版本中,默認情況下,由于建立一個合適的目錄,因為Valgrind可以創建必要的FIFO(命名管道)用于通信目的,GDB服務器未啟用。您可以嘗試使用GDB服務器,但是您需要使用該標志--vgdb=yes或?顯式啟用它?--vgdb=full。

此外,您將需要選擇一個臨時目錄(a)可由Valgrind寫入,(b)支持FIFO。這是難點。通常/sdcard滿足要求(a),但是(b)因為是VFAT文件系統而VFAT不支持管道而失敗。可能你可以嘗試是?/data/local,?/data/local/Inst(如果你安裝了Valgrind的存在),或者?/data/data/name.of.my.app,如果你正在運行特定的應用程序,它有它自己的這種形式的目錄。這最后的可能性可能是最高的成功概率。

您可以通過--with-tmpdir=配置時間標志指定臨時目錄或在運行Valgrind時(在Android設備上,而不是Android NDK開發主機上)設置環境變量TMPDIR。另一個選擇是使用--vgdb-prefix=Valgrind命令行選項來指定FIFO的目錄。

我們希望將來有一個更好的故事,用于Android上的臨時目錄處理。困難在于,與標準Unixes不同,沒有單個臨時文件目錄可靠地在所有設備和方案中工作。

3.2.5。監視Valgrind gdbserver的命令處理

Valgrind gdbserver通過“monitor命令”提供了額外的Valgrind特定功能。這樣的監視命令可以從GDB命令行或從shell命令行發送,或者由客戶端程序使用VALGRIND_MONITOR_COMMAND客戶端請求發送。有關Valgrind核心監視器命令的列表,請參閱?Valgrind監視器命令,無論選擇了Valgrind工具。

以下工具提供特定于工具的監視器命令:

  • Memcheck監視器命令

  • Callgrind監視器命令

  • Massif監視器命令

  • Helgrind監視器命令

特定于工具的監視器命令的示例是Memcheck監視器命令leak_check full reachable any。這要求對所分配的內存塊進行全面報告。要執行此泄漏檢查,請使用GDB命令:

(gdb)monitor leak_check完全可達任何

GDB將發送leak_check?命令到Valgrind gdbserver。如果Valgrind gdbserver將其識別為Valgrind core monitor命令,則會自動執行monitor命令。如果它不被認可,則假設它是具體的工具,并被交給該工具執行。例如:

(gdb)monitor leak_check完全可達任何 == 2418 == 1塊中的100字節仍然可以在損失記錄1中1 == 2418 == at 0x4006E9E:malloc(vg_replace_malloc.c:236) == 2418 == by 0x804884F:main(prog.c:88) == == 2418 == 2418 == LEAK SUMMARY: == 2418 ==絕對丟失:0塊0塊 == 2418 ==間接丟失:0個字節,0個塊 == 2418 ==可能丟失:0個字節的0個塊 == 2418 ==仍然可達:100個字節的1個塊 == 2418 == suppress:0個字節,0個塊 == == 2418 (GDB)

與其他GDB命令一樣,Valgrind gdbserver將接受縮寫的監視器命令名稱和參數,只要給定的縮寫是明確的。例如,上述?leak_check?命令也可以鍵入:

(gdb)mo lfra

這些字母mo被GDB識別為縮寫monitor。所以GDB將字符串發送l f r a到Valgrind gdbserver。這個字符串中提供的字母對Valgrind gdbserver來說是明確的。因此,這將給出與未縮寫的命令和參數相同的輸出。如果提供的縮寫不明確,則Valgrind gdbserver會報告可匹配的命令列表(或參數值):

(gdb)mo v。n v。可以匹配v.set v.info v.wait v.kill v.translate v.do (gdb)mo vi n n_errs_found 0 n_errs_shown 0(vgdb-error 0) (GDB)

而不是從GDB發送監視器命令,您也可以從shell命令行發送這些命令。例如,以下命令行在shell中給出時,將導致進程3145執行相同的泄漏搜索:

vgdb --pid = 3145 leak_check完全可達任何 vgdb --pid = 3145 lfra

請注意,在單獨調用vgdb之后,Valgrind gdbserver會自動繼續執行該程序。從GDB發送的監視命令不會導致程序繼續:使用GDB命令(如“繼續”或“下一個”)顯式控制程序執行。

3.2.6。Valgrind gdbserver線程信息

Valgrind的gdbserver?info threads使用Valgrind特定的信息豐富了GDB?命令的輸出。操作系統的線程號后跟Valgrind的該線程的內部索引(“tid”)和Valgrind調度程序線程狀態:

(gdb)信息線程4 Thread 6239(tid 4 VgTs_Yielding)0x11f2832 in _ll_sysinfo_int80()from /lib/ld-linux.so.2 * 3線程6238(tid 3 VgTs_Runnable)make_error(s = 0x8048b76“從倫敦調用”)在prog.c:202 Thread 6237(tid 2 VgTs_WaitSys)0x001f2832 in _ll_sysinfo_int80()from /lib/ld-linux.so.21 prog 6234(tid 1 VgTs_Yielding)main(argc = 1,argv = 0xbedcc274)在prog.c:105 (GDB)

3.2.7。檢查和修改Valgrind影子寄存器

當給出選項--vgdb-shadow-registers=yes時,Valgrind gdbserver將允許GDB檢查和/或修改Valgrind的影子寄存器。需要GDB 7.1或更高版本才能工作。對于x86和amd64,需要GDB版本7.2或更高版本。

對于每個CPU寄存器,Valgrind內核保留兩個影子寄存器集。這些影子寄存器可以通過給出一個后綴s1?或s2分別為第一個和第二個影子寄存器從GDB訪問。例如,eax可以使用以下命令檢查x86寄存器?及其兩個陰影:

(gdb)p $ eax $ 1 = 0 (gdb)p $ eaxs1 $ 2 = 0 (gdb)p $ eaxs2 $ 3 = 0 (GDB)

浮動影子寄存器由GDB顯示為無符號整數值,而不是浮點值,因為預期這些陰影值主要用于memcheck有效位。

Intel / amd64 AVX寄存器ymm0?也ymm15有它們的影子寄存器。然而,GDB使用兩個“半”寄存器呈現陰影值。例如,半影子寄存器?ymm9是?xmm9s1(組1的下半部分),ymm9hs1(組1的?上半部分),?xmm9s2(組2的下半部分),?ymm9hs2(組2的上半部分)。請注意半登記名稱的不一致表示法:下半部分以a開頭x,上半部分以a開頭,y?并且h在陰影后綴之前。

AVX影子寄存器的特殊表現是由于GDB獨立地檢索寄存器的下半部分和上半部分ymm。然而,GDB不知道陰影半記錄必須被顯示組合。

3.2.8。Valgrind gdbserver的限制

使用Valgrind gdbserver進行調試與本機調試非常相似。Valgrind的gdbserver實現是相當完整的,因此提供了大部分GDB調試功能。但是有一些限制和特點:

  • “停止”命令的精度。

    諸如“step”,“next”,“stepi”,斷點和觀察點等GDB命令將停止執行進程。使用該選項--vgdb=yes,該過程可能不會在精確請求的指令中停止。相反,它可能繼續執行當前的基本塊,并停止在以下基本塊之一。這與Valgrind gdbserver必須調整塊以允許停止在所請求的精確指令的事實有關。目前,不支持重新檢測當前正在執行的塊。因此,如果GDB請求的動作(例如單步或插入斷點)意味著重新檢測當前塊,則GDB操作可能無法精確執行。

    當正在執行的基本塊尚未被調試時,此限制適用。當gdbserver由于工具報告錯誤或觀察點而被激活時,通常會發生這種情況。如果在斷點之后激活了gdbserver塊,或者在執行斷點之前已經插入了斷點,則該塊已經被調試了。

    如果使用該選項--vgdb=full,則GDB“stop-at”命令將被精確地遵守。不利之處在于,這需要對每個指令進行附加調用,調用gdbserver幫助函數,這相當于開銷(對于memcheck為+ 500%)?--vgdb=no。--vgdb=yes與之相比,選項具有可忽視的開銷--vgdb=no。

  • 處理器寄存器和標志值。

    當Valgrind gdbserver停止錯誤時,在斷點上,或者由于Valgrind內核進行了優化,單步執行,寄存器和標志值可能并不總是最新的。默認值?--vex-iropt-register-updates=unwindregs-at-mem-access?可確保在每次存儲器訪問(即內存異常點)時進行堆棧跟蹤(通常為PC / SP / FP)所需的寄存器是最新的。使用以下值禁用某些優化將增加寄存器和標志值的精度(為每個選項給出對于memcheck的典型性能影響)。

    • --vex-iropt-register-updates=allregs-at-mem-access?(+ 10%)確保所有寄存器和標志在每次存儲器訪問時都是最新的。
    • --vex-iropt-register-updates=allregs-at-each-insn?(+ 25%)確保所有寄存器和標志在每個指令時都是最新的。

    請注意,--vgdb=full(+ 500%,見上文“停止”命令的精度)自動激活--vex-iropt-register-updates=allregs-at-each-insn。

  • Valgrind gdbserver支持硬件觀察點。

    如果選定的工具提供支持,Valgrind gdbserver可以模擬硬件觀察點。目前,只有Memcheck提供硬件觀察點模擬。由Memcheck提供的硬件觀察點模擬比GDB軟件觀察點快得多,GDB軟件觀察點由GDB在每個指令之后檢查被監視區域的值。硬件觀察點模擬還提供讀取觀察點。Memcheck的硬件觀察點模擬與實際硬件觀察點相比有一些限制。但是,模擬觀察點的數量和長度不受限制。

    通常,(實際)硬件觀察點的數量有限。例如,x86架構最多支持4個硬件觀察點,每個觀察點觀察1,2,4或8個字節。Valgrind gdbserver對模擬硬件觀察點的數量沒有任何限制。正在觀看的內存區域的長度也沒有限制。使用GDB版本7.4或更高版本可以充分利用Valgrind gdbserver的模擬硬件觀察點的靈活性。以前的GDB版本不明白Valgrind gdbserver觀察點沒有長度限制。

    Memcheck通過將觀看的地址范圍標記為不可尋址來實現硬件觀察點模擬。當硬件觀察點被刪除時,該范圍被標記為可尋址并定義。可尋址但未定義的內存區域的硬件觀察點模擬工作正常,但是在刪除觀察點時定義的區域具有不良的副作用。

    寫入監視區域的準確指令可能不會報告寫入點,除非--vgdb=full給出了選項。讀取觀察點將始終按照讀取監視內存的準確說明進行報告。

    最好避免使用不可尋址(還))內存的硬件觀察點:在這種情況下,GDB將會回退到非常慢的軟件觀察點。另外,如果不在兩個調試會話之間退出GDB,如果在程序啟動時監視的內存區域不可尋址,則前一個會話的硬件觀察點將作為軟件觀察點重新插入。

  • 在ARM內部進行共享庫。

    由于未知的原因,在ARM上進入共享庫可能會失敗。解決方法是使用?ldd命令查找共享庫列表及其加載地址,并使用GDB命令“add-symbol-file”通知GDB加載地址。例:

    (gdb)shell ldd ./proglibc.so.6 => /lib/libc.so.6(0x4002c000)/lib/ld-linux.so。(0x40000000) (gdb)add-symbol-file /lib/libc.so.6 0x4002c000 從文件“/lib/libc.so.6”中添加符號表.text_addr = 0x4002c000 (y或n)y 從/lib/libc.so.6...(找到調試符號)讀取符號...完成。 (GDB)

  • ARM和PPC32 / 64需要GDB版本。

    您必須使用能夠讀取gdbserver發送的XML目標描述的GDB版本。如果使用“expat”庫配置和構建GDB,則這是標準設置。如果您的GDB未配置XML支持,則在使用“target”命令時會報告錯誤消息。調試將不起作用,因為GDB將無法從Valgrind gdbserver獲取寄存器。對于使用Thumb指令集的ARM程序,必須使用7.1或更高版本的GDB版本,因為早期版本在Thumb代碼中具有下一個/步驟/斷點的問題。

  • 堆放在PPC32 / PPC64上。

    在PPC32 / PPC64,棧展開葉功能(即不調用任何其他函數的函數)正常工作,只有當你給的選項?--vex-iropt-register-updates=allregs-at-mem-access?或--vex-iropt-register-updates=allregs-at-each-insn。您還必須通過此選項,以便在信號被GDB捕獲時獲得精確的堆棧。

  • 斷點遇到多次。

    一些指令(例如x86“rep movsb”)由Valgrind使用循環來翻譯。如果在這樣的指令上放置了一個斷點,斷點將被多次遇到 - 一次執行指令的“隱含”循環的每個步驟。

  • 執行Valgrind gdbserver的下層函數調用。

    GDB允許用戶在被調試的進程內“調用”函數。這種呼叫在GDB術語中被稱為“次要呼叫”。劣質呼叫的典型用途是執行打印復雜數據結構的人類可讀版本的功能。要進行一個較低的調用,請使用GDB“print”命令,后跟該函數調用及其參數。作為示例,以下GDB命令會導致對被調試進程執行的libc“printf”函數的較低調用:

    (gdb)p printf(“正在調試的進程具有pid%d \ n”,getpid()) $ 5 = 36 (GDB)

    Valgrind gdbserver支持較差的函數調用。當一個劣質的電話正在運行時,Valgrind工具會像往常一樣報告錯誤。如果您不希望有這樣的錯誤停止執行劣質呼叫,您可以使用v.set vgdb-error在呼叫之前設置一個較大的值,然后在呼叫完成時手動將其重置為其原始值。

    為了執行劣質調用,GDB會更改寄存器,如程序計數器,然后繼續執行程序。在多線程程序中,所有的線程都會繼續進行,而不僅僅是線程指示進行較低的調用。如果另一個線程報告錯誤或遇到斷點,則劣質評估的評估將被放棄。

    請注意,劣質函數調用是強大的GDB功能,但應謹慎使用。例如,如果正在調試的程序在函數“printf”中被停止,則強制通過劣質調用對printf的遞歸調用將非常可能產生問題。Valgrind工具還可能為劣質調用增加了另一個級別的復雜性,例如在Inferior調用期間報告工具錯誤或由于完成了儀器設置。

  • 連接到或中斷Valgrind進程在系統調用中被阻止。

    連接或中斷系統調用阻塞的Valgrind進程需要“ptrace”系統調用才能使用。出于安全考慮,這可能會在您的內核中被禁用。

    運行程序時,Valgrind的調度程序會定期檢查是否有任何可以由gdbserver處理的工作。不幸的是,只有當進程的至少一個線程可運行時,才會進行該檢查。如果進程的所有線程在系統調用中被阻止,則不會發生檢查,Valgrind調度程序將不會調用gdbserver。在這種情況下,vgdb中繼應用程序將“強制”調用gdbserver,而不需要Valgrind調度程序的干預。

    這種強制調用Valgrind gdbserver是由vgdb使用ptrace系統調用實現的。在正確實現的內核上,由vgdb完成的ptrace調用不會影響在Valgrind下運行的程序的行為。然而,如果他們這樣做,給--max-invoke-ms=0vgdb中繼應用程序的選項將禁用ptrace調用的使用。禁用vgdb中的ptrace使用的結果是,在系統調用中阻塞的Valgrind進程不能從GDB喚醒或中斷,直到它執行足夠的基本塊,以使Valgrind調度程序的正常檢查生效。

    當在vgdb中禁用ptrace時,可以通過為該選項賦予較低的值來增加Valgrind gdbserver對命令或中斷的響應--vgdb-poll。如果您的應用程序在大多數情況下在系統調用中被阻止,則使用非常低的值--vgdb-poll會導致更快地調用gdbserver。Valgrind的調度程序執行的gdbserver輪詢非常有效,因此增加的查詢頻率不應導致嚴重的性能下降。

    當在vgdb中禁用ptrace時,由GDB發送的查詢數據包可能需要大量時間才能由Valgrind gdbserver處理。在這種情況下,GDB可能會遇到協議超時。為避免這種情況,您可以使用GDB命令“set remotetimeout”來增加超時值。

    Ubuntu 10.10及更高版本可能會將ptrace的范圍限制為調用ptrace的進程的子進程。由于Valgrind進程不是vgdb的子進程,因此這種受限制的范圍會導致ptrace調用失敗。為避免這種情況,Valgrind將自動允許屬于同一用戶標識符的所有進程通過使用PR_SET_PTRACER“追蹤”Valgrind進程。

    系統調用中阻止的取消阻止進程目前在Mac OS X和Android上尚未實施。因此,您無法連接或中斷Mac OS X或Android系統調用中阻止的進程。

  • 更改寄存器值。

    當線程處于狀態Runnable或Yielding時,Valgrind gdbserver將僅修改線程的寄存器的值。在其他狀態(通常為WaitSys)中,嘗試更改寄存器值將失敗。除此之外,這意味著對系統調用中的線程不執行劣質調用,因為Valgrind gdbserver不會執行系統調用重啟。

  • 不支持的GDB功能。

    GDB提供了大量的調試功能,并沒有全部支持。具體來說,不支持以下內容:可逆調試和跟蹤點。

  • 未知的限制或問題。

    GDB,Valgrind和Valgrind gdbserver的組合可能具有未知的其他限制和問題。如果遇到奇怪或意想不到的行為,請隨時報告錯誤。但首先請確認GDB或GDB遠程協議不是固有的限制或問題。您可以通過檢查使用GDB包的標準gdbserver部分時的行為來執行此操作。

3.2.9。vgdb命令行選項

用法:?vgdb [OPTION]... [[-c] COMMAND]...

vgdb(“Valgrind to GDB”)是一個小程序,用作Valgrind和GDB或shell之間的中介。因此,它有兩種使用模式:

  • 作為獨立實用程序,它從shell命令行使用,將監視器命令發送到在Valgrind下運行的進程。對于此用法,vgdb OPTION(s)必須跟隨monitor命令發送。要發送多個命令,請使用該-c選項分隔。

  • 結合GDB“target remote |”?命令,它用作GDB和Valgrind gdbserver之間的中繼應用程序。對于這種用法,只能給出OPTION,但不能給出COMMAND。

  • vgdb?接受以下選項:

    --pid=<number>

    指定vgdb必須連接到的進程的PID。如果可以連接多個Valgrind gdbserver,則此選項很有用。如果--pid沒有給出參數并且多個Valgrind gdbserver進程正在運行,vgdb將報告這些進程的列表,然后退出。

    --vgdb-prefix

    如果要更改用于Valgrind gdbserver和vgdb之間的通信的FIFO(命名管道)的默認前綴,則必須將其賦予Valgrind和vgdb。

    --wait=<number>

    指示vgdb以指定的秒數搜索可用的Valgrind gdbservers。這樣可以啟動一個vgdb進程,然后再啟動與目標vgdb進行通信的Valgrind gdbserver。當與--vgdb-prefix您要等待的進程唯一的情況一起使用時,此選項很有用。另外,如果--wait在GDB“target remote”命令中使用參數,則必須將GDB remotetimeout設置為大于-wait參數值的值。有關--max-invoke-ms設置remotetimeout值的示例,請參閱選項?(如下)。

    --max-invoke-ms=<number>

    提供毫秒數,之后vgdb將強制調用嵌入在Valgrind中的gdbserver。默認值為100毫秒。值為0將禁用強制調用。當vgdb連接到Valgrind gdbserver時,使用強制調用,Valgrind進程在系統調用中阻止其所有線程。

    如果指定了較大的值,則可能需要將GDB“remotetimeout”的值從其默認值2秒增加。您應該確保超時(以秒為單位)大于該--max-invoke-ms值。例如,對于--max-invoke-ms=5000以下GDB命令是合適的:

    (gdb)set remotetimeout 6

    --cmd-time-out=<number>

    指示獨立的vgdb退出,如果連接到其中的Valgrind gdbserver不以指定的秒數處理命令。默認值是永遠不會超時。

    --port=<portnr>

    指示vgdb使用tcp / ip并在指定的端口nr上監聽GDB,而不是使用管道與GDB進行通信。使用tcp / ip允許在一臺計算機上運行GDB并調試在另一臺目標計算機上運行的Valgrind進程。例:

    #在目標計算機上,使用valgrind啟動您的程序 valgrind --vgdb-error = 0 prog 然后在另一個shell中運行: vgdb --port = 1234

    在承載GDB的計算機上,執行以下命令:

    gdb程序 (gdb)target remote targetip:1234

    其中targetip是目標計算機的IP地址或主機名。

    -c

    要向獨立的vgdb提供多個命令,請通過選項分隔命令-c。例:

    vgdb v.set log_output -c leak_check any
    -l

    指示獨立的vgdb報告運行的Valgrind gdbserver進程的列表,然后退出。

    -D

    指示獨立的vgdb顯示Valgrind gdbserver使用的共享內存的狀態。顯示Valgrind gdbserver共享內存狀態后,vgdb將退出。

    -d

    指示vgdb產生調試輸出。提供多個-d參數以增加詳細程度。當給予-d中繼vgdb時,您最好將vgdb的標準錯誤(stderr)重定向到一個文件,以避免GDB和vgdb調試輸出之間的交互。

    3.2.10。Valgrind監視器命令

    本節介紹Valgrind監視器命令,無論選擇了Valgrind工具,都可以使用。有關特定于工具的命令,請參閱Memcheck Monitor命令,?Helgrind Monitor命令,?Callgrind Monitor命令和?Massif Monitor命令。

    監視器命令可以通過使用獨立的vgdb或從GDB通過使用GDB的“監視器”命令(請參閱由Valgrind gdbserver監視命令處理)從shell命令行發送。客戶端程序也可以使用VALGRIND_MONITOR_COMMAND客戶端請求啟動它們。

    • help [debug]指示Valgrind的gdbserver給出Valgrind內核和工具的所有監視器命令的列表。可選的“debug”參數還指出了針對Valgrind內部調試的監視器命令的幫助。

    • v.info all_errors?顯示到目前為止發現的所有錯誤。

    • v.info last_error?顯示最后發現的錯誤。

    • v.info location <addr>輸出位置信息<addr>。可能的描述如下:全局變量,本地(堆棧)變量,分配或釋放的塊,...生成的信息取決于工具和給予valgrind的選項。一些工具(例如memcheck和helgrind)可以為客戶端堆塊生成更詳細的信息。例如,這些工具顯示堆棧跟蹤分配堆塊的位置。如果一個工具不能替代malloc / free / ...函數,則不會描述客戶機堆塊。使用該選項--read-var-info=yes獲取有關全局或本地(堆棧)變量的更詳細信息。

      (gdb)monitor v.info位置0x8050b20位置0x8050b20是全局var“mx”內的0個字節在tc19_shadowmem.c上聲明:19(gdb)mo v.in loc 0x582f33c位置0x582f33c是0字節的局部變量“info”在tc19_shadowmem.c:282,在線程3的框架#1中聲明 (GDB)
    • v.info n_errs_found [msg]顯示到目前為止發現的錯誤的數量,到目前為止顯示的錯誤的nr和--vgdb-error參數的當前值。附加可選?msg(一個或多個單詞)。通常,這可以用于在進程輸出文件之間的幾個測試之間插入標記,序列中只有一個進程啟動。這允許將Valgrind報告的錯誤與產生這些錯誤的特定測試相關聯。

    • v.info open_fds顯示打開的文件描述符和與文件描述符相關的詳細信息的列表。這只有--track-fds=yes?在Valgrind啟動時才能使用。

    • v.set {gdb_output | log_output | mixed_output}允許重定向Valgrind輸出(例如工具檢測到的錯誤)。默認設置為?mixed_output。

      使用mixed_outputValgrind輸出到Valgrind日志(通常是stderr),而GDB顯示交互式GDB監視器命令(eg?v.info last_error)的輸出。

      由于gdb_outputGDB顯示了Valgrind輸出和交互式GDB監視器命令輸出。

      使用log_outputValgrind輸出和交互式GDB監視器命令輸出將轉到Valgrind日志。

    • v.wait [ms (default 0)]指示Valgrind gdbserver以毫秒為單位睡眠“ms”,然后繼續。當從獨立的vgdb發送時,如果這是最后一個命令,則Valgrind進程將繼續執行客戶機進程。典型的用法是使用vgdb向Valgrind gdbserver發送一個“no-op”命令,以便繼續執行guest虛擬機進程。

    • v.kill請求gdbserver終止進程。這可以從獨立的vgdb使用,以正確地殺死當前正在期待vgdb連接的Valgrind進程。

    • v.set vgdb-error <errornr>?動態地更改--vgdb-error參數的值?。典型的用法是--vgdb-error=0從命令行開始?,然后設置幾個斷點,將vgdb-error值設置為一個巨大的值,并繼續執行。

    以下Valgrind監視器命令可用于調查Valgrind或其gdbserver在發生問題或錯誤時的行為。

    • v.do expensive_sanity_check_general?執行各種理智檢查。特別是Valgrind堆的理智得到驗證。如果您懷疑您的程序和/或Valgrind有一個錯誤的Valgrind數據結構錯誤,這將非常有用。當Valgrind工具向連接的GDB報告客戶端錯誤時,也可以使用它,以便在繼續執行之前驗證Valgrind的完整性。

    • v.info gdbserver_status顯示gdbserver狀態。在出現問題(例如通信)的情況下,這將顯示一些相關Valgrind gdbserver內部變量的值。請注意,與斷點和觀察點相關的變量(例如斷點地址數和觀察點數)將為零,因為默認情況下,GDB將在執行停止時刪除所有觀察點和斷點,并在恢復執行時重新插入調試過程。您可以使用GDB命令更改此GDB行為?set breakpoint always-inserted on。

    • v.info memory [aspacemgr]顯示了Valgrind內部堆管理的統計信息。如果提供了選項--profile-heap=yes,將會輸出詳細的統計數據。使用可選參數?aspacemgr。將輸出由valgrind地址空間管理器維護的段列表。請注意,此列表的段始終在Valgrind日志中輸出。

    • v.info exectxt顯示有關Valgrind記錄的“可執行上下文”(即堆棧跟蹤)的信息。對于一些程序,Valgrind可以記錄非常多的這種堆棧跟蹤,導致高內存使用。此監視器命令顯示所有記錄的堆棧跟蹤,然后顯示一些統計信息。這可以用來分析大量堆棧跟蹤的原因。通常,如果v.info memory“exectxt”競技場顯示出顯著的內存使用,您將使用此命令。

    • v.info scheduler顯示有關線程的各種信息。首先,它輸出主機棧跟蹤,即正在執行的Valgrind代碼。然后,對于每個線程,它將輸出線程狀態。對于未終止的線程,狀態后面是guest(客戶端)堆棧跟蹤。最后,對于每個活動線程或對于尚未重新使用的每個終止的線程槽,它顯示valgrind堆棧的最大使用。

      顯示客戶端堆棧跟蹤允許將由Valgrind展開器生成的堆棧跟蹤與由GDB + Valgrind gdbserver生成的堆棧跟蹤進行比較。注意GDB和Valgrind調度器狀態有自己的線程編號方案。要使GDB線程號和對應的Valgrind調度程序線程號之間的鏈接,請使用GDB命令info threads。此命令的輸出顯示GDB線程號和valgrind'tid'。'tid'是輸出的線程號v.info scheduler。當使用callgrind工具時,callgrind monitor命令?status輸出內部callgrind信息,關于它保存的堆棧/調用圖。

    • v.info stats顯示各種valgrind核心和工具統計。有了這個,Valgrind和工具統計可以在運行時檢查,即使沒有選項--stats=yes。

    • v.info unwind <addr> [<len>]顯示地址范圍[addr,addr + len-1]的CFI展開調試信息。默認值<len>為1,給出了<addr>中的指令的展開信息。

    • v.set debuglog <intvalue>將Valgrind調試日志級別設置為<intvalue>。這允許動態地更改Valgrind的日志級別,例如當檢測到問題時。

    • v.set hostvisibility [yes*|no]值“yes”表示gdbserver指示GDB可以查看Valgrind的“主機”(內部)狀態/內存。“否”禁用此訪問權限。當啟動主機時,GDB可以查看Valgrind全局變量。例如,要在x86上檢查memcheck工具的Valgrind全局變量,請執行以下設置:

      (gdb)monitor v.set hostvisibility yes (gdb)add-symbol-file / path / to / tool / executable / file / memcheck-x86-linux 0x38000000 從文件“/ path / to / tool / executable / file / memcheck-x86-linux”中添加符號表.text_addr = 0x38000000 (y或n)y 從/path/to/tool/executable/file/memcheck-x86-linux...done讀取符號。 (GDB)

      之后,可以訪問memcheck-x86-linux中定義的變量,例如

      (gdb)p / x vgPlain_threads [1] .os_state $ 3 = {lwpid = 0x4688,threadgroup = 0x4688,parent = 0x0, valgrind_stack_base = 0x62e78000,valgrind_stack_init_SP = 0x62f79fe0, exitcode = 0x0,fatalsig = 0x0} (gdb)p vex_control $ 5 = {iropt_verbosity = 0,iropt_level = 2, iropt_register_updates = VexRegUpdUnwindregsAtMemAccess, iropt_unroll_thresh = 120,guest_max_insns = 60,guest_chase_thresh = 10, guest_chase_cond = 0'\ 000'} (GDB)
    • v.translate <address> [<traceflags>]顯示address包含給定跟蹤標志的塊的翻譯。該traceflags值位模式也有類似的含義Valgrind的的?--trace-flags選項。它可以十六進制(例如0x20)或十進制(例如32)或二進制1s和0s位(例如0b00100000)給出。traceflags的默認值為0b00100000,對應于“儀表后顯示”。該命令的輸出總是進入Valgrind日志。

      附加位標志0b100000000(位8)在--trace-flags選項中沒有等效項。它可以跟蹤gdbserver特定的儀器。請注意,該位8只能在跟蹤中添加gdbserver儀器。如果gdbserver工具由于某些其他原因而處于活動狀態,則將其設置為0將不會禁用跟蹤,例如因為此地址處有斷點或因為gdbserver處于單步執行模式。

    3.3。功能包裝

    Valgrind允許調用某些指定的函數被攔截并重新路由到不同的用戶提供的函數。這可以做任何它喜歡的,通常檢查的論據,向原來的,并可能檢查結果。可以包裝任何數量的功能。

    函數換行對于以某種方式對API進行測試非常有用。例如,Helgrind在POSIX pthreads API中包裝函數,因此可以了解線程狀態更改,核心能夠將功能包含在MPI(消息傳遞)API中,以便知道與消息到達/離開。這些信息通常通過在包裝函數中使用客戶端請求傳遞給Valgrind,盡管確切的機制可能會有所不同。

    3.3.1。一個簡單的例子

    假設我們要包裝一些功能

    int foo(int x,int y){return x + y; }

    包裝器是相同類型的函數,但是具有將其標識為包裝的特殊名稱foo。包裝器需要包括支持宏valgrind.h。這是一個簡單的包裝器,它打印參數和返回值:

    #include <stdio.h> #include“valgrind.h” int I_WRAP_SONAME_FNNAME_ZU(NONE,foo)(int x,int y) {int結果;OrigFn fn;VALGRIND_GET_ORIG_FN(FN);printf(“foo's wrapper:args%d%d \ n”,x,y);CALL_FN_W_WW(result,fn,x,y);printf(“foo's wrapper:result%d \ n”,result);返回結果; }

    為了變得活躍,包裝器只需要存在于與其包裝的函數相同的進程'地址空間中的文本部分中,并且其ELF符號名稱對于Valgrind可見。在實踐中,這意味著要么編譯到一個?.o和它連接,或編譯的.so和?LD_PRELOAD在荷蘭國際集團它。后者是更方便的,因為它不要求重新鏈接。

    所有包裝紙都有上述形式。有三個關鍵的宏:

    I_WRAP_SONAME_FNNAME_ZU:這將生成包裝器的真實名稱。這是Valgrind讀取符號表信息時注意到的編碼名稱。它說的是:我是一個命名的函數的包裝器,foo它在ELF共享對象中找到一個空(“?NONE”)soname字段。規范機制是強大的,因為通配符可以用于聲名和函數名。細節將在下面討論。

    VALGRIND_GET_ORIG_FN:一旦在包裝器中,首先要抓住原始地址(以及需要的任何其他支持信息)。它存儲在不透明類型的值中OrigFn。使用信息獲取?VALGRIND_GET_ORIG_FN。在調用同一個線程中的任何其他包裝函數之前,使這個宏調用非常重要。

    CALL_FN_W_WW:最終我們想要調用被包裝的函數。直接調用它不起作用,因為這只是讓我們回到包裝器并導致無限循環。相反,結果lvalue?OrigFn和參數被交給一個形式的宏的家庭之一?CALL_FN_*。這些導致Valgrind調用原來的,并避免遞歸回包裝。

    3.3.2。包裝規格

    這個方案具有獨立的優點。可以以正常方式編譯包裝器的對象代碼,并且不依賴外部腳本告訴Valgrind哪個包裝器涉及哪些原件。

    每個包裝器都有一個名字,在最常見的情況下說:我是名稱匹配FNPATT,其ELF“soname”匹配SOPATT的任何函數的包裝器。FNPATT和SOPATT都可以包含通配符(星號)和其他通常不被視為有效的C標識符名稱的字符(空格,點,@等)。

    需要這種靈活性才能為POSIX pthread函數編寫強大的包裝器,通常我們不完全確定函數名稱或soname,或者我們想要一次包裝一整套函數。

    例如,pthread_create?在GNU libpthread中通常是一個版本化的符號 - 一個名字以...結尾的符號?@GLIBC_2.3。因此,我們不確定它的真實姓名。我們也想覆蓋任何形式的聲納libpthread.so*。所以包裝的標題將是

    int I_WRAP_SONAME_FNNAME_ZZ(libpthreadZdsoZd0,pthreadZucreateZAZa)(...正式...){ ... 身體 ... }

    為了將不尋常的字符寫為有效的C函數名稱,使用Z編碼方案。名稱是字面上的,除了一個首都Z作為轉義字符,具有以下編碼:

    Za編碼*Zp +Zc:Zd。祖_Zh - Zs(空格)ZA @ZZ ZZL(#僅在valgrind 3.3.0及更高版本ZR)#只在valgrind 3.3.0及更高版本

    因此libpthreadZdsoZd0是的soname的編碼libpthread.so.0?和pthreadZucreateZAZa是函數名的編碼pthread_create@*。

    宏I_WRAP_SONAME_FNNAME_ZZ?構造了一個包裝器名稱,其中soname(第一個組件)和函數名稱(第二個組件)都是Z編碼的。編碼函數名稱可能是令人厭煩的,并且通常是不必要的,因此I_WRAP_SONAME_FNNAME_ZU可以使用第二個宏?。該_ZU變體對于為C ++函數編寫包裝也是有用的,其中函數名通常使用一些其他約定在Z中發揮重要作用而被破壞。不得不第二次編碼很快就會變得混亂。

    由于函數名字段可能包含通配符,它??可以是任何東西,包括只是*。soname也是如此。但是,一些ELF對象(特別是主要的可執行文件)沒有聲名。任何缺少soname的對象都被視為它的soname?NONE,這就是為什么上面的原始例子有一個名字?I_WRAP_SONAME_FNNAME_ZU(NONE,foo)。

    請注意,ELF對象的soname與其文件名不同,盡管它通常是相似的。您可以libfoo.so使用該命令?找到對象的soname?readelf -a libfoo.so | grep soname。

    3.3.3。包裝語義

    包裝器替代無限功能系列的能力是強大的,但是在ELF對象出現和消失的情況下(dlopen'd和dlclose'd)在飛行中會帶來復雜性。Valgrind試圖在這種情況下保持明智的行為。

    例如,假設一個進程已經削減(帶有soname的ELF對象)object1.so,其中包含?function1。它function1立即開始使用?。

    過了一會兒,它變得渾濁wrappers.so,里面包含一個包裝function1(soname)?object1.so。所有后續調用?function1都將重新路由到包裝器。

    如果wrappers.so是以后dlclose'd,呼叫function1自然地路由到原來的。

    或者,如果object1.so?是dlclose'd但wrappers.so仍然保留,則導出的包裝器將wrappers.so?變為不活動的,因為無法獲取 - 沒有原始文件可以再調用。然而,Valgrind記得包裝仍然存在。如果?object1.so最終再次出現,包裝將再次活躍。

    簡而言之,valgrind會檢查所有代碼加載/卸載事件,以確保當前活動的包裝器集合保持一致。

    第二個可能的問題是沖突的包裝器。很容易加載兩個或更多的包裝紙,這兩個包裝紙都稱為包裝紙,用于第三個功能。在這種情況下,Valgrind會在第二次出現時抱怨有沖突的包裝者,并且只會榮獲第一名。

    3.3.4。調試

    弄清楚發生了什么事情,因為包裝的動態性質可能很困難。該?--trace-redir=yes選項可以通過在每個mmap/?munmap?事件影響代碼(文本)后顯示重定向子系統的完整狀態?。

    有兩個中心概念:

    • “重定向規范”是一個(soname pattern,fnname pattern)對與一個代碼地址的綁定。這些綁定是通過使用I_WRAP_SONAME_FNNAME_{ZZ,_ZU}?宏創建的名稱編寫函數創建的?。

    • “活動重定向”是當前有效的代碼地址綁定的代碼地址。

    包裝和重定向子系統的狀態包括一組規范和一組主動綁定。通過觀看?代碼(文本)部分的所有mmap/?munmap事件來獲取/丟棄規格?。從規范中重新計算主動綁定集,以及對規范集進行任何更改后,所有已知的符號名稱。

    --trace-redir=yes?在任何此類事件之后顯示兩個集合的內容。

    -v?每次首次使用活動規范時,都會打印一行文本。

    因此,為了最大化調試效率,您將需要使用這兩個選項。

    一個最后的評論。功能包裝設備與Valgrind的替換(重定向)指定功能的能力密切相關,例如將調用重定向?malloc到其自身的實現。實際上,替換功能可以被認為是不稱為原始的包裝功能。然而,為了使實現更加健壯,兩種截取(包裝與替換)的處理方式不同。

    --trace-redir=yes顯示替換和包裝功能的規范和綁定。為了區分兩者,使用打印R->包裝打印替換綁定?W->。

    3.3.5。限制 - 控制流程

    在大多數情況下,函數包裝實現是強大的。唯一重要的注意事項是:在包裝器中,在調用任何其他包裝功能之前,請OrigFn使用該信息?VALGRIND_GET_ORIG_FN。一旦你有了?OrigFn,任意調用之間的遞歸和longjumps之間的包裝應該正常工作。包裝函數之間從來沒有任何交互,只是替換函數(例如malloc),所以你可以malloc從包裝器中安全地調用?etc。

    上述評論對于{x86,amd64,ppc32,arm,mips32,s390} -linux是正確的。由于ppc64-linux ABI(可能設計不佳)ppc64-linux功能的包裝更加脆弱。這要求使用一個影子堆棧來跟蹤包裝和替換功能的條目/出口。這給出了兩個限制:首先,包裝器的長時間跳過會迅速導致災難,因為陰影堆棧將無法正確清除。其次,由于陰影棧具有有限的大小,因此包裝/替換功能之間的遞歸僅可能達到有限的深度,Valgrind不得不中止運行。這個深度目前是16個電話。

    對于所有平臺({x86,amd64,ppc32,ppc64,arm,mips32,s390} -linux),所有上述注釋都適用于每個線程。換句話說,包裝是線程安全的:每個線程必須單獨遵守上述限制,但不需要進行任何類型的跨線程合作。

    3.3.6。限制 - 原始功能簽名

    如上面的例子所示,要調用原來你必須使用宏的形式CALL_FN_*。由于技術原因,無法創建單個宏來處理所有參數類型和數字,因此提供了覆蓋最常見情況的一系列宏。在下文中,'W'表示機器字型的值(指針或C?long),'v'表示C的void類型。目前可用的宏是:

    CALL_FN_v_v - 調用類型為void fn(void)的原始文件 CALL_FN_W_v - 調用long fn(void)類型的原始文件CALL_FN_v_W - 調用類型為void fn(long)的原始文件 CALL_FN_W_W - 調用long fn(long)類型的原始文件CALL_FN_v_WW - 調用類型為void fn(long,long)的原始文件 CALL_FN_W_WW - 調用long fn(long,long)類型的原始文件CALL_FN_v_WWW - 調用類型為void fn(long,long,long)的原始文件 CALL_FN_W_WWW - 調用long fn(long,long,long)CALL_FN_W_WWWW - 調用long fn(long,long,long,long)類型的原始文件 CALL_FN_W_5W - 調用long fn(long,long,long,long,long) CALL_FN_W_6W - 調用long fn(long,long,long,long,long,long) 等等,直到 CALL_FN_W_12W

    可以根據需要擴展支持的類型集。令人遺憾的是,這種限制存在。功能包裝已被證明難以實施,具有一定顯然不可避免的水平。經過幾次實施嘗試,目前的安排似乎是最不利的權衡。至少它在動態鏈接和動態代碼加載/卸載的情況下可靠地工作。

    您不應嘗試用不同類型簽名的包裝器包裝一個類型簽名的功能。這樣的詭計一定會導致崩潰或奇怪的行為。這不是函數包裝實現的限制,只是反映了如果你不小心,它給你掃射的力量來射擊自己。想象一下,您可以通過編寫一個與任何soname中的任何函數名稱相匹配的包裝器,實際上就是一個聲稱是進程中所有函數的包裝器的破壞。

    3.3.7。例子

    在源代碼樹中,?memcheck/tests/wrap[1-8].c提供了一系列的例子,從很簡單到相當先進。

    mpi/libmpiwrap.c是一個包裝大而復雜的API(MPI-2接口)的例子。這個文件定義了近300個不同的包裝器。

    總結

    以上是生活随笔為你收集整理的使用和了解Valgrind核心:高级主题的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。