之前使用Xcode調(diào)試在LLDB中只用了打印、看日志輸入、再復(fù)雜點(diǎn)就是po一個(gè)屬性的值,前段時(shí)間看了篇關(guān)于LLDB的文章,感覺之前完全白白浪費(fèi)了Xcode提供這么好的調(diào)試工具。這段時(shí)間學(xué)習(xí)實(shí)驗(yàn),此文供記錄學(xué)習(xí)。
LLDB 概述
LLDB全稱 " Low Level Debugger ", 是由蘋果出品。標(biāo)準(zhǔn)的 LLDB 提供了一組廣泛的命令,旨在與熟悉的 GDB 命令兼容。 除了使用標(biāo)準(zhǔn)配置外,還可以很容易地自定義 LLDB 以滿足實(shí)際需要,帶給我們更豐富的流程控制和數(shù)據(jù)檢測(cè)的調(diào)試功能。
LLDB 作用
- 允許你在程序運(yùn)行的特定時(shí)暫停它;
- 查看變量的值;
- 執(zhí)行自定的指令;
- 按照你所認(rèn)為合適的步驟來操作程序的進(jìn)展;
LLDB控制臺(tái)
Xcode中內(nèi)嵌了LLDB控制臺(tái),在Xcode中代碼的下方,我們可以看到LLDB控制臺(tái)??旖萱I是command + shift + y。LLDB控制臺(tái)平時(shí)會(huì)輸出一些log信息。如果我們想輸入命令調(diào)試,必須讓程序進(jìn)入暫停狀態(tài)。

