iOS lldb斷點(diǎn)調(diào)試

  Xcode的使用中總是離不開(kāi)調(diào)試這個(gè)環(huán)境,在一年多的iOS開(kāi)發(fā)時(shí)間中,我更多地依賴于XCode本身提供的GUI工具來(lái)進(jìn)行調(diào)試,而對(duì)LLDB敬而遠(yuǎn)之,這段時(shí)間好好學(xué)習(xí)了LLDB的使用,發(fā)覺(jué)我錯(cuò)過(guò)了太多東西了……因此做一個(gè)比較完備的總結(jié),也希望在寫(xiě)這篇文章的過(guò)程中進(jìn)一步學(xué)習(xí)LLDB調(diào)試的各種實(shí)踐方法。

LLDB闡述

LLDB 是一個(gè)有著 REPL 的特性和 C++ ,Python 插件的開(kāi)源調(diào)試器。LLDB 綁定在 Xcode 內(nèi)部,存在于主窗口底部的控制臺(tái)中。調(diào)試器允許你在程序運(yùn)行的特定時(shí)暫停它,你可以查看變量的值,執(zhí)行自定的指令,并且按照你所認(rèn)為合適的步驟來(lái)操作程序的進(jìn)展。

lldb下,相信大家用的較多的是po,或者使用條件斷點(diǎn)。但是面對(duì)復(fù)雜的問(wèn)題時(shí),比如.a靜態(tài)庫(kù),就需要其他調(diào)試方法了。

breakpoint

給某個(gè)文件的某一行下斷點(diǎn)??梢允褂萌缦聝煞N方法,比如我想給Person.m文件的10行下一個(gè)斷點(diǎn)。可以使用如下的方法。

(lldb) breakpoint set --file Person.m --line 10

如果出現(xiàn)如下提示則說(shuō)明設(shè)置斷點(diǎn)成功

