iOS調(diào)試之LLDB
Xcode內(nèi)嵌了LLDB控制臺(tái)入撒,在Xcode代碼編輯區(qū)的下方。shift + cmd + y
可以顯示隱藏LLDB控制臺(tái)陨献。
LLDB語(yǔ)法
<command> [<subcommand> <subcommand>...] <action> [-options option-value] [argument argument...]
<command>
和subcommand
:LLDB調(diào)試命令的名稱帚屉。命令和子命令按 層級(jí)結(jié)構(gòu)來(lái)排列:一個(gè)命令對(duì)象為跟隨其的子命令創(chuàng)建一個(gè)上下文,子命令又為其子命令創(chuàng)建一個(gè)上下文牵祟,以此類推。action
:執(zhí)行命令的操作抖格。option
:命令選項(xiàng)诺苹。argument
:命令的參數(shù)。-
[]
:表示命令是可選的雹拄,可以有也可也沒(méi)有收奔。breakpoint set -n main
<command>
:breakpoint 。沒(méi)有<subcommand>
滓玖。action
:set 坪哄。option
: -n 表示根據(jù)方法name設(shè)置斷點(diǎn)。argument
: main 表示方法名為main 。
原始(Raw)命令
LLDB支持不帶命令選項(xiàng)(options)的原始(raw)命令翩肌,原始命令會(huì)將命令后面的所有東西當(dāng)做參數(shù)(arguement)傳遞模暗。不過(guò)很多原始命令也可以帶命令選項(xiàng),當(dāng)你使用命令選項(xiàng)的時(shí)候念祭,需要在命令選項(xiàng)后面加--
區(qū)分命令選項(xiàng)和參數(shù)兑宇。
e.g: 常用的expression就是raw命令,一般情況下我們使用expression打印一個(gè)東西是這樣的:
(lldb) expression count
(int) $2 = 4
當(dāng)我們想打印一個(gè)對(duì)象的時(shí)候粱坤。需要使用-O命令選項(xiàng)隶糕,我們應(yīng)該用--將命令選項(xiàng)和參數(shù)區(qū)分:
(lldb) expression -O -- self
<ViewController: 0x7f9000f17660>
唯一匹配原則
假如根據(jù)前n個(gè)字母已經(jīng)能唯一匹配到某個(gè)命令,則只寫(xiě)前n個(gè)字母等效于寫(xiě)下完整的命令站玄。
e.g: 前面提到我設(shè)置斷點(diǎn)的命令枚驻,我們可以使用唯一匹配原則簡(jiǎn)寫(xiě),下面2條命令等效:
breakpoint set -n main
br s -n main
~/.lldbinit
LLDB有了一個(gè)啟動(dòng)時(shí)加載的文件~/.lldbinit
蜒什,每次啟動(dòng)都會(huì)加載测秸。所以一些初始化的事兒疤估,我們可以寫(xiě)入~/.lldbinit
中灾常,比如給命令定義別名等。但是由于這時(shí)候程序還沒(méi)有真正運(yùn)行铃拇,也有部分操作無(wú)法在里面玩钞瀑,比如設(shè)置斷點(diǎn)。
LLDB命令
expresssion
expression命令的作用是執(zhí)行一個(gè)表達(dá)式慷荔,并將表達(dá)式返回的結(jié)果輸出雕什。expression的完整語(yǔ)法是這樣的:
expression <cmd-options> -- <expr>
-
<cmd-options>
:命令選項(xiàng),一般情況下使用默認(rèn)的即可显晶,不需要特別標(biāo)明贷岸。 -
--
: 命令選項(xiàng)結(jié)束符,表示所有的命令選項(xiàng)已經(jīng)設(shè)置完畢磷雇,如果沒(méi)有命令選項(xiàng)偿警,--
可以省略。 -
<expr>
: 要執(zhí)行的表達(dá)式唯笙。
expression能夠?qū)崿F(xiàn)兩個(gè)主要功能螟蒸。
- 執(zhí)行某個(gè)表達(dá)式。
我們?cè)诖a運(yùn)行過(guò)程中崩掘,可以通過(guò)執(zhí)行某個(gè)表達(dá)式來(lái)動(dòng)態(tài)改變程序運(yùn)行的軌跡七嫌。
假如我們?cè)谶\(yùn)行過(guò)程中,突然想把self.view顏色改成紅色苞慢,看看效果诵原。我們不必寫(xiě)下代碼,重新run,只需暫停程序绍赛,用expression改變顏色鞋拟,再刷新一下界面,就能看到效果惹资。
// 改變顏色
(lldb) expression -- self.view.backgroundColor = [UIColor redColor]
// 刷新界面
(lldb) expression -- (void)[CATransaction flush] - 將返回值輸出贺纲。
也就是說(shuō)我們可以通過(guò)expression來(lái)打印東西。
假如我們想打印self.view:
(lldb) expression -- self.view
(UIView *) $1 = 0x00007fe322c18a10
p
&print
&call
- print: 打印某個(gè)東西褪测,可以是變量和表達(dá)式猴誊。
- p: 可以看做是print的簡(jiǎn)寫(xiě)。
- call: 調(diào)用某個(gè)方法侮措。
表面上看起來(lái)他們可能有不一樣的地方懈叹,實(shí)際都是執(zhí)行某個(gè)表達(dá)式(變量也當(dāng)做表達(dá)式),將執(zhí)行的結(jié)果輸出到控制臺(tái)上分扎。所以你可以用p調(diào)用某個(gè)方法澄成,也可以用call打印東西
e.g: 下面代碼效果相同:
(lldb) expression -- self.view
(UIView *) $5 = 0x00007fb2a40344a0
(lldb) p self.view
(UIView *) $6 = 0x00007fb2a40344a0
(lldb) print self.view
(UIView *) $7 = 0x00007fb2a40344a0
(lldb) call self.view
(UIView *) $8 = 0x00007fb2a40344a0
(lldb) e self.view
(UIView *) $9 = 0x00007fb2a40344a0
po
我們知道,OC里所有的對(duì)象都是用指針表示的畏吓,所以一般打印的時(shí)候墨状,打印出來(lái)的是對(duì)象的指針,而不是對(duì)象本身菲饼。如果我們想打印對(duì)象肾砂。我們需要使用命令選項(xiàng):-O。為了更方便的使用宏悦,LLDB為expression -O --定義了一個(gè)別名:po镐确。
(lldb) expression -- self.view
(UIView *) $13 = 0x00007fb2a40344a0
(lldb) expression -O -- self.view
<UIView: 0x7fb2a40344a0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7fb2a4018c80>>
(lldb) po self.view
<UIView: 0x7fb2a40344a0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7fb2a4018c80>>
thread
有時(shí)候我們想要了解線程堆棧信息,可以使用thread backtrace饼煞。
thread backtrace作用是將線程的堆棧打印出來(lái)源葫。我們來(lái)看看他的語(yǔ)法。
thread backtrace -c <count> -s <frame-index> -e <boolean>
thread backtrace
后面跟的都是命令選項(xiàng):
-c
:設(shè)置打印堆棧的幀數(shù)(frame)砖瞧。
-s
:設(shè)置從哪個(gè)幀(frame)開(kāi)始打印息堂。
-e
:是否顯示額外的回溯。
實(shí)際上這些命令選項(xiàng)我們一般不需要使用芭届。
e.g: 當(dāng)發(fā)生crash的時(shí)候储矩,我們可以使用thread backtrace查看堆棧調(diào)用
(lldb) thread backtrace
* thread #1: tid = 0xdd42, 0x000000010afb380b libobjc.A.dylibobjc_msgSend + 11, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT) frame #0: 0x000000010afb380b libobjc.A.dylib
objc_msgSend + 11
* frame #1: 0x000000010aa9f75e TLLDB-[ViewController viewDidLoad](self=0x00007fa270e1f440, _cmd="viewDidLoad") + 174 at ViewController.m:23 frame #2: 0x000000010ba67f98 UIKit
-[UIViewController loadViewIfRequired] + 1198
frame #3: 0x000000010ba682e7 UIKit-[UIViewController view] + 27 frame #4: 0x000000010b93eab0 UIKit
-[UIWindow addRootViewControllerViewIfPossible] + 61
frame #5: 0x000000010b93f199 UIKit-[UIWindow _setHidden:forced:] + 282 frame #6: 0x000000010b950c2e UIKit
-[UIWindow makeKeyAndVisible] + 42
我們可以看到crash發(fā)生在-ViewController viewDidLoad中的第23行,只需檢查這行代碼是不是干了什么非法的事兒就可以了褂乍。
LLDB還為backtrace專門(mén)定義了一個(gè)別名:bt持隧,他的效果與thread backtrace相同,如果你不想寫(xiě)那么長(zhǎng)一串字母逃片,直接寫(xiě)下bt即可:
(lldb) bt
thread return
Debug的時(shí)候屡拨,也許會(huì)因?yàn)楦鞣N原因只酥,我們不想讓代碼執(zhí)行某個(gè)方法,或者要直接返回一個(gè)想要的值呀狼。這時(shí)候就該thread return上場(chǎng)了裂允。
thread return <expr>
thread return
可以接受一個(gè)表達(dá)式,調(diào)用命令之后直接從當(dāng)前的frame返回表達(dá)式的值哥艇。
例如我們有個(gè)方法:
- (NSInteger)testThreadReturn{
NSLog(@"thread return");
return 10;
}
我們?nèi)绻胱尳Y(jié)果返回20绝编, 首先在方法體的第一行打一個(gè)斷點(diǎn),當(dāng)程序運(yùn)行到方法時(shí)會(huì)暫停貌踏,這時(shí)候我們?cè)贚LDB窗口里輸入:
(lldb) thread return 20
c & n & s & finish
一般在調(diào)試程序的時(shí)候十饥,我們經(jīng)常用到下面這4個(gè)按鈕:
這4個(gè)按鈕的LLDB命令:
- c/ continue/ thread continue: 這三個(gè)命令效果都等同于上圖中第一個(gè)按鈕的。表示程序繼續(xù)運(yùn)行祖乳。
- n/ next/ thread step-over: 這三個(gè)命令效果等同于上圖第二個(gè)按鈕逗堵。表示單步運(yùn)行。
- s/ step/ thread step-in: 這三個(gè)命令效果等同于上圖第三個(gè)按鈕眷昆。表示進(jìn)入某個(gè)方法蜒秤。
- finish/ step-out: 這兩個(gè)命令效果等同于第四個(gè)按鈕。表示直接走完當(dāng)前方法亚斋,返回到上層frame作媚。
thread其他不常用的命令
thread 相關(guān)的還有其他一些不常用的命令,這里就簡(jiǎn)單介紹一下即可伞访,如果需要了解更多掂骏,可以使用命令help thread查閱轰驳。
- thread jump: 直接讓程序跳到某一行厚掷。由于ARC下編譯器實(shí)際插入了不少retain,release命令级解。跳過(guò)一些代碼不執(zhí)行很可能會(huì)造成對(duì)象內(nèi)存混亂發(fā)生crash冒黑。
- thread list: 列出所有的線程。
- thread select: 選擇某個(gè)線程勤哗。
- thread until: 傳入一個(gè)line的參數(shù)抡爹,讓程序執(zhí)行到這行的時(shí)候暫停。
- thread info: 輸出當(dāng)前線程的信息芒划。
frame
棧幀冬竟,一次函數(shù)調(diào)用為一幀。隨便打個(gè)斷點(diǎn)民逼,我們?cè)诳刂婆_(tái)上輸入命令bt泵殴,可以打印出來(lái)所有的frame。如果仔細(xì)觀察拼苍,這些frame和左邊紅框里的堆棧是一致的笑诅。平時(shí)我們看到的左邊的堆棧就是frame。
frame variable
通過(guò)frame variable命令,可以打印出當(dāng)前frame的所有變量吆你。
(lldb) frame variable
(ViewController *) self = 0x00007fd2f511cf60
(SEL) _cmd = "viewDidLoad"
(NSInteger) res = 0
可以看到弦叶,他將self
,_cmd
,res
等本地變量都打印了出來(lái)。
如果我們要需要打印指定變量妇多,也可以給frame variable傳入?yún)?shù):
(lldb) frame variable self->_count
(NSInteger) self->_count = 0
不過(guò)frame variable只接受變量作為參數(shù)伤哺,不接受表達(dá)式,也就是說(shuō)我們無(wú)法使用frame variable self.string者祖,因?yàn)閟elf.string是調(diào)用string的getter方法默责。所以一般打印指定變量,我更喜歡用p或者po咸包。
其他不常用命令
一般frame variable打印所有變量用得比較多桃序,frame還有2個(gè)不怎么常用的命令:
frame info: 查看當(dāng)前frame的信息。
(lldb) frame info
frame #0: 0x0000000101bf87d5 TLLDB-[ViewController text:](self=0x00007fa158526e60, _cmd="text:", ret=YES) + 37 at ViewController.m:38 frame select: 選擇某個(gè)frame烂瘫。 (lldb) frame select 1 frame #1: 0x0000000101bf872e TLLDB
-[ViewController viewDidLoad](self=0x00007fa158526e60, _cmd="viewDidLoad") + 78 at ViewController.m:23
20
21 - (void)viewDidLoad {
22 [super viewDidLoad];
-> 23 [self text:YES];
24 NSLog(@"1");
25 NSLog(@"2");
26 NSLog(@"3");
breakpoint
調(diào)試過(guò)程中媒熊,我們用得最多的可能就是斷點(diǎn)了。LLDB中的斷點(diǎn)命令也非常強(qiáng)大坟比。
breakpoint set
breakpoint set命令用于設(shè)置斷點(diǎn)芦鳍,LLDB提供了很多種設(shè)置斷點(diǎn)的方式:
- 使用-n根據(jù)方法名設(shè)置斷點(diǎn):
e.g: 我們想給所有類中的viewWillAppear:設(shè)置一個(gè)斷點(diǎn):
(lldb) breakpoint set -n viewWillAppear:
Breakpoint 13: 33 locations. - 使用-f指定文件:
e.g: 我們只需要給ViewController.m文件中的viewDidLoad設(shè)置斷點(diǎn):
(lldb) breakpoint set -f ViewController.m -n viewDidLoad
Breakpoint 22: where = TLLDB`-[ViewController viewDidLoad] + 20 at ViewController.m:22, address = 0x000000010272a6f4
這里需要注意,如果方法未寫(xiě)在文件中(比如寫(xiě)在category文件中葛账,或者父類文件中)柠衅,指定文件之后,將無(wú)法給這個(gè)方法設(shè)置斷點(diǎn)籍琳。 - 使用-l指定文件某一行設(shè)置斷點(diǎn):
e.g: 我們想給ViewController.m第38行設(shè)置斷點(diǎn)
(lldb) breakpoint set -f ViewController.m -l 38
Breakpoint 23: where = TLLDB`-[ViewController text:] + 37 at ViewController.m:38, address = 0x000000010272a7d5 - 使用-c設(shè)置條件斷點(diǎn):
e.g: text:方法接受一個(gè)ret的參數(shù)菲宴,我們想讓ret == YES的時(shí)候程序中斷:
(lldb) breakpoint set -n text: -c ret == YES
Breakpoint 7: where = TLLDB`-[ViewController text:] + 30 at ViewController.m:37, address = 0x0000000105ef37ce - 使用-o設(shè)置單次斷點(diǎn):
e.g: 如果剛剛那個(gè)斷點(diǎn)我們只想讓他中斷一次:
(lldb) breakpoint set -n text: -o
'breakpoint 3': where = TLLDB`-[ViewController text:] + 30 at ViewController.m:37, address = 0x000000010b6f97ce
breakpoint command
有的時(shí)候我們可能需要給斷點(diǎn)添加一些命令,比如每次走到這個(gè)斷點(diǎn)的時(shí)候趋急,我們都需要打印self對(duì)象喝峦。我們只需要給斷點(diǎn)添加一個(gè)po self命令,就不用每次執(zhí)行斷點(diǎn)再自己輸入po self了呜达。
breakpoint command add
breakpoint command add命令就是給斷點(diǎn)添加命令的命令谣蠢。
e.g: 假設(shè)我們需要在ViewController的viewDidLoad中查看self.view的值。
我們首先給-[ViewController viewDidLoad]
添加一個(gè)斷點(diǎn)查近。
(lldb) breakpoint set -n "-[ViewController viewDidLoad]"
'breakpoint 3': where = TLLDB-[ViewController viewDidLoad] + 20 at ViewController.m:23, address = 0x00000001055e6004 可以看到添加成功之后眉踱,這個(gè)breakpoint的id為3,然后我們給他增加一個(gè)命令:po self.view霜威。 (lldb) breakpoint command add -o "po self.view" 3
-o完整寫(xiě)法是
--one-liner`谈喳,表示增加一條命令。3表示對(duì)id為3的breakpoint增加命令侥祭。
添加完命令之后叁执,每次程序執(zhí)行到這個(gè)斷點(diǎn)就可以自動(dòng)打印出self.view的值了茄厘。
如果我們一下子想增加多條命令,比如我想在viewDidLoad中打印當(dāng)前frame的所有變量谈宛,但是我們不想讓他中斷次哈,也就是在打印完成之后,需要繼續(xù)執(zhí)行吆录。我們可以這樣寫(xiě):
(lldb) breakpoint command add 3
Enter your debugger command(s). Type 'DONE' to end.
> frame variable
> continue
> DONE
多次對(duì)同一個(gè)斷點(diǎn)添加命令窑滞,后面命令會(huì)將前面命令覆蓋。
breakpoint command list
如果想查看某個(gè)斷點(diǎn)已有的命令恢筝,可以使用breakpoint command list哀卫。
e.g: 我們查看一下剛剛的斷點(diǎn)3已有的命令
(lldb) breakpoint command list 3
'breakpoint 3':
Breakpoint commands:
frame variable
continue
可以看到一共有2條命令,分別為frame variable和continue撬槽。
breakpoint command delete
有增加就有刪除此改,breakpoint command delete可以讓我們刪除某個(gè)斷點(diǎn)的命令。
e.g: 我們將斷點(diǎn)3中的命令刪除:
(lldb) breakpoint command delete 3
(lldb) breakpoint command list 3
Breakpoint 3 does not have an associated command.
breakpoint list
如果我們想查看已經(jīng)設(shè)置了哪些斷點(diǎn)侄柔,可以使用breakpoint list共啃。
(lldb) breakpoint list
Current breakpoints:
4: name = '-[ViewController viewDidLoad]', locations = 1, resolved = 1, hit count = 0
4.1: where = TLLDB`-[ViewController viewDidLoad] + 20 at ViewController.m:23, address = 0x00000001055e6004, resolved, hit count = 0
breakpoint disable/enable
有的時(shí)候我們可能暫時(shí)不想要某個(gè)斷點(diǎn),可以使用breakpoint disable讓某個(gè)斷點(diǎn)暫時(shí)失效暂题。
(lldb) breakpoint disable 4
1 breakpoints disabled.
當(dāng)我們又需要這個(gè)斷點(diǎn)的時(shí)候移剪,可以使用breakpoint enable再次讓他生效
(lldb) breakpoint enable 4
1 breakpoints enabled.
breakpoint delete
如果我們覺(jué)得這個(gè)斷點(diǎn)以后再也用不上了,可以用breakpoint delete直接刪除斷點(diǎn).
e.g: 刪除斷點(diǎn)4
(lldb) breakpoint delete 4
1 breakpoints deleted; 0 breakpoint locations disabled.
如果我們想刪除所有斷點(diǎn)薪者,只需要不指定breakpoint delete參數(shù)即可
(lldb) breakpoint delete
About to delete all breakpoints, do you want to do that?: [Y/n] y
All breakpoints removed. (1 breakpoint)
未完待續(xù)纵苛。。言津。