開發(fā)iOS的時(shí)候常常會(huì)用到調(diào)試跟蹤,如何正確的使用調(diào)試器來debug十分重要。
Xcode里有內(nèi)置的Debugger,老版使用的是GDB,Xcode自4.3之后默認(rèn)使用的就是LLDB了。
GDB: UNIX及UNIX-like下的調(diào)試工具。
LLDB: 開源的內(nèi)置于XCode的具有REPL(read-eval-print-loop)特征的Debugger,其可以安裝C++或者Python插件。
兩個(gè)都是調(diào)試用的Debugger,只是LLDB是比較高級(jí)的版本,或者在調(diào)試開發(fā)iOS應(yīng)用時(shí)比較好用,lldb與gdb命令名的對(duì)照表:http://lldb.llvm.org/lldb-gdb.html
開始使用LLDB
在什么地方可以輸入這個(gè)命令?
首先, 在程序里你需要的地方設(shè)置斷點(diǎn)。
當(dāng)斷點(diǎn)斷住的時(shí)候你就能看到我們進(jìn)入LLDB調(diào)試器了。

這時(shí)就可以使用一些LLDB命令來進(jìn)行一些調(diào)試了。
一些Xcode調(diào)試快捷鍵:
command+shift+Y 打開調(diào)試窗口
command+Y 調(diào)試運(yùn)行程序
command+option+P 繼續(xù)
command+shift+O 跳過
command+shift+I 進(jìn)入
command+shift+T 跳出
常用命令
<li>help</li>
最簡(jiǎn)單命令 help 會(huì)列舉出所有的命令。如果你忘記了一個(gè)命令是做什么的,或者想知道更多的話,你可以通過 help <command> 來了解更多細(xì)節(jié),例如 help print 或者 help thread。


<li>print</li>
試試 print 命令:

LLDB 實(shí)際上會(huì)作前綴匹配。所以你也可以使用 prin,pri,或者 p。但你不能使用 pr,因?yàn)?LLDB 不能消除和 process 的歧義。
結(jié)果中有個(gè) $0。實(shí)際上你可以使用它來指向這個(gè)結(jié)果。試試 print $0 + 7,你會(huì)看到 106。任何以美元符開頭的東西都是存在于 LLDB 的命名空間的,它們是為了幫助你進(jìn)行調(diào)試而存在的。
輸出view 下 subview 的數(shù)量
//由于 subview 的數(shù)量是一個(gè) int 類型的值,所以我們使用命令p:
(lldb)p (int)[[[self view] subviews] count]
直接調(diào)用方法改變背景顏色之類
其實(shí)使用p,po,call都可以調(diào)用方法,只是p和po都是用于輸出的有返回值的。call一般只在不需要顯示輸出,或是方法無返回值時(shí)使用。
(lldb)p [self.view setBackgroundColor:[UIColor redColor]]
(lldb)p (void)[CATransaction flush]
上述的p一般使用call比較好,因?yàn)榉椒ㄊ菦]有返回值的。
<li>p objects</li>
命令p objects跟p很像。p輸出的是基本類型,po輸出的Objective-C對(duì)象。調(diào)試器會(huì)輸出這個(gè) object 的 description。
po (print object 的縮寫):

<li>expression</li>
如果想改變一個(gè)值怎么辦?其實(shí)這時(shí)候我們要用到的是 expression 這個(gè)方便的命令。
expression的簡(jiǎn)寫就是e??梢杂胑xpression來聲明新的變量,也可以改變已有變量的值。我們看到e聲明的都是$開頭的變量。我們?cè)谑褂脮r(shí)也需要加上$符號(hào)。
創(chuàng)建新的變量示例:

注意:如果上面這里輸入以下命令,會(huì)發(fā)生錯(cuò)誤。說明lldb無法判定某一步的計(jì)算結(jié)果是什么數(shù)據(jù)類型,這時(shí)需要強(qiáng)制類型轉(zhuǎn)換來告訴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 命令可用于尋址,有多個(gè)組合命令。
比較實(shí)用的用法是用于尋找棧地址對(duì)應(yīng)的代碼位置。
如:
NSArray *arr=[[NSArray alloc] initWithObjects:@"1",@"2", nil];
NSLog(@"%@",arr[2]);
這段代碼有明顯的錯(cuò)誤,程序運(yùn)行這段代碼后會(huì)拋出下面的異常:
*** 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
現(xiàn)在,我們懷疑出錯(cuò)的地址是0x0000000100004af8(可以根據(jù)執(zhí)行文件名判斷,或者最小的棧地址)。為了進(jìn)一步精確定位,我們可以輸入以下的命令:
(lldb)image lookup --address 0x0000000100004af8
(lldb)im loo -a 0x0000000100004af8
命令執(zhí)行后返回:
Address: ControlStyleDemo[0x0000000100004af8] (ControlStyleDemo.__TEXT.__text + 13288)
Summary: ControlStyleDemo`-[RootViewController viewDidLoad] + 312 at RootViewController.m:53
可以看到,出錯(cuò)的位置是<code>RootViewController.m</code>的第53行。
<li>call</li>
call即是調(diào)用的意思。其實(shí)上述的po和p也有調(diào)用的功能。
因此一般只在不需要顯示輸出,或是方法無返回值時(shí)使用call。
和上面的命令一樣,我們?cè)趘iewDidLoad:里面設(shè)置斷點(diǎn),然后在程序中斷的時(shí)候輸入下面的命令:
call [self.view setBackgroundColor:[UIColor redColor]]
繼續(xù)運(yùn)行程序,看看view的背景顏色是不是變成紅色的了!
在調(diào)試的時(shí)候靈活運(yùn)用call命令可以起到事半功倍的作用。
<li>bt</li>
打印調(diào)用堆棧,加all可打印所有thread的堆棧。
<li>流程控制命令</li>
實(shí)際上使用xcode自帶的可視化工具來控制“繼續(xù)”“暫停”“下一步”“進(jìn)入”“跳出”更簡(jiǎn)單,但這里還是列出其所對(duì)應(yīng)的命令名:
繼續(xù):process continue, continue, c
下一步:thread step-over, next, n
進(jìn)入:thread step-in, step, s
跳出:thread step-out, finish, f
<li>thread return</li>
執(zhí)行thread return命令可以使得當(dāng)前函數(shù)立即返回,也就是說,后續(xù)代碼都不會(huì)執(zhí)行了。當(dāng)然執(zhí)行此命令可能會(huì)使得arc的計(jì)數(shù)追蹤出現(xiàn)錯(cuò)亂。
thread return命令需要一個(gè)參數(shù)來指明函數(shù)強(qiáng)制返回時(shí)的返回值。
<li>斷點(diǎn)命令</li>
斷點(diǎn)有很多進(jìn)階使用方法:條件斷點(diǎn)、條件執(zhí)行、記錄日志、自動(dòng)繼續(xù)、重復(fù)斷點(diǎn)跳過。
使用xcode提供的可視化工具來操作是很容易的:

<li>調(diào)試中執(zhí)行任意代碼</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的顏色、尺寸、甚至創(chuàng)建controller來push。
<li>watchpoint</li>
watchpoint可以在某個(gè)變量被寫入/讀取時(shí)暫停程序運(yùn)行:
(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
實(shí)際上可以使用watchpoint來監(jiān)視任意一段內(nèi)存的讀寫。
使用XCode也可以方便地創(chuàng)建watchpoint。
XCode的可視化debug工具中的watch是一個(gè)write類型watchpoint(也就是默認(rèn)的)
另外,上述語(yǔ)句中 v是variable的簡(jiǎn)寫,同樣的,set可以簡(jiǎn)寫為s,watch可以簡(jiǎn)寫為wa,而-w后面的參數(shù)是不可以簡(jiǎn)寫的必須為read、write或者read_write。
當(dāng)前在arm和x86上,我們一次最多創(chuàng)建4個(gè)watchpoint,繼續(xù)創(chuàng)建會(huì)提示錯(cuò)誤。
<li>查看內(nèi)存</li>
使用XCode的可視化工具來查看memory,要注意watch memory of "p" 和watch memory of "*p"的區(qū)別。
手動(dòng)執(zhí)行命令可以help x或者 help memory。