LLDB詳解 一、引言 二、LLDB命令 三、使用LLDB 四、擴展LLDB 四、參考資料

一、引言

1.1 目的

程序員日常編寫代碼的時間遠小於調試的時間,所以熟練掌握調試工具能夠大幅降低調試所要花的時間,從而大幅提高工作效率。本文針對iOS開發過程中通過Xcode與/和LLDB進行調試進行的講解,以提升對LLDB的掌握。如果同時能夠幫助到廣大網友,那麼我講十分開心。

1.2 日常調試

在日常工作中,大部分調試動作都是設置一些斷點,在程序被暫停時查看執行邏輯、變量的值等,然後定位問題,並根據具體問題修改代碼。這應該是大部分程序員的日常調試行爲與狀態。此種調試行爲僅使用LLDB所提供的極少的能力。
除此之外,在大部分情況下程序員會藉助在源代碼裏添加NSLog之類的日誌代碼,來進行輔助調試。

1.3 日常調試的弊端

  1. 效率低下
  • 比如,在某個循環中,需要在第N次循環才暫停,但是前N-1次也需要暫停與跳過
  • 比如,想查看某些UI效果,需要臨時修改代碼,重新運行
  • 比如,調試過程中的修改,需要反覆多次,每次都重新編譯與運行花費時間較多
  • 比如,在一個複雜的UI結構中,不知道某個按鈕的點擊事件被誰響應
  • 比如,某個元素佈局錯誤,不知道誰引起了佈局錯誤,花大力氣進行查找
  1. 會在正常代碼之外引入非必要代碼
  • 添加的NSLog之類代碼沒有刪除
  • 添加的其他有作用的調試代碼遺漏刪除(如,把測試過程中的數據請求地址發佈到了證實版本)

二、LLDB命令

LLDB是開源、內置於XCode、具有REPL(read-eval-print-loop)特徵的調試器,其提供C++、Python接口。在日常的開發和調試過程中給開發人員帶來了非常多的幫助。在Xcode5.0的時候,LLDB全面取代了GDB,使調試工作更加高效、便捷。LLDB是LLVM的一部分。本節重點講述LLDB的命令,這些命令大部分可以可通過Xcode提的GUI界面來完成,也可以使用Xcode提供的調試控制檯以交互命令的方式執行。



2.1 命令概述

LLDB命令的格式格式如下,其中[]表示是可選部分。

<command> [<subcommand> [<subcommand>...]] <action> [-options [option-value]] [argument[argument...]]

  • command
    • 基於LLDB運行環境創建執上下文環境,最終的命令在此上下文環境中執行
  • subcommand
    • 對command創建的上下文環境進行修改,創建更具體的新的上下文環境。子命令以空格分隔,用於組織相關操作。
  • action
    • 在command、subcomand所形成的上下文環境中需要執行的動作
  • options
    • 動作所需要的選項、命令可能包含一個或多個選項 (option)
    • 選項以空格分隔,以雙短劃線 (--)開頭,組合修改要執行的操作
    • 有些option還提供使用單個短劃線 (-) 的簡寫形式。例如,第一次導致程序停止後自動刪除breakpoint的breakpoint set --one-shot命令,可以寫爲breakpoint set -o。
    • 有些option還會傳遞值,option和值用空格分隔。例如,爲breakpoint set命令傳遞--name option,選項值爲函數、方法名稱,這樣就可以爲指定函數、方法添加breakpoint。
    • 有些命令組合使用options。例如,breakpoint set命令組合使用--file和--line選項,併爲其指定對應文件名稱和行號。
    • Option values和arguments中的空格用雙引號保護。如果參數中有雙引號,則可以使用反斜槓()標誌雙引號。LLDB等效的使用單引號和雙引號。例如:
      (lldb)command [subcommand] -option "some \"quoted\" string"
      也可以寫爲:
      (lldb) command [subcommand] -option 'some "quoted" string'
      這樣的命令解析設計有助於LLDB命令語法在所有命令中保持統一。
    • 如果命令即接受options,又接受自由形式 (free form) 的arguments,則必須在最後一個option和第一個argument間放置以空格分隔的雙短劃線,如expression命令。這樣能夠確保以短劃線開頭的argument被解釋爲argument,而不會因其與option都以短劃線開頭而被錯誤解釋。
  • arguement
    • 命令所需要參數
    • 命令可能需要一個或多個參數,參數以空格分隔,用於指示要執行的操作。例如,breakpoint disable命令參數傳遞要禁用的breakpoint。breakpoint disable 1 2表示禁用breakpoint ID爲1和2的斷點。

