iOS調(diào)試技巧之LLDB

前言

今天花了一天的時間終于把iOS的幾種常見的調(diào)試方法給學(xué)習(xí)了一下敲才,在這里給大家分享一下LLDB的使用晦墙,同時也是為了忘記以后,方便查找鉴逞。

LLDB介紹

LLDB 是一個有著 REPL 的特性和 C++ ,Python 插件的開源調(diào)試器记某。LLDB 綁定在 Xcode 內(nèi)部,存在于主窗口底部的控制臺中构捡。調(diào)試器允許你在程序運行的特定時暫停它液南,你可以查看變量的值,執(zhí)行自定的指令勾徽,并且按照你所認(rèn)為合適的步驟來操作程序的進展滑凉。

基礎(chǔ)

這里有一個簡單的小程序,它會打印一個字符串喘帚。注意斷點已經(jīng)被加在第 8 行畅姊。斷點可以通過點擊 Xcode 的源碼窗口的側(cè)邊槽進行創(chuàng)建。

Image_2014-11-20_at_10.01.46_PM.png

程序會在這一行停止運行吹由,并且控制臺會被打開若未,允許我們和調(diào)試器交互。那我們應(yīng)該打些什么呢倾鲫?
help
最簡單命令是 help
粗合,它會列舉出所有的命令萍嬉。如果你忘記了一個命令是做什么的,或者想知道更多的話隙疚,你可以通過 help <command>
來了解更多細(xì)節(jié)壤追,例如 help print
或者 help thread如果你甚至忘記了 help命令是做什么的,你可以試試help help不過你如果知道這么做供屉,那就說明你大概還沒忘光這個命令行冰。??
print
打印值很簡單;只要試試 print
命令:
Image_2014-11-20_at_10.09.38_PM.png

LLDB 實際上會作前綴匹配伶丐。所以你也可以使用 prin
悼做,pri,或者 p哗魂。但你不能使用 pr贿堰,因為 LLDB 不能消除和 process的歧義 (幸運的是 p并沒有歧義)。
你可能還注意到了啡彬,結(jié)果中有個
0羹与。實際上你可以使用它來指向這個結(jié)果。試試 print
0 + 7,你會看到 106庶灿。任何以美元符開頭的東西都是存在于 LLDB 的命名空間的纵搁,它們是為了幫助你進行調(diào)試而存在的。
expression
如果想改變一個值怎么辦往踢?你或許會猜 modify腾誉。其實這時候我們要用到的是 expression這個方便的命令。

這不僅會改變調(diào)試器中的值峻呕,實際上它改變了程序中的值利职。這時候繼續(xù)執(zhí)行程序,將會打印 42 red balloons瘦癌。神奇吧猪贪。
注意,從現(xiàn)在開始讯私,我們將會偷懶分別以 p和 e來代替 print和 expression热押。
什么是 print 命令
考慮一個有意思的表達式:p count = 18。如果我們運行這條命令斤寇,然后打印 count的內(nèi)容桶癣。我們將看到它的結(jié)果與expression count = 18一樣。和 expression不同的是娘锁,print命令不需要參數(shù)牙寞。比如 e -h +17中,你很難區(qū)分到底是以 -h為標(biāo)識莫秆,僅僅執(zhí)行 +17呢间雀,還是要計算 17和 h的差值尤慰。連字符號確實很讓人困惑,你或許得不到自己想要的結(jié)果雷蹂。
幸運的是,解決方案很簡單杯道。用 --來表征標(biāo)識的結(jié)束匪煌,以及輸入的開始。如果想要 -h作為標(biāo)識党巾,就用 e -h -- +17萎庭,如果想計算它們的差值,就使用 e -- -h +17齿拂。因為一般來說不使用標(biāo)識的情況比較多驳规,所以 e --就有了一個簡寫的方式,那就是print署海。
輸入 help print吗购,然后向下滾動,你會發(fā)現(xiàn):

'print' is an abbreviation for 'expression --'.
(print是 `expression --` 的縮寫)

打印對象
嘗試輸入

p objects

輸出會有點啰嗦

(NSString *) $7 = 0x0000000104da4040 @"red balloons"

如果我們嘗試打印結(jié)構(gòu)更復(fù)雜的對象砸狞,結(jié)果甚至?xí)?/p>

(lldb) p @[ @"foo", @"bar" ]
(NSArray *) $8 = 0x00007fdb9b71b3e0 @"2 objects" 

實際上捻勉,我們想看的是對象的 description
方法的結(jié)果。我么需要使用 -O(字母 O刀森,而不是數(shù)字 0) 標(biāo)志告訴 expression命令以 對象(Object) 的方式來打印結(jié)果踱启。

(lldb) e -O -- $8
<__NSArrayI 0x7fdb9b71b3e0>(
  foo,
  bar
)

幸運的是,e -o --有也有個別名研底,那就是 po
(print object 的縮寫)埠偿,我們可以使用它來進行簡化:

(lldb) po $8
<__NSArrayI 0x7fdb9b71b3e0>(
foo,
bar)
(lldb) po @"lunar"
lunar
(lldb) p @"lunar"
(NSString *) $13 = 0x00007fdb9d0003b0 @"lunar"

打印變量
可以給 print指定不同的打印格式。它們都是以 print/<fmt>或者簡化的 p/<fmt>格式書寫榜晦。下面是一些例子:
默認(rèn)的格式

(lldb) p 1616

十六進制:

(lldb) p/x 16
0x10

二進制 (t代表 two):

(lldb) p/t 16
0b00000000000000000000000000010000
(lldb) p/t (char)16
0b00010000

你也可以使用 p/c打印字符冠蒋,或者 p/s打印以空終止的字符串 (譯者注:以 '\0' 結(jié)尾的字符串)。 這里是格式的完整清單乾胶。
變量
現(xiàn)在你已經(jīng)可以打印對象和簡單類型浊服,并且知道如何使用 expression
命令在調(diào)試器中修改它們了。現(xiàn)在讓我們使用一些變量來減少輸入量胚吁。就像你可以在 C 語言中用 int a = 0來聲明一個變量一樣牙躺,你也可以在 LLDB 中做同樣的事情。不過為了能使用聲明的變量腕扶,變量必須以美元符開頭孽拷。

(lldb) e int $a = 2
(lldb) p $a * 19
38
(lldb) e NSArray *$array = @[ @"Saturday", @"Sunday", @"Monday" ]
(lldb) p [$array count]
2
(lldb) po [[$array objectAtIndex:0] uppercaseString]
SATURDAY
(lldb) p [[$array objectAtIndex:$a] characterAtIndex:0]
error: no known method '-characterAtIndex:'; cast the message send to the method's return type
error: 1 errors parsing expression

悲劇了,LLDB 無法確定涉及的類型 (譯者注:返回的類型)半抱。這種事情常常發(fā)生脓恕,給個說明就好了:

(lldb) p (char)[[$array objectAtIndex:$a] characterAtIndex:0]
'M'
(lldb) p/d (char)[[$array objectAtIndex:$a] characterAtIndex:0]
77

變量使調(diào)試器變的容易使用得多膜宋,想不到吧???
流程控制
當(dāng)你通過 Xcode 的源碼編輯器的側(cè)邊槽 (或者通過下面的方法) 插入一個斷點炼幔,程序到達斷點時會就會停止運行秋茫。
調(diào)試條上會出現(xiàn)四個你可以用來控制程序的執(zhí)行流程的按鈕。


從左到右乃秀,四個按鈕分別是:continue肛著,step over,step into跺讯,step out枢贿。
第一個,continue 按鈕刀脏,會取消程序的暫停局荚,允許程序正常執(zhí)行 (要么一直執(zhí)行下去,要么到達下一個斷點)愈污。在 LLDB 中耀态,你可以使用 process continue命令來達到同樣的效果,它的別名為 continue
暂雹,或者也可以縮寫為 c茫陆。
第二個,step over 按鈕擎析,會以黑盒的方式執(zhí)行一行代碼簿盅。如果所在這行代碼是一個函數(shù)調(diào)用,那么就不會跳進這個函數(shù)揍魂,而是會執(zhí)行這個函數(shù)桨醋,然后繼續(xù)。LLDB 則可以使用 thread step-over现斋,next喜最,或者 n命令。
如果你確實想跳進一個函數(shù)調(diào)用來調(diào)試或者檢查程序的執(zhí)行情況庄蹋,那就用第三個按鈕瞬内,step in,或者在LLDB中使用 thread step in限书,step虫蝶,或者 s命令。注意倦西,當(dāng)前行不是函數(shù)調(diào)用時能真,next和 step
效果是一樣的。
大多數(shù)人知道 c,n 和 s粉铐,但是其實還有第四個按鈕疼约,step out。如果你曾經(jīng)不小心跳進一個函數(shù)蝙泼,但實際上你想跳過它程剥,常見的反應(yīng)是重復(fù)的運行 n直到函數(shù)返回。其實這種情況汤踏,step out 按鈕是你的救世主织鲸。它會繼續(xù)執(zhí)行到下一個返回語句 (直到一個堆棧幀結(jié)束) 然后再次停止。
例子
考慮下面一段程序:

假如我們運行程序茎活,讓它停止在斷點,然后執(zhí)行下面一些列命令:

p i
n
s
p i
finish
p i
frame info

這里琢唾,frame info會告訴你當(dāng)前的行數(shù)和源碼文件载荔,以及其他一些信息;查看 help frame采桃,help thread和 help process來獲得更多信息懒熙。這一串命令的結(jié)果會是什么?看答案之前請先想一想普办。

(lldb) p i
(int) $0 = 99
(lldb) n
2014-11-22 10:49:26.445 DebuggerDance[60182:4832768] 101 is odd!
(lldb) s
(lldb) p i
(int) $2 = 110
(lldb) finish
2014-11-22 10:49:35.978 DebuggerDance[60182:4832768] 110 is even!
(lldb) p i
(int) $4 = 99
(lldb) frame info
frame #0: 0x000000010a53bcd4 DebuggerDance`main + 68 at main.m:17

