一、引言
1.1 目的
程序員日常編寫代碼的時間遠小於調試的時間,所以熟練掌握調試工具能夠大幅降低調試所要花的時間,從而大幅提高工作效率。本文針對iOS開發過程中通過Xcode與/和LLDB進行調試進行的講解,以提升對LLDB的掌握。如果同時能夠幫助到廣大網友,那麼我講十分開心。
1.2 日常調試
在日常工作中,大部分調試動作都是設置一些斷點,在程序被暫停時查看執行邏輯、變量的值等,然後定位問題,並根據具體問題修改代碼。這應該是大部分程序員的日常調試行爲與狀態。此種調試行爲僅使用LLDB所提供的極少的能力。
除此之外,在大部分情況下程序員會藉助在源代碼裏添加NSLog之類的日誌代碼,來進行輔助調試。
1.3 日常調試的弊端
- 效率低下
- 比如,在某個循環中,需要在第N次循環才暫停,但是前N-1次也需要暫停與跳過
- 比如,想查看某些UI效果,需要臨時修改代碼,重新運行
- 比如,調試過程中的修改,需要反覆多次,每次都重新編譯與運行花費時間較多
- 比如,在一個複雜的UI結構中,不知道某個按鈕的點擊事件被誰響應
- 比如,某個元素佈局錯誤,不知道誰引起了佈局錯誤,花大力氣進行查找
- 會在正常代碼之外引入非必要代碼
- 添加的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代碼(難度高,有興趣的高手可以嘗試並分享給大家)。