2.2.1 命令形式

相同的命令可以用不同的形式實現,例如——以下命令表示同一操作:驗證變量someVariable描述,並輸出結果。

  • 規範形式(canonical form)expression --object-description -- someVariable。
  • 縮寫形式 (abbreviated form)e -O -- someVariable。
  • 別名 (alias)po someVariable。可以爲任何命令創建別名,以方便執行常見操作。

2.2.2 命令補全(Command Completion)

LLDB支持源文件名,符號名,文件名,等等的命令補全(Commmand Completion)。終端窗口中的補全是通過在命令行中輸入一個製表符來初始化的。Xcode控制檯中的補全與在源碼編輯器中的補全方式是一樣的:補全會在第三個字符被鍵入時自動彈出,或者通過Esc鍵手動取消彈出。

命令中的私有選項可以有不同的完成者(completers)。如breakpoint中的--file <path>選項作爲源文件的完成者,--shlib <path>選項作爲當前加載的庫的完成者,等等。這些行爲是特定的,例如,如果指定--shlib <path>,且以--file <path>結尾,則LLDB只會列出由--shlib <path>指定的共享類庫。

2.2 命令詳解

2.2.1 幫助類命令

2.2.1.1 help

help命令可以查看LLDB的幫助文檔,方便進一步瞭解LLDB命令結構。
只輸入help命令,將返回所有命令列表,如下是部分命令:

Debugger commands:
  apropos           -- List debugger commands related to a word or subject.
  breakpoint        -- Commands for operating on breakpoints (see 'help b' for
                       shorthand.)
  command           -- Commands for managing custom LLDB commands.
  disassemble       -- Disassemble specified instructions in the current
                       target.  Defaults to the current function for the
                       current thread and stack frame.
  expression        -- Evaluate an expression on the current thread.  Displays
                       any returned value with LLDB's default formatting.
  frame             -- Commands for selecting and examing the current thread's
                       stack frames.
  gdb-remote        -- Connect to a process via remote GDB server.
                       If no host is specifed, localhost is assumed.
                       gdb-remote is an abbreviation for 'process connect
                       --plugin gdb-remote connect://<hostname>:<port>'
  gui               -- Switch into the curses based GUI mode.

可以通過help command來查看命令的具體幫助信息

2.2.1.2 apropos

當我們只記得某個單詞,但是不太確定屬於某個命令、子命令等時,可以通過此命令進行查找,以獲取更加詳細的信息。

(lldb) apropos jump
The following commands may relate to 'jump':
  _regexp-jump -- Set the program counter to a new address.
  thread jump  -- Sets the program counter to a new address.
  jump         -- Set the program counter to a new address.

2.2.2 expression

此命令在當前線程執行表達式並把結果輸出,輸出結果是默認採用LLDB內置的格式化程序。此命令最重要的用途有兩個:被用來輸出結果(我們經常使用的是po、p,就是它的簡寫)、修改變量的值

2.2.2.1 po

po命令在當前線程上執行表達式,並輸出表達式的計算結果。
此命令其實是expression -O --的簡寫形式,其中-O實際上是--object-description,此選項表示使用類型提供的描述來輸出結果,對於OC語言來說就是descriprtion。

'po' is an abbreviation for 'expression -O  --'

-O ( --object-description )
            Display using a language-specific description API, if possible.

