IOS LLDB斷點除錯
原文地址:http://www.jianshu.com/p/d6a0a5e39b0e
LLDB闡述
LLDB 是一個有著 REPL 的特性和 C++ ,Python 外掛的開源偵錯程式。LLDB 繫結在 Xcode 內部,存在於主視窗底部的控制檯中。偵錯程式允許你在程式執行的特定時暫停它,你可以檢視變數的值,執行自定的指令,並且按照你所認為合適的步驟來操作程式的進展。(摘自 與偵錯程式共舞)
它的基本語法為
<command> [<subcommand> [<subcommand>...]] <action> [-options [option-value]] [argument [argument...]]
在XCode中,我們需要在程式暫停進入除錯狀態時才能使用LLDB,可以通過breakpoint、watchpoint、或者XCode的除錯臺自帶的暫停按鈕使程式暫停。
快捷鍵
為了方便查詢,就直接在文章開頭po出常用的快捷鍵選單把~
快捷鍵功能 | 命令 |
---|---|
暫停/繼續 | cmd + ctrl + Y |
控制檯顯示/隱藏 | cmd + Y |
游標切換到控制檯 | cmd + shift + C |
清空控制檯 | cmd + K |
step over | F6 |
step into | F7 |
step out | F8 |
HELP
LLDB的入門與Linux命令入門類似,可以通過執行help
命令來查詢這個命令的意義和詳細引數
從描述中我們能看出thread backtrace
是用來查詢暫停時的執行緒堆疊的,並瞭解了可以帶入的入參。
唯一匹配原則
LLDB有個很省事的特性,如果輸入的字母已經能匹配到某個命令,就可以直接執行,等於輸入了完整的命令。
可以看到expression
與e
是等價的
變數查詢與修改
expression
expression
可簡寫為e
,作用為執行一個表示式,首當其衝,它肯定可以用來查詢當前堆疊變數的值。當然
e
的更主要的用法是通過執行表示式,動態修改當前執行緒堆疊變數的值,從而達到除錯的目的(其實查詢也很主要,只是會用另一種方式查詢)。 比如,我們可以在某個if..else..
的語句前打上斷點,直接修改條件表示式的值,使程式覆蓋了不同分支,而不用苦心積慮地停止程式、hard code變數值來進行除錯,節省了一大坨修改與編譯時間。在上面這份測試程式碼,在進入條件判斷語句前打了斷點,那我們可以通過
e
命令,來自由控制程式走向任何一個分支。我們也可以通過執行表示式,實時改變當前的UI介面,方便介面程式碼的除錯,比如我們可以執行下面程式碼來改變當前UI,讓cellItem的邊框顯示出來,以判斷我們的介面佈局是否正確。
- e @import UIKit
- e cellItem.layer.borderWidth = 1
這裡有個特殊的問題,由於程式已經被斷點暫停了,因此執行UI更新的執行緒也被暫停了。我們可以通過讓程式繼續執行,也可以通過另一條表示式來更新UI。
e (void)[CATransaction flush]
我們也可以用
call
來代替expression --
,其實我覺得用e
更方便。 =。=p
、po
在上面說過,在除錯中,我們一般用
e
命令來修改變數,而查詢變數一般用p
與po
命令。po
的作用為列印物件,事實上,我們可以通過help po
得知,po
是expression -O --
的簡寫,我們可以通過它打印出物件,而不是列印物件的指標。而值得一提的是,在help expression
返回的幫助資訊中,我們可以知道,po
命令會嘗試呼叫物件的description
方法來取得物件資訊,因此我們也可以過載某個物件的description
方法,使我們除錯的時候能獲得可讀性更強,更全面的資訊。- -(NSString*)description
- {
- return [NSString stringWithFormat:@"Portal[%@, %@, %@, %@, %@, %@, %@]", ssid, mpUrl, ticket, authUrl, _openid, _tid, extend];
- }
p
即是print
,也是expression --
的縮寫,與po
不同,它不會打出物件的詳細資訊,只會打印出一個$符號,數字,再加上一段地址資訊。由於po
命令下,物件的description
有可能被隨便亂改,沒有輸出地址訊息。$符號在LLDB中代表著變數的分配。每次使用p後,會自動為你分配一個變數,後面再次想使用這個變數時,就可以直接使用。我們可以直接使用這個地址做一些轉換,獲取物件的資訊
斷點
breakpoint
所有除錯都是由斷點開始的,我們接觸的最多,就是以breakpoint
命令為基礎的斷點。 一般我們對breakpoint
命令使用得不多,而是在XCode的GUI介面中直接新增斷點。除了直接觸發程式暫停供除錯外,我們可以進行進一步的配置。- 新增condition,一般用於多次呼叫的函式或者循壞的程式碼中,在作用域內達到某個條件,才會觸發程式暫停
- 忽略次數,這個很容易理解,在忽略觸發幾次後再觸發暫停
- 新增Action,為這個斷點新增子命令、指令碼、shell命令、聲效(有個毛線用)等Action,我的理解是一個指令碼化的功能,我們可以在斷點的基礎上新增一些方便除錯的指令碼,提高除錯效率。
- 自動繼續,配合上面的新增Action,我們就可以不用一次又一次的暫停程式進行除錯來查詢某些值(大型程式中斷一次還是會有卡頓),直接用Action將需要的資訊列印在控制檯,一次性檢視即可。
除去在程式碼中直接點選新增斷點外,我們也可以在
command + 7
breakpoint頁面下直接新增相關的斷點。我們常用的有 Exception Breakpoint 與 Symbolic Breakpoint- Add Exception Breakpoint Exception Breakpoint為異常斷點。在某些情況下,TableView的資料來源與UI操作不一致,或者容器插入了nil的指標,將訊息傳至野指標,都會導致程式的crash,並且LLDB輸出的資訊不是很友好。加上異常斷點,能夠使程式在丟擲異常的棧自動暫停,可直接定位導致丟擲異常的程式碼。在一般的開發流程中,都建議開啟這個異常斷點,反正你總是會crash的嘿嘿。
- Add Symbolic Breakpoint Symbolic Breakpoint 為符號斷點。有時候,我們並不清楚程式會在什麼情況下呼叫某一個函式,那我們可以通過符號斷點來獲取呼叫該函式時的程式堆疊。當然,在自己實現的類,我們也可以在該函式實現的地方打上斷點,但如果需要定位其他框架提供的API的呼叫,就只能使用符號斷點啦。
當然,LLDB的
breakpoint
命令也可以實現上述的功能,因為不常用,所以這裡就簡單列舉一些用法。 breakpoint set -n trigger //在所有類的trigger函式實現中打上斷點- 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時觸發斷點
- breakpoint set -n trigger -o //單次斷點
- breakpoint command add -o "frame info" 3 //在設定的三號斷點加入子命令frame info
- breakpoint list // 列出所有斷點
- breakpoint delete 3 //刪除3號斷點
watchpoint
有時候我們會關心類的某個屬性什麼時候被人修改了,最簡單的方法當然就是在setter的方法打斷點,或者在
@property
的屬性生命行打上斷點。這樣當物件的setter方法被呼叫時,就會觸發這個斷點。當然這麼做是有缺點的,對於直接訪問記憶體地址的修改,setter方法的斷點並沒有辦法監控得到,因此我們需要用到
watchpoint
命令。watchpoint
命令在XCode的GUI中也可以直接使用,當程式暫停時,我們能對當前程式棧中的變數設定watchpoint。值得注意的是,watchpoint是直接設定到該變數所在的記憶體地址上的,所以當這個變數釋放了後,watchpoint仍然是對這個地址的記憶體生效的。我們也可以在LLDB中直接用
watchpoint
命令,可以通過選項實現更多效果。- watchpoint set self->testVar //為該變數地址設定watchpoint
- watchpoint set expression 0x00007fb27b4969e0 //為該記憶體地址設定watchpoint,記憶體地址可從前文提及的`p`命令獲取
- watchpoint command add -o 'frame info' 1 //為watchpoint 1號加上子命令 `frame info`
- watchpoint list //列出所有watchpoint
- watchpoint delete // 刪除所有watchpoint
堆疊
thread
和bt
bt
即是thread backtrace
,作用是打印出當前執行緒的堆疊資訊。當程式發生了crash後,我們可以用該命令打印出發生crash的當前的程式堆疊,查詢出發生crash的呼叫路徑。由於比較常用,所以LLDB直接給它一個特殊的bt
別名。thread
另一個比較常用的用法是thread return
,除錯的時候,我們希望在當前執行的程式堆疊直接返回一個自己想要的值,可以執行該命令直接返回。thread return <expr>
在這個斷點中,我們可以執行
thread return NO
讓該函式呼叫直接返回NO
,在除錯中輕鬆覆蓋任何函式的返回路徑。frame
frame
即是幀,其實就是當前的程式堆疊,我們輸入bt
命令,打印出來的其實是當前執行緒的frame。在除錯中,一般我們比較關心當前堆疊的變數值,我們可以使用frame variable
來獲取全部變數值。當然也可以輸入特定變數名,來獲取單獨的變數值,如frame v self-> testVar
來獲取testVar
的值。