摘要:在末尾,我提到了也可以用來實現(xiàn)拓展腳本。其中最為常用的是和。接受一個字符串作為表達(dá)式,并以的形式返回表達(dá)式求值的結(jié)果。當(dāng)觸發(fā)斷點或收到信號時,就會調(diào)用事先注冊的回調(diào)函數(shù)。對應(yīng)的,撤銷回調(diào)函數(shù)的接口是。本教程剩余部分會提及這一點。
之前寫的《GDB 自動化操作的技術(shù)》一文介紹了可在gdb內(nèi)部使用的DSL(領(lǐng)域特定語言)來自動化gdb的操作。借助該DSL,我們分別實現(xiàn)了一個名為mv的自定義命令,和“對賬”用的調(diào)試腳本。在末尾,我提到了也可以用python來實現(xiàn)拓展腳本。從本篇開始,我會介紹如何使用python來給gdb編寫腳本。由于篇幅所限,該教程會分成四篇,爭取在本周內(nèi)更完。
作為開始的熱身,讓我們用python重新實現(xiàn)前文(《GDB 自動化操作的技術(shù)》)的mv命令。
實現(xiàn)自定義命令引用前文的mv命令實現(xiàn)如下:
# ~/.gdbinit define mv if $argc == 2 delete $arg0 # 注意新創(chuàng)建的斷點編號和被刪除斷點的編號不同 break $arg1 else print "輸入?yún)?shù)數(shù)目不對,help mv以獲得用法" end end # (gdb) help mv 會輸出以下幫助文檔 document mv Move breakpoint. Usage: mv old_breakpoint_num new_breakpoint Example: (gdb) mv 1 binary_search -- move breakpoint 1 to `b binary_search` end
對應(yīng)的python實現(xiàn)如下:
# move.py # 1. 導(dǎo)入gdb模塊來訪問gdb提供的python接口 import gdb # 2. 用戶自定義命令需要繼承自gdb.Command類 class Move(gdb.Command): # 3. docstring里面的文本是不是很眼熟?gdb會提取該類的__doc__屬性作為對應(yīng)命令的文檔 """Move breakpoint Usage: mv old_breakpoint_num new_breakpoint Example: (gdb) mv 1 binary_search -- move breakpoint 1 to `b binary_search` """ def __init__(self): # 4. 在構(gòu)造函數(shù)中注冊該命令的名字 super(self.__class__, self).__init__("mv", gdb.COMMAND_USER) # 5. 在invoke方法中實現(xiàn)該自定義命令具體的功能 # args表示該命令后面所銜接的參數(shù),這里通過string_to_argv轉(zhuǎn)換成數(shù)組 def invoke(self, args, from_tty): argv = gdb.string_to_argv(args) if len(argv) != 2: raise gdb.GdbError("輸入?yún)?shù)數(shù)目不對,help mv以獲得用法") # 6. 使用gdb.execute來執(zhí)行具體的命令 gdb.execute("delete " + argv[0]) gdb.execute("break " + argv[1]) # 7. 向gdb會話注冊該自定義命令 Move()
python腳本完成了,該怎么運行呢?在gdb里使用python腳本,需要用source命令:
(gdb) so ~/move.py (gdb) mv 1 binary_search.cpp:18
在“gdb自動化一的技術(shù)”一文中,我們最后把自定義命令的實現(xiàn)放到~/.gdbinit里面。這樣gdb每次啟動時就會運行它,而無需手動source。直接把python代碼放進~/.gdbinit當(dāng)然是不行的。需要變通一下,在~/.gdbinit加入source ~/move.py。這樣gdb每次啟動時都會替我們source一下。
有兩點需要注意的是:
gdb會用python 3來解釋你的python腳本,除非你用的gdb還處于版本感人的上古時代。
跟一般情況不同,gdb環(huán)境中的sys.path是不包括當(dāng)前目錄的。這意味著,如果你的腳本依賴于當(dāng)前目錄下的其他模塊,你需要手工修改sys.path。比如(gdb) python import sys; sys.path.append("")
gdb的python接口gdb通過gdb模塊提供了不少python接口。其中最為常用的是gdb.execute和gdb.parse_and_eval。
如前所示,gdb.execute可用于執(zhí)行一個gdb命令。默認(rèn)情況下,結(jié)果會輸出到gdb界面上。如果想把輸出結(jié)果轉(zhuǎn)存到字符串中,設(shè)置to_string為True:gdb.execute(cmd, to_string=True)。
gdb.parse_and_eval接受一個字符串作為表達(dá)式,并以gdb.Value的形式返回表達(dá)式求值的結(jié)果。舉例說,gdb當(dāng)前上下文中有一個變量i,i等于3。那么gdb.parse_and_eval("i + 1")的結(jié)果是一個gdb.Value的實例,其value屬性的值為4。這跟(gdb) i + 1是等價的。
何為gdb.Value?在gdb會話里,我們可以訪問C/C++類型的值。當(dāng)我們通過python接口跟這些值打交道時,gdb會把它們包裝成一個gdb.Value對象。
舉個例子,struct Point有x跟y兩個成員?,F(xiàn)在假設(shè)當(dāng)前上下文中有一個Point類型的變量point和指向該變量的Point指針p,就意味著:
point = gdb.parse_and_eval("point") point["x"] # 等價于point.x point["y"] # 等價于point.y point.referenced_value() # 等價于&point p = gdb.parse_and_eval("p") point2 = p.dereference() # 等價于*p point2["x"] # 等價于(*p).x,也即p->x
有時候我們需要轉(zhuǎn)換gdb.Value的類型。如果能在gdb上下文內(nèi)完成轉(zhuǎn)換,那倒是不難:gdb.parse_and_eval("(TypeX)$a")。
但如果只能在python代碼這一邊完成轉(zhuǎn)換,倒是有些復(fù)雜,需要使用gdb.Type類型:typeX_point = point.cast(gdb.lookup_type("TypeX"))。gdb.Value有一個cast方法用于類型轉(zhuǎn)換,接收一個gdb.Type對象。我們還需要使用lookup_type來構(gòu)建一個gdb.Type對象??瓷先ナ峭?。值得注意的是,"TypeX *"和"TypeX &"并非獨立的類型。如果你要獲得類型X的指針/引用,需要這么寫gdb.lookup_type("X").pointer()/gdb.lookup_type("X").reference()。
另外一個常用的接口是gdb.events.stop.connect。你可以使用該接口注冊gdb停止時的回調(diào)函數(shù)。當(dāng)gdb觸發(fā)斷點或收到信號時,就會調(diào)用事先注冊的回調(diào)函數(shù)。對應(yīng)的,撤銷回調(diào)函數(shù)的接口是gdb.events.stop.disconnect。
bps = gdb.breakpoints() if bps is None: raise gdb.GdbError("No breakpoints") last_breakpoint_num = bps[-1].number def commands(event): if not isinstance(event, gdb.BreakpointEvent): return if last_breakpoint_num in (bp.number for bp in event.breakpoints): gdb.execute("info locals") gdb.execute("info args") gdb.events.stop.connect(commands)
借助這些接口,我們可以這樣重新實現(xiàn)前文用到的“對賬”腳本:
# malloc_free.py from collections import defaultdict, namedtuple import atexit import time import gdb Entry = namedtuple("Entry", ["addr", "bt", "timestamp", "size"]) MEMORY_POOL = {} MEMORY_LOST = defaultdict(list) def comm(event): if isinstance(event, gdb.SignalEvent): return # handle BreakpointEvent for bp in event.breakpoints: if bp.number == 1: addr = str(gdb.parse_and_eval("p")) bt = gdb.execute("bt", to_string=True) timestamp = time.strftime("%H:%M:%S", time.localtime()) size = int(gdb.parse_and_eval("size")) if addr in MEMORY_POOL: MEMORY_LOST[addr].append(MEMORY_POOL[addr]) MEMORY_POOL[addr] = Entry(addr, bt, timestamp, size) elif bp.number == 2: addr = gdb.parse_and_eval("p") if addr in MEMORY_POOL: del MEMORY_POOL[addr] gdb.execute("c") def dump_memory_lost(memory_lost, filename): with open(filename, "w") as f: for entries in MEMORY_LOST.values(): for e in entries: f.write("Timestamp: %s Addr: %s Size: %d" % ( e.timestamp, e.addr, e.size)) f.write(" %s " % e.bt) atexit.register(dump_memory_lost, MEMORY_LOST, "/tmp/log") # Write to result file once signal catched gdb.events.stop.connect(comm) gdb.execute("set pagination off") gdb.execute("b my_malloc") # breakpoint 1 gdb.execute("b my_free") # breakpoint 2 gdb.execute("c")
用法:sudo gdb -q -p $(pidof $your_project) -x malloc_free.py。
小結(jié)對比于前文的DSL實現(xiàn),“對賬”腳本的python實現(xiàn)里直接完成了對數(shù)據(jù)的處理,免去了額外寫一個腳本來處理輸出結(jié)果。能夠靈活方便地處理數(shù)據(jù)——這是諸如python一類的通用語言對于領(lǐng)域特定語言的優(yōu)勢。當(dāng)然,領(lǐng)域特定語言在其擅長的領(lǐng)域里,具有通用語言無法比擬的親和力——直接輸入gdb命令,顯然比每次都gdb.execute("xxx")要順暢得多。無論是自定義的mv命令,還是“對賬”腳本,python實現(xiàn)都要比DSL實現(xiàn)更長。當(dāng)然,python比照DSL來說,有其自身的長處。本教程剩余部分會提及這一點。
如果說本篇主要講了如何用python實現(xiàn)DSL實現(xiàn)過的內(nèi)容,那么接下來幾篇將關(guān)注于如何用python實現(xiàn)DSL實現(xiàn)不了的內(nèi)容。敬請期待。
完整的python API參見官方文檔:https://sourceware.org/gdb/current/onlinedocs/gdb/Python-API.html
另外本人寫過一個gdb接口的輔助模塊,包裝了常用的gdb接口: https://github.com/spacewander/debugger-utils 。感興趣的話可以參考下里面的實現(xiàn)。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/38000.html
摘要:歡迎來到用拓展的第三篇。它們必須以開頭,以此區(qū)別于來自于上下文的函數(shù)。提供的基類名為。不過有一個區(qū)別是,的方法通常會返回一個對象,表示調(diào)用該函數(shù)后的返回值。它不能像通常意義上的函數(shù)獨立使用,只能跟某個命令搭配。具體實現(xiàn)參見用拓展第一篇。 歡迎來到《用python拓展gdb》的第三篇。上一篇我們談到了pretty printer,一個需要python支持的特性。這一篇我們談?wù)摿硪粋€需要p...
摘要:歡迎來到用拓展的最后一篇。對于通用語言來說,暴露的接口不過是又一個庫而已。這兩者間的通訊使用協(xié)議。該客戶端可以向外界暴露出調(diào)試時的信息。用拓展系列到此就結(jié)束了。 歡迎來到《用python拓展gdb》的最后一篇。第一篇結(jié)尾,我提到了通用語言相對于領(lǐng)域特定語言的一項優(yōu)勢,即在處理數(shù)據(jù)上更加靈活。其實通用語言還有著另一樣優(yōu)勢,領(lǐng)域特定語言只能局限在宿主程序中使用,而通用語言則無此限制。對于通...
摘要:歡迎來到用拓展的第二篇。到目前為止,我們都是在用實現(xiàn)內(nèi)置領(lǐng)域特定語言也能實現(xiàn)的效果。這就是的全部要求了。構(gòu)造函數(shù)接收一個表示被打印的的必選。在后被調(diào)用,可用于打印復(fù)雜的成員。能通過來自定義打印方式,無疑為的使用打開新的大門。 歡迎來到《用python拓展gdb》的第二篇。在上一篇,我們學(xué)習(xí)了gdb提供的常用python接口,并用python實現(xiàn)了自定義命令和調(diào)試腳本。 到目前為止,我們...
摘要:背景這幾天一直在查一個線上程序住的問題這個程序總是在運行分鐘后住通過以下的一些調(diào)試手段發(fā)現(xiàn)是打日志的時候因為滿被了日志是默認(rèn)打到的無論日志級別而我這個程序是被另一個程序調(diào)起的父進程沒有接收子進程的導(dǎo)致了被打滿在調(diào)試的過程中用到了以下幾種調(diào)試 FROM http://kamushin.github.io/debug/python.html 背景 這幾天一直在查一個線上程序 hang 住的...
閱讀 948·2021-11-22 12:09
閱讀 3715·2021-09-27 13:36
閱讀 1404·2021-08-20 09:37
閱讀 4027·2019-12-27 12:22
閱讀 2365·2019-08-30 15:55
閱讀 2370·2019-08-30 13:16
閱讀 2832·2019-08-26 17:06
閱讀 3442·2019-08-23 18:32