其使用非常簡單,但是其背後的原理比較複雜。通過下圖,我們可以看到其最少要經過兩次編譯與運行,所花的時間成本還是比較高的。


2.2.2.2 p

p命令在當前線程上執行表達式,並輸出表達式的計算結果,此結果以LLDB內置的格式標識。此命令其實是expression --的簡寫形式。

'p' is an abbreviation for 'expression --'

其使用非常簡單,但是其背後的原理比較複雜。與po命令相比,其第二次是採用動態類型解析、並使用LLDB內置的類型描述輸出表達式的結果。


2.2.2.3 v

此命令不屬於expression,但是其與po、p作用類似,所以一併在這裏介紹。
命令的作用是:顯示當前堆棧幀的變量,默認爲所有參數和當前範圍內的變量。
可以指定聚合變量的子項例如“var->child.x”。 'frame variable' 中的 -> 和 [] 運算符不調用運算符重載(如果存在),但直接訪問指定的元素。

'v' is an abbreviation for 'frame variable'

其使用非常簡單,但是其背後的原理比較複雜。與po、p相比,其背後是最簡單的邏輯。


2.2.2.4 po-VS-p-VS-v

以下是po、p、v的對比


2.2.3 breakpoint

此命令是與用於設置、取消、修改斷點等操作。

2.2.3.1 設置斷點

使用breakpoint 的子命令 set 設置斷點
具體信息可以通過help breakpoint set 查詢所有可選的選項(options)以及參數(arguments).

// 按方法名稱設置--name(-n):
(lldb) breakpoint set -n greet
Breakpoint 2: where = Greeter`Greeter.Greeter.greet(personNamed: Swift.String) -> () + 127 at Greeter.swift:9:12, address = 0x00000001000034cf
// 按行數設置--line(-l):
(lldb) breakpoint set -l 18
Breakpoint 3: where = Greeter`main + 14 at Greeter.swift:18:15, address = 0x000000010000315e
按文件設置--file(-f):
(lldb) breakpoint set -f Greeter.swift -l 20
Breakpoint 6: where = Greeter`main + 34 at Greeter.swift:20:1, address = 0x0000000100003172
// 根據語言捕獲異常--language-exception(-E):
(lldb) breakpoint set -E Objc
Breakpoint 7: no locations (pending).


// 可以通過--exception-typename(-O) 指定要捕獲的異常類型:
(lldb)  breakpoint set -E Swift -O EnumErrorType
Breakpoint 8: no locations (pending).
// pending,說明某塊還沒有被加載

2.2.3.2 獲取所有斷點

此命令獲取的是所有的斷點,包括通過GUI設置的斷點和通過Xcode 所提供的的LLDB交互窗口。

// 獲取斷點列表: breakpoint list
(lldb) breakpoint list
Current breakpoints:
1: name = 'sayHello', locations = 0 (pending)

2: name = 'greet', locations = 1
  2.1: where = Greeter`Greeter.Greeter.greet(personNamed: Swift.String) -> () + 127 at Greeter.swift:9:12, address = Greeter[0x00000001000034cf], unresolved, hit count = 0

3: file = '/Users/jackli/Desktop/Roborock/Greeter.swift', line = 18, exact_match = 0, locations = 1
  3.1: where = Greeter`main + 14 at Greeter.swift:18:15, address = Greeter[0x000000010000315e], unresolved, hit count = 0

4: file = 'main.swift', line = 20, exact_match = 0, locations = 0 (pending)

5: file = 'Greet.swift', line = 20, exact_match = 0, locations = 0 (pending)

6: file = 'Greeter.swift', line = 20, exact_match = 0, locations = 1
  6.1: where = Greeter`main + 34 at Greeter.swift:20:1, address = Greeter[0x0000000100003172], unresolved, hit count = 0

7: Exception breakpoint (catch: off throw: on) the correct runtime exception handler will be determined when you run

