Xcode的使用中總是離不開調(diào)試這個環(huán)境,在一年多的iOS開發(fā)時間中妥泉,我更多地依賴于XCode本身提供的GUI工具來進行調(diào)試,而對LLDB敬而遠之相种,這段時間好好學習了LLDB的使用遣妥,發(fā)覺我錯過了太多東西了……因此做一個比較完備的總結(jié),也希望在寫這篇文章的過程中進一步學習LLDB調(diào)試的各種實踐方法谈跛。
LLDB闡述
LLDB 是一個有著 REPL 的特性和 C++ ,Python 插件的開源調(diào)試器羊苟。LLDB 綁定在 Xcode 內(nèi)部,存在于主窗口底部的控制臺中感憾。調(diào)試器允許你在程序運行的特定時暫停它蜡励,你可以查看變量的值,執(zhí)行自定的指令阻桅,并且按照你所認為合適的步驟來操作程序的進展凉倚。
lldb下,相信大家用的較多的是po鳍刷,或者使用條件斷點占遥。但是面對復雜的問題時俯抖,比如.a靜態(tài)庫输瓜,就需要其他調(diào)試方法了。
breakpoint
給某個文件的某一行下斷點芬萍∮却В可以使用如下兩種方法,比如我想給Person.m文件的10行下一個斷點柬祠”毕罚可以使用如下的方法。
(lldb) breakpoint set --file Person.m --line 10
如果出現(xiàn)如下提示則說明設(shè)置斷點成功
Breakpoint 2: where = BreakPointDemo`-[Person foo] + 23 at Foo.m:10, address = 0x000000010b22e687
也可以使用簡寫的形式如下漫蛔。
(lldb) breakpoint set -f Foo.m -l 10
給一個函數(shù)下斷點
(lldb) breakpoint set --name foo
(lldb) breakpoint set -n foo
一次性給多個函數(shù)下斷點
(lldb) breakpoint set --name foo --name bar
OC的方法嗜愈,可以使用以下兩種方式打斷點,第二種S需要大寫
(lldb) breakpoint set --selector foo
(lldb) breakpoint set -S foo
使用 正則匹配 你要打斷點的函數(shù)莽龟。這個不限語言
(lldb) breakpoint set -r cFoo
(lldb) breakpoint set -r foo
也可以指定加載的動態(tài)庫
(lldb) breakpoint set --shlib foo.dylib --name foo
(lldb) breakpoint set -s foo.dylib -n foo
我們同樣可以對命令進行簡寫蠕嫁。下面兩個命令的效果是一樣的
(lldb) breakpoint set -n "-[Foo foo]"
(lldb) br s -n "-[Foo foo]"
查看有多少斷點可以使用
(lldb) breakpoint list
打印結(jié)果如下:
Current breakpoints:
1: file = '/Users/jianquan/Xcode/BreakPointDemo/BreakPointDemo/ViewCo ntroller.m', line = 20, exact_match = 0, locations = 0 (pending)
2: file = '/Users/jianquan/Xcode/BreakPointDemo/BreakPointDemo/ViewController.mm', line = 33, exact_match = 0, locations = 1, resolved = 1, hit count = 0
2.1: where = BreakPointDemo`::-[ViewController viewDidLoad]() + 186 at ViewController.mm:34, address = 0x0000000105f8362a, resolved, hit count = 0
......
我們可以對斷點進行相關(guān)的操作,比如在執(zhí)行到2.1斷點的時候打印追蹤軌跡毯盈。bt是
(lldb) breakpoint command add 2.1
Enter your debugger command(s). Type 'DONE' to end.
> bt
> DONE
刪除所有斷點
(lldb) breakpoint delete
watchpoint
觀察變量值的具體變化
比如我需要觀察某個變量a的值變化,我可以使用如下命令
(lldb) watchpoint set variable a
bt命令用來追蹤程序的運行過程
(lldb) bt
* thread #1: tid = 0x5c52c2, 0x000000010ff465fe BreakPointDemo`::-[ViewController viewDidLoad](self=0x00007f932cc07c50, _cmd="viewDidLoad") + 158 at ViewController.mm:36, queue = 'com.apple.main-thread', stop reason = watchpoint 1
* frame #0: 0x000000010ff465fe BreakPointDemo`::-[ViewController viewDidLoad](self=0x00007f932cc07c50, _cmd="viewDidLoad") + 158 at ViewController.mm:36
frame #1: 0x000000011112ba3d UIKit`-[UIViewController loadViewIfRequired] + 1258
......
我們可以使用frame命令查看變量a的具體值剃毒。
(lldb) frame variable a
(int) a = 100
補充一點watchpoint list的東西。這個命令包括了三個可選參數(shù),我們可以使用help命令查看具體的值
(lldb) help watchpoint list
-b ( --brief )
Give a brief description of the watchpoint (no location info).
-f ( --full )
Give a full description of the watchpoint and its locations.
-v ( --verbose )
Explain everything we know about the watchpoint (for debugging
debugger bugs).
-b是比較簡略的信息赘阀,-f是比較全面的信息益缠,-v是完整的信息。經(jīng)過我的實驗基公,如果使用watchpoint list,默認的是 watchpoint list -f幅慌。
process
使用process命令也可以做很多有趣的操作。具體能做什么酌媒,我們也可使用help命令查看
(lldb) process help
attach -- Attach to a process.
connect -- Connect to a remote debug service.
continue -- Continue execution of all threads in the current process.
detach -- Detach from the current target process.
handle -- Manage LLDB handling of OS signals for the current target
......
查看更詳細的命令使用help <command> <subcommand>欠痴。比如
(lldb) help process attach
thread
其實這個功能主要就是斷點調(diào)試里面的如下這個功能。
我們可以使用thread命令來做一些斷點的操作秒咨,具體有那些命令我們可以使用thread help進行查看
(lldb) thread help
......
select -- Change the currently selected thread.
step-in -- Source level single step, stepping into calls.
Defaults to current thread unless specified.
step-inst -- Instruction level single step, stepping into calls.
Defaults to current thread unless specified.
step-inst-over -- Instruction level single step, stepping over calls.
Defaults to current thread unless specified.
step-out -- Finish executing the current stack frame and stop after
returning. Defaults to current thread unless
specified.
step-over -- Source level single step, stepping over calls.
Defaults to current thread unless specified.
step-scripted -- Step as instructed by the script class passed in the -C
option.
until -- Continue until a line number or address is reached by
the current or specified thread. Stops when returning
from the current function as a safety measure.
用得比較多的應(yīng)該是 step-開頭的這幾個命令喇辽,使用起來很容易。我個人感覺比用鼠標點擊斷點好用多了~
檢查當前進程的狀態(tài)雨席,可以使用如下命令
lldb) thread list
Process 22323 stopped
* thread #1: tid = 0x62d0d7, 0x00000001082185fe BreakPointDemo`::-[ViewController viewDidLoad](self=0x00007ff81b60ab20, _cmd="viewDidLoad") + 158 at ViewController.mm:36, queue = 'com.apple.main-thread', stop reason = step until
......
*表明的就是當前的線程菩咨,可以使用如下的命令得到線程的回溯,這個詞我也不確定怎么表達好陡厘,backtrace抽米,也可以說是追蹤。
(lldb) thread backtrace
* thread #1: tid = 0x354e72, 0x0000000103ce94bc RunTimeUse`-[ViewController viewDidLoad](self=0x00007ffea04050e0, _cmd="viewDidLoad") + 140 at ViewController.m:24, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x0000000103ce94bc RunTimeUse`-[ViewController viewDidLoad](self=0x00007ffea04050e0, _cmd="viewDidLoad") + 140 at ViewController.m:24
frame #1: 0x0000000104e21c99 UIKit`-[UIViewController loadViewIfRequired] + 1258
frame #2: 0x0000000104e220cc UIKit`-[UIViewController view] + 27
frame #3: 0x0000000104cebc51 UIKit`-[UIWindow addRootViewControllerViewIfPossible] + 71
frame #4: 0x0000000104cec3a2 UIKit`-[UIWindow _setHidden:forced:] + 293
frame #5: 0x0000000104cffcb5 UIKit`-[UIWindow makeKeyAndVisible] + 42
frame #6: 0x0000000104c78c89 UIKit`-[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 4818
frame #7: 0x0000000104c7ede9 UIKit`-[UIApplication _runWithMainScene:transitionContext:completion:] + 1731
frame #8: 0x0000000104c7bf69 UIKit`-[UIApplication workspaceDidEndTransaction:] + 188
frame #9: 0x0000000107dd4723 FrontBoardServices`__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 24
frame #10: 0x0000000107dd459c FrontBoardServices`-[FBSSerialQueue _performNext] + 189
frame #11: 0x0000000107dd4925 FrontBoardServices`-[FBSSerialQueue _performNextFromRunLoopSource] + 45
frame #12: 0x0000000104802311 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
frame #13: 0x00000001047e759c CoreFoundation`__CFRunLoopDoSources0 + 556
frame #14: 0x00000001047e6a86 CoreFoundation`__CFRunLoopRun + 918
frame #15: 0x00000001047e6494 CoreFoundation`CFRunLoopRunSpecific + 420
frame #16: 0x0000000104c7a7e6 UIKit`-[UIApplication _run] + 434
frame #17: 0x0000000104c80964 UIKit`UIApplicationMain + 159
frame #18: 0x0000000103ce9b1f RunTimeUse`main(argc=1, argv=0x00007fff5bf165e8) + 111 at main.m:14
frame #19: 0x000000010763d68d libdyld.dylib`start + 1
frame #20: 0x000000010763d68d libdyld.dylib`start + 1
當然我們?nèi)绻肟此芯€程的backtrace糙置,可以使用thread backtrace all命令云茸。內(nèi)容太多,我這里就不演示log輸出了谤饭。
如果我們想單獨查看某個線程标捺,我們可以先使用thread select 2跳到某個具體的線程,然后再進行其他操作揉抵,比如thread backtrace
為了方便的觀測架構(gòu)參數(shù)和本地變量亡容,我們可以使用 frame variable 命令
如果我什么參數(shù)也不加,將會把所有的參數(shù)和本地變量到打印出來冤今。
(lldb) frame variable
(ViewController *) self = 0x00007ffea04050e0
(SEL) _cmd = "viewDidLoad"
(Person *) p = 0x0000610000001940
要打印某個變量需要在參數(shù)里面指定闺兢,這個命令我們在前面也使用過,比如要查看self
(lldb) frame variable self
(ViewController *) self = 0x00007ff81b60ab20
更進一步,我們可以查看一些子元素
(lldb) frame variable self->isa
(Class) self->isa = ViewController
命令雖然不是完整的表達式解釋器戏罢,當時可以識別一些基本的操作 比如 &, *, ->, []屋谭,不是重載運算符,數(shù)組也可以使用龟糕,因為數(shù)組本身也是指針桐磁。
(lldb) frame variable *self
(ViewController) *self = {
UIViewController = {
UIResponder = {
NSObject = {
isa = ViewController
}
......
}
和之前thread命令很類似,我可以使用frame select去選擇另外的一個frame
(lldb) frame select 9
如果想看更復雜的數(shù)據(jù)翩蘸,我們可以使用expression命令
(lldb) expression self
(ViewController *) $0 = 0x00007fefa4705110
更復雜一些所意,我們可以用來輸出一個表達式
(lldb) expr (int) printf ("I have a pointer 0x%llx.\n", self)
I have a pointer 0x7fefa4705110.
(int) $1 = 33
call
其實這個命令完全可以使用po進行替代,call一般可以用來調(diào)用不需要返回值的調(diào)試命令,比如更改View的背景顏色扶踊,以下兩個命令都可以達到相似的作用泄鹏,更改當前View的背景顏色值。
(lldb) po [self.view setBackgroundColor:[UIColor redColor]]
(lldb) call [self.view setBackgroundColor:[UIColor redColor]]
image
這個比較實用秧耗,可用于尋找棧地址對應(yīng)的代碼位置备籽。
//測試image命令使用
NSArray *a = @[@"1"];
NSLog(@"%@",a[1]);
很顯然,數(shù)組越界了分井,以下顯示崩潰信息
RunTimeUse[46698:3510999] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSSingleObjectArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]'
*** First throw call stack:
(
0 CoreFoundation 0x000000010934d34b __exceptionPreprocess + 171
1 libobjc.A.dylib 0x0000000108dae21e objc_exception_throw + 48
2 CoreFoundation 0x00000001093a5bdf -[__NSSingleObjectArrayI objectAtIndex:] + 111
3 RunTimeUse 0x00000001087d9480 -[ViewController viewDidLoad] + 320
4 UIKit 0x0000000109911c99 -[UIViewController loadViewIfRequired] + 1258
5 UIKit 0x00000001099120cc -[UIViewController view] + 27
6 UIKit 0x00000001097dbc51 -[UIWindow addRootViewControllerViewIfPossible] + 71
7 UIKit 0x00000001097dc3a2 -[UIWindow _setHidden:forced:] + 293
8 UIKit 0x00000001097efcb5 -[UIWindow makeKeyAndVisible] + 42
9 UIKit 0x0000000109768c89 -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 4818
10 UIKit 0x000000010976ede9 -[UIApplication _runWithMainScene:transitionContext:completion:] + 1731
11 UIKit 0x000000010976bf69 -[UIApplication workspaceDidEndTransaction:] + 188
12 FrontBoardServices 0x000000010c8c4723 __FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 24
13 FrontBoardServices 0x000000010c8c459c -[FBSSerialQueue _performNext] + 189
14 FrontBoardServices 0x000000010c8c4925 -[FBSSerialQueue _performNextFromRunLoopSource] + 45
15 CoreFoundation 0x00000001092f2311 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
16 CoreFoundation 0x00000001092d759c __CFRunLoopDoSources0 + 556
17 CoreFoundation 0x00000001092d6a86 __CFRunLoopRun + 918
18 CoreFoundation 0x00000001092d6494 CFRunLoopRunSpecific + 420
19 UIKit 0x000000010976a7e6 -[UIApplication _run] + 434
20 UIKit 0x0000000109770964 UIApplicationMain + 159
21 RunTimeUse 0x00000001087d9acf main + 111
22 libdyld.dylib 0x000000010c12d68d start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)
程序奔潰在3车猬,地址為:0x00000001087d9480,
因為我的Demo名字叫RunTimeUse尺锚。其他的名字很明顯是系統(tǒng)的庫珠闰。雖然log的21行也有RunTimeUse,但是經(jīng)過觀察應(yīng)該是main函數(shù),不在考慮范圍之內(nèi)瘫辩。
我們使用image的 lookup命令伏嗜,可以很快的定位到具體的代碼行。
(lldb) image lookup --address 0x00000001087d9480
Address: RunTimeUse[0x0000000100001480] (RunTimeUse.__TEXT.__text + 320)
Summary: RunTimeUse`-[ViewController viewDidLoad] + 320 at ViewController.m:28
可以看出伐厌,奔潰在ViewController.m中的第28行承绸。