LLDB 語法
<command> [<subcommand> [<subcommand>...]] <action> [-options [option-value]] [argument [argument...]]
<command>(命令)和<subcommand>(子命令):LLDB調(diào)試命令的名稱。命令和子命令按層級(jí)結(jié)構(gòu)來排列:一個(gè)命令對(duì)象為跟隨其的子命令對(duì)象創(chuàng)建一個(gè)上下文,子命令又為其子命令創(chuàng)建一個(gè)上下文,依此類推。
<action>:執(zhí)行命令的操作
<options>:命令選項(xiàng)
<arguement>:命令的參數(shù)
[]:表示命令是可選的,可以有也可以沒有
舉個(gè)例子,假設(shè)我們給main.m中16行設(shè)置一個(gè)斷點(diǎn),我們使用下面的命令:
breakpoint set -f main.m -l 16
與上面語法結(jié)構(gòu)對(duì)應(yīng)的是:
command: breakpoint 添加斷點(diǎn)命令
action: set 表示設(shè)置斷點(diǎn)
option: -f 表示在某文件添加斷點(diǎn)
arguement: mian.m 表示要添加斷點(diǎn)的文件名為mian.m
option: -l 表示某一行
arguement: 16 表示第16行
上面的print命令只是LLDB調(diào)試中的一個(gè)很簡單但很常用的命令,除此之外還有很多有可能用到的命令:
apropos -- 列出與單詞或主題相關(guān)的調(diào)試器命令
breakpoint -- 在斷點(diǎn)上操作的命令 (詳情使用'help b'查看)
bugreport -- 用于創(chuàng)建指定域的錯(cuò)誤報(bào)告
command -- 用于管理自定義LLDB命令的命令
disassemble -- 拆分當(dāng)前目標(biāo)中的特定說明。 默認(rèn)為當(dāng)前線程和堆棧幀的當(dāng)前函數(shù)
expression -- 求當(dāng)前線程上的表達(dá)式的值。 以LLDB默認(rèn)格式顯示返回的值
frame -- 用于選擇和檢查當(dāng)前線程的堆棧幀的命令
gdb-remote -- 通過遠(yuǎn)程GDB服務(wù)器連接到進(jìn)程。 如果未指定主機(jī),則假定為localhost
gui -- 切換到基于curses的GUI模式
help -- 顯示所有調(diào)試器命令的列表,或提供指定命令的詳細(xì)信息
kdp-remote -- 通過遠(yuǎn)程KDP服務(wù)器連接到進(jìn)程。 如果沒有指定UDP端口,則假定端口41139
language -- 指定源語言
log -- 控制LLDB內(nèi)部日志記錄的命令
memory -- 用于在當(dāng)前目標(biāo)進(jìn)程的內(nèi)存上操作的命令
platform -- 用于管理和創(chuàng)建平臺(tái)的命令
plugin -- 用于管理LLDB插件的命令
process -- 用于與當(dāng)前平臺(tái)上的進(jìn)程交互的命令
quit -- 退出LLDB調(diào)試器
register -- 命令訪問當(dāng)前線程和堆棧幀的寄存器
script -- 使用提供的代碼調(diào)用腳本解釋器并顯示任何結(jié)果。 如果沒有提供代碼,啟動(dòng)交互式解釋器。
settings -- 用于管理LLDB設(shè)置的命令
source -- 檢查當(dāng)前目標(biāo)進(jìn)程的調(diào)試信息所描述的源代碼的命令
target -- 用于在調(diào)試器目標(biāo)上操作的命令
thread -- 用于在當(dāng)前進(jìn)程中的一個(gè)或多個(gè)線程上操作的命令
type -- 在類型系統(tǒng)上操作的命令
version -- 顯示LLDB調(diào)試器版本
watchpoint -- 在觀察點(diǎn)上操作的命令
縮寫命令 (使用 'help command alias'查看更多信息):
add-dsym -- ('target symbols add') 通過指定調(diào)試符號(hào)文件的路徑,或使用選項(xiàng)指定下載符號(hào)的模塊,將調(diào)試符號(hào)文件添加到目標(biāo)的當(dāng)前模塊中的一個(gè)
attach -- ('_regexp-attach') 通過ID或名稱附加到進(jìn)程
b -- ('_regexp-break') 使用幾種簡寫格式之一設(shè)置斷點(diǎn)
bt -- ('_regexp-bt') 顯示當(dāng)前線程的調(diào)用堆棧。通過數(shù)字參數(shù)設(shè)置最多顯示幀數(shù)。參數(shù)“all”顯示所有線程
c -- ('process continue') 繼續(xù)執(zhí)行當(dāng)前進(jìn)程中的所有線程
call -- ('expression --') 計(jì)算當(dāng)前線程上的表達(dá)式,使用LLDB的默認(rèn)格式顯示返回的值
continue -- ('process continue') 繼續(xù)執(zhí)行當(dāng)前進(jìn)程中的所有線程
detach -- ('process detach') 脫離當(dāng)前目標(biāo)進(jìn)程
di -- ('disassemble') 拆分當(dāng)前目標(biāo)中的特定說明。 默認(rèn)為當(dāng)前線程和堆棧幀的當(dāng)前函數(shù)
dis -- ('disassemble') 同上
display -- ('_regexp-display') 在每次停止時(shí)計(jì)算表達(dá)式(請(qǐng)參閱'help target stop-hook')
down -- ('_regexp-down') 選擇一個(gè)新的堆棧幀。默認(rèn)為移動(dòng)一個(gè)幀,數(shù)字參數(shù)可以指定值
env -- ('_regexp-env') 查看和設(shè)置環(huán)境變量的簡寫
exit -- ('quit') 退出LLDB調(diào)試器
f -- ('frame select') 從當(dāng)前線程中通過索引選擇當(dāng)前堆棧幀(參見'thread backtrace')
file -- ('target create') 使用參數(shù)作為主要可執(zhí)行文件創(chuàng)建目標(biāo)
finish -- ('thread step-out') 完成當(dāng)前堆棧幀的執(zhí)行并返回后停止。 默認(rèn)為當(dāng)前線程
image -- ('target modules') 用于訪問一個(gè)或多個(gè)目標(biāo)模塊的信息的命令
j -- ('_regexp-jump') 將程序計(jì)數(shù)器設(shè)置為新地址
jump -- ('_regexp-jump') 同上
kill -- ('process kill') 終止當(dāng)前目標(biāo)進(jìn)程
l -- ('_regexp-list') 使用幾種簡寫格式之一列出相關(guān)的源代碼
list -- ('_regexp-list') 同上
n -- ('thread step-over') 源級(jí)單步執(zhí)行、步進(jìn)調(diào)用,默認(rèn)當(dāng)前線程
next -- ('thread step-over') 同上
nexti -- ('thread step-inst-over') 指令級(jí)單步執(zhí)行、步進(jìn)調(diào)用,默認(rèn)當(dāng)前線程
ni -- ('thread step-inst-over') 同上
p -- ('expression --') 計(jì)算當(dāng)前線程上表達(dá)式的值,以LLDB默認(rèn)格式顯示返回值
parray -- ('expression -Z %1 --') 同上
po -- 計(jì)算當(dāng)前線程上的表達(dá)式。顯示由類型作者控制的格式的返回值。
poarray -- ('expression -O -Z %1 --') 計(jì)算當(dāng)前線程上表達(dá)式的值,以LLDB默認(rèn)格式顯示返回值
print -- ('expression --') 同上
q -- ('quit') 退出LLDB調(diào)試器
r -- ('process launch -X true --') 在調(diào)試器中啟動(dòng)可執(zhí)行文件
rbreak -- ('breakpoint set -r %1') 在可執(zhí)行文件中設(shè)置斷點(diǎn)或斷點(diǎn)集
repl -- ('expression -r -- ') E計(jì)算當(dāng)前線程上表達(dá)式的值,以LLDB默認(rèn)格式顯示返回值
run -- ('process launch -X true --') 在調(diào)試器中啟動(dòng)可執(zhí)行文件
s -- ('thread step-in') 源級(jí)單步執(zhí)行、步進(jìn)調(diào)用,默認(rèn)當(dāng)前線程
si -- ('thread step-inst') 指令級(jí)單步執(zhí)行、步進(jìn)調(diào)用,默認(rèn)當(dāng)前線程
sif -- 遍歷當(dāng)前塊,如果直接步入名稱與TargetFunctionName匹配的函數(shù),則停止
step -- ('thread step-in') 源級(jí)單步執(zhí)行、步進(jìn)調(diào)用,默認(rèn)當(dāng)前線程
stepi -- ('thread step-inst') 指令級(jí)單步執(zhí)行、步進(jìn)調(diào)用,默認(rèn)當(dāng)前線程
t -- ('thread select') 更改當(dāng)前選擇的線程
tbreak -- ('_regexp-tbreak') 使用幾種簡寫格式之一設(shè)置單次斷點(diǎn)
undisplay -- ('_regexp-undisplay') 每次停止時(shí)停止顯示表達(dá)式(由stop-hook索引指定)
up -- ('_regexp-up') 選擇較早的堆棧幀。 默認(rèn)為移動(dòng)一個(gè)幀,數(shù)值參數(shù)可以指定任意數(shù)字
x -- ('memory read') 從當(dāng)前目標(biāo)進(jìn)程的內(nèi)存中讀取
上面的命令不需要都記住,記住常用的幾個(gè)如: p, po, call, breakpoint, call, expression 等 ,其他需要的時(shí)候再查,通過“help”命令顯示所有調(diào)試命令的列表,或查詢指定命令的詳細(xì)信息。
LLDB 常用命令
LLDB擁有大量有用的調(diào)試工具。
獲取變量值:expression, e, print, po, p
獲取執(zhí)行環(huán)境+特定語言命令:bugreport, frame, language
執(zhí)行流程控制:process, breakpoint, thread, watchpoint
其他:command,platform,gui
下面列一些常用的命令:
expression
expression命令的作用是執(zhí)行一個(gè)表達(dá)式,并將表達(dá)式返回的結(jié)果輸出。
expression命令的格式
expression <cmd-options> -- <expr>
<cmd-options>:命令選項(xiàng),一般情況下使用默認(rèn)的即可,不需要特別標(biāo)明。
--: 命令選項(xiàng)結(jié)束符,表示所有的命令選項(xiàng)已經(jīng)設(shè)置完畢,如果沒有命令選項(xiàng),--可以省略
<expr>: 要執(zhí)行的表達(dá)式
<font color=gray size=4>例如</font>
expression -O -- testStr
-O 是命令選項(xiàng)
testStr 是執(zhí)行的表達(dá)式
<font color=gray size=4>再例如</font>
expression testStr 和 expression -- testStr
兩個(gè)命令功能是一致的,沒有命令選項(xiàng)時(shí)可以省略--
expression最基本的功能是打印和修改變量的值,你可以在運(yùn)行時(shí)執(zhí)行幾乎任何表達(dá)式或命令。
// 改變顏色
(lldb) expression self.view.backgroundColor = UIColor.brown //改變顏色
(lldb) expression CATransaction.flush() //刷新
(lldb)
// 改變屬性值
(lldb) expression testStr // 打印testStr
(String) $R0 = "123"
(lldb) expression testStr = "abc" //修改testStr的值
(lldb) expression testStr
(String) $R2 = "abc" //testStr值調(diào)試期間變成abc
expression 擁有大約30個(gè)命令選項(xiàng)
下面列出了幾個(gè)比較常用的選項(xiàng):
-D ( --depth ) - 設(shè)置打印聚合類型的遞歸深度(默認(rèn)無限遞歸)。
-O ( --object-desctiption ) - 打印description方法。
-T ( --show-types ) - 顯示每個(gè)變量的類型。
-f ( --format ) - 設(shè)置輸出格式。
-i ( --ignore-breakpoints ) - 運(yùn)行表達(dá)式時(shí)忽略表達(dá)式內(nèi)的斷點(diǎn)。
p、 print、 e、 call
實(shí)際上call、 p、print這三個(gè)指令都是 expression 指令的別名, 實(shí)際上的運(yùn)行效果是一樣的,舉例說明,請(qǐng)看一下代碼:
(lldb) call testStr
(String) $R8 = "abc"
(lldb) p testStr
(String) $R9 = "abc"
(lldb) print testStr
(String) $R10 = "abc"
(lldb) e testStr
(String) $R11 = "abc"
po
po self 命令與 expression -O -- self功能類似。
breakpoint
開發(fā)調(diào)試的第一步就是設(shè)置斷點(diǎn),我們用的最多的就是breakpoint命令,但我們很少在LLDB 中使用breakpoint命令,大多在Xcode GUI界面中設(shè)置斷點(diǎn)。除了直接直接出發(fā)暫停調(diào)試外,我們還可以進(jìn)一步的設(shè)置。

