Advanced+Apple+Debugging(4)

來源:互聯網
上載者:User

標籤:lldb

不管你使用的是Swift, Objective-C, C++, C,或者其他的程式設計語言, 你都需要學習如何建立一個斷點.在Xcode這樣的GUI程式中, 在編輯介面的左邊點一下建立一個斷點是非常簡單的, 但是在LLDB控制台中可以讓你更靈活的控制斷點.
在本章中, 你將會學到LLDB中所有關於斷點的知識.

Signals

在本章中, 你將會使用我提供的一個工程; 這個工程的名字叫做Signals, 你可以在資源檔夾裡面找到它.

page42image6392.png
用Xcode開啟Signals項目.Signals是一個以美式橄欖球項目為主題的APP, 顯示一些叫做nerdily的×××型打法.
在程式內部, 這個項目會監測幾個Unix signals並在Signals程式收到這些訊號的時候顯示這些訊號.

Unix signals是進程之間通訊的基本形式. 例如, signals中的SIGSTOP可以儲存一個進程的狀態並且暫停進程的執行.與之對應的是SIGCONT, 他告訴程式繼續執行.這兩個signals都可以被調試器用來暫停或繼續程式的執行.

在前面這些章節中這算是一個有趣的程式, 因為它不僅用來瀏覽Unix 處理signal, 而且會高亮顯示(LLDB)控制的線程在處理經過的Unix signals時發生了什麼.預設情況下, LLDB在處理不同的signals時有自訂的動作.當附加了LLDB以後有些signals是不會經過被控制的線程的.

為了顯示signal, 你既可以在應用程式中增加一個Signal, 也可以在外部的應用程式中發送一個signal, 比如在Terminal中.

此外, 這裡有一個UISwitch可以切換處理signal的代碼塊, 代碼塊的名字叫做sigprocmask來啟用或者禁用signal的處理.

最後, Signal應用程式還有在應用程式裡增加SIGSTOP的Timeout按鈕, 從根本上凍結應用程式.然而, 如果LLDB附加到了Signals應用程式上 (並且在預設情況下就是這樣做的, 當你通過Xcode來構建和運行Signals的時候), 調用SIGSTOP可以讓你在Xcode裡用LLDB檢查線程的至此NG狀態.

確保選中的是iPhone 7 Simulator. 構建並運行這個APP.一旦項目運行起來了以後, 在Xcode的控制台裡找到並暫停調試器.

page43image20376.png

繼續運行Xcode並留意觀察模擬器.當調試器暫停然後繼續執行的時候一行新資料會被添加到UITableView上. 這是Signals監測器監測到Unix signal的SIGSTOP事件時添加的一個資料.當線程被停止的時候, 任何新的signals都不會被立即處理, 因為可以認為程式 已經停止了.
Xcode斷點

在你學完之前, LLDB控制台的斷點會是一個亮點, 非常值得用來替代你再Xode中學到的斷點;
Symbolic breakpoints 是一個Xcode中一個非常好的調試功能.它允許你在應用程式中的某些symbol處設定斷點.一個非常簡單的例子就是-[NSObject init], 用的是NSObject執行個體的 init方法.
在Xcode中可以靈活的使用symbolic斷點, 一旦你建立了一個斷點, 下次你啟動應用程式的時候可以不用重新輸入.
現在你將會用symbolic斷點來顯示出所有被建立的NSObject的執行個體.
如果APP正在運行那麼就殺掉APP的進程.然後切換到Breakpoint導覽列.在左下角點擊+按鈕, 並選擇 Symbolic Breakpoint選項.

page44image11344.png

會出現一個彈窗.在彈窗的Symbol部分輸入:-[NSObject init].在Action下面, 選擇Add Action然後在下拉選項中選擇Debugger Command, 接下來在輸入框中輸入po [$arg1 class].
最後,選擇 Automatically continue after evaluating actions. 你的彈窗看起來應該是下面的樣子:
page44image11776.png

