無論你是在技術棧中使用 Swift,Objective-C,C++,C,還是完全不同的語言,都需要學習如何創建斷點。 在 Xcode 中點擊側面板很容易使用 GUI 創建一個斷點,但是 LLDB 控制臺可以更好地控制斷點。
在本章中,你將學習所有關于斷點的知識,以及如何使用 LLDB 創建斷點。
信號(Signals)
在這一章中,你將會看到我提供的一個項目。 它叫做信號(Signals),你可以在本章的資源包中找到它。
使用 Xcode 打開信號項目。 信號是一個基本的主從模板項目,采用美式足球應用程序的主題,顯示一些名稱奇怪的調用。
在內部,這個項目監聽幾個 Unix 信號,并在信號項目收到信號時顯示出來。
Unix 信號是進程間通信的基本形式。 例如,其中一個信號 SIGSTOP 可用于保存狀態并暫停進程的執行,而其對應的 SIGCONT 被發送到程序以恢復執行。 這兩個信號可以被調試器用來暫停和繼續程序的執行。
這是一個有趣的應用程序,因為它不僅探索了 Unix 信號處理,而且還突出了當控制進程(LLDB)處理將 Unix 信號傳遞給受控進程時發生的情況。 默認情況下,LLDB 具有處理不同信號的自定義操作。 有些信號在 LLDB 被連接時不會傳遞到受控進程。
為了顯示信號,你可以從應用程序內部發出一個信號,或者從其他應用程序(如終端)在外部發送信號。
另外,還有一個 UISwitch 來切換信號處理。 當切換開關時,它調用 C 函數 sigprocmask 來禁用或啟用信號的處理。
最后,應用程序有一個 Timeout 的導航欄按鈕,它在應用程序中發出 SIGSTOP 信號,實質上是“凍結”程序。 但是,如果將 LLDB 連接到信號程序(當你通過 Xcode 進行構建和運行時默認連接),調用 SIGSTOP 將允許你在 Xcode 中使用 LLDB 檢查執行狀態。
確保 iPhone X 模擬器被選為目標。 構建并運行應用程序。 一旦項目運行,導航到 Xcode 控制臺并暫停調試器。
恢復 Xcode 并留意模擬器。 每當調試器停止時,UITableView 將添加新的一行然后恢復執行。 這是通過信號項目監視 SIGSTOP Unix 信號事件并在數據模型出現時向里面添加一行來實現的。 當一個進程停止時,任何新的信號都不會被立即處理,因為這個程序是有點停止的。
Xcode 斷點
在你通過 LLDB 控制臺學習酷且耀眼的斷點之前,值得一提的是,你可以通過 Xcode 單獨實現。
符號斷點(Symbolic Breakpoint)是 Xcode 的一個很好的調試功能。 它們讓你在應用程序中的某個符號上設置一個斷點。 一個符號的例子是 -[NSObject init],它指向 NSObject 實例的 init 方法。
Xcode 中符號斷點的簡潔之處在于,一旦你輸入了一個符號斷點,下一次程序啟動時就不必再輸入它。
現在你將嘗試使用符號斷點來顯示所有將要創建的 NSObject 實例。
如果應用程序正在運行,關閉它。 接下來,切換到 Breakpoint Navigator。 在左下方,單擊加號按鈕選擇 Symbolic Breakpoint... 選項。
一個彈出窗口會出現。 在彈出窗口的 Symbol 部分輸入:-[NSObject init]。 在 Action 下,選擇 Add Action,然后從下拉列表中選擇 Debugger Command。 接下來,在下面的框中輸入 po [$arg1 class]。
最后,選中 Automatically continue after evaluating actions。 你的彈出窗口應該類似于以下內容:
構建并運行應用程序。 當通過控制臺運行信號程序的時候,Xcode 會顯示所有類初始化的名字,在查看的時候,有相當多。
你設置了一個每次 -[NSObject init] 被調用時觸發的斷點 。 當斷點觸發時,一條命令在 LLDB 中運行,并且程序自動繼續執行。
注意:在第十章“匯編寄存器調用約定”中,你將學習如何正確使用和操作寄存器,但是現在,只需知道 $arg1 與 $rdi 寄存器是同義的,可以簡單地認為是 init 被調用時持有類的實例。
一旦檢查完所有類名稱的顯示,通過在斷點導航器中右鍵單擊斷點并選擇“Delete Breakpoint”來刪除符號斷點。
除了符號斷點之外,Xcode 還支持另外幾種類型的錯誤斷點。 其中之一是異常斷點(Exception Breakpoint)。 有時候,你的程序出了問題,只是單單崩潰。 發生這種情況時,第一個反應應該是啟用異常斷點,每次拋出異常時都會觸發異常斷點。 Xcode 會告訴你崩潰行,這大大有助于追捕造成這次崩潰的罪魁禍首。
最后,有 Swift 錯誤斷點(Swift Error Breakpoint),它會在 Swift 拋出一個錯誤時通過在 swift_willThrow 方法上創建一個斷點來停止。 如果你使用可能容易出錯的 API,這是一個很好的選擇,因為它可以讓你快速診斷情況,而不會錯誤地假設代碼的正確性。
LLDB 斷點語法
現在你已經快速了解了使用 Xcode 的 IDE 調試功能,現在是時候了解如何通過 LLDB 控制臺創建斷點了。 為了創建有用的斷點,你需要學習如何查詢你正在尋找的東西。
image 命令是一個很好的工具來幫助反思細節,這對設置斷點很重要。
本書中你將使用兩種配置來進行代碼搜索。 第一種是以下內容:
(lldb) image lookup -n "-[UIViewController viewDidLoad]"
該命令顯示 -[UIViewController viewDidLoad] 函數的實現地址(該方法的偏移地址位于框架二進制文件中)。 -n 參數告訴 LLDB 查找符號或函數名稱。 輸出結果如下:
1 match found in /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk//System/Library/Frameworks/UIKit.framework/UIKit:
Address: UIKit[0x00000000001c67c8] (UIKit.__TEXT.__text + 1854120)
Summary: UIKit`-[UIViewController viewDidLoad]
另一種有用的,類似的命令是這樣的:
(lldb) image lookup -rn test
這是一個區分大小寫的正則表達式,查找單詞“test”。 如果在任何地方發現了小寫單詞“test”,那么在任何函數中,在當前可執行進程中加載的任何模塊(即 UIKit,Foundation,Core Data 等等),這個命令將顯示結果。
注意:如果需要精確匹配(如果查詢中包含空格,請加上引號)請使用 -n 參數,執行正則表達式搜索請使用 -rn 參數。 -n 命令有助于找出確切的參數來匹配斷點,尤其是在處理 Swift 時,而 -rn 參數選項將會在本書中重點介紹,因為好用的正則表達式可以消除相當多的輸入 —— 很快你就會發現。
Objective-C 屬性
學習如何查詢已加載的代碼對于學習如何在代碼中創建斷點是至關重要的。 Objective-C 和 Swift 在由編譯器創建時都有特定的屬性簽名,當查找代碼時會產生不同的查詢策略。
例如,信號項目中聲明了以下 Objective-C 類:
@interface TestClass : NSObject
@property (nonatomic, strong) NSString *name;
@end
編譯器將為屬性 name 的 setter 和 getter 生成代碼。 getter 將如下所示:
-[TestClass name]
...而 setter 就像這樣:
-[TestClass setName:]
構建并運行應用程序,然后暫停調試器。 接下來,通過在 LLDB 中輸入以下內容來驗證這些方法是否存在:
(lldb) image lookup -n "-[TestClass name]"
在控制臺輸出中,你會得到如下類似的東西:
1 match found in /Users/derekselander/Library/Developer/Xcode/DerivedData/Signals-atqcdyprrotlrvdanihoufkwzyqh/Build/Products/Debug-iphonesimulator/Signals.app/Signals:
Address: Signals[0x0000000100001d60] (Signals.__TEXT.__text + 0)
Summary: Signals`-[TestClass name] at TestClass.h:28
LLDB 將顯示有關可執行進程中包含的函數的信息。 輸出可能看起來很可怕,但這里有一些好消息。
注意:當查詢匹配到很多代碼時,image lookup 命令可以產生大量的輸出,這些輸出可能會非常難以觀察。在二十二章“SB 示例,查找改進”中,你將構建一個更清晰的命令替代 LLDB 的 image lookup 命令,以避免查看太多的輸出。
控制臺輸出告訴你 LLDB 能夠發現這個函數是在 Signals 可執行進程中實現的,在 __text 部分的 __TEXT 段的偏移量為 0x0000000100001d60。 LLDB 也能夠知道這個方法是在 TestClass.h 的第 28 行中聲明的。
你也可以檢查 setter,就像這樣:
(lldb) image lookup -n "-[TestClass setName:]"
你會得到類似于前一個命令的輸出,這次顯示了 name 的實現地址和 setter 聲明。
Objective-C 屬性和點符號
對于開始使用 Objective-C(或僅僅 Swift)的開發人員來說,經常會引起誤解的是屬性的 Objective-C 點符號語法。
Objective-C 點符號是一個有些爭議的編譯器特性,它允許屬性快速生成 getter 或 setter。
考慮以下的內容:
TestClass *a = [[TestClass alloc] init];
// Both equivalent for setters
[a setName:@"hello, world"];
a.name = @"hello, world";
// Both equivalent for getters
NSString *b;
b = [a name]; // b = @"hello, world"
b = a.name; // b = @"hello, world"
在上面的例子中,-[TestClass setName:] 方法被調用兩次,即使使用點符號。 對于 getter 也可以這樣說,-[TestClass name]。 當你處理 Objective-C 代碼并嘗試給屬性點符號的 setter 和 getter 方法創建斷點時,這很重要。
Swift 屬性
在 Swift 中,屬性的語法有很大不同。 看看 SwiftTestClass.swift 中包含的代碼:
class SwiftTestClass: NSObject {
var name: String!
}
確保信號項目在 LLDB 中運行并暫停。 通過在調試窗口中輸入 Command + K 來清除 LLDB 控制臺。
在 LLDB 控制臺中,輸入以下內容:
(lldb) image lookup -rn Signals.SwiftTestClass.name.setter
你會得到類似于下面的輸出:
1 match found in /Users/derekselander/Library/Developer/Xcode/DerivedData/Signals-atqcdyprrotlrvdanihoufkwzyqh/Build/Products/Debug-iphonesimulator/Signals.app/Signals:
Address: Signals[0x000000010000cc70] (Signals.__TEXT.__text + 44816)
Summary: Signals`Signals.SwiftTestClass.name.setter : Swift.ImplicitlyUnwrappedOptional<Swift.String> at SwiftTestClass.swift:28
在輸出結果 Summary 一詞后尋找信息。 這里有幾個有趣的事情需要注意。
你看到函數名稱有多長!? 這整個內容需要輸入一個有效的 Swift 斷點! 如果你想在這個 setter 上設置一個斷點,你必須輸入以下內容:
(lldb) b Signals.SwiftTestClass.name.setter : Swift.ImplicitlyUnwrappedOptional<Swift.String>
使用正則表達式是引出這個怪物的不錯的替代方案。
除了你生成的 Swift 函數名的長度之外,請注意 Swift 屬性是如何形成的。 包含屬性 name 的函數簽名擁有緊跟在屬性后面的單詞 setter。 也許同樣的慣例也適用于 getter 方法?
使用以下正則表達式查詢語句同時搜索 SwiftTestClass 中 name 屬性的 setter 和 getter:
(lldb) image lookup -rn Signals.SwiftTestClass.name
這使用正則表達式查詢語句來顯示所有包含短語 Signals.SwiftTestClass.name 的內容。
由于這是一個正則表達式,因此句點(.)被評估為通配符,這反過來又匹配實際函數簽名中的句點。
你會得到相當多的輸出,每當你在控制臺輸出中看到 Summary 一詞時,都會得到磨煉。 你會發現輸出匹配 getter(Signals.SwiftTestClass.name.getter),setter(Signals.SwiftTestClass.name.setter),以及兩個包含 materializeForSet —— Swift 構造函數的輔助方法的方法。
Swift 屬性的函數名稱有一個模式:
ModuleName.Classname.PropertyName.(getter|setter)
在代碼中創建智能斷點時,顯示方法,查找模式以及縮小搜索范圍的能力是發現 Swift / Objective-C 語言內部結構的好方法。
最后...創造斷點
現在你知道如何查詢代碼中函數和方法的存在,是時候開始為它們創建斷點了。
如果你已經運行了信號應用程序,請停止并重新啟動應用程序,然后按暫停按鈕停止應用程序并啟動 LLDB 控制臺。
有幾種不同的方法來創建斷點。 最基本的方法是簡單地輸入字母 b,后面跟你的斷點名稱。 這在 Objective-C 和 C 中相當簡單,因為名稱很短并且很容易輸入(例如 -[NSObject init] 或 -[UIView setAlpha:])。 這在 C++ 和 Swift 里非常棘手,因為編譯器會將方法變成帶有相當長名稱的符號。
由于 UIKit 主要是 Objective-C(至少在編寫本文時!),請使用 b 參數創建一個斷點,如下所示:
(lldb) b -[UIViewController viewDidLoad]
你會看到以下輸出:
Breakpoint 1: where = UIKit`-[UIViewController viewDidLoad], address = 0x0000000102bbd788
當你創建一個有效的斷點時,控制臺會顯示關于該斷點的一些信息。 在這種特殊情況下,斷點創建為斷點 1,因為這是此特定調試會話中的第一個斷點。 在創建更多斷點時,此斷點 ID 將增加。
恢復調試器。 一旦你恢復執行,一個新的 SIGSTOP 信號將被顯示。 點擊單元格以調出詳情的 UIViewController。 當調用詳情視圖控制器的 viewDidLoad 時,程序應該暫停。
注意:和許多簡寫命令一樣,b 是另一個更長的 LLDB 命令的縮寫。 嘗試運行 b 命令的 help 來自己弄清楚實際的命令,并學習所有可以在后臺做的很酷的技巧。
除了 b 命令之外,還有另一個更長的斷點設置命令,它提供了許多選項。 你將在接下來的幾章中探索這些選項。 許多命令將來自 breakpoint set 命令的各種選項。
正則表達式斷點和范圍
另一個非常強大的命令是正則表達式斷點 rbreak,它是斷點集合 -r %1 的縮寫。 你可以使用智能的正則表達式快速創建許多斷點,以便隨時隨地停止。
回到上一個示例,使用極長的 Swift 屬性函數名稱來代替輸入:
(lldb) b Breakpoints.SwiftTestClass.name.setter : Swift.ImplicitlyUnwrappedOptional<Swift.String>
你可以簡單地輸入:
(lldb) rb SwiftTestClass.name.setter
rb 命令將擴展為 rbreak(假設沒有以“rb”開頭的其他 LLDB 命令)。 這將在 SwiftTestClass 中的 name 的 setter 屬性上創建一個斷點。
更簡化一點,你可以簡單地使用以下內容:
(lldb) rb name\.setter
這將在所有包含短語 name.setter 的內容上生成斷點。 如果項目中沒有任何其他名為 name 的 Swift 屬性,這將生效; 否則,將為每個包含具有 setter 的“name”屬性的類創建多個斷點。
讓我們來了解一下這些正則表達式的復雜性。
在 UIViewController 的每個 Objective-C 實例方法上創建一個斷點。 在 LLDB 會話中輸入以下內容:
(lldb) rb '\-\[UIViewController\ '
丑陋的反斜杠是轉義符,表示你希望字符在正則表達式里。 因此,此查詢會在包含字符串 -[UIViewController 后跟一個空格的每個方法上中斷。
但是等等…… Objective-C 的類別怎么樣? 它們采用 (-|+)[ClassName(categoryName) method] 的形式。 你還必須重寫正則表達式以包含類別。
在 LLDB 會話中輸入以下內容,并在提示時輸入 y 以確認:
(lldb) breakpoint delete
此命令將刪除設置的所有斷點。
接下來,輸入以下內容:
(lldb) rb '\-\[UIViewController(\(\w+\))?\
在斷點中的 UIViewController 之后,提供了帶有一個或多個字母數字字符后跟空格的可選括號。
使用正則表達式斷點可以使用單個表達式捕獲各種斷點。
你可以使用 -f 選項將斷點的范圍限制為特定文件。 例如,你可以輸入以下內容:
(lldb) rb . -f DetailViewController.swift
如果你正在調試 DetailViewController.swift,這將非常有用。
它將在此文件中的所有屬性 getter/setter,塊(block)/閉包(closure),擴展(extension)/類別(category)和函數(function)/方法(method)上設置斷點。 -f 稱為范圍限制。
如果你異常瘋狂并且是痛苦的粉絲(醫生稱之為自虐?),你可以省略范圍限制并簡單地這樣做:
(lldb) rb .
這將在所有內容上創造一個斷點…… 是的,所有! 這將在信號項目中的所有代碼上創建斷點,UIKit 和 Foundation 中的所有代碼 ,所有事件運行循環代碼都被(期望上)觸發至 60 赫茲 —— 所有。 因此,如果執行此操作,做好在調試器中輸入不少 continue 的準備。
還有其他方法可以限制搜索范圍。 可以使用 -s 選項限制到單個庫:
(lldb) rb . -s Commons
這將在 Commons 庫中的所有內容上設置斷點,這是一個包含在信號項目中的動態庫。
這不僅限于自己的代碼;你可以使用相同的策略在 UIKit 中的每個函數上創建斷點,如下所示:
(lldb) rb . -s UIKit
即使這仍然有點瘋狂。 在iOS 11.0中有非常多的方法 —— 大約有 86577 個 UIKit 方法。 如何只在命中 UIKit 的第一種方法時停止,并簡單地繼續? -o 選項為此提供了解決方案。 它創造了所謂的“一次性”斷點。 當這些斷點命中時,斷點將被刪除。 所以它只會打一次。
要查看此操作,請在 LLDB 會話中輸入以下內容:
(lldb) breakpoint delete
(lldb) rb . -s UIKit -o
注意:在計算機執行此命令時要耐心等待,因為 LLDB 要創建大量斷點。 還要確保使用的是模擬器,否則你會等很長時間!
接下來,繼續調試器,然后單擊表視圖中的單元格。 調試器在此操作中調用的第一個 UIKit 方法上停止。 最后,繼續調試器,斷點將不再觸發。
其他很酷的斷點選項
-L 選項允許按源語言進行過濾。 因此,如果你只想在信號應用程序的 Commons 模塊中尋找 Swift 代碼,可以執行以下操作:
(lldb) breakpoint set -L swift -r . -s Commons
這將在 Commons 模塊中的每個 Swift 方法上設置斷點。
如果你想在 Swift 的 if let 周圍尋找一些有趣的東西,但完全忘記它在應用程序哪里,該怎么辦? 您可以使用源正則表達式斷點來幫助計算感興趣的位置! 像這樣:
(lldb) breakpoint set -A -p "if let"
這將在包含 if let 的每個源代碼位置創建一個斷點。 你當然可以更加花哨,因為 -p 采用正則表達式斷點來處理復雜的表達式。 -A 選項表示搜索項目已知的所有源文件。
如果要將上述斷點查詢過濾為僅在 MasterViewController.swift 和 DetailViewController.swift,則可以執行以下操作:
(lldb) breakpoint set -p "if let" -f MasterViewController.swift -f DetailViewController.swift
注意 -A 已經消失,以及每個 -f 如何指定文件名。 我很懶,所以我通常會默認 -A 從所有文件入手。
最后,你還可以按特定模塊進行過濾。 如果你想為 Signals 可執行文件中的任何內容創建一個“if let”的斷點(忽略其他框架,如 Commons),你可以這樣做:
(lldb) breakpoint set -p "if let" -s Signals -A
這將獲取所有源文件(-A),但僅將其過濾為屬于 Signals 可執行文件的文件(使用 -s Signals 選項)。
還有沒有一個很酷的斷點選項示例? 好的,你跟我談過這個。 讓我們稍微提高復雜性并制作一個“高級”斷點。
如果你想在 -[UIView setTintColor:] 上設置一個斷點,但只有在從 Signals 可執行文件中實現的代碼中調用該方法時才會停止該怎么做?
有幾種方法可以實現這一點,有一個創造性方法來實現 —— 使用斷點條件或 -c 選項。
首先,你需要弄清楚 Signals 可執行文件中的代碼駐留在內存中的上限和下限。 通常,代碼位于 __TEXT 段的 __text 部分。 不要擔心這對于現在意味著什么,我們將在后面的章節中介紹 Mach-O 文件格式的細節。 現在,只需將 __TEXT 段視為每個可執行文件和框架所具有的可讀和可執行代碼的分組。
你可以使用 LLDB 轉儲 Signals 可執行文件中的 Mach-O 段和部分,使用以下命令:
(lldb) image dump sections Signals
抓取 __TEXT 段的上限和下限,因為實際的可執行代碼將駐留在這些地址范圍中。
對于我的情況,地址范圍從 0x0000000108056000 開始,到 0x0000000108067000 結束(你的地址范圍將不同)。 因此,我可以使用以下斷點僅在 -[UIView setTintColor:] 從 Signals 可執行文件中的任何代碼中調用時停止。
(lldb) breakpoint set -n "-[UIView setTintColor:]" -c "*(uintptr_t*)$rsp <= 0x0000000108067000 && *(uintptr_t*)$rsp >= 0x0000000108056000"
這里使用 x86_64 調用約定的知識(這只適用于 64 位 iOS 模擬器),以及調用函數時堆棧指針寄存器的工作方式。 我們不會詳細介紹它是如何工作的,但快速總結一下,在 x86_64 匯編中,在調用函數之后,堆棧指針將包含一個指向調用函數的返回地址的指針。 你將在第12章“匯編和堆棧”中深入研究堆棧指針寄存器和基址指針寄存器。
為了使這個斷點策略發揮作用,你還要做最后一件事。 通常,如果你創建斷點,LLDB 將在函數開頭跳過一些有助于設置邏輯的匯編指令(稱為函數序言)。 當發生這種情況時,堆棧指針的頭部將不再包含指向返回地址的指針(頭部將指向新的東西)。 這意味著你需要告知 LLDB 在開始設置匯編指令之前就停止。 你可以使用以下命令執行此操作:
(lldb) settings set target.skip-prologue false
在后面的章節中,我將使你將此設置保存到 LLDB 初始化文件(~/.lldbinit)中,因為它對于探索傳遞給函數的參數非常有用。 即使這一點在目前沒有任何意義,請不要擔心 —— 你會到達那里!
修改和刪除斷點
現在你已經基本了解了如何創建這些斷點,你可能想知道如何更改它們。 如果你找到了感興趣的對象并希望刪除斷點,或暫時禁用它,該怎么做? 如果你需要修改斷點以在下次觸發時執行特定操作,該怎么做?
首先,你需要了解如何唯一地標識一個或一組斷點。 你還可以在創建時使用 -N 選項命名斷點…… 如果使用數字你不太喜歡。
構建并運行應用程序以重置 LLDB 會話。 接下來,暫停調試器并在 LLDB 會話中輸入以下內容:
(lldb) b main
輸出看起來像這樣:
Breakpoint 1: 70 locations.
這將創建一個包含 70 個位置的斷點,與各個模塊中的“main”函數相匹配。
在這種情況下,斷點 ID 為 1,因為它是你在此會話中創建的第一個斷點。 要查看有關此斷點的詳細信息,可以使用 breakpoint list 子命令。 輸入以下內容:
(lldb) breakpoint list 1
輸出看起來類似于下面的截斷輸出:
1: name = 'main', locations = 70, resolved = 70, hit count = 0
1.1: where = Signals`main at AppDelegate.swift, address = 0x00000001098b1520, resolved, hit count = 0
1.2: where = Foundation`-[NSThread main], address = 0x0000000109bfa9e3, resolved, hit count = 0
1.3: where = Foundation`-[NSBlockOperation main], address = 0x0000000109c077d6, resolved, hit count = 0
1.4: where = Foundation`-[NSFilesystemItemRemoveOperation main], address = 0x0000000109c40e99, resolved, hit count = 0
1.5: where = Foundation`-[NSFilesystemItemMoveOperation main], address = 0x0000000109c419ee, resolved, hit count = 0
1.6: where = Foundation`-[NSInvocationOperation main], address = 0x0000000109c6aee4, resolved, hit count = 0
1.7: where = Foundation`-[NSDirectoryTraversalOperation main], address = 0x0000000109caefa6, resolved, hit count = 0
1.8: where = Foundation`-[NSOperation main], address = 0x0000000109cfd5e3, resolved, hit count = 0
1.9: where = Foundation`-[_NSFileAccessAsynchronousProcessAssertionOperation main], address = 0x0000000109d55ca9, resolved, hit count = 0
1.10: where = UIKit`-[_UIFocusFastScrollingTest main], address = 0x000000010b216598, resolved, hit count = 0
1.11: where = UIKit`-[UIStatusBarServerThread main], address = 0x000000010b651e97, resolved, hit count = 0
1.12: where = UIKit`-[_UIDocumentActivityDownloadOperation main], address = 0x000000010b74f718, resolved, hit count = 0
這顯示了該斷點的詳細信息,包括包含“main”一詞的所有位置。
更簡潔的方法是輸入以下內容:
(lldb) breakpoint list 1 -b
這將為你提供在視覺上更容易的輸出。 如果你有一個封裝了很多斷點的斷點 ID,那么這個簡短的標志就是一個很好的解決方案。
如果要查詢 LLDB 會話中的所有斷點,只需省略 ID,如下所示:
(lldb) breakpoint list
你還可以指定多個斷點 ID 和范圍:
(lldb) breakpoint list 1 3
(lldb) breakpoint list 1-3
使用 breakpoint delete 刪除所有斷點有點笨拙。 你可以簡單地使用 breakpoint list 命令中使用的相同 ID 模式來刪除集合。
你可以通過指定 ID 來刪除單個斷點,如下所示:
(lldb) breakpoint delete 1
但是,“main”的斷點有 70 個位置(可能更多或更少,具體取決于 iOS 版本)。你也可以刪除單個位置,如下所示:
(lldb) breakpoint delete 1.1
這將刪除斷點 1 的第一個子斷點,結果是只刪除一個 main 函數斷點,同時保持其余的 main 斷點處于活動狀態。
接下來?
你在本章中已經學習了很多內容。 斷點是一個很大的主題,掌握快速找到感興趣的內容的藝術對于成為調試專家至關重要。 你還開始使用正則表達式探索函數搜索。 現在是了解正則表達式語法的好時機,因為你將在本書的其余部分使用大量正則表達式。
查看 https://docs.python.org/2/library/re.html 以學習(或重新學習)正則表達式。 嘗試弄清楚如何進行不區分大小寫的斷點查詢。
你剛剛開始發現編譯器如何在 Objective-C 和 Swift 中生成函數。 嘗試找出停止 Objective-C 塊或 Swift 閉包的語法。 完成后,嘗試設計一個僅在信號項目的 Commons 框架內的 Objective-C 塊上停止的斷點。 這些是你將來需要構建更復雜斷點的正則表達式技能。