Advanced+Apple+Debugging(5)

來源:互聯網
上載者:User

標籤:lldb

現在你已經學習了如何建立斷點, 因此調試器會在你的代碼裡停下來, 現在是時候從你調試的程式裡擷取一些有用的資訊了.
你應該會經常想要查看對象的執行個體變數. 但是, 你知道嗎你甚至可以通過LLDB執行任意代碼?詳細說就是通過Objective-C的運行時你可以聲明,初始化,並且注入代碼來協助你理解應用程式.
在本章中你將會學習到expression命令.這條命令允許你在調試器中執行任意代碼.

格式化p 和 po

你可能很熟悉go-to這個調試命令.po這個命令經常用來列印出對象的資訊.可以是一個對象的執行個體變數, 也可以是一個對象的引用, 還可以是一個寄存器裡的對象.它甚至可以是記憶體裡任意對象的記憶體位址.
如果你在LLDB控制台中查看po的快速協助, 你會發現po實際是一個運算式expression -O --的縮寫. -o參數用來列印出對象的description.po的另一個兄弟指令p是省略掉-o選項的運算式的縮寫expression --.
p列印出的資訊取決於LLDB type system.LLDB的值的格式化類型決定了它的輸出並且是完全可以自訂的(後面你就會看到).
是時候學習一下如何用p和po擷取他們的內容了.在本章中你依然後使用Signals項目.
在Xcode中開啟Signals項目.接下來開啟MasterViewController.swift並且在這個類的上方加上下面的代碼:

override var description: String {
return "Yay! debugging " + super.description
}
在viewDidLoad中的super.viewDidLoad()下面加上下面的代碼:

print("(self)")
現在在MasterViewController.swift中在你剛剛添加的列印方法的下面建立一個斷點.
構建並運行APP:

page56image16240.png

當Signals在viewDidLoad()中停下來的時候, 在LLDB控制台中輸入下面的代碼:
(lldb) po self
你應該會看到下面這些輸出:

Yay! debugging <Signals.MasterViewController: 0x7f8a0ac06b70>
注意一下print語句的輸出和它與你在調試器中執行po self輸出的匹配度.
你也可以更進一步. NSObject有另外一個用來調試的description方法叫debugDescription.現在來嘗試實現一下. 在description變數定義的下面添加以下代碼:

override var debugDescription: String {
return "debugDescription: " + super.debugDescription
}
構建並運行應用程式.當調試器在斷點處停下來的時候, 再次列印self:

(lldb) po self
LLDB控制台的輸出看起來應該是下面的樣子:

debugDescription: Yay! debugging <Signals.MasterViewController:
0x7fb71fd04080>
注意看po self和print self在你實現了debugDescription之後的輸出有什麼不同. 當你在LLDB中列印一個對象的時候調用的是debugDescription而不是description, 注意到了嗎!
正如你看到了, 當NSObject類或者它的子類有一個description或者debugDescription方法的時候會影響到po的輸出.
那麼哪些對象需要重寫description方法呢?你可以簡單的通過image lookup命令加一個Regex捕獲那些重寫了此方法的對象.你在前面章節中學到的內容將要派上用場了.
例如, 如果你想要知道哪些Objective-C類重寫了debugDescription方法, 你可以通過下面的命令查詢所有這些方法:

(lldb) image lookup -rn ‘\ debugDescription]‘
根據輸出的內容可以看到, Foundation架構的作者已經在許多foundation類型(例如:NSArray)裡面添加了debugDescription, 讓我們在調試的時候更簡單. 此外還有一些私人的類也重寫了debugDescription方法.
你可以能會注意到在列表裡有CALayer類. 讓我們看一下在CALayer類中description和debugDescription有哪些不同.
在LLDB控制台中輸入下面的內容:

(lldb) po self.view!.layer.description
你將會看到類似下面的輸出:

"<CALayer: 0x61000022e980>"
只有一點點的資訊. 現在輸入下面的內容:

(lldb) po self.view!.layer
你將會看到下面這些輸出:

