Advanced+Apple+Debugging(12)

來源:互聯網
上載者:User

標籤:lldb

在這一段剩下的章節中, 你將會聚焦於Python指令碼上.
正如前一章中指出的, image lookup -rn命令正在被淘汰的路上. 是時候來建立一個漂亮的指令碼來顯示內容了.
下面是你現在用image lookup -rn命令能夠擷取到的內容:

圖片.png

當你學完這一章之後, 你將會有一個可以查的清除的叫做lookup的指令碼.
圖片.png

此外, 你將會給lookup命令添加一組參數來為新的搜尋添加一些提示.
指令碼建立的自動化

包含在本章starter目錄下的這個項目是兩個能讓你在更輕鬆的建立LLDB指令碼是Python 指令碼. 這兩個指令碼如下:
? generate_new_script.py: 這將會用你給的名字建立一個新的綱要指令碼然後將它粘貼到generate_new_script所在的同一個目錄下.
? lldbinit.py: 這個指令碼將會枚舉它所在的目錄下的所有指令碼(以.py結尾的檔案)然後嘗試將它們載入到LLDB中. 此外, 如果這個目錄下有.txt拓展名的檔案, LLDB將會嘗試通過command import載入這些檔案的內容.
將本章starter目錄下的兩個檔案粘貼到你的~/lldb/目錄裡.
把這些檔案放在正確的位置之後, 進到你的~/.lldbinit檔案裡然後添加下面一行代碼:

command script import ~/lldb/lldbinit.py
這將會載入lldbinit.py檔案, lldbinit.py檔案會枚與它同目錄下的所有的.py檔案和.txt檔案容納後將這些檔案載入到LLDB中. 這就意味著從現在開始, 只需要簡單的將指令碼添加到~/lldb目錄下面就可以在LLDB啟動的時候自動載入到LLDB中.

建立lookup command

將你的新工具設定完成之後, 開啟一個終端視窗. 啟動一個新的LLDB執行個體:

lldb
正如期望的那樣, 你會迅速的看到LLDB的問候語.
確保你已經存在的LLDB指令碼沒有任何的編譯錯誤:

(lldb) reload_script
如果輸出了一些錯誤, 是時候試一下你的新命令了__generate_script(generate_new_script.py 檔案裡實現的命令).
在LLD中, 輸入:

(lldb) __generate_script lookup
如果一切都如我們期望的那樣, 你將會得到一些類似下面的輸出:

Opening "/Users/derekselander/lldb/lookup.py"...
此外, 一個Finder視窗將會彈出來向你展示當前檔案所在的位置. 你可以用這些指令碼做的事情是十分瘋狂的, 是吧?
將Finder視窗保留幾秒鐘--不要關閉它. 回到LLDB終端視窗中容納後使用reload_script命令.
因為lookup.py指令碼與lldbinit.py指令碼在同一個目錄下並且你剛剛重新載入了~/.lldbinit中的內容, 現在你要試一下lookup.py檔案是否正常工作. 輸入下面這個命令:

(lldb) lookup
你將會得到類似下面的輸出:

Hello! the lookup command is working!
現在你可以建立並使用至少兩個LLDB自訂命令. 耶, 你可以在一個命令裡設定所有的內容, 但是我現在手動控制我指令碼的載入.

lldbinit目錄結構建議

我自己的lldbinit檔案結構也許是有參考價值的. 儘管這一部分的內容不是必要的, 但是更多的是建議你如何組織你所有的LLDB的自訂指令碼和內容.
我更傾向於保持我的~/.lldbinit檔案儘可能簡單並且使用一個像lldbinit.py一樣的指令碼載入特定目錄下的所有內容. Facebook的fblldb.py做了同樣的事情(具有同樣的作用). 如果你有興趣的話就把它下載下來看看.
我將那個目錄添加到版本控制裡這樣在我需要將同樣的邏輯轉移到不同的電腦上的時候, 或者我做錯了事情需要復原的時候就很方便.
例如, 我實際的~/.lldbinit檔案(在實際工作中而不是寫這本書的時候)僅僅包含下面的內容:

command script import /Users/derekselander/lldb_repo/lldb_commands/
lldbinit.py
command script import /Users/derekselander/chisel/chisel/fblldb.py
lldb_repo是一個公有的git倉庫https://github.com/DerekSelander/lldb, 這個倉庫裡包含了一些為你想工程設計的LLDB指令碼.
我同樣有Facebook的Chisel在版本控制上, 因此無論什麼時候那些開發人員更新了一些內容, 發布了一些有趣的版本, 我只需要pull一下Chisel倉庫 https://github.com/facebook/chisel的最新版本我就可以擁有下次運行LLDB時需要的一切, 或者通過reload_script重新載入我的指令碼.
在我的lldb_commands目錄裡, 我所有的Python指令碼都作為兩個text檔案存放起來. 一個text檔案叫做cmds.txt並儲存著我所有的command regex和commandalias. 另一個text檔案叫做settings.txt, 這個檔案是我用來補充一些LLDB設定的.
例如, 此刻我的settings.txt檔案的內容如下:

settings set target.skip-prologue false
settings set target.x86-disassembly-flavor intel
在這本書前面的章節你已經將這些設定添加到你的~/.lldbinit檔案裡, 但是我更願意將這些LLDB的設定與我自訂的LLDB命令分開, 以便我檢索~/.lldbinit檔案裡的命令的時候不會丟失.
然而, 在這本書裡, 我選擇將每一章的內容都單獨作為一個指令碼安裝.這就意味著你需要將內容手動的添加到你的~/.lldbinit檔案裡以便你知道發什麼事情. 當你看完這本書的時候你應該審視新的結構實現, 因為按照建議的這種布局做有幾個好處. 這些好處如下:

調用reload_script僅顯示這個命令~/.lldbinit正在載入; 它不會顯示子指令碼被載入了. 例如 這將會反饋lldbinit.py被載入了, 到那時不會反饋lldbinit.py檔案本身被載入的內容.這讓建立指令碼變的更容易因為我經常用reload_script作為檢查我最後使用的指令碼的任何錯誤訊息的方式. 執行reload_script後產生的輸出越少, 在控制台中需要檢查的錯誤就越少.
正如你注意到的, ~/.lldbinit檔案裡的內容越少你i就可以越輕鬆的在不同電腦間切換, 尤其是將它的內容添加到版本控制之後.
最後, 用這樣的實現儘可能簡單的添加新的指令碼. 只需要將它們添加到lldbinit.py檔案所在的目錄裡然後下一次它就會被載入. 備選方案是你手動的將指令碼的路徑添加到~/.lldbinit檔案裡, 如果你經常做這件事的話你會覺得它很煩人.
在本章中你將使用將指令碼粘貼到~/lldb目錄下的方式然後將它們載入到LLDB中的這種實現策略去儲存指令碼...這一種方法更好一點, 對吧?
實現lookup 命令

正如你在前一章中看到的, 在lookup命令的背後foundation其實相當簡單. 最主要的秘密是使用SBTarget的FindGlobalFunctions API. 在那之後, 你所需要做的就是用你喜歡的方式格式化這些輸出.
你將繼續使用Allocator項目, 在本章的starter目錄下可以找到這個項目.
開啟這個項目, 構建並在iPhone 7 plus模擬器上運行. 你將會使用這個項目測試本章中你新的lookup命令的查詢功能.
項目運行起來之後, 暫停這個應用程式並進到LLDB中.
我的記憶有點模糊不清了. 這個FindGlobalFunctions需要指定哪個參數? 在LLDB中輸入下面內容:

(lldb) script help(lldb.SBTarget.FindGlobalFunctions)
你將會得到下面的輸出, 輸出展示了方法的簽名:

FindGlobalFunctions(self, *args) unbound lldb.SBTarget method
FindGlobalFunctions(self, str name, uint32_t max_matches, MatchType
matchtype) -> SBSymbolContextList
因為他是一個Python的類, 所以你可以忽略第一個self參數. str參數的名字叫做name將會是你lookuo查詢的內容. max_matches 指出你想要觸發的最大數量. 如果你指定的數字是0, 它將會返回所有可用的匹配. matchType 參數是一個lldb Python枚舉值, 用這個枚舉值你可以執行不同類型的搜尋, 比如正則或非正則.
因為正則搜尋是唯一能行的通的方法, 所以你將要使用LLDB的枚舉值lldb.eMatchTypeRegex.其他的枚舉值可以在https://lldb.llvm.org/python_reference/_lldb%27-module.html#eMatchTypeRegex上找到. 是時候將這些功能實現在lookup.py指令碼上了. 用你最愛的編輯器開啟~/lldb/lookup.py. 在handle_command的末尾找到下面的代碼:

Uncomment if you are expecting at least one argumentclean_command = shlex.split(args[0])[0]

result.AppendMessage(‘Hello! the lookup command is working!‘)
刪除上面的代碼, 用下面的代碼替代它, 確保你的縮排保持原狀:

#1
clean_command = shlex.split(args[0])[0]
#2
target = debugger.GetSelectedTarget()
#3
contextlist = target.FindGlobalFunctions(clean_command, 0, lldb.eMatchTypeRegex)
#4
result.AppendMessage(str(contextlist))
下面是對上面代碼的解讀:

包含傳到這個指令碼裡的命令的乾淨的版本, 使用你在第二十章看到的同樣的魔法.
通過SBDebugger抓取SBTarget執行個體.
將clean_command傳遞給FindGlobalFunctions API並調用FindGlobalFunctions. 你傳入了0用了設定了結果沒有數量上的上限並且傳入了eMatchTypeRegex類型的匹配來使用Regex搜尋.
你正在將contextlist轉換到一個Python str然後將它添加到SBCommandReturnObject.
回到Xcode, 用LLDB控制台重新載入指令碼的內容:
(lldb) reload_script
試著運行一下lookup命令. 還記得你在上一章中研究的DSObjectiveCObject類嗎? 通過LLDN提取出相關的所有內容:

lookup DSObjectiveCObject
你將會得到一些實際上看起來比image lookup -rnDSObjectiveCObject更糟糕的輸出:

圖片.png

使用LLDB的指令碼命令來弄清楚進一步瀏覽了哪一個API:

(lldb) script k = lldb.target.FindGlobalFunctions(‘DSObjectiveCObject‘,0, lldb.eMatchTypeRegex)
這個命令將會重複lookup.py指令碼所做的事情並且將SBSymbolContextList執行個體的值賦值給k. 在瀏覽API名字的時候我是短變數名字的粉絲--如果你沒有注意到的話.
瀏覽SBSymbolContextList的文檔:

(lldb) gdocumentation SBSymbolContextList
這條命令會提取出SBSymbolContextList實現的或者重寫的搜尋方法. 這裡會有很多方法. 但是請將注意力放在itergetitem.

圖片.png

這對你的指令碼來說是一個好事, 因為這意味著SBSymbolContextList是可迭代以及可索引的. 幾秒鐘之前, 你剛剛通過LLDB把一個SBSymbolContextList的執行個體賦值給了名字叫做k的變數.
在LLDB控制台中, 使用索引來抓取k對象裡的子項.
(lldb) script k[0]
這相當於輸入了script k.getitem(0).
你將會得到一些類似下面的輸出:

<lldb.SBSymbolContext; proxy of <Swig Object of type
‘lldb::SBSymbolContext *‘ at 0x113a83780> >
很好理解! SBSymbolContextList持有一個由SBSymbolContext的組成的數組.
使用print命令擷取這個SBSymbolContext的上下文:

(lldb) script print k[0]
你的輸出與我的可能不同, 但是我擷取到了代表[DSObjectiveCObject setLastName:]的SBSymbolContext, 像下面這樣:

Module: file = "/Users/derekselander/Library/Developer/Xcode/
DerivedData/Allocator-czsgsdzfgtmanrdjnydkbzdmhifw/Build/Products/Debug-
iphonesimulator/Allocator.app/Allocator", arch = "x86_64"
CompileUnit: id = {0x00000000}, file = "/Users/derekselander/iOS/dbg/s4-
custom-lldb-commands/22. Ex 1, Improved Lookup/projects/final/Allocator/
Allocator/DSObjectiveCObject.m", language = "objective-c"
Function: id = {0x100000268}, name = "-[DSObjectiveCObject
setLastName:]", range = [0x0000000100001c00-0x0000000100001c37)
FuncType: id = {0x100000268}, decl = DSObjectiveCObject.h:33,
compiler_type = "void (NSString *)"
Symbol: id = {0x0000001e}, range =
[0x0000000100001c00-0x0000000100001c40), name="-[DSObjectiveCObject
setLastName:]"
你將會使用屬性或者getter方法從SBSymbolContext裡抓取函數的名字.
做這件事情最簡單的方式是通過SBSymbolContext的symbol屬性抓取SBSymbol.這裡的SBSymbol裡包含一個name屬性, 這個屬性會返回一個讓你快樂的Python字串.
在你的LLDB控制台中驗證一下這個邏輯:

(lldb) script print k[0].symbol.name
在我這裡, 我收到了下面的內容:

-[DSObjectiveCObject setLastName:]
這些資訊足夠構建出你的指令碼了. 你將會迭代SBSymbolContextList裡的內一個子項並列印出它找到的函數名字.
回到lookup.py指令碼的頭部並修改handle_command函數的內容. 找到下面的代碼:

#3
contextlist = target.FindGlobalFunctions(clean_command, 0, lldb.eMatchTypeRegex)
#4
result.AppendMessage(str(contextlist))
用下面的代碼來替換(縮排要正確!):

contextlist = target.FindGlobalFunctions(clean_command, 0,
lldb.eMatchTypeRegex)
output = ‘‘
for context in contextlist:
output += context.symbol.name + ‘\n\n‘
result.AppendMessage(output)
現在你正在迭代SBSymbolContextList裡返回的所有SBSymbolContext, 提取出函數的名字並用兩個分行符號來分割它們.
回到Xcode中, 然後重新載入你的指令碼:

(lldb) reload_script
然後在LLDB中測試一下你更新以後的lookup命令:

(lldb) lookup DSObjectiveCObject
你將會得到一些比之前更漂亮的輸出:

-[DSObjectiveCObject setLastName:]
-[DSObjectiveCObject .cxx_destruct]
-[DSObjectiveCObject setFirstName:]
-[DSObjectiveCObject eyeColor]
-[DSObjectiveCObject init]
-[DSObjectiveCObject lastName]
-[DSObjectiveCObject setEyeColor:]
-[DSObjectiveCObject firstName]
這就是全部的好東西, 但是我想看看這些函數在進程裡的什麼位置. 我想將所有的函數組合起來放到一個特定的模組裡(一個SBModule), 當它們被列印出來的時候通過將模組的名字和這個模組被調用的次數放在頭部分割開來.
回到lookup.py檔案中. 現在你要建立兩個新的函數.
第一個函數叫做generateFunctionDictionary, 這個函數將會帶一個SBBreakpointContextList參數並且產生一個Python 列表的字典. 這些字典包含每個模組的keys. 字典中的value, 將會是每一個被調用的SBSymbolContext的Python列表.
第二個函數叫做generateOutput, 這個函數將會沿著你從OptionParser執行個體收到的options解析這些你剛才建立的字典.這個方法將會返回一個字串並列印才控制台中.
在lookup.py指令碼中的handle_command函數下面開始按照下面的方式實現generateModuleDictionary函數:

def generateModuleDictionary(contextlist):
mdict = {}
for context in contextlist: #1
key = context.module.file.fullpath
#2
if not key in mdict:
mdict[key] = []
#3
mdict[key].append(context)
return mdict
下面是對代碼的解讀:

從SBSymbolContext裡開始, 你正在抓取SBModule(module), 然後是SBFileSpec(file), 然後是fullPath的Python字串並且將它賦值給名字叫做key的變數. 抓取fullPath是重要的(取而代之的是, say, SBFileSpec的basename屬性, 因為這裡同樣的basename可能有多個模組).
那個mdict變數準備儲存發現的所有符號, 通過模組隔開. 字典裡的key將會是模組的名字, value將會是在那個模組裡發現的符號的數組.在這一行, 你正在檢查這個字典是否已經包含了這個模組的列表. 如果沒有, 一個空的數組會被設定到這個模組的key上.
你正在將SBSymbolContext執行個體添加到這個模組合適的列表裡. 你可以放心的假設mdict變數裡的每一個key, 這裡將至少有一個或者多個SBSymbolContext執行個體.
注意: 一個擷取唯一的key的更簡單的方法是使用SBModule裡的str()方法(以及LLDB Python模組中的每一個類). 當你在其中一個對象上調用Python的print的時候就會被調用. 然而, 如果你只依靠__str__()你不會學習進程裡的所有這些類, 屬性和方法
在generateModuleDictionary函數下面, 實現generateOutput函數:

def generateOutput(mdict, options, target): #1
output = ‘‘
separator = ‘ 60 + ‘\n‘ #2
for key in mdict:
#3
count = len(mdict[key])
firstItem = mdict[key][0]
#4
moduleName = firstItem.module.file.basename
output += ‘{0}{1} hits in {2}\n{0}‘.format(separator,
#5
for context in mdict[key]:
query = ‘‘
query += context.symbol.name
query += ‘\n\n‘
output += query
return output
下面是對代碼的解讀:

output變數將會是返回的包含傳給SBCommandReturnObject的所有內容字串.
枚舉mdict字典中發現的所有的key.
這將會抓取array裡子項的數量以及每一個數組裡的第一個子項. 稍後你將使用這些資訊查詢模組的名字.
你正在抓模數塊名字以便應用到每一段的頭部輸出.
這將會迭代Python數組中搜尋的SBSymbolContext然後將名字添加到output變數.
在你測試這個指令碼之前還有最後一點要做.
補充handle_command函數裡的代碼以便它可以使用你剛剛建立的這兩個新的方法. 找到下面的代碼:
output = ‘‘
for context in contextlist:
output += context.symbol.name + ‘\n\n‘
並用下面的代碼代替:

mdict = generateModuleDictionary(contextlist)
output = generateOutput(mdict, options, target)
你知道做什麼.回到Xcode中; 重新載入LLDB中的內容:

(lldb) reload_script
檢查你新改善的lookup命令:

(lldb) lookup DSObjectiveCObject
你將會得到一些類似下面的輸出:

8 hits in Allocator

-[DSObjectiveCObject setLastName:]
-[DSObjectiveCObject .cxx_destruct]
-[DSObjectiveCObject setFirstName:]
-[DSObjectiveCObject eyeColor]
-[DSObjectiveCObject init]
-[DSObjectiveCObject lastName]
-[DSObjectiveCObject setEyeColor:]
-[DSObjectiveCObject firstName]
酷. 然後查看所有壹initWith開頭並且包含兩個參數的 Objective-C 方法.

(lldb) lookup initWith(\w+\:){2,2}]
你會得到載入到Allocator進程裡的所有的公有模組和私人模組的方法.

為lookup添加選項

你要保持選項很好很簡單而且只實現兩個不需要其他參數的選項.
你需要實現下面的內容:
? 添加載入地址到每一個查詢上. 這是理想如果你想知道函數在記憶體中的實際位置.
? 僅僅提供一個模組的簡介. 不要產生函數名字, 只列出每一個模組調用的數量.
__generate_script為在lookup.py檔案底部發現的generateOptionParser方法添加了一些預留位置. 在generateOptionParser函數中, 改變這個函數以便它包含下面的代碼:

def generateOptionParser():
usage = "usage: %prog [options] code_to_query"
parser = optparse.OptionParser(usage=usage, prog="lookup")
parser.add_option("-l", "--load_address",
action="store_true",
default=False,
dest="load_address",
help="Show the load addresses for a particular hit")
parser.add_option("-s", "--module_summary",
action="store_true",
default=False,
dest="module_summary",
help="Only show the amount of queries in the module")
return parser
這裡不需要深入解讀這些代碼因為你已經在前面的章節學過這些東西. 你已經建立了兩個支援的選項, -s, 或者--module_summary以及-l, 或者--load_address.
首先你要實現載入地址的選項. 在generateOutput函數中, 找到以context in mdict[key]:開頭的for迴圈迭代SBSymbolContext的代碼.
按照下面的方式修改for迴圈:

for context in mdict[key]:
query = ‘‘
#1
if options.load_address:
#2
start = context.symbol.addr.GetLoadAddress(target) end = context.symbol.end_addr.GetLoadAddress(target) #3
startHex = ‘0x‘ + format(start, ‘012x‘)
endHex = ‘0x‘ + format(end, ‘012x‘)
query += ‘[{}-{}]\n‘.format(startHex, endHex)
query += context.symbol.name
query += ‘\n\n‘
output += query
下面是這些代碼做的事情:

你添加了一個條件句來判斷load_address選項時候被設定了. 如果被設定了, 這將新增內容到輸出上.
這種從SBSymbolContext到SBSymbol(symbol屬性)到SBAddress(addr或者end_addr)然後通過GetLoadAddress擷取Python long. 這裡實際有一個load_addr變數給SBAddress, 但是我已經及時的發現了一個bug, 因此我已經預設使用GetLoadAddressAPI替代. 這個方法期望SBTarget作為一個輸入參數.
在你有了Python long的起始運算式和結束運算式之後, 你已經講他們格式化的很漂亮並且用Python的format函數組合在一起. 如果需要會補上0, 注意它應該是12個數位長度, 並且將它格式化為十六進位.
儲存你的工作成果並且重新查看Xcode和LLDB控制台. 重新載入.
(lldb) reload_script
測試一下你的新選項:

(lldb) lookup -l DSObjectiveCObject
你將會得到一些類似下面的輸出:

8 hits in Allocator

[0x0001099d2c00-0x0001099d2c40]
-[DSObjectiveCObject setLastName:]
[0x0001099d2c40-0x0001099d2cae]
-[DSObjectiveCObject .cxx_destruct]
在這個列表中的一個地址上設定一個斷點看看它是否與函數相匹配.
按照下面的方式做, 用你列表中的地址替換下面的地址:

(lldb) b 0x0001099d2c00
Breakpoint 3: where = Allocator`-[DSObjectiveCObject setLastName:] at
DSObjectiveCObject.h:33, address = 0x00000001099d2c00
做得好!你已經實現了一個多選項的命令!
在最後重新看一下generateOutput.找到下面這一行代碼:

moduleName = firstItem.module.file.basename
在這一行代碼的後面加上下面的內容:

if options.module_summary:
output += ‘{} hits in {}\n‘.format(count, moduleName)
continue
這寫代碼簡單的添加了每一個模組調用的次數並且跳過了了添加實際符號.
就是這些.沒有更多的代碼.儲存, 然後回到Xcode中重新載入你的指令碼:

(lldb) reload_script
測試一下你的module_summary:

(lldb) lookup -s viewWillAppear
你將會得到一些類似下面的代碼:

46 hits in UIKit
1 hits in WebKit
4 hits in Allocator
就是這些!你已經做完了!你已經從草稿中建立了一個更強大的指令碼. 在後面的章節中你將使用這個指令碼搜尋代碼. 當你制定一個寬泛的搜尋範圍的然後想將範圍進一步縮小的時候summary選項是一個強大的工具.

我們為什麼要學這些?

這裡還有更多的選項可以添加到lookup命令上. 你可以通過在SBSymbolContext的SBFunction(通過function屬性)的後面建立一個-S或者-Swift_only選型
去訪問GetLanguage()API.
當你實現了之後, 你也應該添加一個-m或者--module選項過濾出某個模組的內容.
如果你想看看還有哪些選項可用, 可以在https://github.com/DerekSelander/LLDB/blob/master/lldb_commands/lookup.py查看我實現的lookup命令.
享受添加的這些選項吧!

Advanced+Apple+Debugging(12)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.