作者:@李富強Jason 授權本站轉載。
Breakpoints
BreakPoint分類
breakpoint也是有分類的,我這裡的文章內大致按使用的方式分為了 Normal Breakpoint,Exception Breakpoint,OpenGL ES Error breakpoint,Symbolic Breakpoint,Test Failure Breakpoint,WatchPoints。可以按具體的情景使用不同類型的breakpoint,解決問題為根本。
Normal Breakpoint
添加普通斷點就不多說了,在源代碼的右側點擊一下即可。或者,使用快捷鍵:command + \ 來添加和刪除。這兩種方式添加的breakpoints在Xcode上面是可以通過UI看到的。
還有可以通過下面兩個LLDB命令直接在運行時添加斷點,但是這種方式需要注意的是一方面無法通過UI直接看到斷點,另外一方面只存在於本次運行,下一次啟動模擬器重新運行的時候,這些斷點就不生效了。
如上圖,通過“br li”命令打印所有的breakpoint,可以看到一共有3個breakpoint,第一個是通過Xcode的UI添加的,後面兩個分別是通過下面兩個命令添加的:
“breakpoint set -f XXX.m -l XX” 和 “b XXX.m:XX”。
Exception Breakpoint
可以通過下圖中Xcode的UI添加Exception Breakpoint。有時候,比如數組越界或者設置一個空對象等問題,都會拋出一個異常,但是這種類型的錯誤非常難以定位,這個時候就可以使用Exception Breakpoint來進行調試,在異常發生時可以捕捉到並停止程序的執行。OC中的異常是一個常被忽略的地方,但實際上系統框架內這個使用非常廣泛,大部分這種錯誤信息,系統框架都會以異常的形式throw出來,所以善用這種breakpoint的話,我們能大大減少查找錯誤的時間。
例如,當我們添加如下Exception Breakpoint之後(bt 命令後文中會講解,這個命令的作用是在斷點觸發時,打印回調棧信息):
類似下面這樣的數組越界的問題,我們可以很容易就定位到問題所在,不用再毫無頭緒找來找去了:
當斷點暫停執行時,我們可以通過Xcode的UI中查看調用棧信息:
或者查看bt命令打印的調用棧信息:
還有類似如下的錯誤可以通過這種斷點很容易定位到:
,不過這種問題,可以通過使用setValue:forKey:代替來避免。
OpenGL ES Error Breakpoint
同上圖中,在Xcode的breakpoint navigator的下部添加按鈕,選擇”Add OpenGL ES Error Breakpoint”即可。這個breakpoint主要是用來在OpenGL ES發生錯誤時停止程序的運行。
Symbolic Breakpoint
通過Xcode的UI添加symbolic breakpoint的方式同exception breakpoint,彈出框如下:
Symbolic breakpoints 在某個特定的函數或者方法開始執行的時候,暫停程序的執行,通過這種方式添加斷點,我們就不需要知道在源文件中添加,也不需要知道斷點設置在文件的第幾行。
上圖中,最主要的設置是Symbol的內容,可以有如下幾種:
1. A method name,方法名稱,例如 pathsMatchingExtensions: 這樣的方法名稱,會對所有類的這個方法都起作用。
2. A method of a particular class. 特定類的某個方法。例如 ,[SKLine drawHandlesInView],或者 people::Person::name()
3. A function name。函數名稱。例如 ,_objc_msgForward 這樣C函數。
另外,也可以通過命令行的方式添加 Symbolic breakpoints。對C函數添加斷點:
對OC的方法添加斷點:
常用的這個類型的斷點有,objc_exception_throw可以用來代替 Exception Breakpoint,還有一個-[NSObject doesNotRecognizeSelector:] 也比較常用,用於檢測方法調用失敗。
Test Failure Breakpoint
通過Xcode的UI添加方法同上。這個類型的break point 會在 test assertion 失敗的時候暫停程序的執行。
Watchpoints
Watuchpoints是一個用來監聽變量的值的變化或者內存地址的變化的工具,發生變化時會在debugger中觸發一個暫停。對於那些不知道如何准確跟蹤的狀態問題,可以利用這個工具來解決。要設置watchpoint的話,在程序運行到stack frame包含有你想觀察的變量時,讓debugger暫停運行,這個時候變量在當前stack frame的scope內,這個時候才能對該變量設置watchpoint。
你可以在Xcode的GUI中設置watchpoint,在xcode的 Variables View中,把你想觀察的變量保留出來,然後右鍵設置“Watch XXX”。例如下圖,觀察self的title變量,點擊 Watch “_button1ClickCount” 即可。
命令行
或者也可以通過命令行來設置watchpoint:watch set variable _button1ClickCount,詳細命令可以參考:http://lldb.llvm.org/lldb-gdb.html,有好幾種命令可以達到同樣的效果。
上面是對變量進行觀察,實際上我們可以對任意內存地址進行觀察,命令如下:watchpoint set expression — 0x123456,參考:http://stackoverflow.com/questions/21063995/watch-points-on-memory-address
需要注意的是,watchpoint是分類型的,包括read,write或者read_write類型,這個非常容易理解,在讀,寫或者讀寫變量或內存的時候,watchpoint是否被觸發。read,write或read_write跟著-w參數後面表示類型。另外,命令行中,watchpoint還有一些簡寫,set簡寫為s,watch簡寫為wa,variable簡寫為v。
下面的示例是來自 http://www.dreamingwish.com/article/lldb-usage-a.html 網站的幾個命令:
第一個命令是監聽_abc4變量的內存地址write的變化,第二個是監聽_abc4變量read的變化,第三個是監聽_abc3變量read_write的變化。
需要注意的是,通過Xcode的GUI添加的watchpoint為默認類型,即write類型,如果想要添加讀寫都watch的watchpoint,則只能通過命令行工具進行添加了。
使用watchpoint modify -c ‘(XXX==XX)’,則修改watchpoint之後在某個值的時候才會監聽。
編輯選項
BreakPoint Condition
當我們通過Xcode對breakpoint進行編輯時,可以發現normal breakpoint和symbolic breakpoint都有一個”Condition”輸入選項,這個的作用很容易理解,只有在設置的condition表達式為YES的情況下這些斷點才會起作用。
例如,下圖中的breakpoint在判斷字符串相等的時候才會停止運行:
可以注意到這裡使用stringWithUTF8Stirng:方法,原因在於lldb的expression parser有一個bug,不兼容非ASCII字符,需要處理一下才行,否則會報錯“An Objective-C constant string's string initializer is not an array”,參考:http://stackoverflow.com/questions/17192505/error-in-breakpoint-condition
更加簡單一些的例子就不說了,比如 i == 99之類的簡單比較,只要表達式的結果為BOOL類型即可。
Breakpoint Actions
可以看到上面的每種breakpoint編輯選項中基本上都有“Add Action”選項,當breakpoint被觸發時,都首先會執行我們設置的這些action,然後我們才能得到控制權,即Xcode上面才會顯示程序停止執行的UI。這個Action通過例子比較好理解,我們通過上面那個setObject:forKey:的異常來說明。代碼如下:
設置Breakpoint:
可以看到上圖中,我們一共設置了3個action。第一個action,用來打印exception的詳細信息,用法參考:http://stackoverflow.com/questions/17238673/xcode-exception-breakpoint-doesnt-print-details-of-the-exception-being-thrown。
第二個action,我們使用shell命令“say”,讓電腦發聲,把一段文字讀出來。
第三個action,我們使用“bt”命令來打印調用棧信息
設置完成之後,當異常發生時,我們會聽到電腦發聲念上圖中的英文,然後在log中可以看到如下信息,第一行是Exception的描述信息,下面是調用堆棧:
Continuing after Evaluation
看一下breakpoint的編輯彈窗,我們可以發現有一個 “Automatically continue after evaluation actions” checkbox選項。當我們勾選這個checkbox之後,debugger會執行breakpoint中添加的所有的actions,然後繼續執行程序。對於我們來說,除了觸發一大堆command並且執行時間很長的情況之外,程序會很快跳過這個breakpoint,所以我們可能根本不會注意到這個breakpoint的存在。所以,這個選項的功能相當於在執行的最後一個action之後,直接輸入continue命令繼續執行。
有了這個很強大的功能,我們可以直接通過breakpoints來單獨對我們的程序進行修改。在某行代碼時停止執行,使用”expression”命令來直接修改程序的某個變量設置直接修改UI,然後繼續執行。expression / call 配合這個選項的時候,會非常強大,可以很方便實現很多很強大的功能。
例如,我們實現一個如下的功能,把tableview的第一個cell的selectBackgroundView的背景色改為紅色:
action的內容為“expression [[cell selectedBackgroundView] setBackgroundColor:[UIColor redColor]]”,這裡的表達式先不用關心,我們後面LLDB章節會講到,修改之後,當我們點擊cell的時候,cell的背景就會如下圖一樣變紅:
使用這種方式,我們在不需要修改一行代碼的情況下,只需要通過修改breakpoint,就可以實現對UI的各種調試效果。
參考:
https://developer.apple.com/library/ios/recipes/xcode_help-breakpoint_navigator/articles/about_breakpoint_navigator.html
http://www.objc.io/issues/19-debugging/lldb-debugging/
http://blog.csdn.net/freshforiphone/article/details/8546677
https://www.bignerdranch.com/blog/xcode-breakpoint-wizardry/
http://lldb.llvm.org/tutorial.html, LLDB命令大全
LLDB
常用命令
help
直接輸入help命令,列出所有可用的commands。
使用 help
expression
簡寫形式:expr / e。在運行調試時執行表達式,用來直接修改程序的變量的值,或者使用這個命令聲明一個變量對象。
例如:修改變量的值:expression count = 20
或者聲明一個新變量a:
expression命令可以帶有參數,但也會帶來一些問題,例如: e -h +17 命令,這個命令就容易產生混淆,到底-h是參數flag,然後+17是輸入變量,還是說要計算 -h+17 表達式的最終值。LLDB提供的解決方式非常簡單,使用“ -- ”來指定命令參數的終止,命令輸入的開始。例如,e -h -- +17表示前文中命令的第一個解釋,e -- -h+17 表示表示前文中命令的第二個解釋。
簡寫形式:prin / pri / p。但是不能用pr表示,因為會和process混淆。實際上,你會發現,lldb對於命令的簡稱,是頭部匹配方式,只要不相互混淆,可以隨意簡稱某個命令。
實際上,如果在console中輸入“help print”,就會得到’print’ is an abbreviation for ‘expression --‘ 這句話,也就是說print實際上就相當於 expression -- 。這裡就很容易理解下面命令是如何工作的,p _lastPoiID=20,這行命令表達式會被執行。
打印一個對象的話,使用 po 命令,使用expression表示的話就是 expression -O -- XXX
使用 print/
既然我們可以print對象和簡單類型,並且在debugger中通過expression命令直接修改他們,那麼我們可以通過使用一些變量來減輕我們的工作量。前面講過我們可以直接使用expression命令來聲明變量,但需要注意的是,新聲明的變量必須以 $ 符號開始。
最後一個命令報錯了,是因為LLDB沒法確定結果的類型,需要強制轉換一下來告訴LLDB。
call
調用命令,類似expression,一般用戶不需要打印結果或者無返回值的地方
bt
打印調用堆棧信息,使用 bt all命令可以打印出所有thread的調用棧信息
比如說:call [self.view setBackgroundColor:[UIColor redColor]],使用這個命令來設置view controller的背景色為紅色
image
image命令可用於尋址,有多個組合命令。比較實用的用法是用於尋找棧地址對應的代碼位置。詳細用法參考:http://www.starfelix.com/blog/2014/03/17/lldbdiao-shi-ming-ling-chu-tan/,http://blog.csdn.net/hursing/article/details/8745334
常見問題
對中文的兼容
前面文章中講過,有中文時必須使用 [NSString stringWithUTF8String:] 方法,原因在於lldb的expression parser有一個bug,不兼容非ASCII字符,需要處理一下才行,否則會報錯“An Objective-C constant string's string initializer is not an array”,參考:http://stackoverflow.com/questions/17192505/error-in-breakpoint-condition
類型的問題
出現類型不確定或類型不匹配的時候,就會報錯,這個時候必須強制轉換才可以。
比如前文中獲取indexPath的row的方法的表達式:(int)[indexPath row],或者類似下面這種干脆無返回值 p (void)NSLog(@"%@",[self.view viewWithTag:1001]),或者上文中的 p (char)[[$array objectAtIndex:$a] characterAtIndex:0],這些場景中都需要明確輸出的類型。
找不到方法
注意,使用系統框架對象的屬性時不能使用dot語法,比如下圖中的問題。
改成如下的格式才行:
Chisel
基於LLDB對Python插件的支持,http://lldb.llvm.org/python-reference.html,facebook開發開源了一套LLDB命令庫,https://github.com/facebook/chisel,裡面包含了很多很有意思的命令工具。安裝方式很簡單,使用brew工具,詳細參考官方網站,不多說。
安裝之後,使用help命令,在下面可以 user-defined commands 可以找到這個框架提供的一些自定義命令。Chisel提供了非常多很有用的命令行工具,調試UI的時候非常方便,具體可以參考官方網站。
參考
http://www.objc.io/issues/19-debugging/lldb-debugging/
http://www.starfelix.com/blog/2014/03/17/lldbdiao-shi-ming-ling-chu-tan/
http://www.dreamingwish.com/article/lldb-usage-a.html
https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/gdb_to_lldb_transition_guide/document/Introduction.html
http://lldb.llvm.org/lldb-gdb.html
http://lldb.llvm.org/tutorial.html
https://github.com/facebook/chisel
版權聲明:本文為博主原創文章,未經博主允許不得轉載。