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命令:
你可能需要的是 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
可以在調試時動態執行指定表達式,並將結果打印出來。常用於在調試過程中修改變量的值。
如圖設置斷點,然後運行程序。程序中斷後輸入下面的命令:
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 |
|
現在,我們懷疑出錯的地址是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])
這樣就能得到正確的結果了。 另外,lldb是不支持宏的,需要我們自己替換。
找不到方法
常見於輸出frame的時候。比如你可能會得到以下的錯誤信息:
1 2 3 4 |
|
這似乎是lldb的一個bug,無法通過點屬性訪問的方法打印framework裏面的對象,但是自己在app裏面定義的就可以。我們把上面的命令改動一下:
1 2 |
|
總結
通過上面一些簡單的講解,相信朋友們已經知道如何使用LLDB命令來提高自己的效率了。Enjoy it!