8: Swift Error breakpoint the correct runtime exception handler will be determined when you run
需要注意的是: 設置斷點其實是設置了邏輯斷點, 可能會對應一個或多個位置斷點(location).
每個邏輯斷點都有一個按照順序從1開始分配的整型ID. 每一個位置斷點又有一個自己的位置ID, 與邏輯斷點的ID之間以.分隔, 用於定位位置斷點. 示例如下:
2: name = 'greet', locations = 1
  2.1: where = Greeter`Greeter.Greeter.greet(personNamed: Swift.String) -> () + 127 at Greeter.swift:9:12, address = Greeter[0x00000001000034cf], unresolved, hit count = 0

// 邏輯斷點是動態的, 如果程序載入了新的代碼, 會自動載入新的位置斷點.

2.2.3.3 修改斷點

// 修改斷點:
// breakpoint modify
// 使用這個命令修改邏輯斷點或者位置斷點, 需要傳遞邏輯斷點或者位置斷點的ID 作爲參數, 具體參數可以通過help breakpoint modify 查看.

部分常用選項如下:
--condition(-c) 傳入表達式參數, 只有表達式判斷爲true 纔會執行斷點.
ignore-count(-i) 指定跳過斷點的次數, 在該斷點處跳過指定次數後, 纔會執行該斷點.
--one-shot(-o) 執行斷點一次後移除該斷點.
--queue-name(-q) 指定隊列名稱, 該斷點只有在指定隊列上纔會執行.
--thread-name(-T) 指定線程名稱, 該斷點只有在指定的線程上纔會執行.
--thread-id(-t) 指定線程ID(TID), 該斷點只有在指定的線程上纔會執行.
--thread-index(-x) 指定線程index, 該斷點只有在指定的線程上纔會執行.

如下示例修改了斷點ID爲1的斷點, 指定其在執行後,自動移除.
(lldb) breakpoint modify --one-shot 1
我們都知道, 當程序抵達斷點處時, 程序會暫停執行, 此時可以執行lldb命令.
我們也可以通過執行breakpoint command add 命令, 讓程序每次抵達斷點處時, 自動執行我們添加的命令. 如:
在位置斷點1.1處添加指令, 每行可以添加一個指令, Enter 跳轉至下一指令, 輸入DONE結束添加.
(lldb) breakpoint command add 1.1
Enter your debugger command(s). Type 'DONE' to end.
> thread backtrace
> DONE
可以將process continue 作爲最後一個指令, 那麼調試器就會在執行指令後, 自動繼續執行程序, 這在記錄Log的場景下使用是非常方便的.
(lldb) target create "Greeter"
Current executable set to (x86_64).
(lldb) breakpoint list
No breakpoints currently set.
(lldb) breakpoint set -n greet
Breakpoint 1: where = Greeter`Greeter.Greeter.greet(personNamed: Swift.String) -> () + 127 at Greeter.swift:9:12, address = 0x00000001000034cf
(lldb) breakpoint command add 1
Enter your debugger command(s).  Type 'DONE' to end.
> frame variable
> process continue
(lldb) process launch
Process 5997 launched: (x86_64)

2.2.3.4 修改斷點狀態

設置斷點可用狀態(Diable/Enable)
禁用斷點: breakpoint disable
啓用斷點: breakpoint enable
以上命令都需要指定斷點ID參數(支持通配符).
當邏輯斷點設置爲diable時, 它下方的所有位置斷點都將不再生效.
(lldb) breakpoint disable 1
1 breakpoints disabled.
(lldb) breakpoint disable 2.1
1 breakpoints disabled.


(lldb) breakpoint enable 1
1 breakpoints enabled.
(lldb) breakpoint enable 2.1
1 breakpoints enabled.


還可用通配符*指定斷點.
如下示例先是禁用了邏輯斷點1下的所有位置斷點, 而後啓用了位置斷點1.1
(lldb) breakpoint disable 1.*
2 breakpoint disabled*
(lldb) breakpoint enable 1.1
1 breakpoint enabled.

