歡迎來到《用python擴充gdb》的第三篇。上一篇我們談到了pretty printer,一個需要python支援的特性。這一篇我們談論另一個需要python支援的特性,convenience function。
什麼是convenience function
所謂的convenience function,正如其名“便利函式”,指gdb會話中,可用於輔助資料處理的一類函式。
舉個例子:
1 2 3 4 |
(gdb) print foo() $1 = void (gdb) print $_isvoid(foo()) $2 = 1 |
上面的$_isvoid
就是convenience functions。它們必須以$
開頭,以此區別於來自於C/C++上下文的函式。
gdb中內建了一些convenience functions,可惜它們的數量並不多。還好gdb提供了python介面,讓我們能夠新增自定義的convenience functions。
跟自定義命令一樣,該介面也需要使用者繼承特定的類。convenience function提供的基類名為gdb.Function
。使用者需要實現其中的__init__
和invoke(self, *args)
兩個方法,然後通過構造一個物件來向gdb註冊該函式。基本上就是自定義命令的一個翻版。不過有一個區別是,gdb.Function
的invoke
方法通常會返回一個gdb.Value
物件,表示呼叫該函式後的返回值。如果返回的不是gdb.Value
物件,gdb會嘗試把它轉化成對應的gdb.Value
物件。比方說,如果invoke
方法返回的是一個字串,那麼gdb會把該字串包裝成表示字串的gdb.Value
。
比起自定義命令,convenience function有一個劣勢。它不能(像通常意義上的函式)獨立使用,只能跟某個命令搭配。舉個例子,(gdb) $aryType()
是語法不正確的,你只能(gdb) p $aryType()
。即使在除錯指令碼里這一條也成立,單獨一個$setSize("ary", 20)
就不行,需要用p $setSize("ary", 20)
繞過。事實上,用自定義命令setSize "ary" 20
看上去會更順眼。convenience function能幹的事,自定義命令大部分也能幹,導致它的存在感一向很稀薄。
當然,它也不全是個雞肋。convenience function有一個優勢,它可以返回值。這是自定義命令做不到的。屬於它的生存空間也就剩下這麼一點了。
實現一個convenience function
老規矩,還是用我最愛的教學方式,先上示例程式碼。
這次我們嘗試用DSL實現mv
命令的第二版。該版本的mv
接受兩個引數,一個是待移除斷點的位置,另一個是待設定斷點的位置。
mv
具體實現參見《用python擴充gdb》第一篇。由於DSL裡面沒有函式,我們會用python程式碼實現名為findBreakpoint
的convenience function。當然了,如果我們選擇用python實現mv
,就沒有這個需求了。還是創造下機會讓convenience function上一會場吧。
findBreakpoint
的功能是接受一個位置,返回該位置上首個斷點的編號,這樣就能在delete
命令裡移除目標斷點。實現程式碼如下:
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 37 38 |
# mv2.gdb # 使用python...end語句塊,使得我們可以在gdb的DSL檔案裡面編寫python程式碼。 python import os # 1. 繼承gdb.Function class FindBreakpoint(gdb.Function): "Find specific breakpoint with location" def __init__(self): # 2. 註冊函式名字'findBreakpoint' super(self.__class__, self).__init__('findBreakpint') def invoke(self, location): # 3. 不要忘了,invoke方法接受的引數是gdb.Value,所以後面我通過 # string方法來獲得字串值。 bps = gdb.breakpoints() # 獲取全部斷點 if bps is None: raise gdb.GdbError('No breakpoints') for bp in bps: # 由於斷點的location屬性返回的是絕對路徑,把它轉成相對路徑 if os.path.relpath(bp.location) == location.string(): # 4. convenience function需要返回值,gdb會把它包裝成gdb.Value型別 return bp.number raise gdb.GdbError("Specific breakpoint can't be found.") # 5. 最後一步,向gdb會話註冊該函式 FindBreakpoint() end define mv if $argc == 2 # 呼叫它的時候不要忘記'$'字首 set $i = $findBreakpint($arg0) delete $i # 看到我在上面耍的一個trick嗎? # findBreakpint返回的是一個gdb.Value, # 需要把它繫結到DSL變數上,才能在DSL中使用。 break $arg1 ... |
使用方式:gdb a.out -x mv2.gdb
。
1 2 3 4 5 |
(gdb) help function ... function findBreakpint -- Find specific breakpoint with location ... (gdb) mv "gdb.c:4" 5 |
注意mv
第一個引數需要用雙引號括起來,否則gdb會報錯,說找不到符號gdb.c
。
小結
下篇將會是本教程的最後一篇。在這最後一篇裡,我們會看到,如何用python在gdb內跟外部程式互動。希望“gdb + X”的想法能讓你腦洞大開,激發出更多的玩法。敬請期待!