<CALayer:0x61000022e980; position = CGPoint (187.5 333.5); bounds =
CGRect (0 0; 375 667); delegate = <UITableView: 0x7fdd04857c00; frame =
(0 0; 375 667); clipsToBounds = YES; autoresize = W+H; gestureRecognizers
= <NSArray: 0x610000048220>; layer = <CALayer: 0x61000022e980>;
contentOffset: {0, 0}; contentSize: {375, 0}>; sublayers = (<CALayer:
0x61000022d480>, <CALayer: 0x61000022da60>, <CALayer: 0x61000022d8c0>);
masksToBounds = YES; allowsGroupOpacity = YES; backgroundColor = <CGColor
0x6100000a64e0> [<CGColorSpace 0x61800002c580> (kCGColorSpaceICCBased;
kCGColorSpaceModelRGB; sRGB IEC61966-2.1; extended range)] ( 1 1 1 1 )>
這裡有更多的能容-而且更加有用!很顯然Core Animation的開發人員需要通過引用用description擷取更多更清楚的資訊. 但是如果你在調試器中, 你將會看到更多資訊.我們並不清楚他們為什麼要製造這些不同.可能這些debug description需要執行大量的計算, 因此他們只在絕對必要的時候才用到.
接下來, 你應該還停留在調試器中, 嘗試執行p self:

(lldb) p self
你應該會得到一些類似下面的資訊:

(Signals.MasterViewController) $R2 = 0x00007fb71fd04080 {
UIKit.UITableViewController = {
[email protected] = <extracting data from value failed>
_tableViewStyle = 0
_keyboardSupport = nil
_staticDataSource = nil
_filteredDataSource = 0x000061800024bd90
_filteredDataType = 0
}
detailViewController = nil
}
這看起來可能有點嚇人, 但是讓我們來解析一下.
首先, LLDB輸出了self的類名. 在這裡就是Signals.MasterViewController.緊跟著是一個你可以在LLDB中用來引用這個對象的指標. 在上面的例子中就是$R2.你的可能是不同的因為這個數字在你使用LLDB的時候是遞增的.
當你在後面的LLDB會話中想要回到這個對象的時候這個引用是非常有用的, 也許你在不同的範圍裡self不再是同一個對象.在這裡你可以通過R2引用這個對象. 想知道如何引用, 接著往下看:

(lldb) p $R2
你將會看到同樣的輸出.你會在本章後面的內容裡學到更多這種LLDB變數的用法.
在LLDB變數名字的後面是這個對象的地址, 後面跟著一些這個類的明確資訊. 在這裡, 它顯示UITableViewController相關的詳情, MasterViewController的父類, 緊跟著是 detailViewController的執行個體變數.
正如你看到的, p命令輸出的資訊和po命令輸出的資訊是不同的.p的輸出依賴於類型格式, LLDB作者已經添加到Objective-C, Swift, 和其他語言中的每一個內部的資料結構.需要重點注意的是Swift的輸出格式在不同的Xcode發行版中可能有少許的不同.
鑒於類型格式化是LLDB處理的, 如果你想的話你有能力改變它們. 在你的LLDB會話中, 輸入以下命令:

(lldb) type summary add Signals.MasterViewController --summary-string
"Wahoo!"
你已經告訴了LLDB在你列印一個MasterViewController類的執行個體的時候你僅僅只想返回靜態字串, Wahoo!.Signals首碼的實質是為Swift類的鑒於Swift包含這個模組類名來防止命名空間衝突. 現在再次嘗試輸出self, 像這樣:

(lldb) p self
輸出看起來應該像下面這個樣子:

(lldb) (Signals.MasterViewController) $R3 = 0x00007fb71fd04080 Wahoo!
這個格式在通過APP啟動的時候會被LLDB記住, 因此要確保你練習完p命令之後刪除它. 可以用下面的指令在LLDB會話中刪除:

(lldb) type summary clear
輸入p self將會回到LLDB作者預設的實現方式.類型格式化是一個值得我們在後面章節中詳細討論的話題.因為它可以在你沒有原始碼的情況下協助你詳細的調試應用程式.

Swift vs Objective-C調試環境

注意到這裡有兩個調試環境在調試你的代碼的時候是非常重要的:一個非Swift調試環境和一個swift調試環境.預設情況下, 當你在 Objective-C代碼中停下來的時候, LLDB將會使用非Swift(Objective-C)調試環境, 當你在Swift代碼中停下來的時候, LLDB將會使用Swift調試環境.聽起來很合邏輯, 對吧?
如果你將調試器停在了藍色斷點的外面, LLDB預設情況下將會選擇Objective-C環境.確保你之前在GUI裡面建立的斷點依然存在並仍然可用然後構建並運行APP. 當斷點觸發的時候, 在你的LLDB會話裡輸入下面的內容:

(lldb) po [UIApplication sharedApplication]
LLDB將會拋出一個錯誤給你:

error: <EXPR>:3:16: error: expected ‘,‘ separator
[UIApplication sharedApplication]
^ ,
你已經在Swift代碼中停下來了, 所以你在swift環境中.但是你卻嘗試運行Objective-C的代碼.那是行不通的.類似的在Objective-C環境中運行Swift代碼也是行不通的.
你可以用-l選項選擇一個語言強制讓運算式使用 Objective-C環境.然而, 由於po是expression - O --的縮寫, 你將因為提供在--後面的參數而不能夠使用po命令, 這就意味著你將不得不輸入expression. 在LLDB中, 輸入下面的內容:

(lldb) expression -l objc -O -- [UIApplication sharedApplication]
這裡你已經告訴LLDB為Objective-C使用objc語言.如果必要的話你還可以為 Objective-C++用objc++.
LLDB將會輸出shared application的引用.嘗試在Swift裡做同樣的事情. 既然你已經停在了Swift環境裡, 嘗試用Swift的文法列印出UIApplication的引用, 想下面這樣:

(lldb) po UIApplication.shared
你將會得到在Objective-C環境通樣的輸出.輸入continue繼續運行應用程式, 然後在藍色斷點的外面暫停Signals項目.
在這裡, 按下上箭頭按鈕得到你剛才執行的swift命令並看看發生了什麼:

(lldb) po UIApplication.shared
LLDB將會再次拋出一個錯誤:

error: property ‘shared‘ not found on object of type ‘UIApplication‘
記住, 在藍色斷點外面停下會讓LLDB進入Objective-C環境. 這就是為什麼在你嘗試執行Swift代碼的時候會拋出一個錯誤.
你因該時刻注意調試器當前停在什麼樣的語言環境裡.

使用者定義的變數

正如你之前看到的, LLDB在列印出對象的時候會自動維護局部變數.你同樣也可以建立自己的變數.
從程式裡移除所有的斷點構建並運行APP.在藍色斷點外面停止調試器所以預設的是Objective-C環境.在這裡輸入:

(lldb) po id test = [NSObject new]
LLDB將會執行這段代碼, 這將會建立一個新的NSObject對象並儲存在test變數裡.現在嘗試列印這個對象:

(lldb) po test
你將會得到一個類似下面的錯誤:

error: use of undeclared identifier ‘test‘
這是因為你需要讓LLDB記住這個變數你就要用到$修飾符.
再次嘗試聲明test變數在前面加上$:

(lldb) po id $test = [NSObject new]
(lldb) po $test
<NSObject: 0x60000001d190>
這個變數被建立為Objective-C對象.但是如果你想在swfit環境中訪問這個變數會發生什麼呢?嘗試輸入下面的內容:

(lldb) expression -l swift -O -- $test
到現在為止,一直都還不錯.現在嘗試在這個Objective-C類上執行swift風格的代碼.

(lldb) exppression -l swift -O -- $test.description
你將會得到一個類似下面的錯誤:

error: <EXPR>:3:1: error: use of unresolved identifier ‘$test‘
$test.description
^~~~~
如果你在Objective-C環境中建立了一個LLDB變數, 然後轉到了swift環境中, 不要期望一切都會照常工作.隨著時間的流失我們會看到swift和Objective-C通過LLDB橋接的改善.
那麼如何在LLDB中建立應用與真實環境的引用?你可以將引用存在一個對象裡並執行你選擇的任意代碼. 要看實際效果, 可以在MasterViewController的父視圖控制器裡建立一個符號性的斷點, MasterContainerViewController使用了一個Xcode的符號斷點在viewDidLoad方法裡.
在Symbol部分輸入:

Signals.MasterContainerViewController.viewDidLoad () -> ()
要注意參數和傳回值之間的空格, 否則斷點是不生效的.
你的斷點看起來應該是下面這個樣子:

page62image17352.png

構建並運行APP.Xcode現在將會斷點在MasterContainerViewController.viewDidLoad().From there, type the following:

(lldb) p self
鑒於這是你再swift調試環境執行的第一個參數, LLDB將會建立一個變數$R0.在LLDB中輸入continue繼續執行程式.
因為執行移到更大和更好的運行迴圈事件並停留在了viewDidLoad()裡, 所以所以現在你還不能通過使用self來引用 MasterContainerViewController執行個體.
但是你仍然有$R0這個變數!現在你可以應用MasterContainerViewController甚至可以執行任意代碼來協助你調試.
手動的將APP暫停在調試器中, 然後鍵入下面內容:

(lldb) po $R0.title
不幸的是, 你將會得到:

error: use of undeclared identifier ‘$R0‘
你將調試器停在了藍色斷點的外面!記住, LLDB預設的是Objective-C環境. 你需要使用-l選項進入swift環境:

(lldb) expression -l swift -- $R0.title
這將會輸出下面的內容:

(String?) $R1 = "Quarterback"
當然, 這是顯示在導覽列上的視圖控制器的標題.
現在, 輸入下面的內容:

(lldb) expression -l swift -- $R0.title = "! ! ! ! ! "
輸入continue繼續運行APP.

page64image1064.png

正如你看到的你可以輕鬆的操控你想操控的變數.
此外, 你也可以在代碼裡建立一個斷點, 執行代碼, 並且在斷點觸發的時候暫停. 如果你正在調試一些事情並且想用特定的輸入執行一個函數看看它是如何執行的時候這是很有用的.
例如, 你仍然有在viewDidLoad()裡的符號斷點, 所以嘗試執行那個方法去檢查代碼. 暫停程式的執行, 然後輸入:
(lldb) expression -l swift -O -- $R0.viewDidLoad()
什麼事都沒有發生. 沒有觸發斷點. 怎麼會這樣?事實上, MasterContainerViewController已經執行了這個方法, 但是在預設情況下, LLDB在執行命令的時候會忽略任何斷點.你可以用-i選項經用這個功能.
在LLDB控制台中輸入下面的內容:

(lldb) expression -l swift -O -i 0 -- $R0.viewDidLoad()
現在LLDB會在你之前建立的符號斷點處停下來.這種策略是測試方法邏輯的絕佳方法.例如, 你可以實現測試驅動的調試, 通過給一個函數不同的參數來看看它如何處理不同的輸入.

類型格式化

LLDB中一個好的選項是你可以執行基礎資料型別 (Elementary Data Type)的輸出格式. 這讓LLDB成為了一個學習編譯器是格式化基本的C類型的偉大工具.這在你學習後面的彙編章節的時候是必須知道的.
在LLDB控制台中輸入下面的命令:

(lldb) expression -G x -- 10
-G選項告訴LLDB你想要輸出什麼樣的格式. G代表著GDB格式. 你可能不知道, GDB是LLDB前一代的調試器. 在這裡, 使用的x代表著十六進位格式.
你將會看到下面的輸出:

(int) $0 = 0x0000000a
這是將十進位的10作為十六進位輸出.
LLDB還有一種指定輸出格式的更短的文法. 輸入下面的命令:

(lldb) p/x 10
你將會看到和前面一樣的輸出. 但是這一次你輸入的內容更少了!
這對於學習C資料類型的表示形式非常有協助. 例如, 如何用二進位表示數字10呢?

(lldb) p/t 10
/t指明了二進位形式.你將會看到十進位的10是如何用二進位表示的.
負10又是如何表示的呢?

(lldb) p/t -10
由兩部分組成的10進位的10.夠清楚吧!
浮點數10.0又如何用二進位表示呢?

(lldb) p/t 10.0
這可能派上用場!
字元D的ASCII值是怎樣的呢?

(lldb) p/d ‘D‘
所以D是68!/d指定的是十進位形式.
最後, 整數後面隱藏的縮寫是什麼?

(lldb) p/c 1430672467
/c指明了字元形式. 它將數字轉換成二進位, 每8位作為一個整體(1位元組), 然後將每一個位元組都轉換成ASCII字元.在這裡, 它有4個字元代碼STFU.
下面是所有輸出格式的列表(可以參考:[https://sourceware.org/gdb/ onlinedocs/gdb/Output-Formats.html](https://sourceware.org/gdb/ onlinedocs/gdb/Output-Formats.html)):
? x: hexadecimal
? d: decimal
? u: unsigned decimal
? o: octal
? t: binary
? a: address
? c: character constant
? f: float
? s: string
如果這個格式對你來說不夠用, 你可以使用LLDB拓展的格式, 但是你將不能夠使用GDB格式文法.
LLDB的格式可以像下面這樣使用:

(lldb) expression -f Y -- 1430672467
這將會輸出下面的內容:

(int) $0 = 53 54 46 55 STFU
這解釋了之前的FourCC代碼!
LLDB擁有下面的格式(可以參考varformats.html" rel="nofollow">http://lldb.llvm.org/
varformats.html):
? B: boolean
? b: binary
? y: bytes
? Y: bytes with ASCII
? c: character
? C: printable character
? F: complex float
? s: c-string
? i: decimal
? E: enumeration
? x: hex
? f: float
? o: octal
? O: OSType
? U: unicode16
? u: unsigned decimal
? p: pointer

我們為什麼要學習這些?

嘗試通過執行help expression查看其他的expression選項並查看你可以用它們來做什麼.

Advanced+Apple+Debugging(5)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.