LLDB詳解

Xcode po 是什么
你以前調(diào)試是不是這樣打印的犹菱?

NSLog(@"%@", whatIsInsideThisThing);

或者臨時(shí)寫一個(gè)臨時(shí)變量破托?

NSNumber *n = @7; 

或者專門寫個(gè)檢查器來(lái)判斷?

if (1 || theBooleanAtStake) { ... }

或者專門寫一個(gè)方法寸宏?

int calculateTheTrickyValue {
  return 9;

  /*
   先這樣
   ...
}

是不是每次都要重新運(yùn)行程序宁炫,重新開(kāi)始?

代碼已經(jīng)夠多氮凝,你還要多寫幾句代碼羔巢,然后重新編譯運(yùn)行看看結(jié)果對(duì)不對(duì)。如果老板給你的是低配電腦罩阵,那真是叫苦連天案透选!稿壁。

但其實(shí)你不需要重新編譯運(yùn)行幽钢,你完全可以使用調(diào)試器(也加控制臺(tái)),即使你已經(jīng)會(huì)使用打個(gè)斷點(diǎn)常摧,或打個(gè)全局?jǐn)帱c(diǎn)搅吁,但實(shí)際上調(diào)試器還可以做更多事情。

接下來(lái)我們來(lái)重新認(rèn)識(shí)Xcode中的調(diào)試器吧落午!看看到底能做多少事情。

LLDB

LLDB是一個(gè)帶著REPL肚豺、C++特性溃斋,且?guī)в蠵ython插件的開(kāi)源調(diào)試器。LLDB內(nèi)置在了Xcode里面吸申。也就是我們所見(jiàn)到的控制臺(tái)中梗劫。
LLDB 是一個(gè)有著 REPL 的特性和 C++ ,Python 插件的開(kāi)源調(diào)試器。LLDB 綁定在 Xcode 內(nèi)部截碴,存在于主窗口底部的控制臺(tái)中(這里有一個(gè)關(guān)于調(diào)試器如何工作的總體的解釋梳侨。)

咱們以前使用調(diào)試器的時(shí)候,只是簡(jiǎn)單的在Xcode中加一些斷點(diǎn)日丹,然后通過(guò)
(lldb) po value
來(lái)打印值對(duì)不對(duì)走哺?但提到上面的那些缺點(diǎn),這已經(jīng)不能滿足我們調(diào)試的要求了哲虾。
接下來(lái)我們做更酷的事丙躏!

基礎(chǔ)

先寫一個(gè)簡(jiǎn)單的代碼。這里會(huì)打印字符串束凑。然后我們?cè)诖蛴∽址哪切写a加個(gè)斷點(diǎn)晒旅。相信這點(diǎn)大家都會(huì),如果你的確是新手汪诉,那你點(diǎn)下圖中8那個(gè)數(shù)字废恋,就會(huì)看到和圖中一樣的斷點(diǎn)標(biāo)示。

簡(jiǎn)單的小程序

我們運(yùn)行一下,接下來(lái)編譯器運(yùn)行到第8行的時(shí)候就停住了鱼鼓!
這時(shí)我們就可以使用調(diào)試器了 孝常。先看看怎么用。
我們輸入: " help "

help

(lldb) help
Debugger commands:
  apropos           -- List debugger commands related to a word or subject.
  breakpoint        -- Commands for operating on breakpoints (see 'help b' for
                       shorthand.)
  bugreport         -- Commands for creating domain-specific bug reports.
  command           -- Commands for managing custom LLDB commands.
  disassemble       -- Disassemble specified instructions in the current
                       target.  Defaults to the current function for the
                       current thread and stack frame.
  expression        -- Evaluate an expression on the current thread.  Displays
                       any returned value with LLDB's default formatting.
  frame             -- Commands for selecting and examing the current thread's
                       stack frames.
  gdb-remote        -- Connect to a process via remote GDB server.  If no host
                       is specifed, localhost is assumed.
  gui               -- Switch into the curses based GUI mode.
  help              -- Show a list of all debugger commands, or give details
                       about a specific command.
....太長(zhǎng)就不復(fù)制了

然后調(diào)試器打印了一些文字蚓哩,這都是調(diào)試器的命令和說(shuō)明构灸,先簡(jiǎn)單的看一看。

print

我們平時(shí)使用最多的就是p了岸梨,其實(shí)它是print喜颁,輸入看一下:

打印值

實(shí)際上LLDB里面搞了很多別名,比如prin曹阔、pri半开、p等,都是print的別名赃份,使用的效果和print是一樣的寂拆。但是你不能用pr,因?yàn)長(zhǎng)LDB不能區(qū)分你輸入的pr是不是process抓韩。

圖中打印是不是還有個(gè)$0纠永?,這個(gè)東西是用它來(lái)指向這個(gè)結(jié)果的谒拴,比如print $0 + 7也就是計(jì)算count+7的值尝江,輸入后的結(jié)果是106。任何以美元符號(hào)開(kāi)頭的東西都存在與LLDB的命名控件里面英上,它是為了幫助咱們調(diào)試而存在的炭序。

expression

如果要改變一個(gè)值,你可用expression這個(gè)命令

改變一個(gè)值

你看苍日,這個(gè)家伙它不僅改變了調(diào)試器中的值惭聂,它甚至還改變了程序中的值!牛逼吧相恃。
它也有給別名e辜纲。

那么現(xiàn)在為了簡(jiǎn)單一點(diǎn),從現(xiàn)在開(kāi)始豆茫,我們用 pe 來(lái)代替 printexpression侨歉。

什么是 print 命令

print就是打印的意思,比如想打印一個(gè)變量 print sum
這里有一個(gè)很有意思的事情揩魂,你輸入p count = 18expression count = 18幽邓,結(jié)果是一樣樣的!

輸入 help print火脉,然后向下滾動(dòng)牵舵,你會(huì)發(fā)現(xiàn):

'print' is an abbreviation for 'expression --'.   
(print是 `expression --` 的縮寫) `--` 標(biāo)示不需要參數(shù)

打印對(duì)象

嘗試輸入

p objects

控制臺(tái)輸出了

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

如果打印復(fù)雜一點(diǎn)的的對(duì)象柒啤,你會(huì)發(fā)覺(jué)只打印了對(duì)象的指針地址

(lldb) p @[ @"foo", @"bar" ]

(NSArray *) $8 = 0x00007fdb9b71b3e0 @"2 objects" 

其實(shí)我只是想看看這個(gè)對(duì)象的 description 方法的結(jié)果。
那我就需要用-O(是字母O)這個(gè)參數(shù)了畸颅,我們輸入 e -O --$8担巩,就打印了里面的值。

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

其實(shí) e -o -- 也有個(gè)別名没炒,那就是 po (print object 的縮寫)涛癌,咱們用它更簡(jiǎn)單∷突穑看~

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

還可以打印這些

我們可以設(shè)置打印格式拳话,打印格式這么寫:print/<fmt>p/<fmt>
例子:
默認(rèn)的格式

(lldb) p 16
16

十六進(jìn)制:

(lldb) p/x 16
0x10

二進(jìn)制 (t 代表 two):

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

更多格式點(diǎn)這里

變量

前面講完打印對(duì)象种吸、又講完修改變量值弃衍,現(xiàn)在我們?cè)谥v點(diǎn)東西。
我們可以用LLDB定義變量坚俗,是不是很酷镜盯!
咱們只需要記住,申明變量必須要以美元符開(kāi)頭猖败∷倮拢看例子

(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

最后一個(gè)怎么回事!打印不出來(lái)辙浑?
這其實(shí)是因?yàn)闆](méi)有確定返回的類型值激涤。
解決辦法很簡(jiǎn)單,前面加個(gè)類型判呕,就像oc里面的類型強(qiáng)轉(zhuǎn)一樣。

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

搞定了送滞。

流程控制

當(dāng)你通過(guò) Xcode 的源碼編輯器的側(cè)邊槽 (或者通過(guò)下面的方法) 插入一個(gè)斷點(diǎn)侠草,程序到達(dá)斷點(diǎn)時(shí)會(huì)就會(huì)停止運(yùn)行。

調(diào)試條上會(huì)出現(xiàn)四個(gè)你可以用來(lái)控制程序的執(zhí)行流程的按鈕犁嗅。

[圖片上傳中...(image-653a78-1528639982303-8)]

從左到右边涕,四個(gè)按鈕分別是:continue,step over褂微,step into功蜓,step out。

第一個(gè)宠蚂,continue 按鈕式撼,會(huì)取消程序的暫停,允許程序正常執(zhí)行 (要么一直執(zhí)行下去求厕,要么到達(dá)下一個(gè)斷點(diǎn))著隆。在 LLDB 中扰楼,你可以使用 process continue 命令來(lái)達(dá)到同樣的效果,它的別名為 continue美浦,或者也可以縮寫為 c弦赖。

第二個(gè),step over 按鈕浦辨,會(huì)以黑盒的方式執(zhí)行一行代碼蹬竖。如果所在這行代碼是一個(gè)函數(shù)調(diào)用,那么就不會(huì)跳進(jìn)這個(gè)函數(shù)流酬,而是會(huì)執(zhí)行這個(gè)函數(shù)币厕,然后繼續(xù)。LLDB 則可以使用 thread step-over康吵,next劈榨,或者 n 命令。

如果你確實(shí)想跳進(jìn)一個(gè)函數(shù)調(diào)用來(lái)調(diào)試或者檢查程序的執(zhí)行情況晦嵌,那就用第三個(gè)按鈕同辣,step in,或者在LLDB中使用 thread step in惭载,step旱函,或者 s 命令。注意描滔,當(dāng)前行不是函數(shù)調(diào)用時(shí)棒妨,nextstep 效果是一樣的。

大多數(shù)人知道 c含长,ns券腔,但是其實(shí)還有第四個(gè)按鈕,step out拘泞。如果你曾經(jīng)不小心跳進(jìn)一個(gè)函數(shù)纷纫,但實(shí)際上你想跳過(guò)它,常見(jiàn)的反應(yīng)是重復(fù)的運(yùn)行 n 直到函數(shù)返回陪腌。其實(shí)這種情況辱魁,step out 按鈕是你的救世主。它會(huì)繼續(xù)執(zhí)行到下一個(gè)返回語(yǔ)句 (直到一個(gè)堆棧幀結(jié)束) 然后再次停止诗鸭。

例子

考慮下面一段程序:

[圖片上傳中...(image-fe03d7-1528639982303-7)]

假如我們運(yùn)行程序染簇,讓它停止在斷點(diǎn),然后執(zhí)行下面一些列命令:

p i
n
s
p i
finish
p i
frame info

這里强岸,frame info 會(huì)告訴你當(dāng)前的行數(shù)和源碼文件锻弓,以及其他一些信息;查看 help frame请唱,help threadhelp process 來(lái)獲得更多信息弥咪。這一串命令的結(jié)果會(huì)是什么过蹂?看答案之前請(qǐng)先想一想。

(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 命令一直運(yùn)行到 isEven() 函數(shù)的 return聚至,然后立刻停止酷勺。注意即使它還在 17 行,其實(shí)這行已經(jīng)被執(zhí)行過(guò)了扳躬。

Thread Return

調(diào)試時(shí)脆诉,還有一個(gè)很棒的函數(shù)可以用來(lái)控制程序流程:thread return 。它有一個(gè)可選參數(shù)贷币,在執(zhí)行時(shí)它會(huì)把可選參數(shù)加載進(jìn)返回寄存器里击胜,然后立刻執(zhí)行返回命令,跳出當(dāng)前棧幀役纹。這意味這函數(shù)剩余的部分不會(huì)被執(zhí)行偶摔。這會(huì)給 ARC 的引用計(jì)數(shù)造成一些問(wèn)題,或者會(huì)使函數(shù)內(nèi)的清理部分失效促脉。但是在函數(shù)的開(kāi)頭執(zhí)行這個(gè)命令辰斋,是個(gè)非常好的隔離這個(gè)函數(shù),偽造返回值的方式 瘸味。

讓我們稍微修改一下上面代碼段并運(yùn)行:

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

斷點(diǎn)

我們都把斷點(diǎn)作為一個(gè)停止程序運(yùn)行,檢查當(dāng)前狀態(tài)旁仿,追蹤 bug 的方式藕夫。但是如果我們改變和斷點(diǎn)交互的方式,很多事情都變成可能枯冈。

斷點(diǎn)允許控制程序什么時(shí)候停止毅贮,然后允許命令的運(yùn)行。

想象把斷點(diǎn)放在函數(shù)的開(kāi)頭尘奏,然后用 thread return 命令重寫函數(shù)的行為嫩码,然后繼續(xù)。想象一下讓這個(gè)過(guò)程自動(dòng)化罪既,聽(tīng)起來(lái)不錯(cuò),不是嗎铡恕?

管理斷點(diǎn)

Xcode 提供了一系列工具來(lái)創(chuàng)建和管理斷點(diǎn)琢感。我們會(huì)一個(gè)個(gè)看過(guò)來(lái)并介紹 LLDB 中等價(jià)的命令 (是的,你可以在調(diào)試器內(nèi)部添加斷點(diǎn))探熔。

在 Xcode 的左側(cè)面板驹针,有一組按鈕。其中一個(gè)看起來(lái)像斷點(diǎn)诀艰。點(diǎn)擊它打開(kāi)斷點(diǎn)導(dǎo)航柬甥,這是一個(gè)可以快速管理所有斷點(diǎn)的面板饮六。

在這里你可以看到所有的斷點(diǎn) - 在 LLDB 中通過(guò) breakpoint list (或者 br li) 命令也做同樣的事兒。你也可以點(diǎn)擊單個(gè)斷點(diǎn)來(lái)開(kāi)啟或關(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)建斷點(diǎn)

在上面的例子中苛蒲,我們通過(guò)在源碼頁(yè)面器的滾槽 16 上點(diǎn)擊來(lái)創(chuàng)建斷點(diǎn)卤橄。你可以通過(guò)把斷點(diǎn)拖拽出滾槽,然后釋放鼠標(biāo)來(lái)刪除斷點(diǎn) (消失時(shí)會(huì)有一個(gè)非潮弁猓可愛(ài)的噗的一下的動(dòng)畫)窟扑。你也可以在斷點(diǎn)導(dǎo)航頁(yè)選擇斷點(diǎn),然后按下刪除鍵刪除漏健。

要在調(diào)試器中創(chuàng)建斷點(diǎn)嚎货,可以使用 breakpoint set 命令。

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

也可以使用縮寫形式 br蔫浆。雖然 b 是一個(gè)完全不同的命令 (_regexp-break 的縮寫)殖属,但恰好也可以實(shí)現(xiàn)和上面同樣的效果。

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

也可以在一個(gè)符號(hào) (C 語(yǔ)言函數(shù)) 上創(chuàng)建斷點(diǎn)瓦盛,而完全不用指定哪一行

(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

這些斷點(diǎn)會(huì)準(zhǔn)確的停止在函數(shù)的開(kāi)始洗显。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)建符號(hào)斷點(diǎn),你可以點(diǎn)擊斷點(diǎn)欄左側(cè)的 + 按鈕谭溉。

[圖片上傳中...(image-7907e9-1528639982302-5)]

然后選擇第三個(gè)選項(xiàng):

[圖片上傳中...(image-18611-1528639982302-4)]

這時(shí)會(huì)出現(xiàn)一個(gè)彈出框墙懂,你可以在里面添加例如 -[NSArray objectAtIndex:] 這樣的符號(hào)斷點(diǎn)。這樣每次調(diào)用這個(gè)函數(shù)的時(shí)候扮念,程序都會(huì)停止损搬,不管是你調(diào)用還是蘋果調(diào)用。

如果你 Xcode 的 UI 上右擊任意斷點(diǎn)柜与,然后選擇 "Edit Breakpoint" 的話巧勤,會(huì)有一些非常誘人的選擇。

這里弄匕,斷點(diǎn)已經(jīng)被修改為只有當(dāng) i99 的時(shí)候才會(huì)停止颅悉。你也可以使用 "ignore" 選項(xiàng)來(lái)告訴斷點(diǎn)最初的 n次調(diào)用 (并且條件為真的時(shí)候) 的時(shí)候不要停止。

接下來(lái)介紹 'Add Action' 按鈕...

斷點(diǎn)行為 (Action)

上面的例子中迁匠,你或許想知道每一次到達(dá)斷點(diǎn)的時(shí)候 i 的值剩瓶。我們可以使用 p i 作為斷點(diǎn)行為。這樣每次到達(dá)斷點(diǎn)的時(shí)候城丧,都會(huì)自動(dòng)運(yùn)行這個(gè)命令延曙。

[圖片上傳中...(image-2ef2f1-1528639982302-2)]

你也可以添加多個(gè)行為,可以是調(diào)試器命令亡哄,shell 命令枝缔,也可以是更直接的打印:

[圖片上傳中...(image-14fb32-1528639982302-1)]

可以看到它打印 i蚊惯,然后大聲念出那個(gè)句子愿卸,接著打印了自定義的表達(dá)式灵临。

下面是在 LLDB 而不是 Xcode 的 UI 中做這些的時(shí)候,看起來(lái)的樣子趴荸。

(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 

接下來(lái)說(shuō)說(shuō)自動(dòng)化儒溉。

賦值后繼續(xù)運(yùn)行

看編輯斷點(diǎn)彈出窗口的底部,你還會(huì)看到一個(gè)選項(xiàng): "Automatically continue after evaluation actions." 赊舶。它僅僅是一個(gè)選擇框睁搭,但是卻很強(qiáng)大。選中它笼平,調(diào)試器會(huì)運(yùn)行你所有的命令园骆,然后繼續(xù)運(yùn)行≡⒌鳎看起來(lái)就像沒(méi)有執(zhí)行任何斷點(diǎn)一樣 (除非斷點(diǎn)太多锌唾,運(yùn)行需要一段時(shí)間,拖慢了你的程序)夺英。

這個(gè)選項(xiàng)框的效果和讓最后斷點(diǎn)的最后一個(gè)行為是 continue 一樣晌涕。選框只是讓這個(gè)操作變得更簡(jiǎn)單。調(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í)行斷點(diǎn)后自動(dòng)繼續(xù)運(yùn)行痛悯,允許你完全通過(guò)斷點(diǎn)來(lái)修改程序余黎!你可以在某一行停止,運(yùn)行一個(gè) expression 命令來(lái)改變變量载萌,然后繼續(xù)運(yùn)行惧财。

例子

想想所謂的"打印調(diào)試"技術(shù)吧,不要這么做:

NSLog(@"%@", whatIsInsideThisThing);

而是用個(gè)打印變量的斷點(diǎn)替換 log 語(yǔ)句扭仁,然后繼續(xù)運(yùn)行垮衷。

也不要:

int calculateTheTrickyValue {
  return 9;

  /*
   Figure this out later.
   ...
}

而是加一個(gè)使用 thread return 9 命令的斷點(diǎn),然后讓它繼續(xù)運(yùn)行乖坠。

符號(hào)斷點(diǎn)加上 action 真的很強(qiáng)大搀突。你也可以在你朋友的 Xcode 工程上添加一些斷點(diǎn),并且加上大聲朗讀某些東西的 action熊泵⊙銮ǎ看看他們要花多久才能弄明白發(fā)生了什么。??

完全在調(diào)試器內(nèi)運(yùn)行

在開(kāi)始舞蹈之前顽分,還有一件事要看一看轩勘。實(shí)際上你可以在調(diào)試器中執(zhí)行任何 C/Objective-C/C++/Swift 的命令。唯一的缺點(diǎn)就是不能創(chuàng)建新函數(shù)... 這意味著不能創(chuàng)建新的類怯邪,block,函數(shù)花墩,有虛擬函數(shù)的 C++ 類等等悬秉。除此之外澄步,它都可以做。

我們可以申請(qǐng)分配一些字節(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 命令)和泌,來(lái)看看新數(shù)組中的四個(gè)字節(jié):

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

我們也可以去掉 3 個(gè)字節(jié) (x 命令需要斜引號(hào)村缸,因?yàn)樗挥幸粋€(gè)內(nèi)存地址的參數(shù),而不是表達(dá)式武氓;使用 help x 來(lái)獲得更多信息):

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

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

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

讓我們起舞

現(xiàn)在我們已經(jīng)知道基本的步調(diào)了,是時(shí)候開(kāi)始跳舞并玩一些瘋狂的事情了忠烛。我曾經(jīng)寫過(guò)一篇 NSArray 深度探究的博客属提。這篇博客用了很多 NSLog 語(yǔ)句,但實(shí)際上我的所有探索都是在調(diào)試器中完成的美尸≡┮椋看看你能不能弄明白怎么做的,這會(huì)是一個(gè)有意思的練習(xí)师坎。

不用斷點(diǎn)調(diào)試

程序運(yùn)行時(shí)恕酸,Xcode 的調(diào)試條上會(huì)出現(xiàn)暫停按鈕,而不是繼續(xù)按鈕:

點(diǎn)擊按鈕會(huì)暫停 app (這會(huì)運(yùn)行 process interrupt 命令胯陋,因?yàn)?LLDB 總是在背后運(yùn)行)蕊温。這會(huì)讓你可以訪問(wèn)調(diào)試器,但看起來(lái)可以做的事情不多惶岭,因?yàn)樵诋?dāng)前作用域沒(méi)有變量寿弱,也沒(méi)有特定的代碼讓你看。

這就是有意思的地方按灶。如果你正在運(yùn)行 iOS app症革,你可以試試這個(gè): (因?yàn)槿肿兞渴强稍L問(wèn)的)

    (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>>

你可以看到整個(gè)層次。Chiselpviews 就是這么實(shí)現(xiàn)的鸯旁。

更新UI

有了上面的輸出噪矛,我們可以獲取這個(gè) view:

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

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

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

但是只有程序繼續(xù)運(yùn)行之后才會(huì)看到界面的變化。因?yàn)楦淖兊膬?nèi)容必須被發(fā)送到渲染服務(wù)中铺罢,然后顯示才會(huì)被更新艇挨。

渲染服務(wù)實(shí)際上是一個(gè)另外的進(jìn)程 (被稱作 backboardd)。這就是說(shuō)即使我們正在調(diào)試的內(nèi)容所在的進(jìn)程被打斷了韭赘,backboardd 也還是繼續(xù)運(yùn)行著的缩滨。

這意味著你可以運(yùn)行下面的命令,而不用繼續(xù)運(yùn)行程序:

(lldb) e (void)[CATransaction flush]

即使你仍然在調(diào)試器中,UI 也會(huì)在模擬器或者真機(jī)上實(shí)時(shí)更新脉漏。Chisel 為此提供了一個(gè)別名叫做 caflush苞冯,這個(gè)命令被用來(lái)實(shí)現(xiàn)其他的快捷命令,例如 hide <view>侧巨,show <view> 以及其他很多命令舅锄。所有 Chisel 的命令都有文檔,所以安裝后隨意運(yùn)行 help show 來(lái)看更多信息司忱。

Push 一個(gè) View Controller

想象一個(gè)以 UINavigationController 為 root ViewController 的應(yīng)用皇忿。你可以通過(guò)下面的命令,輕松地獲取它:

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

然后 push 一個(gè) 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]

最后運(yùn)行下面的命令:

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

navigation Controller 就會(huì)立刻就被 push 到你眼前坦仍。

查找按鈕的 target

想象你在調(diào)試器中有一個(gè) $myButton 的變量鳍烁,可以是創(chuàng)建出來(lái)的,也可以是從 UI 上抓取出來(lái)的桨踪,或者是你停止在斷點(diǎn)時(shí)的一個(gè)局部變量老翘。你想知道,按鈕按下的時(shí)候誰(shuí)會(huì)接收到按鈕發(fā)出的 action锻离。非常簡(jiǎn)單:

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

現(xiàn)在你或許想在它發(fā)生的時(shí)候加一個(gè)斷點(diǎn)铺峭。在 -[MagicEventListener _handleTap:] 設(shè)置一個(gè)符號(hào)斷點(diǎn)就可以了,在 Xcode 和 LLDB 中都可以汽纠,然后你就可以點(diǎn)擊按鈕并停在你所希望的地方了卫键。

觀察實(shí)例變量的變化

假設(shè)你有一個(gè) UIView,不知道為什么它的 _layer 實(shí)例變量被重寫了 (糟糕)虱朵。因?yàn)橛锌赡懿⒉簧婕暗椒椒ɡ蚵覀儾荒苁褂梅?hào)斷點(diǎn)。相反的碴犬,我們想監(jiān)視什么時(shí)候這個(gè)地址被寫入絮宁。

首先,我們需要找到 _layer 這個(gè)變量在對(duì)象上的相對(duì)位置:

(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 中服协。

非重寫方法的符號(hào)斷點(diǎn)

假設(shè)你想知道 -[MyViewController viewDidAppear:] 什么時(shí)候被調(diào)用绍昂。如果這個(gè)方法并沒(méi)有在MyViewController 中實(shí)現(xiàn),而是在其父類中實(shí)現(xiàn)的偿荷,該怎么辦呢窘游?試著設(shè)置一個(gè)斷點(diǎn),會(huì)出現(xiàn)以下結(jié)果:

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

因?yàn)?LLDB 會(huì)查找一個(gè)符號(hào)跳纳,但是實(shí)際在這個(gè)類上卻找不到忍饰,所以斷點(diǎn)也永遠(yuǎn)不會(huì)觸發(fā)。你需要做的是為斷點(diǎn)設(shè)置一個(gè)條件 [self isKindOfClass:[MyViewController class]]寺庄,然后把斷點(diǎn)放在 UIViewController上艾蓝。正常情況下這樣設(shè)置一個(gè)條件可以正常工作力崇。但是這里不會(huì),因?yàn)槲覀儧](méi)有父類的實(shí)現(xiàn)饶深。

viewDidAppear: 是蘋果實(shí)現(xiàn)的方法餐曹,因此沒(méi)有它的符號(hào);在方法內(nèi)沒(méi)有 self 敌厘。如果想在符號(hào)斷點(diǎn)上使用 self,你必須知道它在哪里 (它可能在寄存器上朽合,也可能在棧上俱两;在 x86 上秦驯,你可以在 $esp+4 找到它)惜颇。但是這是很痛苦的,因?yàn)楝F(xiàn)在你必須至少知道四種體系結(jié)構(gòu) (x86主届,x86-64讲婚,armv7尿孔,armv64)。想象你需要花多少時(shí)間去學(xué)習(xí)命令集以及它們每一個(gè)的調(diào)用約定筹麸,然后正確的寫一個(gè)在你的超類上設(shè)置斷點(diǎn)并且條件正確的命令活合。幸運(yùn)的是,這個(gè)在 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酵紫,會(huì)打開(kāi)一個(gè) Python REPL告嘲。你也可以輸入一行 python 語(yǔ)句作為 script 命令 的參數(shù),這可以運(yùn)行 python 語(yǔ)句而不進(jìn)入REPL:

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

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

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

然后再 LLDB 中運(yùn)行:

command script import ~/myCommands.py

或者把這行命令放在 /.lldbinit 里橄唬,這樣每次進(jìn)入 LLDB 時(shí)都會(huì)自動(dòng)運(yùn)行。Chisel 其實(shí)就是一個(gè) Python 腳本的集合参歹,這些腳本拼接 (命令) 字符串 仰楚,然后讓 LLDB 執(zhí)行。很簡(jiǎn)單泽示,不是嗎缸血?

緊握調(diào)試器這一武器

LLDB 可以做的事情很多。大多數(shù)人習(xí)慣于使用 p械筛,po捎泻,nsc埋哟,但實(shí)際上除此之外笆豁,LLDB 可以做的還有很多郎汪。掌握所有的命令 (實(shí)際上并不是很多),會(huì)讓你在揭示代碼運(yùn)行時(shí)的運(yùn)行狀態(tài)闯狱,尋找 bug煞赢,強(qiáng)制執(zhí)行特定的運(yùn)行路徑時(shí)獲得更大的能力。你甚至可以構(gòu)建簡(jiǎn)單的交互原型 - 比如要是現(xiàn)在以 modal 方式彈出一個(gè) View Controller 會(huì)怎么樣哄孤?使用調(diào)試器照筑,一試便知。

這篇文章是為了想你展示 LLDB 的強(qiáng)大之處瘦陈,并且鼓勵(lì)你多去探索在控制臺(tái)輸入命令凝危。

打開(kāi) LLDB,輸入 help晨逝,看一看列舉的命令蛾默。你嘗試過(guò)多少?用了多少捉貌?

但愿 NSLog 看起來(lái)不再那么吸引你去用支鸡,每次編輯再運(yùn)行并不有趣而且耗時(shí)。

調(diào)試愉快趁窃!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末牧挣,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子棚菊,更是在濱河造成了極大的恐慌浸踩,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件统求,死亡現(xiàn)場(chǎng)離奇詭異检碗,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)码邻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門折剃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人像屋,你說(shuō)我怎么就攤上這事怕犁。” “怎么了己莺?”我有些...
    開(kāi)封第一講書人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵奏甫,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我凌受,道長(zhǎng)阵子,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任胜蛉,我火速辦了婚禮挠进,結(jié)果婚禮上色乾,老公的妹妹穿的比我還像新娘。我一直安慰自己领突,他們只是感情好暖璧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著君旦,像睡著了一般澎办。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上金砍,一...
    開(kāi)封第一講書人閱讀 51,488評(píng)論 1 302
  • 那天浮驳,我揣著相機(jī)與錄音,去河邊找鬼捞魁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛离咐,可吹牛的內(nèi)容都是我干的谱俭。 我是一名探鬼主播,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼宵蛀,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼昆著!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起术陶,我...
    開(kāi)封第一講書人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤凑懂,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后梧宫,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體接谨,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年塘匣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了脓豪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡忌卤,死狀恐怖扫夜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情驰徊,我是刑警寧澤笤闯,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站棍厂,受9級(jí)特大地震影響颗味,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜勋桶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一脱衙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧捐韩,春花似錦退唠、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至仅政,卻和暖如春垢油,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背圆丹。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工滩愁, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人辫封。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓硝枉,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親倦微。 傳聞我的和親對(duì)象是個(gè)殘疾皇子妻味,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354

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

  • 你是否曾經(jīng)苦惱于理解你的代碼,而去嘗試打印一個(gè)變量的值欣福? NSLog(@"%@", whatIsInsideThi...
    木易林1閱讀 955評(píng)論 0 4
  • 轉(zhuǎn)載 與調(diào)試器共舞 - LLDB 的華爾茲: https://objccn.io/issue-19-2/ 推薦:i...
    F麥子閱讀 3,332評(píng)論 0 10
  • 你是否曾經(jīng)苦惱于理解你的代碼责球,而去嘗試打印一個(gè)變量的值? NSLog(@"%@", whatIsInsideThi...
    paraneaeee閱讀 1,192評(píng)論 0 7
  • 你是否曾經(jīng)苦惱于理解你的代碼拓劝,而去嘗試打印一個(gè)變量的值雏逾? NSLog(@"%@", whatIsInsideThi...
    我是啊梁閱讀 804評(píng)論 1 1
  • 與調(diào)試器共舞 - LLDB 的華爾茲 nangege 2014/12/19 你是否曾經(jīng)苦惱于理解你的代碼,而去嘗試...
    McDan閱讀 881評(píng)論 0 0