LLVM下的調(diào)試器LLDB

如果你在平時(shí)的開發(fā)中從未使用過調(diào)試器,那你恐怕不知道一個(gè)調(diào)試器的作用有多大。你可能只滿足于通過printf或者NSLog輸出信息用于調(diào)試。但你只要試著嘗試在調(diào)試中開始使用調(diào)試器LLDB,你會(huì)馬上感受到調(diào)試器給你帶來的便利。LLDBLLVM下的調(diào)試器。Xcode從4.0開始編譯器開始改用LLVM,相應(yīng)的調(diào)試器也從gdb

改為LLDB。而從 Xcode5.0開始所有工程也被自動(dòng)設(shè)置為使用LLDB。下面本文從初學(xué)者的角度講解在日常的開發(fā)中如何使用LLDB以及LLDB常用的命令。

p 或 po :輸出基本類型

$0-9 :持續(xù)變量

expr :動(dòng)態(tài)執(zhí)行調(diào)試表達(dá)式

call :調(diào)用,一般用于無返回值和不要需要顯示返回輸出

image :命令用于尋址,有多個(gè)組合命令

bt :打印調(diào)用堆棧

初識(shí)LLDB

你可能從未使用過LLDB,那讓我們先來熱熱身。 在調(diào)試器中最常用到的命令是p(用于輸出基本類型)或者po(用于輸出 Objective-C 對(duì)象)。如下,你可以通過輸入po 和 view 來輸出 view 的信息:

po [self view]

隨后調(diào)試器會(huì)輸出這個(gè) object 的 description。在這個(gè)例子中可能是這樣的信息:

(UIView *) $1 = 0x0824c800 ; layer = ; contentOffset: {0, 0}>

什么?在什么地方可以輸入這個(gè)命令?OK,首先,我們需要先設(shè)置一個(gè)斷點(diǎn)。如下圖所示,我在viewDidLoad:中設(shè)置了一個(gè)了一個(gè)斷點(diǎn):

接下來運(yùn)行程序,然后程序會(huì)停留在斷點(diǎn)處,從下圖你可以看到在什么地方輸入LLDB命令:

你可能需要的是 view 下 subview 的數(shù)量。由于 subview 的數(shù)量是一個(gè) int 類型的值,所以我們使用命令p:

p (int)[[[self view] subviews] count]

最后你看到的輸出會(huì)是:

(int) $2 = 2

是不是很簡單?

細(xì)心的朋友可能會(huì)發(fā)現(xiàn)輸出的信息中帶有$1、$2的字樣。實(shí)際上,我們每次查詢的結(jié)果會(huì)保存在一些持續(xù)變量中($[0-9]+),這樣你可以在后面的查詢中直接使用這些值。比如現(xiàn)在我接下來要重新取回$1的值:

(lldb) po $1 (UIView *) $1 = 0x0824c800 ; layer = ; contentOffset: {0, 0}>

可以看到,我們依然可以取到之前[self view]的值。

LLDB命令還可以用在斷點(diǎn)上,詳細(xì)的使用可以參見這個(gè)文章

常用命令

下面補(bǔ)充說明其它一些常用的命令:

expr

可以在調(diào)試時(shí)動(dòng)態(tài)執(zhí)行指定表達(dá)式,并將結(jié)果打印出來。常用于在調(diào)試過程中修改變量的值。

如圖設(shè)置斷點(diǎn),然后運(yùn)行程序。程序中斷后輸入下面的命令:

expr a=2

你會(huì)看到如下的輸出:

(int) $0 = 2

繼續(xù)運(yùn)行程序,程序輸出的信息是:

實(shí)際值:2

很明顯可以看出,變量a的值被改變。 除此之外,還可以使用這個(gè)命令新聲明一個(gè)變量對(duì)象,如:

expr int $b=2 p $b

下面的命令用于輸出新聲明對(duì)象的值。(注意,對(duì)象名前要加$)

call

call即是調(diào)用的意思。其實(shí)上述的po和p也有調(diào)用的功能。因此一般只在不需要顯示輸出,或是方法無返回值時(shí)使用call。 和上面的命令一樣,我們依然在viewDidLoad:里面設(shè)置斷點(diǎn),然后在程序中斷的時(shí)候輸入下面的命令:

call [self.view setBackgroundColor:[UIColor redColor]]

繼續(xù)運(yùn)行程序,看看view的背景顏色是不是變成紅色的了!在調(diào)試的時(shí)候靈活運(yùn)用call命令可以起到事半功倍的作用。

bt

打印調(diào)用堆棧,加all可打印所有thread的堆棧。不詳細(xì)舉例說明,感興趣的朋友可以自己試試。

image

image 命令可用于尋址,有多個(gè)組合命令。比較實(shí)用的用法是用于尋找棧地址對(duì)應(yīng)的代碼位置。 下面我寫了一段代碼

NSArray *arr=[[NSArray alloc] initWithObjects:@"1",@"2", nil]; NSLog(@"%@",arr[2]);

這段代碼有明顯的錯(cuò)誤,程序運(yùn)行這段代碼后會(huì)拋出下面的異常:

1234567891011121314151617181920212223242526272829303132

