1. 程式人生 > >IOS LLDB斷點除錯

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 overF6
step intoF7
step outF8

HELP

LLDB的入門與Linux命令入門類似,可以通過執行help命令來查詢這個命令的意義和詳細引數

從描述中我們能看出thread backtrace是用來查詢暫停時的執行緒堆疊的,並瞭解了可以帶入的入參。

唯一匹配原則

LLDB有個很省事的特性,如果輸入的字母已經能匹配到某個命令,就可以直接執行,等於輸入了完整的命令。

可以看到expressione是等價的

變數查詢與修改

  1. expression

    expression 可簡寫為e,作用為執行一個表示式,首當其衝,它肯定可以用來查詢當前堆疊變數的值。

    當然e的更主要的用法是通過執行表示式,動態修改當前執行緒堆疊變數的值,從而達到除錯的目的(其實查詢也很主要,只是會用另一種方式查詢)。 比如,我們可以在某個if..else..的語句前打上斷點,直接修改條件表示式的值,使程式覆蓋了不同分支,而不用苦心積慮地停止程式、hard code變數值來進行除錯,節省了一大坨修改與編譯時間。

    在上面這份測試程式碼,在進入條件判斷語句前打了斷點,那我們可以通過e命令,來自由控制程式走向任何一個分支。

    我們也可以通過執行表示式,實時改變當前的UI介面,方便介面程式碼的除錯,比如我們可以執行下面程式碼來改變當前UI,讓cellItem的邊框顯示出來,以判斷我們的介面佈局是否正確。

    1. e @import UIKit
    2. e cellItem.layer.borderWidth = 1

    這裡有個特殊的問題,由於程式已經被斷點暫停了,因此執行UI更新的執行緒也被暫停了。我們可以通過讓程式繼續執行,也可以通過另一條表示式來更新UI。

     e (void)[CATransaction flush]

    我們也可以用 call 來代替 expression --,其實我覺得用e更方便。 =。=

  2. ppo

    在上面說過,在除錯中,我們一般用e命令來修改變數,而查詢變數一般用ppo命令。po的作用為列印物件,事實上,我們可以通過help po得知,poexpression -O --的簡寫,我們可以通過它打印出物件,而不是列印物件的指標。而值得一提的是,在 help expression 返回的幫助資訊中,我們可以知道,po命令會嘗試呼叫物件的 description 方法來取得物件資訊,因此我們也可以過載某個物件的description方法,使我們除錯的時候能獲得可讀性更強,更全面的資訊。

    1. -(NSString*)description
    2. {
    3. return [NSString stringWithFormat:@"Portal[%@, %@, %@, %@, %@, %@, %@]", ssid, mpUrl, ticket, authUrl, _openid, _tid, extend];
    4. }

    p即是print,也是expression --的縮寫,與po不同,它不會打出物件的詳細資訊,只會打印出一個$符號,數字,再加上一段地址資訊。由於po命令下,物件的description 有可能被隨便亂改,沒有輸出地址訊息。

    $符號在LLDB中代表著變數的分配。每次使用p後,會自動為你分配一個變數,後面再次想使用這個變數時,就可以直接使用。我們可以直接使用這個地址做一些轉換,獲取物件的資訊

斷點

  1. 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函式實現中打上斷點

    1. breakpoint set -f ViewController.m -n trigger //在ViewController.m中的trigger方法打上斷點
    2. breakpoint set -f ViewController.m -l 50 //在ViewController.m的50行打上斷點
    3. breakpoint set -f ViewController.m -n trigger: -c testCondition > 5 //在ViewController.m中的trigger方法打上斷點並新增condition, testCondition大於5時觸發斷點
    4. breakpoint set -n trigger -o //單次斷點
    5. breakpoint command add -o "frame info" 3 //在設定的三號斷點加入子命令frame info
    6. breakpoint list // 列出所有斷點
    7. breakpoint delete 3 //刪除3號斷點
  2. watchpoint

    有時候我們會關心類的某個屬性什麼時候被人修改了,最簡單的方法當然就是在setter的方法打斷點,或者在@property的屬性生命行打上斷點。這樣當物件的setter方法被呼叫時,就會觸發這個斷點。

    當然這麼做是有缺點的,對於直接訪問記憶體地址的修改,setter方法的斷點並沒有辦法監控得到,因此我們需要用到watchpoint命令。watchpoint命令在XCode的GUI中也可以直接使用,當程式暫停時,我們能對當前程式棧中的變數設定watchpoint。值得注意的是,watchpoint是直接設定到該變數所在的記憶體地址上的,所以當這個變數釋放了後,watchpoint仍然是對這個地址的記憶體生效的。

    我們也可以在LLDB中直接用watchpoint命令,可以通過選項實現更多效果。

    1. watchpoint set self->testVar //為該變數地址設定watchpoint
    2. watchpoint set expression 0x00007fb27b4969e0 //為該記憶體地址設定watchpoint,記憶體地址可從前文提及的`p`命令獲取
    3. watchpoint command add -o 'frame info' 1 //為watchpoint 1號加上子命令 `frame info`
    4. watchpoint list //列出所有watchpoint
    5. watchpoint delete // 刪除所有watchpoint

堆疊

  1. threadbt

    bt即是thread backtrace,作用是打印出當前執行緒的堆疊資訊。當程式發生了crash後,我們可以用該命令打印出發生crash的當前的程式堆疊,查詢出發生crash的呼叫路徑。由於比較常用,所以LLDB直接給它一個特殊的bt別名。thread另一個比較常用的用法是 thread return,除錯的時候,我們希望在當前執行的程式堆疊直接返回一個自己想要的值,可以執行該命令直接返回。

     thread return <expr>

    在這個斷點中,我們可以執行 thread return NO讓該函式呼叫直接返回NO ,在除錯中輕鬆覆蓋任何函式的返回路徑。

  2. frame

    frame即是幀,其實就是當前的程式堆疊,我們輸入bt命令,打印出來的其實是當前執行緒的frame。在除錯中,一般我們比較關心當前堆疊的變數值,我們可以使用frame variable來獲取全部變數值。當然也可以輸入特定變數名,來獲取單獨的變數值,如frame v self-> testVar來獲取testVar的值。

End