相信每個(gè)iOS開(kāi)發(fā)者都會(huì)斷點(diǎn),斷點(diǎn)之后,就能使用lldb
的命令進(jìn)行操作,基本上每個(gè)人都用過(guò)po
這個(gè)命令吧.它在斷點(diǎn)的時(shí)候可以非常方便的查看各種值.但是,如果是相對(duì)復(fù)雜的場(chǎng)景,自定義調(diào)試命令就顯得十分必要了.
幾種方式
LLDB提供了幾種讓你自定義命令的方式:
1.別名(alias),使用簡(jiǎn)單,但是這個(gè)只能用來(lái)實(shí)現(xiàn)沒(méi)有輸入的命令
2.正則命令(command regex),可以通過(guò)正則表達(dá)式來(lái)捕獲輸入,并且將其應(yīng)用到命令中.但是這個(gè)在執(zhí)行多行命令的時(shí)候非常不方便,并且,這個(gè)只能有一個(gè)輸入?yún)?shù).
3.橋接腳本(script bridging) 基于python,這個(gè)很好的權(quán)衡了方便和復(fù)雜性.并且可以做任何LLDB能做的事情.
script bridging
就是今天我想寫(xiě)的,官方文檔寫(xiě)的實(shí)在是無(wú)力吐槽.文筆能力有限,如果有問(wèn)題,可以追問(wèn).算是拋磚引玉了.
需求
定義一個(gè)findclass
命令,它能夠?qū)崿F(xiàn)打印出所有運(yùn)行時(shí)的類(lèi).用法如下:
1.沒(méi)有參數(shù),則打印所有類(lèi)
findclass
2.帶參數(shù),則只打印類(lèi)名中包含參數(shù)(GUI)的類(lèi)
findclass GUI
準(zhǔn)備工作
前面說(shuō)了,這個(gè)腳本實(shí)際是基于python的,就目前來(lái)說(shuō)(2017年07月13日16:59:15)LLDB中使用的還是python2.x
,所以為了一致性,請(qǐng)做個(gè)檢查.
- 檢查L(zhǎng)LDB中python版本:
打開(kāi)一個(gè)終端,輸入:
lldb
然后你會(huì)發(fā)現(xiàn)終端行中會(huì)出現(xiàn)以(lldb)
開(kāi)頭的行,然后依次輸入如下命令:
script import sys
script print (sys.version)
script
表明接下來(lái)的輸入是腳本(也就是python)語(yǔ)句.后面的部分是python,一個(gè)導(dǎo)入,一個(gè)調(diào)用,不多贅述.
可以看到我的是2.7.10.
- 查看系統(tǒng)python版本
再開(kāi)個(gè)終端,或者使用quit
退出LLDB
,輸入:
python --version
驗(yàn)證是統(tǒng)一的,如果不統(tǒng)一,可以先行嘗試后面的,有問(wèn)題,就改下系統(tǒng)python的版本吧.具體怎么改版本這里不贅述,自行Google
實(shí)現(xiàn)
在終端中的簡(jiǎn)單實(shí)現(xiàn)
為了方便理解,我們首先在終端中用個(gè)非常簡(jiǎn)單的函數(shù).
1.創(chuàng)建目錄(這個(gè)隨意)
mkdir ~/lldb
touch findclass.py
2.編輯findclass.py(注意縮進(jìn))
def findclass(debugger,command,result,internal_dict):
print("hello lldb")
3.加載
因?yàn)槲覀兊奈募欠旁谖覀冏越夸浵?所以LLDB
根本不可能知道它的存在.所以需要經(jīng)過(guò)下面幾步加載:
注意:都是打開(kāi)終端,輸入lldb
進(jìn)入lldb
模式之后
command script import ~/lldb/findclass.py
command : 管理LLDB自定義命令
script : 用腳本實(shí)現(xiàn)的自定義命令
import : 導(dǎo)入文件路徑
上面做的僅僅是將路徑加入到了LLDB的認(rèn)知(相當(dāng)于環(huán)境變量),所以如果要執(zhí)行命令.還需要繼續(xù):
script import findclass
上面做的是導(dǎo)入模塊.好了,已經(jīng)將模塊導(dǎo)入了.為了方便的使用,我們還要進(jìn)行下一步:
command script add -f findclass.findclass findclass
command : 管理LLDB自定義命令
script : 用腳本實(shí)現(xiàn)的自定義命令
add : 添加一個(gè)LLDB命令
-f : function 用于綁定一個(gè)ptyhon函數(shù)
findclass.findclass : findclass模塊(默認(rèn)一個(gè)文件是一個(gè)模塊)中的findclass函數(shù)
findclass: 映射成的LLDB
命令
現(xiàn)在在終端輸入findclass
,終端就會(huì)輸出:
hello lldb
恭喜,這就是一個(gè)簡(jiǎn)單的LLDB
自定義命令了!
優(yōu)化
如果你剛剛手抖關(guān)閉了LLDB
的終端,當(dāng)你再次輸入的時(shí)候,你會(huì)發(fā)現(xiàn)findclass
命令無(wú)效了.這可蛋疼了,以后每次開(kāi)終端都這么弄,要死人.
我們希望,LLDB
每次啟動(dòng),都會(huì)加載我們的命令.LLDB
有個(gè)內(nèi)置函數(shù)__lldb_init_module
,這是一個(gè)鉤子,當(dāng)你的模塊被引入LLDB
的時(shí)候就會(huì)調(diào)用.
1.將我們的findclass.py
修改成如下樣式:
def findclass(debugger,command,result,internal_dict):
print("hello lldb")
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand('command script add -f findclass.findclass findclass')
2.編輯~/.lldbinit
,就是用戶(hù)根目錄下的.lldbinit
注意:需要打開(kāi)finder的顯示隱藏文件功能.如果沒(méi)有打開(kāi),新開(kāi)個(gè)終端窗口,輸入:
defaults write com.apple.finder AppleShowAllFiles -bool true
找到文件之后,在文件末尾添加:
command script import ~/lldb/findclass.py
這個(gè)文件是LLDB
初始化的時(shí)候就會(huì)調(diào)用的.這樣我們就形成了一個(gè)鏈條:
初始化文件(.lldbinit)--->加載findclass.py--->執(zhí)行__lldb_init_module-->完成自定義命令添加
好了,現(xiàn)在無(wú)論重新關(guān)閉打開(kāi)多少次,進(jìn)入lldb
,這個(gè)命令也是有效的了.
如果測(cè)試確實(shí)如此,那么就可以關(guān)了終端.
在Xcode中
隨便打開(kāi)一個(gè)原來(lái)項(xiàng)目(或者你新建一個(gè)),弄個(gè)斷點(diǎn),就是為了觸發(fā)lldb.
這時(shí)候,你輸入findclass
,可以看到,控制臺(tái)有輸出了! 這就是一個(gè)自定義命令了!
真正實(shí)現(xiàn)功能
如果是僅僅為了打印,就不用都這么多圈子了.
現(xiàn)在開(kāi)始實(shí)現(xiàn)我們的需求:
1.刪除原來(lái)的打印語(yǔ)句
2.定義一個(gè)要執(zhí)行的表達(dá)式,當(dāng)然這里就是利用OC的runtime獲取類(lèi)的程序了.runtime在這里不贅述.
codeString = r'''
@import Foundation;
int numClasses;
Class * classes = NULL;
classes = NULL;
numClasses = (int)objc_getClassList(NULL, 0);
NSMutableString *returnString = [NSMutableString string];
classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);
numClasses = (int)objc_getClassList(classes, numClasses);
for (int i = 0; i < numClasses; i++) {
Class c = classes[i];
[returnString appendFormat:@"%s,", class_getName(c)];
}
free(classes);
returnString;
'''
可以看到,我們把整個(gè)代碼賦值給codeString
這個(gè)字符串. r
是python的語(yǔ)法,為了防止轉(zhuǎn)義字符串的.如果不了解可以參考python學(xué)習(xí)之 字符串前'r'的用法
3.獲取運(yùn)行結(jié)果
res = lldb.SBCommandReturnObject() #1
debugger.GetCommandInterpreter().HandleCommand("expression -lobjc -O -- " + codeString, res) #2
returnVal = res.GetOutput() #3
#1 獲取命令執(zhí)行的返回值對(duì)象
#2 執(zhí)行一段命令(codeString),并將命令執(zhí)行結(jié)果放到返回值對(duì)象(res)中
#3 將res的輸出(可以理解為,就是剛剛OC代碼中的returnString)賦值給returnVal
4.python優(yōu)化顯示
剩下的事情就簡(jiǎn)單了,就是處理好換行.還有過(guò)濾.
我們的函數(shù)有個(gè)command
參數(shù),這個(gè)參數(shù)就是用戶(hù)的輸入:
findclass aaa
這個(gè)aaa
就是command
參數(shù).
resultArray = returnVal.split(",")
if not command: # 沒(méi)有輸入,顯示所有類(lèi)
print returnVal.replace(",", "\n").replace("\n\n\n", "")
else:
filteredArray = filter(lambda className: command in className, resultArray)
filteredResult = "\n".join(filteredArray)
result.AppendMessage(filteredResult) #1
#1 能夠讓命令執(zhí)行完,輸出filteredResult,也就是我們需要類(lèi)的列表.
好了,到Xcode中試試吧!!!
注意:如果要生效,要先過(guò)了斷點(diǎn),然后重新觸發(fā)斷點(diǎn).
完整代碼:
import lldb
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand('command script add -f findclass.findclass findclass')
def findclass(debugger, command, result, internal_dict):
codeString = r'''
@import Foundation;
int numClasses;
Class * classes = NULL;
classes = NULL;
numClasses = (int)objc_getClassList(NULL, 0);
NSMutableString *returnString = [NSMutableString string];
classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);
numClasses = (int)objc_getClassList(classes, numClasses);
for (int i = 0; i < numClasses; i++) {
Class c = classes[i];
[returnString appendFormat:@"%s,", class_getName(c)];
}
free(classes);
returnString;
'''
res = lldb.SBCommandReturnObject()
debugger.GetCommandInterpreter().HandleCommand("expression -lobjc -O -- " + codeString, res)
returnVal = res.GetOutput()
resultArray = returnVal.split(",")
if not command: # No input supplied
print returnVal.replace(",", "\n").replace("\n\n\n", "")
else:
filteredArray = filter(lambda className: command in className, resultArray)
filteredResult = "\n".join(filteredArray)
result.AppendMessage(filteredResult)
擴(kuò)展更多
現(xiàn)在你已經(jīng)有個(gè)架子了,以后想實(shí)現(xiàn)什么自定義命令,就先用OC的runtime實(shí)現(xiàn)了,然后替換上面例子中的OC代碼,保證有個(gè)正確的返回值就好了.歡迎大家留言分享創(chuàng)意.