2.2.3.5 刪除斷點

刪除斷點
(lldb) breakpoint delete 1
1 breakpoints deleted; 2 breakpoint locations disabled.

2.2.4 watchpoint

其與breakpoint很類似,主要用於在感知到內存發生變化後觸發斷點。內存斷點受硬件寄存器數量的限制。

// 針對變量設置內存斷點
(lldb) watchpoint set variable places
Watchpoint created: Watchpoint 1: addr = 0x100004a40 size = 8 state = enabled type = w
    declare @ 'main.swift:7'
    watchpoint spec = 'places'
    new value: 1 value

// 針對表達式返回值設置內存斷點
(lldb) watchpoint set expression -- (int *)$places + 8
Watchpoint created: Watchpoint 2: addr = 0x100005f33 size = 8 state = enabled type = w
    new value: 0x0000000000000000

// 默認情況下,內存斷點監視對變量或地址的寫訪問。 通過傳遞值爲 read、write 或 read_write 的 --watch (-w) 選項來指定對監視器的訪問類型。

// 默認情況下,觀察點使用目標機器的指針字節大小監視讀取和寫入。 通過傳遞值爲 1、2、4 或 8 的 --size (-s) 選項,更改用於監視區域的字節數。

內存斷點的刪除、修改等操作同breakpoint

2.2.5 thread

  • n 下一行(next的簡寫)
  • c 繼續(continue的簡寫)
  • s 進入函數(step into的簡寫)
  • finish 跳出當前函數
    上面是命令行的輸入,對應下面的GUI按鈕。


// 返回某值
thread return value 

// 跳過2條語句
thread jump --by 2

// 這兩個命令也與流程控制類似,不過它們屬於frame
up\down

2.2.6 image

// 列出所有依賴庫

(lldb) image list
[  0] 69F6C476-555D-3987-B162-E62C9A1D718C 0x000000010008c000 /var/mobile/Containers/Bundle/Application/D6B51B8B-00F8-443F-9B8E-EF47C2330D2B/WhatsApp.app/WhatsApp (0x000000010008c000)
[  1] 3134CFB2-F722-310E-A2C7-42AE4DC131AB 0x00000001017ec000 /Library/MobileSubstrate/MobileSubstrate.dylib (0x00000001017ec000)
[  2] 4DB79D94-6764-3180-99C9-5615E29073E7 0x0000000196848000 /Users/piao/Library/Developer/Xcode/iOS DeviceSupport/9.0.2 (13A452)/Symbols/usr/lib/libSystem.B.dylib
// 查找ASLR偏移
(lldb) image list -f -o WhatsApp
[  0] /var/mobile/Containers/Bundle/Application/D6B51B8B-00F8-443F-9B8E-EF47C2330D2B/WhatsApp.app/WhatsApp 0x000000000008c000(0x000000010008c000)
ASLR偏移:0x000000000008c000
// 打印加載模塊列表

(lldb) image list -f -o // 通常帶這兩個參數使用
[  0] 40E417A4-F966-3DB4-B028-B0272DC016A7 0x000a0000 /Users/piao/Library/Developer/Xcode/DerivedData/debug-bdkhskdqykkoqmhjedilckzvpuls/Build/Products/Debug-iphoneos/debug.app/debug 
      /Users/piao/Library/Developer/Xcode/DerivedData/debug-bdkhskdqykkoqmhjedilckzvpuls/Build/Products/Debug-iphoneos/debug.app.dSYM/Contents/Resources/DWARF/debug