構建並運行APP.當Signals程式啟動並執行時候Xcode將會在控制台輸出初始化的所有的類名, 你會看到非常的多.
在這裡你設定了一個-[NSObject init]每調用一次都會觸發的斷點.當斷點觸發的時候LLDB會運行一條指令, 並自動讓應用程式繼續執行.
注意: 在第十章“Assembly, Registers and Calling Convention”中你將會學到如果恰當的使用和操縱寄存器, 但是現在只需要簡單的知道$arg1與寄存器中的$rdi的同義字, 可以簡單的看作是調用init方法的類的一個執行個體.
如果你完成了查看所有類名的提取操作, 然後用在breakpoint導覽列中點擊右鍵選擇刪除斷點的方法刪除斷點.
除了symbolic斷點, Xcode還支援幾種其他錯誤類型的斷點.其中有一個叫Exception Breakpoint.有時候你的程式會拋出一些錯誤,這些錯誤會導致崩潰.你的第一反應應該就是啟用exception breakpoint. Xcode將會自動定位到出問題的那行代碼, 這對於捕獲崩潰有很大的協助.
最後, 還有一個斷點類型是Swift Error Breakpoint.這種類型的斷點本質上是在在swift_willThrow方法中建立一個斷點並在swift拋出異常的時候觸發.在我們使用那些容易出錯的APIs的時候這是我們可以用的一個非常好的選項.它可以讓你在沒有對代碼的對錯做出判斷的時候快速的診斷出現的問題.

LLDB斷點文法

現在你已經掌握了Xcode中調試崩潰功能的課程, 是時候學習一下如何通過LLDB控制台建立斷點了. 為了建立有用的斷點, 你需要學習一下如何查詢你要找的東西.
image命令是一個查看內部詳情的極其有用的工具, 對於設定斷點來說是至關重要的.
本書中在搜尋代碼的時候有兩個至關重要的配置.第一個就是:

(lldb) image lookup -n "-[UIViewController viewDidLoad]"
這個命令可以提取出-[UIViewController viewDidLoad]函數的載入地址.-n參數告訴LLDB可以尋找symbol或者函數名.輸出的可能是下面這個樣子:

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選項(如果你查詢的內容中包含空格則需要用引號引起來).-n只能協助你提取出匹配到的斷點的準確的參數, 尤其是處理swift的時候, 同時-rn選項在本書中將會經常用到, 因為你很快就會發現一個漂亮的Regex可以減少許多輸入.

Objective-C的屬性

學習如何在代碼中查詢載入的代碼是學習如何建立斷點的最終目標.
無論是Objective-C 還是Swift代碼在被編譯器建立的時候都有指定的屬性簽名, 我們需要使用不同的斷點策略.
例如在Signals項目中下面的Objective-C類聲明了一個屬性:

@interface TestClass : NSObject
@property (nonatomic, strong) NSString *name;br/>@end
getter看起來是這個樣子:

-[TestClass name]
setter方法看起來是這個樣子:

-[TestClass setName:]
構建並運行APP, 然後暫停調試器. 接下來在LLDB中鍵入以下命令驗證一下這些方法是存在的:

(lldb) image lookup -n "-[TestClass name]"
在控制台中你將會看到下面這些輸出:

