天天看點

LLDB調試指令初探

轉自:http://www.starfelix.com/blog/2014/03/17/lldbdiao-shi-ming-ling-chu-tan/

LLDB調試指令初探

如果你在平時的開發中從未使用過調試器,那你恐怕不知道一個調試器的作用有多大。你可能隻滿足于通過

printf

或者

NSLog

輸出資訊用于調試。但你隻要試着嘗試在調試中開始使用調試器LLDB,你會馬上感受到調試器給你帶來的便利。

LLDB是LLVM下的調試器。Xcode從4.0開始編譯器開始改用LLVM,相應的調試器也從gdb改為LLDB。而從 Xcode5.0開始所有工程也被自動設定為使用LLDB。下面本文從初學者的角度講解在日常的開發中如何使用LLDB以及LLDB常用的指令。

初識LLDB

你可能從未使用過LLDB,那讓我們先來熱熱身。 在調試器中最常用到的指令是

p

(用于輸出基本類型)或者

po

(用于輸出 Objective-C 對象)。如下,你可以通過輸入po 和 view 來輸出 view 的資訊:

po [self view]
           

随後調試器會輸出這個 object 的 description。在這個例子中可能是這樣的資訊:

(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:

中設定了一個了一個斷點:

LLDB調試指令初探

接下來運作程式,然後程式會停留在斷點處,從下圖你可以看到在什麼地方輸入LLDB指令:

LLDB調試指令初探

你可能需要的是 view 下 subview 的數量。由于 subview 的數量是一個 int 類型的值,是以我們使用指令

p

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

最後你看到的輸出會是:

(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]的值。

LLDB指令還可以用在斷點上,詳細的使用可以參見這個文章

常用指令

下面補充說明其它一些常用的指令:

  • expr

可以在調試時動态執行指定表達式,并将結果列印出來。常用于在調試過程中修改變量的值。

LLDB調試指令初探

如圖設定斷點,然後運作程式。程式中斷後輸入下面的指令:

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]);
           

這段代碼有明顯的錯誤,程式運作這段代碼後會抛出下面的異常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
      
*** 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時代就開始使用調試器的,你會發現,有些指令如

p

call

等指令和gdb下是一緻的。其實這些指令是LLDB一些指令的别名,比如

p

frame 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])
           

這樣就能得到正确的結果了。

繼續閱讀