摘要:在末尾,我提到了也可以用來實現拓展腳本。其中最為常用的是和。接受一個字符串作為表達式,并以的形式返回表達式求值的結果。當觸發斷點或收到信號時,就會調用事先注冊的回調函數。對應的,撤銷回調函數的接口是。本教程剩余部分會提及這一點。
之前寫的《GDB 自動化操作的技術》一文介紹了可在gdb內部使用的DSL(領域特定語言)來自動化gdb的操作。借助該DSL,我們分別實現了一個名為mv的自定義命令,和“對賬”用的調試腳本。在末尾,我提到了也可以用python來實現拓展腳本。從本篇開始,我會介紹如何使用python來給gdb編寫腳本。由于篇幅所限,該教程會分成四篇,爭取在本周內更完。
作為開始的熱身,讓我們用python重新實現前文(《GDB 自動化操作的技術》)的mv命令。
實現自定義命令引用前文的mv命令實現如下:
# ~/.gdbinit define mv if $argc == 2 delete $arg0 # 注意新創建的斷點編號和被刪除斷點的編號不同 break $arg1 else print "輸入參數數目不對,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
對應的python實現如下:
# move.py # 1. 導入gdb模塊來訪問gdb提供的python接口 import gdb # 2. 用戶自定義命令需要繼承自gdb.Command類 class Move(gdb.Command): # 3. docstring里面的文本是不是很眼熟?gdb會提取該類的__doc__屬性作為對應命令的文檔 """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. 在構造函數中注冊該命令的名字 super(self.__class__, self).__init__("mv", gdb.COMMAND_USER) # 5. 在invoke方法中實現該自定義命令具體的功能 # args表示該命令后面所銜接的參數,這里通過string_to_argv轉換成數組 def invoke(self, args, from_tty): argv = gdb.string_to_argv(args) if len(argv) != 2: raise gdb.GdbError("輸入參數數目不對,help mv以獲得用法") # 6. 使用gdb.execute來執行具體的命令 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自動化一的技術”一文中,我們最后把自定義命令的實現放到~/.gdbinit里面。這樣gdb每次啟動時就會運行它,而無需手動source。直接把python代碼放進~/.gdbinit當然是不行的。需要變通一下,在~/.gdbinit加入source ~/move.py。這樣gdb每次啟動時都會替我們source一下。
有兩點需要注意的是:
gdb會用python 3來解釋你的python腳本,除非你用的gdb還處于版本感人的上古時代。
跟一般情況不同,gdb環境中的sys.path是不包括當前目錄的。這意味著,如果你的腳本依賴于當前目錄下的其他模塊,你需要手工修改sys.path。比如(gdb) python import sys; sys.path.append("")
gdb的python接口gdb通過gdb模塊提供了不少python接口。其中最為常用的是gdb.execute和gdb.parse_and_eval。
如前所示,gdb.execute可用于執行一個gdb命令。默認情況下,結果會輸出到gdb界面上。如果想把輸出結果轉存到字符串中,設置to_string為True:gdb.execute(cmd, to_string=True)。
gdb.parse_and_eval接受一個字符串作為表達式,并以gdb.Value的形式返回表達式求值的結果。舉例說,gdb當前上下文中有一個變量i,i等于3。那么gdb.parse_and_eval("i + 1")的結果是一個gdb.Value的實例,其value屬性的值為4。這跟(gdb) i + 1是等價的。
何為gdb.Value?在gdb會話里,我們可以訪問C/C++類型的值。當我們通過python接口跟這些值打交道時,gdb會把它們包裝成一個gdb.Value對象。
舉個例子,struct Point有x跟y兩個成員?,F在假設當前上下文中有一個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
有時候我們需要轉換gdb.Value的類型。如果能在gdb上下文內完成轉換,那倒是不難:gdb.parse_and_eval("(TypeX)$a")。
但如果只能在python代碼這一邊完成轉換,倒是有些復雜,需要使用gdb.Type類型:typeX_point = point.cast(gdb.lookup_type("TypeX"))。gdb.Value有一個cast方法用于類型轉換,接收一個gdb.Type對象。我們還需要使用lookup_type來構建一個gdb.Type對象。看上去是挺啰嗦。值得注意的是,"TypeX *"和"TypeX &"并非獨立的類型。如果你要獲得類型X的指針/引用,需要這么寫gdb.lookup_type("X").pointer()/gdb.lookup_type("X").reference()。
另外一個常用的接口是gdb.events.stop.connect。你可以使用該接口注冊gdb停止時的回調函數。當gdb觸發斷點或收到信號時,就會調用事先注冊的回調函數。對應的,撤銷回調函數的接口是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)
借助這些接口,我們可以這樣重新實現前文用到的“對賬”腳本:
# 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。
小結對比于前文的DSL實現,“對賬”腳本的python實現里直接完成了對數據的處理,免去了額外寫一個腳本來處理輸出結果。能夠靈活方便地處理數據——這是諸如python一類的通用語言對于領域特定語言的優勢。當然,領域特定語言在其擅長的領域里,具有通用語言無法比擬的親和力——直接輸入gdb命令,顯然比每次都gdb.execute("xxx")要順暢得多。無論是自定義的mv命令,還是“對賬”腳本,python實現都要比DSL實現更長。當然,python比照DSL來說,有其自身的長處。本教程剩余部分會提及這一點。
如果說本篇主要講了如何用python實現DSL實現過的內容,那么接下來幾篇將關注于如何用python實現DSL實現不了的內容。敬請期待。
完整的python API參見官方文檔:https://sourceware.org/gdb/current/onlinedocs/gdb/Python-API.html
另外本人寫過一個gdb接口的輔助模塊,包裝了常用的gdb接口: https://github.com/spacewander/debugger-utils 。感興趣的話可以參考下里面的實現。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/38000.html
摘要:歡迎來到用拓展的第三篇。它們必須以開頭,以此區別于來自于上下文的函數。提供的基類名為。不過有一個區別是,的方法通常會返回一個對象,表示調用該函數后的返回值。它不能像通常意義上的函數獨立使用,只能跟某個命令搭配。具體實現參見用拓展第一篇。 歡迎來到《用python拓展gdb》的第三篇。上一篇我們談到了pretty printer,一個需要python支持的特性。這一篇我們談論另一個需要p...
摘要:歡迎來到用拓展的最后一篇。對于通用語言來說,暴露的接口不過是又一個庫而已。這兩者間的通訊使用協議。該客戶端可以向外界暴露出調試時的信息。用拓展系列到此就結束了。 歡迎來到《用python拓展gdb》的最后一篇。第一篇結尾,我提到了通用語言相對于領域特定語言的一項優勢,即在處理數據上更加靈活。其實通用語言還有著另一樣優勢,領域特定語言只能局限在宿主程序中使用,而通用語言則無此限制。對于通...
摘要:歡迎來到用拓展的第二篇。到目前為止,我們都是在用實現內置領域特定語言也能實現的效果。這就是的全部要求了。構造函數接收一個表示被打印的的必選。在后被調用,可用于打印復雜的成員。能通過來自定義打印方式,無疑為的使用打開新的大門。 歡迎來到《用python拓展gdb》的第二篇。在上一篇,我們學習了gdb提供的常用python接口,并用python實現了自定義命令和調試腳本。 到目前為止,我們...
摘要:背景這幾天一直在查一個線上程序住的問題這個程序總是在運行分鐘后住通過以下的一些調試手段發現是打日志的時候因為滿被了日志是默認打到的無論日志級別而我這個程序是被另一個程序調起的父進程沒有接收子進程的導致了被打滿在調試的過程中用到了以下幾種調試 FROM http://kamushin.github.io/debug/python.html 背景 這幾天一直在查一個線上程序 hang 住的...
閱讀 929·2021-11-22 12:09
閱讀 3704·2021-09-27 13:36
閱讀 1390·2021-08-20 09:37
閱讀 4007·2019-12-27 12:22
閱讀 2353·2019-08-30 15:55
閱讀 2358·2019-08-30 13:16
閱讀 2817·2019-08-26 17:06
閱讀 3433·2019-08-23 18:32