1 match found in /Users/derekselander/Library/Developer/Xcode/
DerivedData/Signals-bqrjxlceauwfuihjesxmgfodimef/Build/Products/Debug-
iphonesimulator/Signals.app/Signals:
Address: Signals[0x0000000100001470] (Signals.TEXT.text + 0)
Summary: Signals`-[TestClass name] at TestClass.h:28
LLDB將會提取出可執行檔中包含的函數的資訊.這些代碼看起來有點嚇人, 但是卻有一些有用的資訊在裡面.
這些輸出告訴你LLDB在Signals可執行檔中能夠找到這些函數的實現.並且在__TEXT分段中準確的位移量是0x0000000100001470.LLDB還告訴我們這個方法生命在TestClass.h檔案中的第28行.
你同樣可以用下面的方法查看setter方法:

(lldb) image lookup -n "-[TestClass setName:]"
你應該會得到和之前類似的輸出, 這一次顯示的是name這個屬性setter方法的實現和實現的地址.

Swift 屬性

在Swift中聲明屬性的文法有些不同. 看一下SwiftTestClass.swift檔案你會發現下面的代碼:

class SwiftTestClass: NSObject {
var name: String!
}
確保Signals項目正在運行並且暫停在LLDB中. 你可以按下Command + K快速的清空LLDB的控制台.
在LLDB控制台中輸入以下命令:

(lldb) image lookup -rn Signals.SwiftTestClass.name.setter
你將會得到一些類似下面的輸出:

2 matches found in /Users/derekselander/Library/Developer/Xcode/
DerivedData/Signals-bqrjxlceauwfuihjesxmgfodimef/Build/Products/Debug-
iphonesimulator/Signals.app/Signals:
Address: Signals[0x000000010000aba0] (Signals.TEXT.text +
38704)
Summary: Signals@objc Signals.SwiftTestClass.name.setter :<br/>Swift.ImplicitlyUnwrappedOptional&lt;Swift.String&gt; at SwiftTestClass.swift<br/>Address: Signals[0x000000010000ac60] (Signals.__TEXT.__text + 38896)<br/>Summary: SignalsSignals.SwiftTestClass.name.setter :
Swift.ImplicitlyUnwrappedOptional<Swift.String> at SwiftTestClass.swift
在輸出中找到Summary這個單詞.這裡有兩個有趣的事情需要注意.
首先, 有兩個symbols被發現了.第一個和第二個有著同樣的名字;然而, 第一個有@objc的首碼. 這是編譯器加上去的特定的函數是用來作為一個橋接函數的. 這有助於swift和Objective-C的混編.
第二, 你看到了函數名字是多麼的長嗎?要建立一個有效Swift斷點就需要這麼一個完整的名字.

如果你想在這個setter方法中設定一個斷點, 你需要按照下面的方法做:

(lldb) b Signals.SwiftTestClass.name.setter :
Swift.ImplicitlyUnwrappedOptional<Swift.String>
使用Regex可以減少大量的輸入.
撇開你產生的swift函數名的長度不說, 注意一下Swift的屬性的形式.在屬性name函數的簽名中單詞setter緊跟在屬性的後面. 也許getter方法也有著同樣的格式?
嘗試捕獲一下SwiftTestClass類的name屬性的getter和setter方法, 與此同時, 使用下面的Regex來查詢:

(lldb) image lookup -rn Signals.SwiftTestClass.name
這條命令使用Regex查詢並提取Signals.SwiftTestClass.name中的一切.
由於這是一個Regex, 所以.是一個萬用字元, 同時在函數簽名單重又用來匹配點文法.
你會得到一些合理的輸出, 但是每一次在控制台的輸出中你都會看到Summary.你會發現輸出匹配了getter, (Signals.SwiftTestClass.name.getter) , setter,(Signals.SwiftTestClass.name.setter), @objc對應的橋接符, 以及一個包含materializeForSet的方法, 這個方法你再後面會學到.
下面是Swift屬性函數名的樣本:

ModuleName.Classname.PropertyName.(getter|setter)
提取方法,找到樣本和縮小你搜尋範圍的能力, 是在你代碼中建立智能斷點解開Swift/Objective-C語言奧秘的非常偉大的途徑.

最後...建立斷點

現在你已經知道了如何在你的代碼中查詢已經存在的函數和方法, 是時候在這些方法上建立斷點了.
如果你的Signals項目正在運行, 停止並重啟這個程式, 然後點擊暫停按鈕停止應用程式並進入LLDB控制台.
有幾種不同的方法建立斷點.最簡單的方法是輸入小寫字母b緊跟著後面是函數名.這在Objective-C 和 C當中非常的簡單, 因為它的名字很短也很容易輸入(例如, -[NSObject init]). 而在C++和swift中卻十分複雜, 因為編譯器會為函數返回給你一個相當長的名字.
鑒於UIKit主要是用Objective-C(至少在寫這本書的時候是這樣實現的), 嘗試用 b參數建立一個斷點, 像下面這樣:

(lldb) b -[UIViewController viewDidLoad]
你會看到下面這些輸出:

Breakpoint 1: where = UIKit`-[UIViewController viewDidLoad], address =
0x0000000102bbd788
每當你建立了一個有效斷點, 控制台都會輸出一些關於這個斷點的資訊.
在這次特定的情況下, 這個斷點作為Breakpoint 1被建立, 因為在這個特定的偵錯工作階段中這是建立的第一個斷點.當你建立更多斷點的時候, 斷點ID會不斷的增長.
繼續運行調試器, 一旦你繼續執行一個新的SIGSTOP會被顯示出來.在儲存格上點擊進入詳情的UIViewController.在詳情視圖控制器調用viewDidLoad的時候程式應該會暫停.