*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 2 beyond bounds [0 .. 1]'*** First throw call stack:(0 CoreFoundation 0x0000000101951495 __exceptionPreprocess + 1651 libobjc.A.dylib? x00000001016b099e objc_exception_throw + 432 CoreFoundation 0x0000000101909e3f -[__NSArrayI objectAtIndex:] + 1753 ControlStyleDemo 0x0000000100004af8 -[RootViewController viewDidLoad] + 3124 UIKit 0x000000010035359e -[UIViewController loadViewIfRequired] + 5625 UIKit 0x0000000100353777 -[UIViewController view] + 296 UIKit 0x000000010029396b -[UIWindow addRootViewControllerViewIfPossible] + 587 UIKit 0x0000000100293c70 -[UIWindow _setHidden:forced:] + 2828 UIKit 0x000000010029cffa -[UIWindow makeKeyAndVisible] + 519 ControlStyleDemo 0x00000001000045e0 -[AppDelegate application:didFinishLaunchingWithOptions:] + 67210 UIKit 0x00000001002583d9 -[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 26411 UIKit 0x0000000100258be1 -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 160512 UIKit 0x000000010025ca0c -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] + 66013 UIKit 0x000000010026dd4c -[UIApplication handleEvent:withNewEvent:] + 318914 UIKit 0x000000010026e216 -[UIApplication sendEvent:] + 7915 UIKit 0x000000010025e086 _UIApplicationHandleEvent + 57816 GraphicsServices 0x0000000103aca71a _PurpleEventCallback + 76217 GraphicsServices 0x0000000103aca1e1 PurpleEventCallback + 3518 CoreFoundation 0x00000001018d3679 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 4119 CoreFoundation 0x00000001018d344e __CFRunLoopDoSource1 + 47820 CoreFoundation 0x00000001018fc903 __CFRunLoopRun + 193921 CoreFoundation 0x00000001018fbd83 CFRunLoopRunSpecific + 46722 UIKit 0x000000010025c2e1 -[UIApplication _run] + 60923 UIKit 0x000000010025de33 UIApplicationMain + 101024 ControlStyleDemo 0x0000000100006b73 main + 11525 libdyld.dylib 0x0000000101fe95fd start + 126 ??? 0x0000000000000001 0x0 + 1)libc++abi.dylib: terminating with uncaught exception of type NSException

現(xiàn)在,我們懷疑出錯(cuò)的地址是0x0000000100004af8(可以根據(jù)執(zhí)行文件名判斷,或者最小的棧地址)。為了進(jìn)一步精確定位,我們可以輸入以下的命令:

image lookup --address 0x0000000100004af8

命令執(zhí)行后返回:

Address: ControlStyleDemo[0x0000000100004af8] (ControlStyleDemo.__TEXT.__text + 13288) Summary: ControlStyleDemo`-[RootViewController viewDidLoad] + 312 at RootViewController.m:53

我們可以看到,出錯(cuò)的位置是RootViewController.m的第53行。

更多的命令可以參見這個(gè)網(wǎng)址

另外,facebook開源了他們擴(kuò)展的LLDB命令庫,有興趣的朋友也可以安裝看看。

簡稱和別名

很多時(shí)候,LLDB完整的命令是很長的。比如前面所說的image lookup --address這個(gè)組合命令。為了方便日常的使用,提高效率,LLDB命令也提供通過簡稱的方式調(diào)用命令。還是這個(gè)命令,我們用簡稱就可以寫為im loo -a,是不是簡單多了。

如果你是從gdb時(shí)代就開始使用調(diào)試器的,你會(huì)發(fā)現(xiàn),有些命令如p、call等命令和gdb下是一致的。其實(shí)這些命令是LLDB一些命令的別名,比如p是frame variable的別名,p view實(shí)際上是frame variable view。除了系統(tǒng)自建的LLDB別名,你也可以自定義別名。比如下面這個(gè)命令

command alias ioa image lookup --address %1

是將我前面所介紹過的一個(gè)命令image lookup --address添加了一個(gè)ioa的別名。然后執(zhí)行下面的命令:

(lldb) ioa 0x0000000100004af8? Address: ControlStyleDemo[0x0000000100004af8] (ControlStyleDemo.__TEXT.__text + 13288)? Summary: ControlStyleDemo`-[RootViewController viewDidLoad] + 312 at RootViewController.m:53

可以看到,我們得到了我們想要的結(jié)果,而命令卻大大縮短。

這里我就不再詳細(xì)展開,有興趣的朋友可以查看這個(gè)網(wǎng)址

常見問題

上面我們簡單的學(xué)習(xí)了如何使用LLDB命令。但有時(shí)我們?cè)谑褂眠@些LLDB命令的時(shí)候,依然可能會(huì)遇到一些問題。比如下面這個(gè)命令。

(lldb) p NSLog(@"%@",[self.view? viewWithTag:1001]) error: 'NSLog' has unknown return type; cast the call to its declared return type error: 1 errors parsing expression

如果在使用LLDB命令中發(fā)現(xiàn)有 unknown type 的類似錯(cuò)誤(多見于id類型,比如NSArray中某個(gè)值),那我們就必須顯式聲明類型。比如上面這個(gè)命令,我們得這么修改。

p (void)NSLog(@"%@",[self.view? viewWithTag:1001])

這樣就能得到正確的結(jié)果了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容