當(dāng)你在創(chuàng)建一個自定義的調(diào)試命令的時候, 你經(jīng)常在你的命令上用一些選項或者參數(shù). 只有一種方法可以執(zhí)行一項任務(wù)的自定義LLDB命令是讓人厭惡的只會一招的小馬駒.
在這一章中, 你將會學(xué)習(xí)如何傳一個可選的參數(shù)作為你自定義命令的參數(shù)來修改你自定義LLDB腳本中的功能和邏輯.
你將會繼續(xù)使用你在前面的章節(jié)中創(chuàng)建的bar(break-after-regex)命令工作. 在這一章中, 你將會通過在你的腳本中添加邏輯處理選項來完成bar命令.
在這一章的結(jié)尾, bar命令將會有邏輯來處理下面的可選參數(shù):
? Non-regular expression search: 使用-n或者--non_regex選項將會導(dǎo)致bar命令用一個非正在表達(dá)式斷點搜索. 這個選項不會帶任何額外的參數(shù).
? Filter by module: 使用-m或者--module選項將會僅僅只搜索特定模塊中的斷點. 這個選項期望有一個額外的指明模塊名字的參數(shù).
? Stop on condition: 使用-c或者--condition選項, 在步出當(dāng)前函數(shù)之后bar命令將會執(zhí)行給定的條件. 如果為True, 執(zhí)行將會停止. 如果為False, 執(zhí)行將會繼續(xù). 這個選項期望一個額外的將會被執(zhí)行而且是作為一個Objective-CBOOL執(zhí)行的字符串代碼作為參數(shù).
這一章將會是內(nèi)容稠密但又很有趣的一章. 確保你手中已經(jīng)有了一杯咖啡!
設(shè)置
如果你已經(jīng)讀過了之前的章節(jié)而且你的bar命令是可以工作的, 然后你可以繼續(xù)使用那個腳本并且可以忽略這一部分內(nèi)容. 否則, 打開本章資源中的start文件夾, 然后復(fù)制BreakAfterRegex.py 文件到~/lldb文件夾中. 確保你的~/.lldbinit文件中已經(jīng)有了在前面的章節(jié)中添加的下面這行內(nèi)容:
command script import ~/lldb/BreakAfterRegex.py
如果你對于這個名師是否成功的加載到LLDB中有什么困惑, 只需要在終端中簡單的新開一個LLDB實例:
lldb
然后查看bar的文檔:
(lldb) help bar

如果你得到了一個錯誤, 它就沒有成功的加載到LLDB中; 但是如果你的到了文檔字符串, 那就表明成功的加載到了LLDB中.
RWDevCon項目
在本章中, 你將會使用一個叫做RWDevcon的APP. 它是一個直播APP, 可以在APP Store中下載https://itunes.apple.com/us/app/rwdevcon-the-tutorial-conference/id958625272?mt=8.

