序:原文 Dancing in the Debugger — A Waltz with LLDB
聲明:譯文有一部分參考自:與調試器共舞 - LLDB 的華爾茲
續...(接上一篇)
流程控制
打斷點的方法(此處不翻譯)
調試工具條:

左—>右,依次為:
continue,step over,step into,step out
。第一個,
continue
按鈕,會取消程序的暫停,允許程序正常執行 (要么一直執行下去,要么到達下一個斷點)。在 LLDB 中,你可以使用process continue
命令來達到同樣的效果,它的別名為continue
,或者也可以縮寫為c
。第二個,
step over
按鈕,會以黑盒的方式執行一行代碼。如果所在這行代碼是一個函數調用,那么就不會
跳進這個函數,而是會執行這個函數,然后繼續。LLDB 則可以使用thread step-over
,next
,或者n
命令。如果你確實想跳進一個函數調用來調試或者檢查程序的執行情況,那就用第三個按鈕,
step into
,或者在LLDB中使用thread step in
,step
,或者s
命令。注意,當前行不是函數調用時,next
和step
效果是一樣的。大多數人知道c
,n
和s
,但是其實還有第四個按鈕,step out
。如果你曾經不小心跳進一個函數,但實際上你想跳過它,常見的反應是重復的運行n
直到函數返回。其實這種情況,step out
按鈕是你的救世主。它會繼續執行到下一個返回語句 (直到一個堆棧幀結束) 然后再次停止。看個例子:

運行程序,讓他停止在斷點,然后執行下面的系列命令:
p i
n
s
p i
finish
p i
frame info
這里,frame info
會告訴你當前的行數和源碼文件,還有其他一些信息;查看 help frame
,help thread
和 help process
來獲得更多信息。這一串命令的結果會是什么?看答案之前請先想一想。
(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()
函數的return
,然后立刻停止。注意即使它還在 17
行,其實這行已經被執行過了。
Thread Return
調試時,還有一個很棒的函數可以用來控制程序流程:thread return
。它有一個可選參數,在執行時它會把可選參數加載進返回寄存器里,然后立刻執行返回命令,跳出當前棧幀。這意味這函數剩余的部分不會被執行
。這會給 ARC 的引用計數造成一些問題,或者會使函數內的清理部分失效。但是在函數的開頭執行這個命令,是個非常好的隔離這個函數,偽造返回值的方式 。
讓我們稍微修改一下上面代碼段并運行:
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
斷點
我們都把斷點作為一個停止程序運行,檢查當前狀態,追蹤 bug 的方式。但是如果我們改變和斷點交互的方式,很多事情都變成可能。
斷點允許控制程序什么時候停止,然后允許命令的運行。
想象把斷點放在函數的開頭,然后用 thread return
命令重寫函數的行為,然后繼續。想象一下讓這個過程自動化,聽起來不錯,不是嗎?
斷點管理
此段無用不翻譯
打開 Xcode 左側面板,右邊第二個是可以快速管理所有斷點的面板。如圖:

在這里你可以看到所有的斷點 - 在 LLDB 中可以通過 breakpoint list
(或者 br li
) 命令也做同樣的事兒。你也可以點擊單個斷點來開啟或關閉 - 在 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.
創建斷點
此段無用不翻譯
要在調試器中創建斷點,可以使用 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
的縮寫),但恰好也可以實現和上面同樣的效果。
(lldb) b main.m:17
Breakpoint 2: where = DebuggerDance`main + 52 at main.m:17, address = 0x000000010a3f6cc4
也可以在一個符號 (C 語言函數) 上創建斷點,而完全不用指定哪一行 :
(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
這些斷點會準確的停止在函數的開始。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 創建符號斷點,方法如圖:

這時會出現一個彈出框,你可以在里面添加例如
-[NSArray objectAtIndex:]
這樣的符號斷點。這樣
每次
調用這個函數的時候,程序都會停止,不管是你調用還是蘋果調用。
如果你 Xcode 的 UI 上右擊任意
斷點,然后選擇 "Edit Breakpoint"
的話,會有一些非常誘人的選擇。

這里,斷點已經被修改為只有當
i是99
的時候才會停止。你也可以使用"ignore"
選項來告訴斷點最初的n
次調用 (并且條件為真的時候) 的時候不要停止。
接下來介紹 'Add Action' 按鈕...
斷點行為 (Action)
上面的例子中,你或許想知道每一次到達斷點的時候 i 的值。我們可以使用 p i 作為斷點行為。這樣每次到達斷點的時候,都會自動運行這個命令。

也可以添加多個行為,可以是調試器命令,shell 命令,也可以是更直接的打印:

可以看到它打印
i
,然后大聲念出那個句子,接著打印了自定義的表達式。
下面是在 LLDB 做這些的時候:
(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
接下來說說自動化。
賦值后繼續運行
看編輯斷點彈出窗口的底部,你還會看到一個選項:*"Automatically continue after evaluation actions."*
。它僅僅是一個選擇框,但是卻很強大。選中它,調試器會運行你所有的命令,然后繼續運行。看起來就像沒有執行任何斷點一樣 (除非斷點太多,運行需要一段時間,拖慢了你的程序)。
這個選項框的效果和讓最后斷點的最后一個行為是continue
一樣。選框只是讓這個操作變得更簡單。調試器的輸出是:
(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
執行斷點后自動繼續運行,允許你完全通過斷點來修改程序!你可以在某一行停止,運行一個 expression
命令來改變變量,然后繼續運行。
#######示例
想想所謂的"打印調試"技術吧,不要這么做:
NSLog(@"%@", whatIsInsideThisThing);
而是用個打印變量的斷點替換 log 語句,然后繼續運行。
也不要:
int calculateTheTrickyValue {
return 9;
/*
Figure this out later.
...
}
而是加一個使用 thread return 9
命令的斷點,然后讓它繼續運行。
符號斷點加上 action 真的很強大。你也可以在你朋友的 Xcode 工程上添加一些斷點,并且加上大聲朗讀某些東西的 action。看看他們要花多久才能弄明白發生了什么。裝逼必備神器??
完全在調試器內運行
此部分感覺無用不翻譯
未完待續...
移步 和 LLDB 調試器來一場說跳就跳的華爾茲(三)
喔
接下來才是 LLDB 在項目中的實際使用場景,不容錯過喔