iOS 開發之LLDB調試命令初探

../art/lldb_in_xc5_command_window_2x.png
若是你在平時的開發中從未使用過調試器,那你恐怕不知道一個調試器的做用有多大。你可能只知足於經過printf或者NSLog輸出信息用於調試。但你只要試着嘗試在調試中開始使用調試器LLDB,你會立刻感覺到調試器給你帶來的便利。
LLDBLLVM下的調試器。Xcode從4.0開始編譯器開始改用LLVM,相應的調試器也從gdb改成LLDB。而從 Xcode5.0開始全部工程也被自動設置爲使用LLDB。下面本文從初學者的角度講解在平常的開發中如何使用LLDB以及LLDB經常使用的命令。html

初識LLDB

你可能從未使用過LLDB,那讓咱們先來熱熱身。 在調試器中最經常使用到的命令是p(用於輸出基本類型)或者po(用於輸出 Objective-C 對象)。以下,你能夠經過輸入po 和 view 來輸出 view 的信息:c++

po [self view]

隨後調試器會輸出這個 object 的 description。在這個例子中多是這樣的信息:git

(UIView *) $1 = 0x0824c800 <UITableView: 0x824c800; frame = (0 20; 768 1004); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x74c3010>; layer = <CALayer: 0x74c2710>; contentOffset: {0, 0}>

什麼?在什麼地方能夠輸入這個命令?OK,首先,咱們須要先設置一個斷點。以下圖所示,我在viewDidLoad:中設置了一個了一個斷點:github

圖二:斷點圖

接下來運行程序,而後程序會停留在斷點處,從下圖你能夠看到在什麼地方輸入LLDB命令:express

圖三:輸入命令位置

你可能須要的是 view 下 subview 的數量。因爲 subview 的數量是一個 int 類型的值,因此咱們使用命令papp

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

最後你看到的輸出會是:oop

(int) $2 = 2

是否是很簡單?
細心的朋友可能會發現輸出的信息中帶有$1$2的字樣。實際上,咱們每次查詢的結果會保存在一些持續變量中($[0-9]+),這樣你能夠在後面的查詢中直接使用這些值。好比如今我接下來要從新取回$1的值:學習

(lldb) po $1
(UIView *) $1 = 0x0824c800 <UITableView: 0x824c800; frame = (0 20; 768 1004); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x74c3010>; layer = <CALayer: 0x74c2710>; contentOffset: {0, 0}>

能夠看到,咱們依然能夠取到以前[self view]的值。ui

LLDB命令還能夠用在斷點上,詳細的使用能夠參見這個文章spa

經常使用命令

下面補充說明其它一些經常使用的命令:

  • expr

能夠在調試時動態執行指定表達式,並將結果打印出來。經常使用於在調試過程當中修改變量的值。
圖四:expr截圖
如圖設置斷點,而後運行程序。程序中斷後輸入下面的命令:

expr a=2

你會看到以下的輸出:

(int) $0 = 2

繼續運行程序,程序輸出的信息是:

實際值:2

很明顯能夠看出,變量a的值被改變。 除此以外,還可使用這個命令新聲明一個變量對象,如:

expr int $b=2
p $b

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

  • call

call便是調用的意思。其實上述的po和p也有調用的功能。所以通常只在不須要顯示輸出,或是方法無返回值時使用call。 和上面的命令同樣,咱們依然在viewDidLoad:裏面設置斷點,而後在程序中斷的時候輸入下面的命令:

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

繼續運行程序,看看view的背景顏色是否是變成紅色的了!在調試的時候靈活運用call命令能夠起到事半功倍的做用。

  • bt

打印調用堆棧,加all可打印全部thread的堆棧。不詳細舉例說明,感興趣的朋友能夠本身試試。

  • image

image 命令可用於尋址,有多個組合命令。比較實用的用法是用於尋找棧地址對應的代碼位置。 下面我寫了一段代碼

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

這段代碼有明顯的錯誤,程序運行這段代碼後會拋出下面的異常:

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 + 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(能夠根據執行文件名判斷,或者最小的棧地址)。爲了進一步精肯定位,咱們能夠輸入如下的命令:

image lookup --address 0x0000000100004af8

命令執行後返回:

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

咱們能夠看到,出錯的位置是RootViewController.m的第53行。


更多的命令能夠參見這個網址
另外,facebook開源了他們擴展的LLDB命令庫,有興趣的朋友也能夠安裝看看。

簡稱和別名

不少時候,LLDB完整的命令是很長的。好比前面所說的image lookup --address這個組合命令。爲了方便平常的使用,提升效率,LLDB命令也提供經過簡稱的方式調用命令。仍是這個命令,咱們用簡稱就能夠寫爲im loo -a,是否是簡單多了。
若是你是從gdb時代就開始使用調試器的,你會發現,有些命令如pcall等命令和gdb下是一致的。其實這些命令是LLDB一些命令的別名,好比pframe variable的別名,p view其實是frame variable view。除了系統自建的LLDB別名,你也能夠自定義別名。好比下面這個命令

command alias ioa image lookup --address %1

是將我前面所介紹過的一個命令image lookup --address添加了一個ioa的別名。而後執行下面的命令:

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

能夠看到,咱們獲得了咱們想要的結果,而命令卻大大縮短。 
這裏我就再也不詳細展開,有興趣的朋友能夠查看這個網址

常見問題

上面咱們簡單的學習瞭如何使用LLDB命令。但有時咱們在使用這些LLDB命令的時候,依然可能會遇到一些問題。

不明類型或者類型不匹配

好比下面這個命令。

(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命令中發現有 unknown type 的相似錯誤(多見於id類型,好比NSArray中某個值),那咱們就必須顯式聲明類型。好比上面這個命令,咱們得這麼修改。

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

這樣就能獲得正確的結果了。 另外,lldb是不支持宏的,須要咱們本身替換。

找不到方法

常見於輸出frame的時候。好比你可能會獲得如下的錯誤信息:

1234
(lldb) po self.view.frameerror: unsupported expression with unknown typeerror: unsupported expression with unknown typeerror: 2 errors parsing expression

這彷佛是lldb的一個bug,沒法經過點屬性訪問的方法打印framework裏面的對象,可是本身在app裏面定義的就能夠。咱們把上面的命令改動一下:

12
(lldb) p (CGRect)[self.view frame](CGRect) $0 = origin=(x=0, y=0) size=(width=320, height=480)

總結

經過上面一些簡單的講解,相信朋友們已經知道如何使用LLDB命令來提升本身的效率了。Enjoy it!

相關文章
相關標籤/搜索