Breakpoint 2: where = BreakPointDemo`-[Person foo] + 23 at Foo.m:10, address = 0x000000010b22e687

也可以使用簡(jiǎn)寫(xiě)的形式如下。

(lldb) breakpoint set -f Foo.m -l 10

給一個(gè)函數(shù)下斷點(diǎn)

(lldb) breakpoint set --name foo
(lldb) breakpoint set -n foo

一次性給多個(gè)函數(shù)下斷點(diǎn)

(lldb) breakpoint set --name foo --name bar

OC的方法,可以使用以下兩種方式打斷點(diǎn),第二種S需要大寫(xiě)

(lldb) breakpoint set --selector foo

(lldb) breakpoint set -S foo

使用 正則匹配 你要打斷點(diǎn)的函數(shù)。這個(gè)不限語(yǔ)言

(lldb) breakpoint set -r cFoo
(lldb) breakpoint set -r foo

也可以指定加載的動(dòng)態(tài)庫(kù)

(lldb) breakpoint set --shlib foo.dylib --name foo 
(lldb) breakpoint set -s foo.dylib -n foo

我們同樣可以對(duì)命令進(jìn)行簡(jiǎn)寫(xiě)。下面兩個(gè)命令的效果是一樣的

(lldb) breakpoint set -n "-[Foo foo]"
(lldb) br s -n "-[Foo foo]"

查看有多少斷點(diǎn)可以使用

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

我們可以對(duì)斷點(diǎn)進(jìn)行相關(guān)的操作,比如在執(zhí)行到2.1斷點(diǎn)的時(shí)候打印追蹤軌跡。bt是

(lldb) breakpoint command add 2.1
Enter your debugger command(s).  Type 'DONE' to end.
> bt
> DONE

刪除所有斷點(diǎn)

(lldb) breakpoint delete

watchpoint

觀察變量值的具體變化
比如我需要觀察某個(gè)變量a的值變化,我可以使用如下命令

(lldb) watchpoint set variable a

bt命令用來(lái)追蹤程序的運(yùn)行過(guò)程

(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

補(bǔ)充一點(diǎn)watchpoint list的東西。這個(gè)命令包括了三個(gè)可選參數(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是比較簡(jiǎn)略的信息,-f是比較全面的信息,-v是完整的信息。經(jīng)過(guò)我的實(shí)驗(yàn),如果使用watchpoint list,默認(rèn)的是 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
......

查看更詳細(xì)的命令使用help <command> <subcommand>。比如

 (lldb) help process attach

thread

其實(shí)這個(gè)功能主要就是斷點(diǎn)調(diào)試?yán)锩娴娜缦逻@個(gè)功能。

1654054-8d533324eaa728a8.png

我們可以使用thread命令來(lái)做一些斷點(diǎn)的操作,具體有那些命令我們可以使用thread help進(jìn)行查看

(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-開(kāi)頭的這幾個(gè)命令,使用起來(lái)很容易。我個(gè)人感覺(jué)比用鼠標(biāo)點(diǎn)擊斷點(diǎn)好用多了~
檢查當(dāng)前進(jìn)程的狀態(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
......

*表明的就是當(dāng)前的線程,可以使用如下的命令得到線程的回溯,這個(gè)詞我也不確定怎么表達(dá)好,backtrace,也可以說(shuō)是追蹤。

(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

當(dāng)然我們?nèi)绻肟此芯€程的backtrace,可以使用thread backtrace all命令。內(nèi)容太多,我這里就不演示log輸出了。

如果我們想單獨(dú)查看某個(gè)線程,我們可以先使用thread select 2跳到某個(gè)具體的線程,然后再進(jìn)行其他操作,比如thread backtrace

為了方便的觀測(cè)架構(gòu)參數(shù)和本地變量,我們可以使用 frame variable 命令

如果我什么參數(shù)也不加,將會(huì)把所有的參數(shù)和本地變量到打印出來(lái)。

(lldb) frame variable
(ViewController *) self = 0x00007ffea04050e0
(SEL) _cmd = "viewDidLoad"
(Person *) p = 0x0000610000001940

要打印某個(gè)變量需要在參數(shù)里面指定,這個(gè)命令我們?cè)谇懊嬉彩褂眠^(guò),比如要查看self

(lldb) frame variable self
(ViewController *) self = 0x00007ff81b60ab20

更進(jìn)一步,我們可以查看一些子元素

(lldb) frame variable self->isa
(Class) self->isa = ViewController

命令雖然不是完整的表達(dá)式解釋器,當(dāng)時(shí)可以識(shí)別一些基本的操作 比如 &, *, ->, [],不是重載運(yùn)算符,數(shù)組也可以使用,因?yàn)閿?shù)組本身也是指針。

(lldb) frame variable *self 

(ViewController) *self = {
UIViewController = {
UIResponder = {
  NSObject = {
    isa = ViewController
  }
......
}

和之前thread命令很類似,我可以使用frame select去選擇另外的一個(gè)frame

(lldb) frame select 9

如果想看更復(fù)雜的數(shù)據(jù),我們可以使用expression命令

(lldb) expression self
(ViewController *) $0 = 0x00007fefa4705110

更復(fù)雜一些,我們可以用來(lái)輸出一個(gè)表達(dá)式

(lldb) expr (int) printf ("I have a pointer 0x%llx.\n", self)
I have a pointer 0x7fefa4705110.
(int) $1 = 33
call

其實(shí)這個(gè)命令完全可以使用po進(jìn)行替代,call一般可以用來(lái)調(diào)用不需要返回值的調(diào)試命令,比如更改View的背景顏色,以下兩個(gè)命令都可以達(dá)到相似的作用,更改當(dāng)前View的背景顏色值。

(lldb) po [self.view setBackgroundColor:[UIColor redColor]]
(lldb) call [self.view setBackgroundColor:[UIColor redColor]]
image

這個(gè)比較實(shí)用,可用于尋找棧地址對(duì)應(yīng)的代碼位置。

//測(cè)試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,

因?yàn)槲业腄emo名字叫RunTimeUse。其他的名字很明顯是系統(tǒng)的庫(kù)。雖然log的21行也有RunTimeUse,但是經(jīng)過(guò)觀察應(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
1654054-7dd117c55d5e5772.png

可以看出,奔潰在ViewController.m中的第28行。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • LLDB的Xcode默認(rèn)的調(diào)試器,它與LLVM編譯器一起,帶給我們更豐富的流程控制和數(shù)據(jù)檢測(cè)的調(diào)試功能。平時(shí)用Xc...
    CoderSC閱讀 1,502評(píng)論 0 2
  • [轉(zhuǎn)]淺談LLDB調(diào)試器文章來(lái)源于:http://www.cocoachina.com/ios/20150126/...
    loveobjc閱讀 2,735評(píng)論 2 6
  • 前言 今天花了一天的時(shí)間終于把iOS的幾種常見(jiàn)的調(diào)試方法給學(xué)習(xí)了一下,在這里給大家分享一下LLDB的使用,同時(shí)也是...
    Peak_One閱讀 11,298評(píng)論 5 33
  • 轉(zhuǎn)載 與調(diào)試器共舞 - LLDB 的華爾茲: https://objccn.io/issue-19-2/ 推薦:i...
    F麥子閱讀 3,457評(píng)論 0 10
  • 不同于大年初一的陽(yáng)光普照、初二云卷云舒,今兒(初三)寒風(fēng)透骨把我從戶外壓回了室內(nèi)。這時(shí)刻多數(shù)人還在夢(mèng)國(guó),我隨...
    變臉加菲喵閱讀 482評(píng)論 0 3

友情鏈接更多精彩內(nèi)容