自定義lldb調(diào)試命令

相信每個(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)意.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末苛坚,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌晨汹,老刑警劉巖踱葛,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件铣猩,死亡現(xiàn)場(chǎng)離奇詭異珊随,居然都是意外死亡洗贰,警方通過(guò)查閱死者的電腦和手機(jī)寄猩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)嫉晶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人田篇,你說(shuō)我怎么就攤上這事替废。” “怎么了泊柬?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵椎镣,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我兽赁,道長(zhǎng)状答,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任刀崖,我火速辦了婚禮惊科,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘亮钦。我一直安慰自己馆截,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布蜂莉。 她就那樣靜靜地躺著蜡娶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪映穗。 梳的紋絲不亂的頭發(fā)上窖张,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音男公,去河邊找鬼荤堪。 笑死合陵,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的澄阳。 我是一名探鬼主播拥知,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼碎赢!你這毒婦竟也來(lái)了低剔?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤肮塞,失蹤者是張志新(化名)和其女友劉穎襟齿,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體枕赵,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡猜欺,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了拷窜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片开皿。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖篮昧,靈堂內(nèi)的尸體忽然破棺而出赋荆,到底是詐尸還是另有隱情,我是刑警寧澤懊昨,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布窄潭,位于F島的核電站,受9級(jí)特大地震影響酵颁,放射性物質(zhì)發(fā)生泄漏嫉你。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一躏惋、第九天 我趴在偏房一處隱蔽的房頂上張望均抽。 院中可真熱鬧,春花似錦其掂、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至攘乒,卻和暖如春贤牛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背则酝。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工殉簸, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留闰集,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓般卑,卻偏偏與公主長(zhǎng)得像武鲁,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蝠检,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容