[  1] 011601C0-F561-3306-846B-94A7C8C841DA 0x2d9e6000 /Users/piao/Library/Developer/Xcode/iOS DeviceSupport/7.1.2 (11D257)/Symbols/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics
查找原始地址信息
在可執行文件或任何共享庫中查找原始地址的信息。
// image lookup -a 表達式
例子:
(lldb) image lookup -a $pc
Address: debug[0x0000b236] (debug.__TEXT.__text + 1254)
Summary: debug`main + 58 at main.m:16
//查找類信息
image lookup --type UIViewController
image lookup -t UIViewController

//查找符號
image lookup --name viewDidLoad
image lookup -n viewDidLoad

//查找地址
image lookup --address 0x000000018ff60490
image lookup -a 0x000000018ff60490

三、使用LLDB

3.1 使用LLDB的方式

日常,我們使用LLDB主要是通過Xcode提供的breakpoint navigator、Debug Area(variables view、console)來完成的。其中,在程序已經暫停的情況下,可以使用console來通過交互式命令來完整使用LLDB提供的能力。
從廣義的角度來說,第四節 擴展LLDB也屬於使用LLDB的方式,不過其與本章的使用具有較大的差異,所以另起一小節來介紹。
作爲日常使用,可以仔細看一下下面圖中的各個選項、文本框應該填寫什麼,來更加充分的使用LLDB提供的能力。


下面,介紹更多的日常實際場景的例子。

3.2 實際使用LLDB

3.2.1 修改返回值

- (int)rtInt {
    NSLog(@"rtInt");
    // 在下面一行設置斷點,並添加如下圖所示內容(方法1)
    int n = 10;
    // 在下面一行設置斷點,並添加如下圖所示內容(方法2)
    return n;
}

- (NSDictionary *)rtDict {
    NSLog(@"rtDict");
    NSDictionary *dict= @{@"key":@"value"};
    // 設置斷點,然後控制檯輸入,並在LLDB的環境裏定義變量,並把變量賦值給我們需要的對象
    // e NSDictionary *$otherDict = @{@"key1":@"value1"}
    // e dict = $otherDict
    // 在LLDB裏可以定義新的變量,變量必須以$開頭
    return dict;
}

3.2.2 輸出佈局信息

(lldb) po [self.view recursiveDescription]
<UIView: 0x7fb5b6909b50; frame = (0 0; 393 852); autoresize = W+H; backgroundColor = <UIDynamicSystemColor: 0x6000020e6b40; name = systemBackgroundColor>; layer = <CALayer: 0x6000035cb6a0>>
   | <UIButton: 0x7fb5b3f04f60; frame = (10 10; 200 80); opaque = NO; layer = <CALayer: 0x6000035fe260>>
   |    | <UIButtonLabel: 0x7fb5b3b157b0; frame = (81.6667 29; 37 22); text = '點我'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x6000016d08c0>>

以上只是簡單的UI調試,可以使用Xcode提供的Debug View Hierarchy來進行更深入的排查。
[圖片上傳失敗...(image-b0acbc-1670762937746)]

通過這些UI所提供的的調試能力,可以查看被截斷的UI、佈局以及創建佈局的調用堆棧、視圖層次、大小等信息。

3.3 修改UI並實時看到

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(10, 10, 200, 80)];
    [self.view addSubview:btn];
    [btn addTarget:self action:@selector(click:) forControlEvents:UIControlEventTouchUpInside];
    [btn setTitleColor:UIColor.redColor forState:UIControlStateNormal];
    [btn setTitle:@"點我"forState:UIControlStateNormal];
}

- (void)click:(UIButton *)btn {
    // 在這裏設斷定,並輸入如下命令
    // e (void)[self.view setBackgroundColor:[UIColor grayColor]] 修改顏色
    // e (void)[CATransaction flush]  // 把顏色的修改通過渲染更新到界面
    NSLog(@"here");
}

注意
以上命令並不總是能成功,在viewDidLoad中設置斷點然後進行操作就不行

3.4 注意返回類型

在上面例子中,如果輸入
e [self.view setBackgroundColor:[UIColor grayColor]]
則會報這個錯誤
error: expression failed to parse:
error: <user expression 0>:1:12: no known method '-setBackgroundColor:'; cast the message send to the method's return type
[self.view setBackgroundColor:[UIColor grayColor]]
~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// 上面的錯誤信息告訴LLDB不知道返回類型,所以需要強制類型轉換
// 這是在使用LLDB過程中經常碰到的問題

3.5 跳過某些語句

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    NSLog(@"%d", __LINE__); // 假設此行是41
    NSLog(@"%d", __LINE__); // 在此行設置斷點,在debug command中輸入:thread jump --by 1
    NSLog(@"%d", __LINE__);
    NSLog(@"%d", __LINE__);
    NSLog(@"%d", __LINE__);
    NSLog(@"%d", __LINE__);

    // 以下是輸出,可以看出跳過了42行
    baseStudy[33708:346268] 41
    baseStudy[33708:346268] 43
    baseStudy[33708:346268] 44
    baseStudy[33708:346268] 45
    baseStudy[33708:346268] 46
}

3.6 命令後執行

// 這是上面設置的命令
// -C 表示後面可以添加新的命令
// -G true標識斷點不停止,直接運行
br set -l 43 -f ViewController.m  -C 'po @"onthway"' -G true


// 可以看到斷點其實被設置3個(因爲循環了3次)
5: file = 'ViewController.m', line = 43, exact_match = 0, locations = 1, resolved = 1, hit count = 2 Options: enabled auto-continue 
    Breakpoint commands:
      po @"onthway"

  5.1: where = baseStudy`-[ViewController viewDidLoad] + 90 at ViewController.m:43:9, address = 0x000000010d564cba, resolved, hit count = 2 