- 添加condition,一般用于多次調(diào)用的函數(shù)或者循壞的代碼中,在作用域內(nèi)達(dá)到某個(gè)條件,才會(huì)觸發(fā)程序暫停
- 忽略次數(shù),這個(gè)很容易理解,在忽略觸發(fā)幾次后再觸發(fā)暫停
- 添加Action,為這個(gè)斷點(diǎn)添加子命令、腳本、shell命令、聲效(有個(gè)毛線用)等Action,我的理解是一個(gè)腳本化的功能,我們可以在斷點(diǎn)的基礎(chǔ)上添加一些方便調(diào)試的腳本,提高調(diào)試效率。
- 自動(dòng)繼續(xù),配合上面的添加Action,我們就可以不用一次又一次的暫停程序進(jìn)行調(diào)試來查詢某些值(大型程序中斷一次還是會(huì)有卡頓),直接用Action將需要的信息打印在控制臺(tái),一次性查看即可。
除去在代碼中直接點(diǎn)擊添加斷點(diǎn)外,我們也可以在 BreakPoint Navigation頁面下直接添加相關(guān)的斷點(diǎn)。我們常用的有 Exception Breakpoint 與 Symbolic Breakpoint


- Add Exception Breakpoint
Exception Breakpoint為異常斷點(diǎn)。在某些情況下,TableView的數(shù)據(jù)源與UI操作不一致,或者容器插入了nil的指針,將消息傳至野指針,都會(huì)導(dǎo)致程序的crash,并且LLDB輸出的信息不是很友好。加上異常斷點(diǎn),能夠使程序在拋出異常的棧自動(dòng)暫停,可直接定位導(dǎo)致拋出異常的代碼。在一般的開發(fā)流程中,都建議開啟這個(gè)異常斷點(diǎn),反正你總是會(huì)crash的嘿嘿。 - Add Symbolic Breakpoint
Symbolic Breakpoint 為符號(hào)斷點(diǎn)。有時(shí)候,我們并不清楚程序會(huì)在什么情況下調(diào)用某一個(gè)函數(shù),那我們可以通過符號(hào)斷點(diǎn)來獲取調(diào)用該函數(shù)時(shí)的程序堆棧。當(dāng)然,在自己實(shí)現(xiàn)的類,我們也可以在該函數(shù)實(shí)現(xiàn)的地方打上斷點(diǎn),但如果需要定位其他框架提供的API的調(diào)用,就只能使用符號(hào)斷點(diǎn)啦。
當(dāng)然,LLDB的breakpoint命令也可以實(shí)現(xiàn)上述的功能,因?yàn)椴怀S?,所以這里就簡單列舉一些用法。 breakpoint set -n trigger //在所有類的trigger函數(shù)實(shí)現(xiàn)中打上斷點(diǎn)
breakpoint set -f ViewController.m -n trigger //在ViewController.m中的trigger方法打上斷點(diǎn)
breakpoint set -f ViewController.m -l 50 //在ViewController.m的50行打上斷點(diǎn)
breakpoint set -f ViewController.m -n trigger: -c testCondition > 5 //在ViewController.m中的trigger方法打上斷點(diǎn)并添加condition, testCondition大于5時(shí)觸發(fā)斷點(diǎn)
breakpoint set -n trigger -o //單次斷點(diǎn)
breakpoint command add -o "frame info" 3 //在設(shè)置的三號(hào)斷點(diǎn)加入子命令frame info
breakpoint list // 列出所有斷點(diǎn)
breakpoint delete 3 //刪除3號(hào)斷點(diǎn)
watchpoint
通過watchpoint 來查看某個(gè)屬性是否有變化,watchpoint是對(duì)地址生效的斷點(diǎn),用watchpoint觀察屬性的地址變化情況。
(lldb) watchpoint set variable self.testArr //觀察一個(gè)名為testArr的屬性值
Watchpoint created: Watchpoint 1: addr = 0x7f81b4607478 size = 8 state = enabled type = w
declare @ '/LLDBDemo/ViewController.swift:16'
watchpoint spec = 'self.testArr'
new value: 1 value