它始終在 17 行的原因是 finish命令一直運行到 isEven() 函數(shù)的 return工扎,然后立刻停止。注意即使它還在 17 行衔蹲,其實這行已經(jīng)被執(zhí)行過了肢娘。
Thread Return
調(diào)試時,還有一個很棒的函數(shù)可以用來控制程序流程:thread return舆驶。它有一個可選參數(shù)橱健,在執(zhí)行時它會把可選參數(shù)加載進返回寄存器里,然后立刻執(zhí)行返回命令沙廉,跳出當(dāng)前棧幀拘荡。這意味這函數(shù)剩余的部分不會被執(zhí)行。這會給 ARC 的引用計數(shù)造成一些問題撬陵,或者會使函數(shù)內(nèi)的清理部分失效珊皿。但是在函數(shù)的開頭執(zhí)行這個命令,是個非常好的隔離這個函數(shù)巨税,偽造返回值的方式 蟋定。
讓我們稍微修改一下上面代碼段并運行:

p i
s
thread return NO
n
p even0
frame info

看答案前思考一下。下面是答案:

(lldb) p i
(int) $0 = 99
(lldb) s
(lldb) thread return NO
(lldb) n
(lldb) p even0
(BOOL) $2 = NO
(lldb) frame info
frame #0: 0x00000001009a5cc4 DebuggerDance`main + 52 at main.m:17

斷點
我們都把斷點作為一個停止程序運行草添,檢查當(dāng)前狀態(tài)溢吻,追蹤 bug 的方式。但是如果我們改變和斷點交互的方式,很多事情都變成可能促王。
斷點允許控制程序什么時候停止犀盟,然后允許命令的運行。

想象把斷點放在函數(shù)的開頭蝇狼,然后用 thread return命令重寫函數(shù)的行為阅畴,然后繼續(xù)。想象一下讓這個過程自動化迅耘,聽起來不錯贱枣,不是嗎?
管理斷點
Xcode 提供了一系列工具來創(chuàng)建和管理斷點颤专。我們會一個個看過來并介紹 LLDB 中等價的命令 (是的纽哥,你可以在調(diào)試器內(nèi)部添加斷點)。

在 Xcode 的左側(cè)面板栖秕,有一組按鈕春塌。其中一個看起來像斷點。點擊它打開斷點導(dǎo)航簇捍,這是一個可以快速管理所有斷點的面板只壳。

Image_2014-11-22_at_11.38.24_AM.png

在這里你可以看到所有的斷點 - 在 LLDB 中通過 breakpoint list
(或者 br li) 命令也做同樣的事兒。你也可以點擊單個斷點來開啟或關(guān)閉 - 在 LLDB 中使用 breakpoint enable <breakpointID>
和 breakpoint disable <breakpointID>:

(lldb) br li
Current breakpoints:
1: file = '/Users/arig/Desktop/DebuggerDance/DebuggerDance/main.m', line = 16, locations = 1, resolved = 1, hit count = 1

    1.1: where = DebuggerDance`main + 27 at main.m:16, address = 0x000000010a3f6cab, resolved, hit count = 1

(lldb) br dis 1
1 breakpoints disabled.
(lldb) br li
Current breakpoints:
1:file='/Users/arig/Desktop/DebuggerDance/DebuggerDance/main.m', line = 16, locations = 1 Options: disabled 

    1.1: where = DebuggerDance`main + 27 at main.m:16, address = 0x000000010a3f6cab, unresolved, hit count = 1

(lldb) br del 1
1 breakpoints deleted; 0 breakpoint locations disabled.
(lldb) br li
No breakpoints currently set.

創(chuàng)建斷點
在上面的例子中暑塑,我們通過在源碼頁面器的滾槽 16上點擊來創(chuàng)建斷點吼句。你可以通過把斷點拖拽出滾槽,然后釋放鼠標(biāo)來刪除斷點 (消失時會有一個非呈赂瘢可愛的噗的一下的動畫)惕艳。你也可以在斷點導(dǎo)航頁選擇斷點,然后按下刪除鍵刪除驹愚。
要在調(diào)試器中創(chuàng)建斷點尔艇,可以使用 breakpoint set命令。

(lldb) breakpoint set -f main.m -l 16
Breakpoint 1: where = DebuggerDance`main + 27 at main.m:16, address = 0x000000010a3f6cab

也可以使用縮寫形式 br么鹤。雖然 b是一個完全不同的命令 (_regexp-break的縮寫)终娃,但恰好也可以實現(xiàn)和上面同樣的效果。

(lldb) b main.m:17
Breakpoint 2: where = DebuggerDance`main + 52 at main.m:17, address = 0x000000010a3f6cc4

也可以在一個符號 (C 語言函數(shù)) 上創(chuàng)建斷點蒸甜,而完全不用指定哪一行

