標籤:lldb
你已經學習了如何建立斷點, 如何列印和修改值, 同時還有當調試器停下來的時候如何執行代碼. 但是你還沒有學會如何在調試器中自由切換和檢查資料.是時候學習一下了!
在本章中, 你將會學習到當LLDB暫停時候如何在調試器的函數裡和函數外自由切換.
這是一個重要的技能因為當值改變的時候你經常想要檢查程式碼片段裡面或者外面的值.
棧 101
當電腦在執行應用程式的時候, 它會將值儲存在棧和堆裡.兩者都有各自的優點.作為一個進階調試者, 你要好好的理解他們是如何工作的.現在, 讓我們簡單的看一下棧.
你可能在電腦相關的知識裡看了很多關於棧的知識.在任何情況下, 它都需要有一個最基本的理解一個線程在執行的時候是如何追蹤代碼和變數的. 這些知識可以讓你方便的使用LLDB在代碼裡面遊走.
棧是LIFO(Last-In-First-Out)用後進先出的隊列來儲存你當前執行的代碼的引用. LIFO的順序意味著無論你最後加入了什麼都會最先移除.將棧想象成一碟盤子.在頂部加一個盤子, 它將會首先被你拿走.
棧指標指向當前棧的頂部. 在盤子的類比中, 棧指標指向盤子的頂部, 告訴你下一次從哪裡取盤子, 或者下一次把盤子放在哪裡.
page69image15224.png
在這張圖表中, 最高的地址顯示在頂部(0xFFFFFFFF)並且較低的地址顯示在底部(0x00000000)展示的棧是向下生長的.
有些圖表的高地址在底部與盤子的類比是相匹配的, 棧是向下生長的.然而, 我相信任何圖表展示的棧都應該是從一個高地址向下生長的否則的話稍後我們在討論棧指標的位移的時候會遇到一些頭疼的問題.
你會在第十二章看到一些棧指標和寄存器的一些更深入的問題“Assembly and the Stack”, 但是在本章中你將瀏覽在棧中的代碼裡步進的幾種不同的方法.
檢查棧的幀
在本章中你將依然使用Signals項目.
在本章中你會用到一點彙編的知識.不要怕!並沒有想象的那麼糟糕.但是, 要確保在本章中你用的是iPhone 7的模擬器因為如果是在iOS真機上的話產生的彙編代碼可能有些不同.這是因為真機用的是ARM 架構, 模擬器使用的是你Mac本地的指令集 x86_64.
在Xcode中開啟Signals項目.接下來, 在下面的函數名字處添加一個符號斷點.確保在函數標誌處添加了空格否則斷點將無法識別這寫符號.
Signals.MasterViewController.viewWillAppear (Swift.Bool) -> ()
這行代碼在MasterViewController’s viewWillAppear(_:)方法處建立了一個符號斷點.
page70image17024.png
構建並運行程式. 正如期望的那樣, 程式將會在MasterViewController的viewWillAppear(_:)方法處停下來.接下來, 在Xcode的左邊找到棧追蹤面板.如果你還沒有看到, 點擊左邊面板的Debug Navigator或者按 Command + 6快速鍵.
確保底部右下角的三個按鈕都是禁用狀態.這些按鈕是用來協助你過濾那些只在你的原始碼裡出現的函數的.鑒於你既要學習公有的代碼又要學習私人的代碼, 你應該總是保持這些按鈕處于禁用狀態以便你可以看到棧追蹤的完整資訊.
page71image1232.png
在調試的導航面板中, 棧追蹤面板將會出現, 並顯示出棧幀的列表, 第一個就是viewWillAppear(_:)函數. 緊跟著的是一個Swift/ Objective-C 橋接方法,@objc MasterViewController.viewWillAppear(Bool) - > ():. 這個方法是自動產生的因此Objective-C可以進入Swift代碼的內部.
在這後面, 有一些UIKit的Objective-C代碼的棧幀.再往下一點, 你會看到一些屬於CoreAnimation的C++的代碼.更深入一點, 你會看到包含在屬於CoreFoundation的CFRunLoop的一組方法.最後, 是main函數來做結尾的(是的, Swift程式仍然有main函數, 只不過是隱藏起來了而已).
你在Xcode中看到的棧追蹤到的資訊就是LLDB可以告訴你的內容的一個樣板. 現在我們來看一下.
在LLDB控制台中輸入以下內容:
(lldb) thread backtrace
你也可以簡單的輸入bt, 也可以達到同樣的效果. 他們實際上是兩個不同的命令, 你也可以同過你可信賴的朋友help來查看他們的不同.
執行了上面的命令之後, 你將會看到一個與你再Xcode的調試欄裡一致的棧追蹤資訊.
在LLDB控制台中輸入下面的指令:
(lldb) frame info
你會得到一些類似下面的輸出:
frame #0: 0x00000001075d0ae0
Signals`MasterViewController.viewWillAppear(animated=<invalid> (0xd1),
self=0x00007fff5862dac0) -> () at MasterViewController.swift:47
正如你看到的, 這個輸出與你在調試欄裡看到的是一致的.所以這就是為什麼你可以在調試欄裡看到相當重要的一切?好, 使用LLDB控制台給你的細分度來控制你想查看的資訊.此外, 你製作的自訂的LLDB指令碼會讓這些命令變得非常有用.我們也知道了Xcode是從哪裡擷取的資訊, 對吧?
讓我們回到調試欄裡看一下, 你會在調用的棧裡面看到一些從0開始遞增的數字.這些數字可以協助你記住你正在查看的棧幀. 輸入下面的命令選擇一個棧:
(lldb) frame select 1
Xcode將會調到@objc的橋接方法裡, 這個方法在棧中的編號是1.
假如你使用的是模擬器而不是一個真機, 你將會得到一些跟下面看起來類似的彙編:
page72image14976.png
注意看彙編中的綠線. 右前方的那條線是callq指令它代表著你之前在執行viewWillAppear(_:)時設定的斷點.
步
在掌握LLDB的時候, 在程式暫停時候你可以做的最重要的三個導航動作就是在程式中步進.通過LLDB, 你可以在代碼中步過, 步入和步出.
它們當中的每一個都可以讓你繼續執行程式的代碼, 但是在一個小的整體中可以讓你檢測程式的代碼是如何執行的.
步過
步過允許你執行調試器當前暫停位置的下一條代碼語句(通常是下一行).這就意味著如果當前語句調用了另外一個函數, LLDB將會繼續運行直到這個函數執行完畢並返回.
讓我來實際看一下.
在LLDB控制台中輸入下面的代碼:
(lldb) run
這會在Xcode不用重新編譯項目的情況下重新啟動Signals程式.Xcode會在你之前建立的符號斷點出停下來.
接下來, 輸入下面的內容:
(lldb) next
調試器會向前移動一行代碼. 這就是步過.簡單, 但是有用!
步入
步入意味著如果下一條語句調用了一個函數, 調試器會移動到這個函數的起始位置並再次暫停.
讓我們實際看一下.
再次從LLDB中啟動斷點程式:
(lldb) run
接下來輸入:
(lldb) step
不幸的是. 程式已經步入了, 因為這行程式碼封裝含了一個函數調用.
在這種情況下, LLDB實際上更像是用“step over”代替了“step into”. 這是因為LLDB在預設情況下會忽略步進一個函數如果這個函數裡面沒有偵錯符號的話.在這裡, 調用的是UIKit裡的函數, 在那裡你並沒有偵錯符號.
然而這裡卻又一個方法來設定LLDB的行為當步入一個沒有偵錯符號的函數的時候.在LLDB中執行下面這條命令並查看這條指令的作用:
(lldb) settings show target.process.thread.step-in-avoid-nodebug
如果為真, 在這些執行個體上步入實際上被當做步過來執行.你也可以改變這個設定(這是你後面做的), 或者告訴調試器忽略這個設定(這是你現在做的).
在LLDB控制台中輸入下面的命令:
(lldb) step -a0
這條指令告訴LLDB無論是否有偵錯符號都執行步入操作.
步出
步出意味著函數將會繼續執行然後當它返回的時候暫停.從棧的視角看, 繼續執行直到棧幀被彈出.
再次運行Signals項目, 這次當調試器暫停時候, 快速的看一下棧追蹤情況.接下來, 在LLDB中輸入下面的命令:
(lldb) finish
你會注意到調試器現在暫停在一個棧追蹤到的函數上. 試著多執行幾次這個命令.記住, 在簡單的按下Enter鍵的同時, LLDB會執行你最後一次輸入的代碼. finish命令會通知LLDB步出當前函數. 對左側面板中一個挨著一個的棧幀要多一些耐心.
在Xcode中步進
儘管你已經知道了很多使用控制台控制的細分指令, Xcode已經為你提供了這些選項就是LLDB控制台上面的按鈕. 當一個程式啟動並執行時候這些按鈕就會出現.
page74image17480.png
他們的順序是步過, 步入和步過.
最後, 步過和步入還有更多的功能. 你可以手動控制執行不同的線程, 通過在點擊這些按鈕的時候按下Control和Shift.
這樣做的結果是步過調試器當前暫停線程, 而其餘的線程仍然暫停.這是一個非常有用的技巧當你調試一些很難調試的並發代碼的時候像網路請求或者GCD代碼的時候.
當然LLDB在控制台中通過使用--run-mode選項來做同樣的事情, 或者更簡單的用-m跟隨合適的選項.
檢測棧中的資料
frame命令中一個非常有趣的命令是frame variable子命令.這個命令將會擷取在你執行的標頭檔中發現的偵錯符號資訊並將指定棧幀的資訊提取出來.感謝調試資訊, frame variable命令通過合適的選項可以簡單的告訴你在你的函數中所有變數的範圍以及任何在你程式中的全域變數.
再次運行Signals項目並且確保你已經觸發了viewWillAppear(_:)中的斷點.接下來 找到棧的頂部即可以通過點擊Xcode調試欄裡的頂部也可以通過在控制台中輸入 frame select 0.
接下來, 輸入下面的內容:
(lldb) frame variable
你會看到類似下面的輸出:
(Bool) animated = false
(Signals.MasterViewController) self = 0x00007fb3d160aad0 {
UIKit.UITableViewController = {
[email protected] = <extracting data from value failed>
_tableViewStyle = 0
_keyboardSupport = nil
_staticDataSource = nil
_filteredDataSource = 0x000061800005f0b0
_filteredDataType = 0
}
detailViewController = nil
}
這回提取出當前棧幀可用的變數和代碼. 如果可能的話, 他也會從當前的可用變數中提取出所有的執行個體變數, 既有公有變數也有私人變數.
如果你是善於觀察的讀者, 你也許會注意到frame variable的輸出與控制台視窗中左側面板中的變數視圖的的內容是一致的.
如果沒有看到變數視圖, 你可以通過點擊Xcode右下角的顯示左側面板的按鈕來展開變數視圖.你可以將frame variable的輸出與變數視圖的內容做一個對比. 你會注意到frame variable與使用蘋果的私人API的變數視圖相比給你提供了變數的更多的資訊.
page76image11200.png
接下來, 輸入下面的內容:
(lldb) frame variable -F self
這是一個查看MasterViewController所有可用的私人變數的簡單方式.它用到了-F選項, 代表著flat.
這將會保持0縮排並且僅僅列印出This will keep the indentation to 0中關於self的資訊.
你將會得到一些類似與下面的輸出:
self = 0x00007fff5540eb40
self =
self =
self =
self = {}
self.detailViewController = 0x00007fc728816e00
self.detailViewController.some =
self.detailViewController.some =
self.detailViewController.some = {}
self.detailViewController.some.signal = 0x00007fc728509de0
正如你看到的, 在處理蘋果的架構的時候這是一種很有吸引力的方式.
我們為什麼要學這些?
在本章中你學到了瀏覽棧幀和他們的內容. 你也學到了如何使用步入, 步出和步過來在代碼中切換.
thread命令還有許多你沒有發現的選項.嘗試通過help thread命令來瞭解他們, 看看你是否能學到一些很酷的命令.
花點時間看一下thread until, thread jump和thread return命令.你再後面會用到它們, 你現在也可以先瞭解一下它們的作用.
Advanced+Apple+Debugging(6)