6: file = 'ViewController.m', line = 43, exact_match = 0, locations = 1, resolved = 1, hit count = 1 Options: enabled auto-continue 
    Breakpoint commands:
      po @"onthway"

  6.1: where = baseStudy`-[ViewController viewDidLoad] + 90 at ViewController.m:43:9, address = 0x000000010d564cba, resolved, hit count = 1 

7: file = 'ViewController.m', line = 43, exact_match = 0, locations = 1, resolved = 1, hit count = 0 Options: enabled auto-continue 
    Breakpoint commands:
      po @"onthway"

  7.1: where = baseStudy`-[ViewController viewDidLoad] + 90 at ViewController.m:43:9, address = 0x000000010d564cba, resolved, hit count = 0 

3.7 執行一次的斷點

// 把上面的斷點語句添加--one-shot true
br set --one-shot true -l 43 -f ViewController.m   -C 'po @"onthway"' -G true

// 輸出3次  ontheway
3: file = '/Users/wangwenjun04/Desktop/myfolder/studyCode/baseStudy/baseStudy/ViewController.m', line = 43, exact_match = 0, locations = 1, resolved = 1, hit count = 3

  3.1: where = baseStudy`-[ViewController viewDidLoad] + 90 at ViewController.m:43:9, address = 0x0000000103ea0cba, resolved, hit count = 3 

4: file = '/Users/wangwenjun04/Desktop/myfolder/studyCode/baseStudy/baseStudy/ViewController.m', line = 50, exact_match = 0, locations = 1, resolved = 1, hit count = 1

  4.1: where = baseStudy`-[ViewController viewDidLoad] + 199 at ViewController.m:50:22, address = 0x0000000103ea0d27, resolved, hit count = 1 