(lldb) b isEven
Breakpoint 3: where = DebuggerDance`isEven + 16 at main.m:4, address = 0x000000010a3f6d00
(lldb) br s -F isEven
Breakpoint 4: where = DebuggerDance`isEven + 16 at main.m:4, address = 0x000000010a3f6d00

這些斷點會準(zhǔn)確的停止在函數(shù)的開始棠耕。Objective-C 的方法也完全可以:

(lldb) breakpoint set -F "-[NSArray objectAtIndex:]"
Breakpoint 5: where = CoreFoundation`-[NSArray objectAtIndex:], address = 0x000000010ac7a950
(lldb) b -[NSArray objectAtIndex:]
Breakpoint 6: where = CoreFoundation`-[NSArray objectAtIndex:], address = 0x000000010ac7a950
(lldb) breakpoint set -F "+[NSSet setWithObject:]"
Breakpoint 7: where = CoreFoundation`+[NSSet setWithObject:], address = 0x000000010abd3820
(lldb) b +[NSSet setWithObject:]
Breakpoint 8: where = CoreFoundation`+[NSSet setWithObject:], address = 0x000000010abd3820

如果想在 Xcode 的UI上創(chuàng)建符號斷點,你可以點擊斷點欄左側(cè)的 +
按鈕柠新。

Image_2014-11-22_at_11.52.50_AM.png

然后選擇第三個選項:

Image_2014-11-22_at_11.54.44_AM.png

這時會出現(xiàn)一個彈出框窍荧,你可以在里面添加例如 -[NSArray objectAtIndex:]這樣的符號斷點。這樣每次調(diào)用這個函數(shù)的時候恨憎,程序都會停止蕊退,不管是你調(diào)用還是蘋果調(diào)用郊楣。
如果你 Xcode 的 UI 上右擊任意斷點,然后選擇 "Edit Breakpoint" 的話瓤荔,會有一些非常誘人的選擇净蚤。

Image_2014-11-22_at_11.58.06_AM.png

這里,斷點已經(jīng)被修改為只有當(dāng) i是 99 的時候才會停止输硝。你也可以使用 "ignore" 選項來告訴斷點最初的 n次調(diào)用 (并且條件為真的時候) 的時候不要停止今瀑。
接下來介紹 'Add Action' 按鈕...
斷點行為 (Action)
上面的例子中,你或許想知道每一次到達斷點的時候 i
的值点把。我們可以使用 p i作為斷點行為橘荠。這樣每次到達斷點的時候,都會自動運行這個命令郎逃。
Screen_Shot_2014-11-22_at_12.01.32_PM.png

你也可以添加多個行為哥童,可以是調(diào)試器命令,shell 命令褒翰,也可以是更直接的打又浮:

可以看到它打印 i,然后大聲念出那個句子影暴,接著打印了自定義的表達式错邦。

下面是在 LLDB 而不是 Xcode 的 UI 中做這些的時候探赫,看起來的樣子型宙。

(lldb) breakpoint set -F isEven
Breakpoint 1: where = DebuggerDance`isEven + 16 at main.m:4, address = 0x00000001083b5d00
(lldb) breakpoint modify -c 'i == 99' 1
(lldb) breakpoint command add 1
Enter your debugger command(s). Type 'DONE' to end.
> p i
> DONE
(lldb) br li 1
1: name = 'isEven', locations = 1, resolved = 1, hit count = 0
     Breakpoint commands: 
        p i
Condition: i == 99 

    1.1: where = DebuggerDance`isEven + 16 at main.m:4, address = 0x00000001083b5d00, resolved, hit count = 0 

接下來說說自動化。
賦值后繼續(xù)運行
看編輯斷點彈出窗口的底部伦吠,你還會看到一個項: "Automatically continue after evaluation actions." 妆兑。它僅僅是一個選擇框,但是卻很強大毛仪。選中它搁嗓,調(diào)試器會運行你所有的命令,然后繼續(xù)運行箱靴∠俟洌看起來就像沒有執(zhí)行任何斷點一樣 (除非斷點太多,運行需要一段時間衡怀,拖慢了你的程序)棍矛。

這個選項框的效果和讓最后斷點的最后一個行為是 continue
一樣。選框只是讓這個操作變得更簡單抛杨。調(diào)試器的輸出是:

(lldb) breakpoint set -F isEven
Breakpoint 1: where = DebuggerDance`isEven + 16 at main.m:4, address = 0x00000001083b5d00
(lldb) breakpoint command add 1
Enter your debugger command(s). Type 'DONE' to end.
> continue
> DONE
(lldb) br li 1
1: name = 'isEven', locations = 1, resolved = 1, hit count = 0   
      Breakpoint commands: 
          continue 
    1.1: where = DebuggerDance`isEven + 16 at main.m:4, address = 0x00000001083b5d00, resolved, hit count = 0

執(zhí)行斷點后自動繼續(xù)運行够委,允許你完全通過斷點來修改程序!你可以在某一行停止怖现,運行一個 expression命令來改變變量茁帽,然后繼續(xù)運行。
例子
想想所謂的"打印調(diào)試"技術(shù)吧,不要這么做:

NSLog(@"%@", whatIsInsideThisThing);

而是用個打印變量的斷點替換 log 語句潘拨,然后繼續(xù)運行吊输。
也不要:

int calculateTheTrickyValue { 
    return 9; 
    /* 
    Figure this out later. 
    ...
}

而是加一個使用 thread return 9命令的斷點,然后讓它繼續(xù)運行战秋。

符號斷點加上 action 真的很強大璧亚。你也可以在你朋友的 Xcode 工程上添加一些斷點,并且加上大聲朗讀某些東西的 action脂信⊙Ⅲ看看他們要花多久才能弄明白發(fā)生了什么。??
完全在調(diào)試器內(nèi)運行
在開始舞蹈之前狰闪,還有一件事要看一看疯搅。實際上你可以在調(diào)試器中執(zhí)行任何 C/Objective-C/C++/Swift 的命令。唯一的缺點就是不能創(chuàng)建新函數(shù)... 這意味著不能創(chuàng)建新的類埋泵,block幔欧,函數(shù),有虛擬函數(shù)的 C++ 類等等丽声。除此之外礁蔗,它都可以做。

我們可以申請分配一些字節(jié):

(lldb) e char *$str = (char *)malloc(8)
(lldb) e (void)strcpy($str, "munkeys")
(lldb) e $str[1] = 'o'
(char) $0 = 'o'
(lldb) p $str
(char *) $str = 0x00007fd04a900040 "monkeys"

我們可以查看內(nèi)存 (使用 x命令)雁社,來看看新數(shù)組中的四個字節(jié):

(lldb) x/4c $str
0x7fd04a900040: monk

我們也可以去掉 3 個字節(jié) (x命令需要斜引號浴井,因為它只有一個內(nèi)存地址的參數(shù),而不是表達式霉撵;使用 help x來獲得更多信息):

(lldb) x/1w `$str + 3`
0x7fd04a900043: keys

做完了之后磺浙,一定不要忘了釋放內(nèi)存,這樣才不會內(nèi)存泄露。(哈,雖然這是調(diào)試器用到的內(nèi)存):

(lldb) e (void)free($str)

讓我們起舞
現(xiàn)在我們已經(jīng)知道基本的步調(diào)了,是時候開始跳舞并玩一些瘋狂的事情了无蜂。我曾經(jīng)寫過一篇 NSArray
深度探究
的博客。這篇博客用了很多 NSLog
語句伦泥,但實際上我的所有探索都是在調(diào)試器中完成的〗跸看看你能不能弄明白怎么做的不脯,這會是一個有意思的練習(xí)。
不用斷點調(diào)試
程序運行時海洼,Xcode 的調(diào)試條上會出現(xiàn)暫停按鈕跨新,而不是繼續(xù)按鈕:


點擊按鈕會暫停 app (這會運行 process interrupt命令,因為 LLDB 總是在背后運行)坏逢。這會讓你可以訪問調(diào)試器域帐,但看起來可以做的事情不多赘被,因為在當(dāng)前作用域沒有變量,也沒有特定的代碼讓你看肖揣。

這就是有意思的地方民假。如果你正在運行 iOS app,你可以試試這個: (因為全局變量是可訪問的)

(lldb) po [[[UIApplication sharedApplication] keyWindow] recursiveDescription]
<UIWindow: 0x7f82b1fa8140; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x7f82b1fa92d0>; layer = <UIWindowLayer: 0x7f82b1fa8400>>
   | <UIView: 0x7f82b1d01fd0; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x7f82b1e2e0a0>>

你可以看到整個層次龙优。Chisel 中 pviews就是這么實現(xiàn)的羊异。
更新UI
有了上面的輸出,我們可以獲取這個 view:

(lldb) e id $myView = (id)0x7f82b1d01fd0

然后在調(diào)試器中改變它的背景色:

(lldb) e (void)[$myView setBackgroundColor:[UIColor blueColor]]

但是只有程序繼續(xù)運行之后才會看到界面的變化彤断。因為改變的內(nèi)容必須被發(fā)送到渲染服務(wù)中野舶,然后顯示才會被更新。

渲染服務(wù)實際上是一個另外的進程 (被稱作 backboardd
)宰衙。這就是說即使我們正在調(diào)試的內(nèi)容所在的進程被打斷了平道,backboardd也還是繼續(xù)運行著的。
這意味著你可以運行下面的命令供炼,而不用繼續(xù)運行程序:

(lldb) e (void)[CATransaction flush]

即使你仍然在調(diào)試器中一屋,UI 也會在模擬器或者真機上實時更新。Chisel 為此提供了一個別名叫做 caflush袋哼,這個命令被用來實現(xiàn)其他的快捷命令冀墨,例如 hide <view>,show <view>以及其他很多命令涛贯。所有 Chisel 的命令都有文檔诽嘉,所以安裝后隨意運行 help show來看更多信息。
Push 一個 View Controller
想象一個以 UINavigationController為 root ViewController 的應(yīng)用疫蔓。你可以通過下面的命令含懊,輕松地獲取它:

(lldb) e id $nvc = [[[UIApplication sharedApplication] keyWindow] rootViewController]

然后 push 一個 child view controller:

(lldb) e id $vc = [UIViewController new]
(lldb) e (void)[[$vc view] setBackgroundColor:[UIColor yellowColor]]
(lldb) e (void)[$vc setTitle:@"Yay!"]
(lldb) e (void)[$nvc pushViewContoller:$vc animated:YES]

最后運行下面的命令:

(lldb) caflush // e (void)[CATransaction flush]

navigation Controller 就會立刻就被 push 到你眼前身冬。
查找按鈕的 target
想象你在調(diào)試器中有一個 $myButton
的變量衅胀,可以是創(chuàng)建出來的,也可以是從 UI 上抓取出來的酥筝,或者是你停止在斷點時的一個局部變量滚躯。你想知道,按鈕按下的時候誰會接收到按鈕發(fā)出的 action嘿歌。非常簡單:

(lldb) po [$myButton allTargets]
{( 
    <MagicEventListener: 0x7fb58bd2e240>
)}
(lldb) po [$myButton actionsForTarget:(id)0x7fb58bd2e240 forControlEvent:0]
<__NSArrayM 0x7fb58bd2aa40>(_
handleTap:
)

現(xiàn)在你或許想在它發(fā)生的時候加一個斷點掸掏。在 -[MagicEventListener _handleTap:]設(shè)置一個符號斷點就可以了,在 Xcode 和 LLDB 中都可以宙帝,然后你就可以點擊按鈕并停在你所希望的地方了丧凤。
觀察實例變量的變化
假設(shè)你有一個 UIView,不知道為什么它的 _layer
實例變量被重寫了 (糟糕)步脓。因為有可能并不涉及到方法愿待,我們不能使用符號斷點浩螺。相反的,我們想監(jiān)視什么時候這個地址被寫入仍侥。

首先要出,我們需要找到 _layer這個變量在對象上的相對位置:

(lldb) p (ptrdiff_t)ivar_getOffset((struct Ivar *)class_getInstanceVariable([MyView class], "_layer"))
(ptrdiff_t) $0 = 8

現(xiàn)在我們知道 ($myView + 8)
是被寫入的內(nèi)存地址:

(lldb) watchpoint set expression -- (int *)$myView + 8
Watchpoint created: Watchpoint 3: addr = 0x7fa554231340 size = 8 state = enabled type = w 
    new value: 0x0000000000000000

這被以 wivar $myView _layer加入到 Chisel 中。
非重寫方法的符號斷點
假設(shè)你想知道 -[MyViewController viewDidAppear:]什么時候被調(diào)用农渊。如果這個方法并沒有在MyViewController中實現(xiàn)患蹂,而是在其父類中實現(xiàn)的,該怎么辦呢砸紊?試著設(shè)置一個斷點传于,會出現(xiàn)以下結(jié)果:

(lldb) b -[MyViewController viewDidAppear:]
Breakpoint 1: no locations (pending).
WARNING: Unable to resolve breakpoint to any actual locations.

因為 LLDB 會查找一個符號,但是實際在這個類上卻找不到醉顽,所以斷點也永遠(yuǎn)不會觸發(fā)格了。你需要做的是為斷點設(shè)置一個條件 [self isKindOfClass:[MyViewController class]],然后把斷點放在 UIViewController上徽鼎。正常情況下這樣設(shè)置一個條件可以正常工作盛末。但是這里不會,因為我們沒有父類的實現(xiàn)否淤。

viewDidAppear:是蘋果實現(xiàn)的方法悄但,因此沒有它的符號;在方法內(nèi)沒有 self石抡。如果想在符號斷點上使用 self檐嚣,你必須知道它在哪里 (它可能在寄存器上,也可能在棧上啰扛;在 x86 上嚎京,你可以在 $esp+4
找到它)。但是這是很痛苦的隐解,因為現(xiàn)在你必須至少知道四種體系結(jié)構(gòu) (x86鞍帝,x86-64,armv7煞茫,armv64)帕涌。想象你需要花多少時間去學(xué)習(xí)命令集以及它們每一個的調(diào)用約定,然后正確的寫一個在你的超類上設(shè)置斷點并且條件正確的命令续徽。幸運的是蚓曼,這個在 Chisel 被解決了。這被成為 bmessage:

(lldb) bmessage -[MyViewController viewDidAppear:]
Setting a breakpoint at -[UIViewController viewDidAppear:] with condition (void*)object_getClass((id)$rdi) == 0x000000010e2f4d28
Breakpoint 1: where = UIKit`-[UIViewController viewDidAppear:], address = 0x000000010e11533c

LLDB 和 Python
LLDB 有內(nèi)建的钦扭,完整的 Python 支持纫版。在LLDB中輸入 script,會打開一個 Python REPL客情。你也可以輸入一行 python 語句作為 script 命令的參數(shù)其弊,這可以運行 python 語句而不進入REPL:

(lldb) script import os
(lldb) script os.system("open http://www.objc.io/")

這樣就允許你創(chuàng)造各種酷的命令会涎。把下面的語句放到文件 ~/myCommands.py中:

def caflushCommand(debugger, command, result, internal_dict): 
    debugger.HandleCommand("e (void)[CATransaction flush]")

然后再 LLDB 中運行:

command script import ~/myCommands.py

或者把這行命令放在 /.lldbinit里,這樣每次進入 LLDB 時都會自動運行瑞凑。Chisel 其實就是一個 Python 腳本的集合末秃,這些腳本拼接 (命令) 字符串 ,然后讓 LLDB 執(zhí)行籽御。很簡單练慕,不是嗎?

LLDB調(diào)試命令初探(copy)

常用命令:

  • expr
    可以在調(diào)試時動態(tài)執(zhí)行指定表達式技掏,并將結(jié)果打印出來铃将。常用于在調(diào)試過程中修改變量的值。
    圖四:expr截圖
    如圖設(shè)置斷點哑梳,然后運行程序劲阎。程序中斷后輸入下面的命令:
expr a=2

你會看到如下的輸出:

(int) $0 = 2

繼續(xù)運行程序,程序輸出的信息是:

實際值:2

很明顯可以看出鸠真,變量a的值被改變悯仙。 除此之外,還可以使用這個命令新聲明一個變量對象吠卷,如:

expr int $b=2
p $b

下面的命令用于輸出新聲明對象的值锡垄。(注意,對象名前要加$)

  • call

call即是調(diào)用的意思祭隔。其實上述的po和p也有調(diào)用的功能货岭。因此一般只在不需要顯示輸出,或是方法無返回值時使用call疾渴。 和上面的命令一樣千贯,我們依然在viewDidLoad:里面設(shè)置斷點,然后在程序中斷的時候輸入下面的命令:

call [self.view setBackgroundColor:[UIColor redColor]]

繼續(xù)運行程序搞坝,看看view的背景顏色是不是變成紅色的了搔谴!在調(diào)試的時候靈活運用call命令可以起到事半功倍的作用。

  • bt

打印調(diào)用堆棧瞄沙,加all可打印所有thread的堆棧己沛。不詳細(xì)舉例說明慌核,感興趣的朋友可以自己試試距境。

  • image

image 命令可用于尋址,有多個組合命令垮卓。比較實用的用法是用于尋找棧地址對應(yīng)的代碼位置垫桂。 下面我寫了一段代碼

NSArray *arr=[[NSArray alloc] initWithObjects:@"1",@"2", nil];
NSLog(@"%@",arr[2]);

這段代碼有明顯的錯誤,程序運行這段代碼后會拋出下面的異常:

*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 2 beyond bounds [0 .. 1]'
*** First throw call stack:
( 
  0 CoreFoundation 0x0000000101951495 __exceptionPreprocess + 165 
  1 libobjc.A.dylib 0x00000001016b099e objc_exception_throw + 43 
  2 CoreFoundation 0x0000000101909e3f -[__NSArrayI objectAtIndex:] + 175 
  3 ControlStyleDemo 0x0000000100004af8 -[RootViewController viewDidLoad] + 312 
  4 UIKit 0x000000010035359e -[UIViewController loadViewIfRequired] + 562 
  5 UIKit 0x0000000100353777 -[UIViewController view] + 29 
  6 UIKit 0x000000010029396b -[UIWindow addRootViewControllerViewIfPossible] + 58 
  7 UIKit 0x0000000100293c70 -[UIWindow _setHidden:forced:] + 282 
  8 UIKit 0x000000010029cffa -[UIWindow makeKeyAndVisible] + 51 
  9 ControlStyleDemo 0x00000001000045e0 -[AppDelegate application:didFinishLaunchingWithOptions:] + 672 
  10 UIKit 0x00000001002583d9 -[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 264 
  11 UIKit 0x0000000100258be1 -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1605 
  12 UIKit 0x000000010025ca0c -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] + 660 
  13 UIKit 0x000000010026dd4c -[UIApplication handleEvent:withNewEvent:] + 3189 
  14 UIKit 0x000000010026e216 -[UIApplication sendEvent:] + 79 
  15 UIKit 0x000000010025e086 _UIApplicationHandleEvent + 578 
  16 GraphicsServices 0x0000000103aca71a _PurpleEventCallback + 762  
  17 GraphicsServices 0x0000000103aca1e1 PurpleEventCallback + 35 
  18 CoreFoundation 0x00000001018d3679 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 41
  19 CoreFoundation 0x00000001018d344e __CFRunLoopDoSource1 + 478
  20 CoreFoundation 0x00000001018fc903 __CFRunLoopRun + 1939 
  21 CoreFoundation 0x00000001018fbd83 CFRunLoopRunSpecific + 467 
  22 UIKit 0x000000010025c2e1 -[UIApplication _run] + 609 
  23 UIKit 0x000000010025de33 UIApplicationMain + 1010 
  24 ControlStyleDemo 0x0000000100006b73 main + 115     
  25 libdyld.dylib 0x0000000101fe95fd start + 1 
  26 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

現(xiàn)在粟按,我們懷疑出錯的地址是0x0000000100004af8(可以根據(jù)執(zhí)行文件名判斷诬滩,或者最小的棧地址)霹粥。為了進一步精確定位,我們可以輸入以下的命令:

image lookup --address 0x0000000100004af8

命令執(zhí)行后返回:

Address: ControlStyleDemo[0x0000000100004af8] (ControlStyleDemo.__TEXT.__text + 13288)
Summary: ControlStyleDemo`-[RootViewController viewDidLoad] + 312 at RootViewController.m:53

我們可以看到疼鸟,出錯的位置是RootViewController.m的第53行后控。

更多的命令可以參見這個網(wǎng)址。另外空镜,facebook開源了他們擴展的LLDB命令庫浩淘,有興趣的朋友也可以安裝看看。

常見問題
上面我們簡單的學(xué)習(xí)了如何使用LLDB命令吴攒。但有時我們在使用這些LLDB命令的時候张抄,依然可能會遇到一些問題。
不明類型或者類型不匹配
比如下面這個命令洼怔。

(lldb) p NSLog(@"%@",[self.view viewWithTag:1001])
error: 'NSLog' has unknown return type; cast the call to its declared return type
error: 1 errors parsing expression

如果在使用LLDB命令中發(fā)現(xiàn)有 unknown type 的類似錯誤(多見于id類型署惯,比如NSArray中某個值),那我們就必須顯式聲明類型镣隶。比如上面這個命令极谊,我們得這么修改。

p (void)NSLog(@"%@",[self.view viewWithTag:1001])

這樣就能得到正確的結(jié)果了安岂。 另外怀酷,lldb是不支持宏的,需要我們自己替換嗜闻。
找不到方法
常見于輸出frame的時候蜕依。比如你可能會得到以下的錯誤信息:

(lldb) po self.view.frame
error: unsupported expression with unknown type
error: unsupported expression with unknown type
error: 2 errors parsing expression

這似乎是lldb的一個bug,無法通過點屬性訪問的方法打framework里面的對象琉雳,但是自己在app里面定義的就可以样眠。我們把上面的命令改動一下:

(lldb) p (CGRect)[self.view frame]
(CGRect) $0 = origin=(x=0, y=0) size=(width=320, height=480)
轉(zhuǎn)自:http://objccn.io/issue-19-2/

轉(zhuǎn)自:http://www.starfelix.com/blog/2014/03/17/lldbdiao-shi-ming-ling-chu-tan/
本文主要是對兩個博客進行了融合翠肘,主要是想為自己當(dāng)做一個記錄用檐束。
推薦:iOS各種調(diào)試技巧豪華套餐 http://www.cnblogs.com/daiweilai/p/4421340.html

歡迎關(guān)注我的個人微信公眾號,免費送計算機各種最新視頻資源束倍!你想象不到的精彩被丧!


0.jpg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市绪妹,隨后出現(xiàn)的幾起案子甥桂,更是在濱河造成了極大的恐慌,老刑警劉巖邮旷,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件黄选,死亡現(xiàn)場離奇詭異,居然都是意外死亡婶肩,警方通過查閱死者的電腦和手機办陷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進店門貌夕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人民镜,你說我怎么就攤上這事啡专。” “怎么了制圈?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵植旧,是天一觀的道長。 經(jīng)常有香客問我离唐,道長病附,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任亥鬓,我火速辦了婚禮完沪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嵌戈。我一直安慰自己覆积,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布熟呛。 她就那樣靜靜地躺著宽档,像睡著了一般。 火紅的嫁衣襯著肌膚如雪庵朝。 梳的紋絲不亂的頭發(fā)上吗冤,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天,我揣著相機與錄音九府,去河邊找鬼椎瘟。 笑死,一個胖子當(dāng)著我的面吹牛侄旬,可吹牛的內(nèi)容都是我干的肺蔚。 我是一名探鬼主播,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼儡羔,長吁一口氣:“原來是場噩夢啊……” “哼宣羊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起汰蜘,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤仇冯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后鉴扫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赞枕,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年坪创,在試婚紗的時候發(fā)現(xiàn)自己被綠了炕婶。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,567評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡莱预,死狀恐怖柠掂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情依沮,我是刑警寧澤涯贞,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站危喉,受9級特大地震影響宋渔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜辜限,卻給世界環(huán)境...
    茶點故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一皇拣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧薄嫡,春花似錦氧急、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至哑蔫,卻和暖如春钉寝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背闸迷。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工瘩蚪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人稿黍。 一個月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓疹瘦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親巡球。 傳聞我的和親對象是個殘疾皇子言沐,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,585評論 2 359

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