注意:像許多快捷命令一樣, b是LLDB命令中一個長單詞的縮寫. 你可以自己運行help b 命令查看它的協助, 並學習它很酷的技巧.
除了b命令以外,還有一個長的breakpoint set命令, 這個命令有很多可用的選項. 你在後面的幾個章節中會用到它. 許多命令都是breakpoint set命令的分支.

正則斷點和範圍

另一個極其有用的命令是Regex斷點rbreak是breakpoint set -r %1.你可以用智能的Regex斷點快速的在你想要停下來的許多地方建立斷點.
讓我們回頭看看之前的很長的那個swift屬性name函數的例子, 與下面的輸入不同的是:

(lldb) b Breakpoints.SwiftTestClass.name.setter :
Swift.ImplicitlyUnwrappedOptional<Swift.String>
你可以輸入:

(lldb) rb SwiftTestClass.name.setter
儘管上面的命令更短一點, 但是也有一個煩人的地方.這個斷點也會捕捉到Objective-C橋接的name屬性的setter方法, 當這個方法被調用的時候, 會強制停止兩次.
你可以給這個斷點添加一個參數^(@).*, 這個參數的本質是說"過濾掉以@xxx開始的函數".在未來的幾個章節中, 你將構建一個執行正則搜尋的命令自動過濾掉這些橋接函數.
從現在開始你只需要處理兩個斷點. 另一種更加簡潔的寫法是, 下面這種形式:

(lldb) rb name.setter
這會在任何包含name.setter的地方建立一個斷點.這在你知道項目裡沒有其他地方swift屬性的明知叫做name的時候是行的通的.否則的話你就會在在每一處都建立一個斷點.
現在嘗試在UIViewController的每一個Objective-C 的執行個體方法裡建立一個斷點. 我們可以在LLDB中輸入下面的指令:

(lldb) rb ‘-[UIViewController\ ‘
反斜線是逸出字元用來指明你再Regex中想要搜尋的原意字元.那麼結果就是, 這條指令會在每一個字串裡包含-[UIViewController後面跟著一個空格的地方停下來.
等一下...在Objective-C的categories裡是怎樣的呢?他們提供了一種(-|+)[ClassName(categoryName) method]的形式.你可以用包含categories的形式重寫Regex.
在LLDB會話中輸入以下命令, 並在提示用輸入y進行確認:

(lldb) breakpoint delete
這條命令會刪除你設定的所有的斷點.
然後輸入下面的內容:

(lldb) rb ‘-[UIViewController((\w+))?\ ‘
這條命令在斷點的UIViewController後面在空格前面提供了一個包含一個或多個字元的括弧選項.
Regex斷點讓你用一個運算式捕獲一個寬泛的斷點.
你可以用-f選項來將斷點指定在特定的檔案中.

(lldb) rb . -f DetailViewController.swift
這在你調試DetailViewController.swift的時候很有用.它會在這個檔案的所有屬性的getters/setters, blocks/closures, extensions/ categories, 和 functions/methods出打下斷點.-f選項是用來限定範圍的.
如果你徹底瘋了或者想要折磨一下自己, 你可以想下面這樣簡單的指定一下範圍:

(lldb) rb .
這會在所有的地方建立一個斷點...是的, 是所有地方!這會在Signals項目的所有代碼裡建立斷點, 所有UIKit的代碼和所有Foundation的代碼, 所有運行迴圈的代碼.結果是, 你需要在調試器中不停的輸入continue才能繼續執行.
還有另外一種方式來指定搜尋的範圍.你可以用-s選項將範圍限制在單一的庫中:

(lldb) rb . -s Commons
這會在Commons庫中的每一個地方設定一個斷點, Commons是Signals項目中包含的一個動態庫.
你也可以用同樣的方法在UIKit的每一個函數裡建立斷點:

(lldb) rb . -s UIKit
這顯然太瘋狂了.在iOS10.0系統裡UIKit裡大概有66,189個方法.如何在UIKit裡只在你第一次觸發這個斷點的時候停下來呢?-o選項提供了一個解決方案.它建立了one-shot型的斷點. 這種斷點在觸發一次後就會自動刪除. 因此只會觸發一次.
要看這種方法的效果, 可以在LLDB會話中輸入下面的命令:

(lldb) breakpoint delete
(lldb) rb . -s UIKit -o
注意:當LLDB執行這些命令的時候請耐心等待, 因為LLDB建立了大量的斷點.並且要確保你使用的是模擬器, 否則的話你將會等待非常長的時間.
接下來, 讓調試器繼續執行, 並在tableView中的一個cell上點擊一下.調試器就會在UIKit的方法第一次調用的時候停下來.最後, 繼續運行調試器, 並且這個斷點將不會再次觸發.

修改和刪除斷點

現在你已經對如何建立斷點有了基本的理解, 你可能還會想知道你如何改變它們. 當你想要修改, 刪除或者暫時停用一個斷點的時候該怎麼做?當你想讓斷點下次觸發的時候執行一個特定的命令你又該怎麼做呢?
首先, 你需要知道怎樣唯一的標記一個或者一組斷點. 在你建立斷點的時候你也可以用-N 選項命名一個斷點...用數字做標記可能真的不適合你.
構建並運行APP來清空LLDB會話.接下來, 暫停調試器並在LLDB會話中輸入一下命令:

(lldb) b main
你可能會看到下面這些輸出:

Breakpoint 1: 20 locations.
這句話的是意思是說你再不同模組中的與main匹配的20個地方建立了斷點.
在這種情況下, 這個斷點的的ID是1, 因為它是你建立的第一個斷點. 要查看這個斷點的詳細資料, 你可以用breakpoint list命令.輸入下面的內容:

(lldb) breakpoint list 1
應該會輸出下面類似的內容:

1: name = ‘main‘, locations = 20, resolved = 20, hit count = 0
1.1: where = Breakpointsmain + 22 at AppDelegate.swift:12, address =<br/>0x00000001057676e6, resolved, hit count = 0<br/>1.2: where = Foundation-[NSThread main], address = 0x000000010584d182,
resolved, hit count = 0
1.3: where = Foundation-[NSBlockOperation main], address =<br/>0x000000010585df4a, resolved, hit count = 0<br/>1.4: where = Foundation-[NSFilesystemItemRemoveOperation main],
address = 0x00000001058990ff, resolved, hit count = 0
1.5: where = Foundation-[NSFilesystemItemMoveOperation main], address<br/>= 0x0000000105899c23, resolved, hit count = 0<br/>1.6: where = Foundation-[NSInvocationOperation main], address =
0x00000001058c4fb9, resolved, hit count = 0
1.7: where = Foundation-[NSDirectoryTraversalOperation main], address<br/>= 0x000000010590a87f, resolved, hit count = 0<br/>1.8: where = Foundation-[NSOperation main], address =
0x000000010595209c, resolved, hit count = 0
1.9: where = UIKit-[UIStatusBarServerThread main], address =<br/>0x00000001068b84f0, resolved, hit count = 0<br/>1.10: where = UIKit-[_UIDocumentActivityItemProvider main], address =
0x000000010691898c, resolved, hit count = 0
1.11: where = UIKit-[_UIDocumentActivityDownloadOperation main],<br/>address = 0x0000000106975d51, resolved, hit count = 0<br/>1.12: where = UIKit-[_UIGetAssetThread main], address =
0x000000010698ef4d, resolved, hit count = 0
1.13: where = UIKit-[UIWebPDFSearchOperation main], address =<br/>0x0000000106ae7c99, resolved, hit count = 0<br/>1.14: where = UIKit-[UIActivityItemProvider main], address =
0x0000000106c4e525, resolved, hit count = 0
1.15: where = MobileCoreServices-[LSOpenOperation main], address =<br/>0x000000010879703c, resolved, hit count = 0<br/>1.16: where = ImageIOmain, address = 0x000000010c87535d, resolved, hit
count = 0
1.17: where = AppSupport-[_CPPowerAssertionThread main], address =<br/>0x000000010ed95f03, resolved, hit count = 0<br/>1.18: where = AppSupport-[CPDistributedMessagingAsyncOperation main],
address = 0x000000010ed9ba53, resolved, hit count = 0
1.19: where = JavaScriptCoreWTF::RunLoop::main(), address =<br/>0x0000000111c68af0, resolved, hit count = 0<br/>1.20: where = ConstantClassesmain, address = 0x0000000114329cd2,
resolved, hit count = 0
這裡顯示了這個斷點的詳細內容, 包含所有包含main這個單詞的位置.
顯示這些資訊的簡介內容的方式是輸入下面的內容:

(lldb) breakpoint list 1 -b
這會輸出一些簡潔的更可視化的資訊.如果你有一個斷點的ID, 並且這個ID裡包含著多個斷點, 這個簡明的標誌是一個好的解決方案.
如果你想要查看LLDB中所有的斷點, 只需要簡單的輸入下面的內容:

(lldb) breakpoint list
你也可以指名多個斷點的ID和範圍:

(lldb) breakpoint list 1 3
(lldb) breakpoint list 1-3
用breakpoint delete命令刪除所有的斷點是一個重量級的操作.你可以簡單的使用斷點的ID來刪除一個斷點或者斷點的集合:
你可以用指定斷點ID的方式刪除一個斷點:

(lldb) breakpoint delete 1
然而, "main"斷點裡麵包含了20個斷點.你也可以用下面這總方式刪除一個斷點:

(lldb) breakpoint delete 1.1
這會刪除1斷點的第一個自斷點.

我們為什麼要學這些?

在本章節中你學到了大量的內容. 斷點是一個很大的話題並且對調試專家來說是一門主要的藝術用來快速的發現並找到實物本質.你也已經瞭解了如何用Regex來搜尋代碼.現在是時候梳理一下Regex的文法了, 在本書的後面你將會用到大量的Regex.
瀏覽 https://docs.python.org/2/library/re.html 來學習Regex.並嘗試找出如何建立不區分大小寫Regex.
現在你只不過是出不得瞭解了一下編譯器是如何產生Objective-C 和 Swift的函數的.嘗試找出如何在Objective-C的blocks或者Swift的closures裡建立斷點的代碼.如果你做到了, 嘗試設計出一個斷點只在Signals項目裡的Commonsframework的Objective-C blocks裡停止的斷點.這些都是你再未來建立更複雜的斷點所需要的技能.

Advanced+Apple+Debugging(4)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.