// 此斷點只其一次作用
7: file = 'ViewController.m', line = 43, exact_match = 0, locations = 1, resolved = 1, hit count = 0 Options: enabled one-shot auto-continue 
    Breakpoint commands:
      po @"onthway"

  7.1: where = baseStudy`-[ViewController viewDidLoad] + 90 at ViewController.m:43:9, address = 0x0000000103ea0cba, resolved, hit count = 0 

3.8 忽略特定次數

假如有以下代碼,當i的值是9的時候會出現問題。那麼按照上述基本的調試方法,在if這一行設置斷點,然後要通過8次的暫停才能在我們想要的地方停住。

NSInteger count = array.count;
for (NSInteger i = 0; i < count; i++) {
    if (i % 2) {
        // 做一些事情
    } else {
        // 做另外一些事情
    }
}

通過設置跳過前8次執行,LLDB自動在我們想要的時候暫停程序的執行。通過設置ingore的次數,我們的調率得到了提升。

3.9 條件斷點

假如我們有如下代碼:

void printName(NSString *name) {
    NSLog(@"name=%@", name);
}

int main(int argc, const char * argv[]) {    
    printName(@"jzm");
    printName(@"xjp");
    printName(@"hjt");
}

我們的需求是在name是xjp時暫停,那麼按照簡單的調試方法,只能在NSLog那一行來設置斷點,然後再每次斷點停住的時候檢查name的值是否是xjp。我們可以更家搞笑,通過設置條件斷點的方式。


3.10 記錄日誌

很多時候,我們是需要記錄程序執行過程的路徑、變量的值、次數等來發現問題,可以通過如下的方式來記錄日誌。
[圖片上傳失敗...(image-e28259-1670762937746)]

除此方式外,還可以通過expression表達時候來記錄日誌。
[圖片上傳失敗...(image-fb046a-1670762937746)]

3.11 過濾日誌

有些情況下,我們添加的日誌、程序的輸出過多,會淹沒我們想要看到的內容,可以通過過濾日誌來實現。



還可以通過mac提供的控制檯程序來實現多字段的過濾。


四、擴展LLDB

LLDB、Xcode提供了便捷的方式進行擴展,這些擴展都基於LLDB提供的接口。

3.1 工程級擴展

我們可以專門針對某個工程進行擴展,其方法很簡單。新建一個用於擴展LLDB的python文件,然後選擇edit scheme進行配置。

setting set prompt [wwj-lldb]$

command alias wflush e (void)[CATransaction flush]
# 修改提示語
setting set prompt [wwj-lldb]$
# 新建一個命令別名,用於刷新UI
command alias wflush e (void)[CATransaction flush]

3.2 系統級擴展

在~/.lldbinit文件中添加自定義命令,如果沒有此文件需要新建。
FaceBook開源的chisel就是採用的此種方式。
其提供了許多擴展命令。

Command Description iOS OS X
pviews Print the recursive view description for the key window. Yes Yes
pvc Print the recursive view controller description for the key window. Yes No
visualize Open a UIImage, CGImageRef, UIView, CALayer, NSData (of an image), UIColor, CIColor, or CGColorRef in Preview.app on your Mac. Yes No
fv Find a view in the hierarchy whose class name matches the provided regex. Yes No
fvc Find a view controller in the hierarchy whose class name matches the provided regex. Yes No
show/hide Show or hide the given view or layer. You don't even have to continue the process to see the changes! Yes Yes
mask/unmask Overlay a view or layer with a transparent rectangle to visualize where it is. Yes No
border/unborder Add a border to a view or layer to visualize where it is. Yes Yes
caflush Flush the render server (equivalent to a "repaint" if no animations are in-flight). Yes Yes
bmessage Set a symbolic breakpoint on the method of a class or the method of an instance without worrying which class in the hierarchy actually implements the method. Yes Yes
wivar Set a watchpoint on an instance variable of an object. Yes Yes
presponder Print the responder chain starting from the given object. Yes Yes
... ... and many more!

其在~/.lldbinit中添加了如下的內容

# ~/.lldbinit

command script import /usr/local/opt/chisel/libexec/fbchisellldb.py

對此感興趣的讀者可以對chisel進行深入研究。

3.3 高階擴展

通過LLVM工程自己編譯LLDB代碼(難度高,有興趣的高手可以嘗試並分享給大家)。

四、參考資料

  1. 全面剖析 LLDB 調試器
  2. LLDB的使用
  3. LLDB調試器使用簡介
  4. LLDB: Beyond "po"
  5. 與調試器共舞 - LLDB 的華爾茲
  6. Advanced Debugging with Xcode and LLDB
  7. 自定義LLDB命令實戰
  8. LLDB基礎
  9. LLDB Debugging Guide
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章