Xcode的使用中總是離不開調(diào)試這個環(huán)境,在一年多的iOS開發(fā)時間中件缸,我更多地依賴于XCode本身提供的GUI工具來進行調(diào)試,而對LLDB敬而遠(yuǎn)之种樱,這段時間好好學(xué)習(xí)了LLDB的使用荆萤,發(fā)覺我錯過了太多東西了……因此做一個比較完備的總結(jié)颠通,也希望在寫這篇文章的過程中進一步學(xué)習(xí)LLDB調(diào)試的各種實踐方法称诗。
LLDB闡述
LLDB 是一個有著 REPL 的特性和 C++ ,Python 插件的開源調(diào)試器蒸痹。LLDB 綁定在 Xcode 內(nèi)部僻爽,存在于主窗口底部的控制臺中虫碉。調(diào)試器允許你在程序運行的特定時暫停它,你可以查看變量的值胸梆,執(zhí)行自定的指令敦捧,并且按照你所認(rèn)為合適的步驟來操作程序的進展。(摘自 與調(diào)試器共舞)
它的基本語法為
<command> [<subcommand> [<subcommand>...]] <action> [-options [option-value]] [argument [argument...]]
在XCode中碰镜,我們需要在程序暫停進入調(diào)試狀態(tài)時才能使用LLDB兢卵,可以通過breakpoint、watchpoint绪颖、或者XCode的調(diào)試臺自帶的暫停按鈕使程序暫停秽荤。
快捷鍵
為了方便查詢,就直接在文章開頭po出常用的快捷鍵菜單把~
快捷鍵功能 | 命令 |
---|---|
暫停/繼續(xù) | cmd + ctrl + Y |
控制臺顯示/隱藏 | cmd + Y |
光標(biāo)切換到控制臺 | cmd + shift + C |
清空控制臺 | cmd + K |
step over | F6 |
step into | F7 |
step out | F8 |
HELP
LLDB的入門與Linux命令入門類似柠横,可以通過執(zhí)行help
命令來查詢這個命令的意義和詳細(xì)參數(shù)
從描述中我們能看出thread backtrace
是用來查詢暫停時的線程堆棧的窃款,并了解了可以帶入的入?yún)ⅰ?/p>
唯一匹配原則
LLDB有個很省事的特性,如果輸入的字母已經(jīng)能匹配到某個命令牍氛,就可以直接執(zhí)行晨继,等于輸入了完整的命令。
可以看到expression
與e
是等價的
變量查詢與修改
-
expression
expression
可簡寫為e
搬俊,作用為執(zhí)行一個表達式紊扬,首當(dāng)其沖曲饱,它肯定可以用來查詢當(dāng)前堆棧變量的值。
當(dāng)然
e
的更主要的用法是通過執(zhí)行表達式珠月,動態(tài)修改當(dāng)前線程堆棧變量的值,從而達到調(diào)試的目的(其實查詢也很主要楔敌,只是會用另一種方式查詢)啤挎。
比如,我們可以在某個if..else..
的語句前打上斷點卵凑,直接修改條件表達式的值庆聘,使程序覆蓋了不同分支,而不用苦心積慮地停止程序勺卢、hard code變量值來進行調(diào)試伙判,節(jié)省了一大坨修改與編譯時間。
在上面這份測試代碼黑忱,在進入條件判斷語句前打了斷點宴抚,那我們可以通過e
命令,來自由控制程序走向任何一個分支甫煞。
我們也可以通過執(zhí)行表達式菇曲,實時改變當(dāng)前的UI界面,方便界面代碼的調(diào)試抚吠,比如我們可以執(zhí)行下面代碼來改變當(dāng)前UI常潮,讓cellItem的邊框顯示出來,以判斷我們的界面布局是否正確楷力。
e @import UIKit
e cellItem.layer.borderWidth = 1
這里有個特殊的問題喊式,由于程序已經(jīng)被斷點暫停了,因此執(zhí)行UI更新的線程也被暫停了萧朝。我們可以通過讓程序繼續(xù)運行岔留,也可以通過另一條表達式來更新UI。
e (void)[CATransaction flush]
> 我們也可以用 `call` 來代替 `expression --`剪勿,其實我覺得用`e`更方便贸诚。 =。=
-
p
厕吉、po
在上面說過酱固,在調(diào)試中,我們一般用
e
命令來修改變量头朱,而查詢變量一般用p
與po
命令运悲。
po
的作用為打印對象,事實上项钮,我們可以通過help po
得知班眯,po
是expression -O --
的簡寫希停,我們可以通過它打印出對象,而不是打印對象的指針署隘。而值得一提的是宠能,在help expression
返回的幫助信息中,我們可以知道磁餐,po
命令會嘗試調(diào)用對象的description
方法來取得對象信息违崇,因此我們也可以重載某個對象的description
方法,使我們調(diào)試的時候能獲得可讀性更強诊霹,更全面的信息羞延。-(NSString*)description { return [NSString stringWithFormat:@"Portal[%@, %@, %@, %@, %@, %@, %@]", ssid, mpUrl, ticket, authUrl, _openid, _tid, extend]; }
p
即是print
,也是expression --
的縮寫脾还,與po
不同伴箩,它不會打出對象的詳細(xì)信息,只會打印出一個$符號鄙漏,數(shù)字嗤谚,再加上一段地址信息。由于po
命令下怔蚌,對象的description
有可能被隨便亂改呵恢,沒有輸出地址消息。
$符號在LLDB中代表著變量的分配媚创。每次使用p后渗钉,會自動為你分配一個變量,后面再次想使用這個變量時钞钙,就可以直接使用鳄橘。我們可以直接使用這個地址做一些轉(zhuǎn)換,獲取對象的信息
斷點
-
breakpoint
所有調(diào)試都是由斷點開始的芒炼,我們接觸的最多瘫怜,就是以breakpoint
命令為基礎(chǔ)的斷點。
一般我們對breakpoint
命令使用得不多本刽,而是在XCode的GUI界面中直接添加斷點鲸湃。除了直接觸發(fā)程序暫停供調(diào)試外,我們可以進行進一步的配置子寓。
- 添加condition暗挑,一般用于多次調(diào)用的函數(shù)或者循壞的代碼中,在作用域內(nèi)達到某個條件斜友,才會觸發(fā)程序暫停
- 忽略次數(shù)炸裆,這個很容易理解,在忽略觸發(fā)幾次后再觸發(fā)暫停
- 添加Action鲜屏,為這個斷點添加子命令烹看、腳本国拇、shell命令、聲效(有個毛線用)等Action惯殊,我的理解是一個腳本化的功能酱吝,我們可以在斷點的基礎(chǔ)上添加一些方便調(diào)試的腳本,提高調(diào)試效率土思。
- 自動繼續(xù)掉瞳,配合上面的添加Action,我們就可以不用一次又一次的暫停程序進行調(diào)試來查詢某些值(大型程序中斷一次還是會有卡頓)浪漠,直接用Action將需要的信息打印在控制臺,一次性查看即可霎褐。
除去在代碼中直接點擊添加斷點外址愿,我們也可以在 command + 7
breakpoint頁面下直接添加相關(guān)的斷點。我們常用的有 Exception Breakpoint 與 Symbolic Breakpoint
- Add Exception Breakpoint
Exception Breakpoint為異常斷點冻璃。在某些情況下响谓,TableView的數(shù)據(jù)源與UI操作不一致,或者容器插入了nil的指針省艳,將消息傳至野指針娘纷,都會導(dǎo)致程序的crash,并且LLDB輸出的信息不是很友好跋炕。加上異常斷點赖晶,能夠使程序在拋出異常的棧自動暫停,可直接定位導(dǎo)致拋出異常的代碼辐烂。在一般的開發(fā)流程中遏插,都建議開啟這個異常斷點,反正你總是會crash的嘿嘿纠修。 - Add Symbolic Breakpoint
Symbolic Breakpoint 為符號斷點胳嘲。有時候,我們并不清楚程序會在什么情況下調(diào)用某一個函數(shù)扣草,那我們可以通過符號斷點來獲取調(diào)用該函數(shù)時的程序堆棧了牛。當(dāng)然,在自己實現(xiàn)的類辰妙,我們也可以在該函數(shù)實現(xiàn)的地方打上斷點鹰祸,但如果需要定位其他框架提供的API的調(diào)用,就只能使用符號斷點啦密浑。
當(dāng)然福荸,LLDB的breakpoint
命令也可以實現(xiàn)上述的功能,因為不常用肴掷,所以這里就簡單列舉一些用法敬锐。 breakpoint set -n trigger //在所有類的trigger函數(shù)實現(xiàn)中打上斷點
breakpoint set -f ViewController.m -n trigger //在ViewController.m中的trigger方法打上斷點
breakpoint set -f ViewController.m -l 50 //在ViewController.m的50行打上斷點
breakpoint set -f ViewController.m -n trigger: -c testCondition > 5 //在ViewController.m中的trigger方法打上斷點并添加condition背传, testCondition大于5時觸發(fā)斷點
breakpoint set -n trigger -o //單次斷點
breakpoint command add -o "frame info" 3 //在設(shè)置的三號斷點加入子命令frame info
breakpoint list // 列出所有斷點
breakpoint delete 3 //刪除3號斷點
watchpoint
有時候我們會關(guān)心類的某個屬性什么時候被人修改了,最簡單的方法當(dāng)然就是在setter的方法打斷點台夺,或者在@property
的屬性生命行打上斷點径玖。這樣當(dāng)對象的setter方法被調(diào)用時,就會觸發(fā)這個斷點颤介。
當(dāng)然這么做是有缺點的梳星,對于直接訪問內(nèi)存地址的修改,setter方法的斷點并沒有辦法監(jiān)控得到滚朵,因此我們需要用到
watchpoint
命令冤灾。watchpoint
命令在XCode的GUI中也可以直接使用,當(dāng)程序暫停時辕近,我們能對當(dāng)前程序棧中的變量設(shè)置watchpoint韵吨。值得注意的是,watchpoint是直接設(shè)置到該變量所在的內(nèi)存地址上的移宅,所以當(dāng)這個變量釋放了后归粉,watchpoint仍然是對這個地址的內(nèi)存生效的。我們也可以在LLDB中直接用
watchpoint
命令漏峰,可以通過選項實現(xiàn)更多效果糠悼。
watchpoint set self->testVar //為該變量地址設(shè)置watchpoint
watchpoint set expression 0x00007fb27b4969e0 //為該內(nèi)存地址設(shè)置watchpoint,內(nèi)存地址可從前文提及的`p`命令獲取
watchpoint command add -o 'frame info' 1 //為watchpoint 1號加上子命令 `frame info`
watchpoint list //列出所有watchpoint
watchpoint delete // 刪除所有watchpoint
堆棧
-
thread
和bt
bt
即是thread backtrace
浅乔,作用是打印出當(dāng)前線程的堆棧信息倔喂。當(dāng)程序發(fā)生了crash后,我們可以用該命令打印出發(fā)生crash的當(dāng)前的程序堆棧靖苇,查詢出發(fā)生crash的調(diào)用路徑滴劲。由于比較常用,所以LLDB直接給它一個特殊的bt
別名顾复。
thread
另一個比較常用的用法是 thread return
班挖,調(diào)試的時候,我們希望在當(dāng)前執(zhí)行的程序堆棧直接返回一個自己想要的值芯砸,可以執(zhí)行該命令直接返回萧芙。
thread return <expr>
在這個斷點中,我們可以執(zhí)行 thread return NO
讓該函數(shù)調(diào)用直接返回NO
假丧,在調(diào)試中輕松覆蓋任何函數(shù)的返回路徑双揪。
- frame
frame
即是幀,其實就是當(dāng)前的程序堆棧包帚,我們輸入bt
命令渔期,打印出來的其實是當(dāng)前線程的frame。
在調(diào)試中,一般我們比較關(guān)心當(dāng)前堆棧的變量值疯趟,我們可以使用frame variable
來獲取全部變量值拘哨。當(dāng)然也可以輸入特定變量名,來獲取單獨的變量值信峻,如frame v self-> testVar
來獲取testVar
的值倦青。
End
全文暫時完~在接下來的實踐與學(xué)習(xí)中,如果有好玩的新東西盹舞,也會補充到這里來产镐。