開發iOS的時候常常會用到調試跟蹤,如何正確的使用調試器來debug十分重要。
Xcode里有內置的Debugger,老版使用的是GDB,Xcode自4.3之后默認使用的就是LLDB了。
GDB: UNIX及UNIX-like下的調試工具。
LLDB: 開源的內置于XCode的具有REPL(read-eval-print-loop)特征的Debugger,其可以安裝C++或者Python插件。
兩個都是調試用的Debugger,只是LLDB是比較高級的版本,或者在調試開發iOS應用時比較好用,lldb與gdb命令名的對照表:http://lldb.llvm.org/lldb-gdb.html
開始使用LLDB
在什么地方可以輸入這個命令?
首先, 在程序里你需要的地方設置斷點。
當斷點斷住的時候你就能看到我們進入LLDB調試器了。
這時就可以使用一些LLDB命令來進行一些調試了。
一些Xcode調試快捷鍵:
command+shift+Y 打開調試窗口
command+Y 調試運行程序
command+option+P 繼續
command+shift+O 跳過
command+shift+I 進入
command+shift+T 跳出
常用命令
<li>help</li>
最簡單命令 help 會列舉出所有的命令。如果你忘記了一個命令是做什么的,或者想知道更多的話,你可以通過 help <command> 來了解更多細節,例如 help print 或者 help thread。
<li>print</li>
試試 print 命令:
LLDB 實際上會作前綴匹配。所以你也可以使用 prin,pri,或者 p。但你不能使用 pr,因為 LLDB 不能消除和 process 的歧義。
結果中有個 $0。實際上你可以使用它來指向這個結果。試試 print $0 + 7,你會看到 106。任何以美元符開頭的東西都是存在于 LLDB 的命名空間的,它們是為了幫助你進行調試而存在的。
輸出view 下 subview 的數量
//由于 subview 的數量是一個 int 類型的值,所以我們使用命令p:
(lldb)p (int)[[[self view] subviews] count]
直接調用方法改變背景顏色之類
其實使用p,po,call都可以調用方法,只是p和po都是用于輸出的有返回值的。call一般只在不需要顯示輸出,或是方法無返回值時使用。
(lldb)p [self.view setBackgroundColor:[UIColor redColor]]
(lldb)p (void)[CATransaction flush]
上述的p一般使用call比較好,因為方法是沒有返回值的。
<li>p objects</li>
命令p objects跟p很像。p輸出的是基本類型,po輸出的Objective-C對象。調試器會輸出這個 object 的 description。
po (print object 的縮寫):
<li>expression</li>
如果想改變一個值怎么辦?其實這時候我們要用到的是 expression 這個方便的命令。
expression的簡寫就是e??梢杂胑xpression來聲明新的變量,也可以改變已有變量的值。我們看到e聲明的都是$開頭的變量。我們在使用時也需要加上$符號。
創建新的變量示例:
注意:如果上面這里輸入以下命令,會發生錯誤。說明lldb無法判定某一步的計算結果是什么數據類型,這時需要強制類型轉換來告訴lldb。
(lldb) p [[$array objectAtIndex:0] characterAtIndex:0]
error: no known method '-characterAtIndex:'; cast the message send to the method's return type
error: 1 errors parsing expression
(lldb) p (char)[[$array objectAtIndex:0] characterAtIndex:0]
'o'
修改已有變量示例:
<li>image</li>
image 命令可用于尋址,有多個組合命令。
比較實用的用法是用于尋找棧地址對應的代碼位置。
如:
NSArray *arr=[[NSArray alloc] initWithObjects:@"1",@"2", nil];
NSLog(@"%@",arr[2]);
這段代碼有明顯的錯誤,程序運行這段代碼后會拋出下面的異常:
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 2 beyond bounds [0 .. 1]'
*** First throw call stack:
(
0 CoreFoundation 0x0000000101951495 __exceptionPreprocess + 165
1 libobjc.A.dylib 0x00000001016b099e objc_exception_throw + 43
2 CoreFoundation 0x0000000101909e3f -[__NSArrayI objectAtIndex:] + 175
3 ControlStyleDemo 0x0000000100004af8 -[RootViewController viewDidLoad] + 312
4 UIKit 0x000000010035359e -[UIViewController loadViewIfRequired] + 562
5 UIKit 0x0000000100353777 -[UIViewController view] + 29
6 UIKit 0x000000010029396b -[UIWindow addRootViewControllerViewIfPossible] + 58
7 UIKit 0x0000000100293c70 -[UIWindow _setHidden:forced:] + 282
8 UIKit 0x000000010029cffa -[UIWindow makeKeyAndVisible] + 51
9 ControlStyleDemo 0x00000001000045e0 -[AppDelegate application:didFinishLaunchingWithOptions:] + 672
10 UIKit 0x00000001002583d9 -[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 264
11 UIKit 0x0000000100258be1 -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1605
12 UIKit 0x000000010025ca0c -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] + 660
13 UIKit 0x000000010026dd4c -[UIApplication handleEvent:withNewEvent:] + 3189
14 UIKit 0x000000010026e216 -[UIApplication sendEvent:] + 79
15 UIKit 0x000000010025e086 _UIApplicationHandleEvent + 578
16 GraphicsServices 0x0000000103aca71a _PurpleEventCallback + 762
17 GraphicsServices 0x0000000103aca1e1 PurpleEventCallback + 35
18 CoreFoundation 0x00000001018d3679 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 41
19 CoreFoundation 0x00000001018d344e __CFRunLoopDoSource1 + 478
20 CoreFoundation 0x00000001018fc903 __CFRunLoopRun + 1939
21 CoreFoundation 0x00000001018fbd83 CFRunLoopRunSpecific + 467
22 UIKit 0x000000010025c2e1 -[UIApplication _run] + 609
23 UIKit 0x000000010025de33 UIApplicationMain + 1010
24 ControlStyleDemo 0x0000000100006b73 main + 115
25 libdyld.dylib 0x0000000101fe95fd start + 1
26 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
現在,我們懷疑出錯的地址是0x0000000100004af8(可以根據執行文件名判斷,或者最小的棧地址)。為了進一步精確定位,我們可以輸入以下的命令:
(lldb)image lookup --address 0x0000000100004af8
(lldb)im loo -a 0x0000000100004af8
命令執行后返回:
Address: ControlStyleDemo[0x0000000100004af8] (ControlStyleDemo.__TEXT.__text + 13288)
Summary: ControlStyleDemo`-[RootViewController viewDidLoad] + 312 at RootViewController.m:53
可以看到,出錯的位置是<code>RootViewController.m</code>的第53行。
<li>call</li>
call即是調用的意思。其實上述的po和p也有調用的功能。
因此一般只在不需要顯示輸出,或是方法無返回值時使用call。
和上面的命令一樣,我們在viewDidLoad:里面設置斷點,然后在程序中斷的時候輸入下面的命令:
call [self.view setBackgroundColor:[UIColor redColor]]
繼續運行程序,看看view的背景顏色是不是變成紅色的了!
在調試的時候靈活運用call命令可以起到事半功倍的作用。
<li>bt</li>
打印調用堆棧,加all可打印所有thread的堆棧。
<li>流程控制命令</li>
實際上使用xcode自帶的可視化工具來控制“繼續”“暫停”“下一步”“進入”“跳出”更簡單,但這里還是列出其所對應的命令名:
繼續:process continue, continue, c
下一步:thread step-over, next, n
進入:thread step-in, step, s
跳出:thread step-out, finish, f
<li>thread return</li>
執行thread return命令可以使得當前函數立即返回,也就是說,后續代碼都不會執行了。當然執行此命令可能會使得arc的計數追蹤出現錯亂。
thread return命令需要一個參數來指明函數強制返回時的返回值。
<li>斷點命令</li>
斷點有很多進階使用方法:條件斷點、條件執行、記錄日志、自動繼續、重復斷點跳過。
使用xcode提供的可視化工具來操作是很容易的:
<li>調試中執行任意代碼</li>
(lldb) e char *$str = (char *)malloc(128)
(lldb) e (void)strcpy($str, "wxrld of warcraft")
(lldb) e $str[1] = 'o'
(char) $0 = 'o'
(lldb) p $str
(char *) $str = 0x00007fd04a900040 "world of warcraft"
(lldb) e (void)free($str)
所以,在debugger中可以修改view的顏色、尺寸、甚至創建controller來push。
<li>watchpoint</li>
watchpoint可以在某個變量被寫入/讀取時暫停程序運行:
(lldb) watchpoint set expression -- (int*)&_abc4
Watchpoint created: Watchpoint 7: addr = 0x15e36d3c size = 4 state = enabled type = w
new value: 0x00000000
(lldb) watchpoint set v -w read _abc4
Watchpoint created: Watchpoint 8: addr = 0x15e36d3c size = 4 state = enabled type = r
watchpoint spec = '_abc4'
new value: 0
(lldb) watchpoint set v -w read_write _abc3
Watchpoint created: Watchpoint 9: addr = 0x15e36d38 size = 4 state = enabled type = rw
watchpoint spec = '_abc3'
new value: 0
實際上可以使用watchpoint來監視任意一段內存的讀寫。
使用XCode也可以方便地創建watchpoint。
XCode的可視化debug工具中的watch是一個write類型watchpoint(也就是默認的)
另外,上述語句中 v是variable的簡寫,同樣的,set可以簡寫為s,watch可以簡寫為wa,而-w后面的參數是不可以簡寫的必須為read、write或者read_write。
當前在arm和x86上,我們一次最多創建4個watchpoint,繼續創建會提示錯誤。
<li>查看內存</li>
使用XCode的可視化工具來查看memory,要注意watch memory of "p" 和watch memory of "*p"的區別。
手動執行命令可以help x或者 help memory。