這個APP是RWDevcon引用的同伴APP, https://www.rwdevcon.com/, 這是一個查看在Ray Wenderlich生氣之前你可以拍多少下Ray Wenderlich的肩膀. 嘗試運行它!我個人的最好值是37!
在這個項目中, 我已經(jīng)建了一個分支
84167c68, 這個分支可以starter文件夾中找到. 然而, 你可以在https://github.com/raywenderlich/RWDevCon-App這個網(wǎng)站上找到更早的版本.找到
start文件夾然后打開, 構(gòu)建, 然后運行這個應(yīng)用程序. 看一個了解一下這個項目.這里不需要瀏覽任何的源代碼. 在
bar命令的幫助下, 你可以瀏覽只能斷點查詢到的不同的有趣的事物.但是在我們做之前, 讓我們先討論一下如何讓
bar命令變得更加強大.
Python模塊optparse
LLDB Python腳本中最可愛的事情是你可以發(fā)揮Python的所有力量以及它所有的模塊.
在Python 2.7中在解析現(xiàn)象和參數(shù)時這里有三個值得查看的非常相關(guān)的模塊: getopt, getopt, 和argparse. getopt是一種地級別的而optparse是正在淘汰的路上因為它在Python 2.7之后的版本被廢棄了. 不幸的是argparse在設(shè)計的時候幾乎是和Python的sys.argv一起使用的-- sys.argv是不能在Python 的LLDB命令腳本中使用的.這就意味著optparse將會是你的go-to選項. Facebook的Chisel, Apple自己自定義的LLDB腳本, 而我全部使用這個模塊. 因此, 它有一點是事實上的解析參數(shù)的標(biāo)準(zhǔn).
optparse模塊可以讓你定義一個OptionParser類型的實例, 一個負(fù)責(zé)解析所有參數(shù)的類. 在這個類工作的時候, 你需要聲明你的命令支持的參數(shù)和選項. 這產(chǎn)生了一種情況因為可選的參數(shù)可能為那個特定的選項帶有額外的值或者沒帶額外的值.
看一個簡單的例子. 思考下面的代碼:
some_command woot -b 34 -a "hello world"
這個命令的名字叫做some_command. 但是傳到這個命令里的參數(shù)和選項是什么呢?
如果你沒有給這個解析器任何的上下文, 然后這個句子就可能是模棱兩可的. 這個解析器不知道-b或者-a選項是否應(yīng)該帶一個參數(shù). 例如, 這個解析器可能會認(rèn)為這個命令傳入了三個參數(shù):[woot, 34, hello world], 和兩個沒有參數(shù)的選項-b, -a. 然兒, 如果解析器期望-b或者-a帶一個參數(shù), 解析器就會將[woot作為參數(shù), 34作為-b的參數(shù)而hello world作為-a的參數(shù).
讓我們更深入到optparse函數(shù)中, 然后看一看我們?nèi)绾问褂盟鼇硖幚眍愃频那闆r.
添加沒有參數(shù)的選項
伴隨著你需要告訴解析器哪些參數(shù)是期望的, 是時候添加你的第一個可以修改bar命令功能的選項來應(yīng)用到?jīng)]有使用正則表達(dá)式的SBBreakpoint, 而不是使用一個普通的正則表達(dá)式.
這個參數(shù)將會通過一個Python的boolean值返回, 這個選項不需要參數(shù). 存在的這個選項就是你需要檢測的boolean值的所有信息. 如果參數(shù)存在, 然后它的值就會是True. 否則, 它的值就是False.
有些腳本的作者會為這個boolean值設(shè)計一個指明必要的參數(shù)的boolean選項而且這個選項的默認(rèn)值可能是True也可能是False如果這個選項沒有賦值的話.
例如, 下面的命令帶了一個選項, -f沒有帶參數(shù):
some_command -f
這將會被轉(zhuǎn)換為:
some_command -f1
這真不是我的風(fēng)格. 但是如果你正在為廣泛的用戶寫腳本的話你可能要考慮這種設(shè)計方式, 因為它給了用戶更詳細(xì)的目的.
好了, 閑聊夠了. 讓我們?nèi)崿F(xiàn)這個解析器的內(nèi)容吧.
打開BreakAfterRegex.py然后在這個文件的頂部添加下面的import語句:
import optparse
import shlex
optparse 模塊包含OptionParser 類, OptionParser 類可以解析指令中額外輸入的任何內(nèi)容.
shlex模塊有一個好用的小Python函數(shù), 這個函數(shù)可以方便的分割參數(shù),并將參數(shù)應(yīng)用到您的指令中, 同時保持字符串不變.
例如, 思考下面的Python代碼:
import shlex
command = '"hello world" "2nd parameter" 34'
shlex.split(command)
這將會產(chǎn)生下面的輸出:
['hello world', '2nd parameter', '34']
這里返回一個python的list, 元素內(nèi)容是解析后的python的string.
但是在你去用split方法之前, 你需要創(chuàng)建一個解析器.
在BreakAfterRegex.py文件的底部, 添加如下代碼:
def generateOptionParser():
'''Gets the return register as a string for lldb
based upon the hardware
'''
usage = "usage: %prog [options] breakpoint_query\n" +\
"Use 'bar -h' for option desc"
#1
parser = optparse.OptionParser(usage=usage, prog='bar')
#2
parser.add_option("-n", "--non_regex",
#3
action="store_true",
#4
default=False,
#5
dest="non_regex",
#6
help="Use a non-regex breakpoint instead")
#7
return parser
讓我們一個參數(shù)一個參數(shù)的解釋一下:
- (編號為#1的這一行)你創(chuàng)建了
OptionParser參數(shù), 然后給他傳了一個usage參數(shù)和prog參數(shù). 如果你搞砸了或者給了parser一個它不知道怎么處理的參數(shù), usage就會被顯示.prog選項是用來定位程序名字的.因為它解決了奇怪的小問題, 這個小問題會讓你運行-h或-help來獲取所有支持的自定義命令, 所以我總是合并它. 如果prog參數(shù)不在這里,-h命令就無法正常的工作. 這是生活中的一個小謎團(tuán). - (編號為#2的)這一行, 給
parser添加了-n和-- non_regex參數(shù). - (編號為#3的這一行)
action參數(shù)告知了, 這段程序運行完之后執(zhí)行什么操作. "store_true"這個選項的應(yīng)用表明parser會存儲Python Boolean True. - (編號為#4的這一行)表明如果
default沒有被明確的傳入值,那么他的默認(rèn)初始值將會是false. - (編號為#5的這一行)
dest參數(shù)用來確定OptionParser解析你的輸入時你給出的屬性的名字.舉個列子, 思考下面的代碼, 這段代碼在指令中解析了一個帶有選項和參數(shù)的python 字符串.
command_args = shlex.split(command)
(options, args) = parser.parse_args(command_args)
options.non_regex
正如你稍后會看到的, parse_args方法產(chǎn)生了一個python的tuple,這個tuple包含兩個list其中一個list包含的是options(叫做options), 另一個list包含的是arguments(叫做args).現(xiàn)在options 變量將會包含non_regex屬性.
- (編號為#6的這一行)
help將會給你幫助文檔. 你可以用--help選項獲取所有的參數(shù)和他們的信息.例如, 當(dāng)這些被正確的設(shè)置到bar指令中之后, 你所要做的就是通過輸入bar -h就能查看包含所有選項以及選項的作用的列表. - (編號為#7的這一行)在你創(chuàng)建了
OptionParser, 并且添加了-n選項之后, 你需要返回OptionParser.
你剛才創(chuàng)建了一個方法, 這個方法可以生成OptionParser實例, 這個實例你需要開始去解析那些參數(shù).現(xiàn)在是時候來用下剛才學(xué)的知識了.
讓我們跳回breakAfterRegex函數(shù)的開始, 移除下面這兩行代碼:
target = debugger.GetSelectedTarget()
breakpoint = target.BreakpointCreateByRegex(command)
然后在它們的位置上添加下面的代碼:
'''Creates a regular expression breakpoint and adds it.
Once the breakpoint is hit, control will step out of the
current function and print the return value. Useful for
stopping on getter/accessor/initialization methods
'''
#1
command = command.replace('\\', '\\\\')
#2
command_args = shlex.split(command, posix=False)
#3
parser = generateOptionParser()
#4
try:
#5
(options, args) = parser.parse_args(command_args)
except:
result.SetError(parser.usage)
return
target = debugger.GetSelectedTarget()
#6
clean_command = shlex.split(args[0])[0]
#7
if options.non_regex:
breakpoint = target.BreakpointCreateByName(clean_command)
else:
breakpoint = target.BreakpointCreateByRegex(
clean_command)
# The rest remains unchanged
確保你的縮進(jìn)是正確的.這些應(yīng)該縮進(jìn)兩個空格, 因為它們?nèi)慷际呛瘮?shù)的一部分.
下面是這些代碼所做的事情:
- 當(dāng)把你的輸入給
OptionParser解析的時候, 它會把slashes作為逃避字符解釋. 例如, \' 會作為 '解釋, 這就意味著在你的命令中需要規(guī)避所有的反斜杠字符. - 正如你在前面幾章中學(xué)到的內(nèi)容, 傳入到你的自定義lldb腳本中的指令參數(shù)是一個python字符串. 你會透過這個變量進(jìn)入
shlex.split方法去獲取一個包含python strs的python list.此外, 這里就是那個幫助對付任何包含特殊字符串(比如:破折號)的輸入posix=False的作用. 否則,OptionParser會錯誤的假定那是一個被傳入的選項. 這很重要因為Objective-C在實例方法中有破折號, 因此你不會希望破折號被錯誤的解釋為一個選項. - 使用剛才新創(chuàng)建的
generateOptionParser函數(shù), 你創(chuàng)建了一個解析器來處理命令行的輸入. - 解析輸入可能很容易出錯. Python通常會拋出異常來處理錯誤. 如果
optparse發(fā)生了錯誤并拋出了異常, 不要吃驚. 如果你沒有在你的腳本中捕獲異常, lldb就會停止工作, 進(jìn)程也會收到重創(chuàng)!因此, 在解析過程中包含一個try-except代碼塊去防止lldb在出現(xiàn)不合理的輸入的時候出現(xiàn)假死. -
OptionParser類有一個parse_args方法. 你要把command_args變量傳入到這個方法里, 并且接收一個元組作為返回值. 這個元組由兩個值組成: 一個值是options,options由所有的選項參數(shù)組成(現(xiàn)在只有non_regex這一個選項). 另外一個值args由 parser解析出來的輸入值組成. - 你將捕獲的第一個參數(shù)賦值給
clean_command變量. 還記得第二行提到的posix=False嗎?那個邏輯將會持有你捕獲到的用圓括號的語法保護(hù)的參數(shù). 如果你沒有將posix設(shè)置為false, 你也可以用args[0]代替, 但是你會由于不能在正則表達(dá)式中使用反斜杠語法而喪失正則表達(dá)式的強大功能. - 你已經(jīng)用掉了第一個選項!你正在檢查選項
non_regex的真實性, 如果是true, 你將會執(zhí)行在SBTarget中的BreakpointCreateByName去產(chǎn)生一個非正則表達(dá)式斷點. 如果non_regex的值是false(你給generateOptionParser函數(shù)傳入的default的默認(rèn)值), 然后你的腳本就會使用正則表達(dá)式搜索. 再說一次, 你所需要做的就是在bar指令的輸入中添加-n來使non_regex為true.