按鈕點(diǎn)擊方法中修改了testArr的值,
(lldb) watchpoint set variable self.testArr //觀察一個(gè)名為testArr的屬性值
Watchpoint created: Watchpoint 1: addr = 0x7f81b4607478 size = 8 state = enabled type = w
declare @ '/LLDBDemo/ViewController.swift:16'
watchpoint spec = 'self.testArr'
new value: 1 value
Watchpoint 1 hit: //檢測(cè)到屬性變化
old value: 1 value
new value: 2 values
thread
可以使用 thread backtrace(或 bt )命令打印線程堆棧信息:


thread backtrace 后面可以添加命令選項(xiàng):
-c:設(shè)置打印堆棧的幀數(shù)(frame)
-s:設(shè)置從哪個(gè)幀(frame)開始打印
-e:是否顯示額外的回溯
Debug 的時(shí)候,也許會(huì)因?yàn)楦鞣N原因,我們不想讓代碼執(zhí)行某個(gè)方法,或者要直接返回一個(gè)想要的值??梢允褂?thread return 命令:
thread return '要返回的值'
thread return 不讓代碼執(zhí)行某個(gè)方法,可以在某個(gè)方法的開始位置設(shè)置一個(gè)斷點(diǎn),當(dāng)程序運(yùn)行到斷點(diǎn)的位置時(shí)直接返回我們?cè)O(shè)置的返回值。
但我實(shí)驗(yàn)了,總是提示錯(cuò)誤:
error: Error returning from frame 0 of thread 1: We only support setting simple integer and float return types at present..
有了解的同學(xué)幫忙指導(dǎo)下。
參考鏈接:
使用 LLDB 調(diào)試 APP
iOS調(diào)試-LLDB學(xué)習(xí)總結(jié)