歡迎來到《用python擴充gdb》的第二篇。在上一篇,我們學習了gdb提供的常用python介面,並用python實現了自定義命令和除錯指令碼。
到目前為止,我們都是在用python實現內建DSL(領域特定語言)也能實現的效果。從本篇開始,我們將繼續上路,去欣賞內建DSL所缺乏的新風景。
下一站,Pretty-Printer。
什麼是Pretty-Printer
當我們在gdb中列印一個類/結構體時,gdb會嘗試輸出該型別的所有成員和它們的值。對於指標,即是輸出指標所指向的地址。如果要想進一步檢視指標指向的值,需要使用p *cls->x@range
這樣的語法,來轉換出該地址上對應的值。畢竟,C/C++是一門接近硬體的語言,如果你不指明某個地址上的具體意義,在計算機看來,不過是些位元組罷了。
如果你除錯過C++的STL容器,就會(驚喜地)發現:gdb並不會把容器裡面各種亂七八糟的成員都列印一通,相反它僅僅輸出容器裡面的資料(除非你使用的gdb版本感人)。這一特性的背後,離不開Pretty-Printer的功勞。Pretty-Printer允許使用者使用python給指定類編寫自定義的列印方式。事實上,gdb內建了一個python指令碼,正是這個指令碼決定了STL容器的列印輸出。
專案中的某個類太過於複雜?
正在使用某個自定義的資料結構?
想要快速看出某個屬性的編碼代表什麼?
Pretty-Printer可以幫你解決以上所有問題。
實現一個Pretty-Printer
跟自定義命令不同,Pretty-Printer不需要使用者去繼承某一類,使用者編寫的Pretty-Printer類僅需要實現指定的方法。你也可以視之為繼承介面。
順便一提,考慮到Pretty-Printer實在太長,請允許我為了偷懶,從下文開始用pprinter來簡寫之。
使用者實現的pprinter會收到一個表示被列印物件的gdb.Value
作為構造引數,另外還需要實現一個to_string
方法,返回一個字串作為該物件的列印結果。
這就是pprinter的全部要求了。此外你還可以實現children
方法用於輸出該類裡面複雜成員的值,display_hint
方法用於定義輸出的樣式。
還是老樣子,邊上程式碼邊解釋。
假設我們有如下一個Buffer結構體的定義:
1 2 3 4 5 6 7 8 9 10 11 12 |
struct Buffer { int used; /// 已使用的數目 int free; /// 未使用的數目 void *data; int8_t encoding; /// 當前儲存的資料型別 /* data的型別取決於encoding的值。encoding和data型別的對應關係如下: 0 -> int8_t 1 -> int16_t 2 -> int32_t 3 -> int64_t */ }; |
現在我們需要編寫一個pprinter,它能夠輸出該Buffer裡面的資料,以及Buffer當前的使用程度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
# pprinter.py class BufferPrinter: def __init__(self, val): "建構函式接收一個表示被列印的Buffer的gdb.Value" self.val = val def to_string(self): """必選。輸出列印的結果。 由於gdb會在呼叫to_string後呼叫children,這裡我們只輸出當前的使用程度。 具體的資料留在children函式中輸出。 """ return "used: %d\nfree: %d\n" % (self.val['used'], self.val['free']) def _iterate(self, pointer, size, encoding): # 根據encoding決定pointer的型別 typestrs = ['int8_t', 'int16_t', 'int32_t', 'int64_t'] pointer = pointer.cast(gdb.lookup_type(typestrs[encoding]).pointer()) for i in range(size): elem = pointer.dereference() pointer = pointer + 1 yield ('[%d]' % i, elem) def children(self): """可選。在to_string後被呼叫,可用於列印複雜的成員。 要求返回一個迭代器,該迭代器每次迭代返回(名字,值)形式的元組。 列印出來的效果類似於“名字 = 值”。 """ return self._iterate(self.val['data'], int(self.val['used']), int(self.val['encoding'])) def display_hint(self): """可選。影響輸出的樣式。 可選值:array/map/string。 返回array表示按類似於vector的方式列印。其它選項同理。 """ return 'array' |
事實上,我們完全可以把children
方法列印的內容放到to_string
中。下面是等價的程式碼:(列印的結果有所不同,不過差異不大)
1 2 3 4 5 6 7 8 9 10 |
def _iterate(self, pointer, size, encoding): ... # 以上保持不變 yield elem def to_string(self): status = "used: %d\nfree: %d\n" % (self.val['used'], self.val['free']) data = '{' + " ".join(self._iterate(self.val['data'], int(self.val['used']), self.val['encoding'])) + '}' return status + data |
註冊Pretty-Printer
接下來是向gdb註冊我們自定義的pprinter:
1 2 3 4 5 6 7 8 9 |
def lookup_buffer(val): """val是一個gdb.Value的例項,通過type屬性來獲取它的型別。 如果型別為Buffer,那麼就使用自定義的BufferPrinter。 """ if str(val.type) == 'Buffer': return BufferPrinter(val) return None gdb.pretty_printers.append(lookup_buffer) |
使用效果如下:
1 2 3 4 5 6 7 8 9 10 |
(gdb) so pprinter.py (gdb) info pretty-printer global pretty-printers: .* bound BufferPrinter (gdb) p buffer $1 = used: 10 free: 0 data: {512 129 512 129 512 129 512 129 512 129 } |
小結
從本篇開始,我們接觸了gdb更多的特性,登上了DSL所無法到達的高處。能通過python來自定義列印方式,無疑為gdb的使用開啟新的大門。現在,gdb工具箱裡又多了項新工具。
專案中的某個類太過於複雜?在pprinter中僅顯示關鍵的成員屬性。
正在使用某個自定義的資料結構?通過編寫pprinter,我們也能像列印STL容器一樣列印出它們的資料。
想要快速看出某個屬性的編碼代表什麼?可以在pprinter中實現編碼到可讀字串的轉換,正如在示例中,我們從encoding中讀出data屬性的型別。
下一篇中,我們會談論另一個內建DSL實現不了的功能——convenience function(可以理解為gdb會話中的內建函式)。敬請期待!