學習筆記之--認識Xcode中的重要成員:lldb偵錯程式

滴水微瀾發表於2017-04-14

之前對lldb偵錯程式瞭解比較少,平時主要用來列印日誌和暫定時用滑鼠檢視屬性資料以及使用p po一些簡單的命令語句。

今天看了一些關於lldb的文章,頓時覺得之前對它瞭解太少了,原來它還有那麼多的功能。

好記性不如爛筆頭,我把方便易用的命令記錄下來,方便以後檢視。


一、ldb的語法結構

lldb的語法結構如下:
<command> [<subcommand> [<subcommand>...]] <action> [-options [option-value]] [argument [argument...]]
翻譯一下:
<命令> [<子命令> [<子命令>...]] <命令操作> [命令選項] [引數 [引數..]]
一個命令物件為跟隨其的子命令物件建立一個上下文,子命令又為其子命令建立一個上下文,依此類推.

舉個例子,假設我們給main方法設定一個斷點,我們使用下面的命令:

breakpoint set -n main
這個命令對應到上面的語法就是:

command: breakpoint 表示斷點命令
action: set 表示對命令怎樣的操作
option: -n 表示根據方法name設定斷點(--name)
arguement: 方法名mian 表示引數mian

二、原始(raw)命令
lldb支援不帶命令選項(options)的原始(raw)命令,原始命令會將命令後面的所有東西當做引數(arguement)傳遞。
不過很多原始命令也可以帶命令選項,當你使用命令選項的時候,需要在命令選項後面加--區分命令選項和引數。

expression命令 就是一個原始命令,舉例如下:
1.列印一個變數
(lldb) expression count
(int) $2 = 4
2.列印一個物件,列印物件時需要用-O命令選項,選項後面是“--”分隔符,後面接著 引數
(lldb) expression -O -- self
<ViewController: 0x7f9000f17660>

三、唯一匹配原則
lldb的命令遵循唯一匹配原則,舉例如下
(lldb) breakpoint set -n touchesBegan:withEvent:
(lldb) bre s -n touchesBegan:withEvent:
這兩個命令的效果是一樣的,因為根據字母"bre"匹配到的命令只有“breakpoint”,根據字母"s"匹配到的命令操作只有“set”

四、lldb常用命令
1.expression
expression命令的作用是執行一個表示式,並將表示式返回的結果輸出。expression的完整語法是這樣的:
expression <cmd-options> -- <expr>
對應的例子如下:
(lldb) expression -O -- self
<ViewController: 0x7f9000f17660>
expression <命令選項> “--”分隔符 執行的引數或表示式

expression是lldb裡面最重要的命令。他能實現2個非常重要的功能。
a.執行表示式。
在程式暫停時,可以通過lldb偵錯程式直接修改頁面屬性,而不需要重新執行程式
// 改變顏色
(lldb) expression -- self.view.backgroundColor = [UIColor redColor]
// 重新整理介面
(lldb) expression -- (void)[CATransaction flush]
b.輸出返回值
(lldb) expression -- self.view
(UIView *) $1 = 0x00007fe322c18a10

2.p 、print 、 call 命令
p、print、call。這三個命令其實都是expression --的別名(--表示不再接受命令選項)是對expression --的一層封裝
它們同樣擁有expression的兩項重要的功能,a.執行表示式,b.輸出返回值
(lldb) p self.count
(CGFloat) $1 = 30
(lldb) print self.count
(CGFloat) $1 = 30
(lldb) call self.count
(CGFloat) $1 = 30
(lldb) expression -- self.count
(CGFloat) $2 = 30

3.po
在OC裡所有的物件都是用指標表示的,所以一般用p、print、call、expression --列印的時候,列印出來的是物件的指標,而不是物件本身。如果我們想列印物件。我們需要使用命令選項:-O。為了更方便的使用,lldb為expression -O --定義了一個別名:po
舉例如下:
(lldb) p self
(SingletonViewController *) $3 = 0x00007fda32a8f9d0
(lldb) po self
<SingletonViewController: 0x7fda32a8f9d0>
更多expression 的學習可以用 help expression獲取,然後細細研究

4.thread
A. thread backtrace 、 bt
在程式暫停時,若要獲取此時執行緒的堆疊資訊,可以通過thread backtrace 命令獲取,該命令的語法如下:
thread backtrace [-c <count>] [-s <frame-index>] [-e <boolean>]
-c:設定列印堆疊的幀數(frame)
-s:設定從哪個幀(frame)開始列印
-e:是否顯示額外的回溯
實際上這些命令選項我們一般不需要使用。當程式暫定後,只需要呼叫thread backtrace命令就能將常用的資訊列印出來.
bt命令 同 thread backtrace 命令一樣,舉例如下:
(lldb) thread backtrace
* thread #1: tid = 0x3ae2, 0x0000000106ccc5a6 MyTestWorkProduct`-[SingletonViewController touchesBegan:withEvent:](self=0x00007fda32a8f9d0, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x00007fda307057b0) + 86 at SingletonViewController.m:61, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1
* frame #0: 0x0000000106ccc5a6 MyTestWorkProduct`-[SingletonViewController touchesBegan:withEvent:](self=0x00007fda32a8f9d0, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x00007fda307057b0) + 86 at SingletonViewController.m:61
frame #1: 0x0000000108972227 UIKit`forwardTouchMethod + 349
frame #2: 0x00000001089720b9 UIKit`-[UIResponder touchesBegan:withEvent:] + 49
frame #3: 0x00000001087d3790 UIKit`-[UIWindow _sendTouchesForEvent:] + 308
frame #4: 0x00000001087d46d4 UIKit`-[UIWindow sendEvent:] + 865
frame #5: 0x000000010877fdc6 UIKit`-[UIApplication sendEvent:] + 263
frame #6: 0x0000000106c15d73 MyTestWorkProduct`-[UIApplication(self=<unavailable>, _cmd=<unavailable>, event=<unavailable>) btg_swizzleSendEvent:] + 72 at UIApplication+BTGMethodSwizzler.m:27 [opt]
frame #7: 0x0000000108759553 UIKit`_UIApplicationHandleEventQueue + 6660
frame #8: 0x000000010b32f301 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
frame #9: 0x000000010b32522c CoreFoundation`__CFRunLoopDoSources0 + 556
frame #10: 0x000000010b3246e3 CoreFoundation`__CFRunLoopRun + 867
frame #11: 0x000000010b3240f8 CoreFoundation`CFRunLoopRunSpecific + 488
frame #12: 0x000000010da06ad2 GraphicsServices`GSEventRunModal + 161
frame #13: 0x000000010875ef09 UIKit`UIApplicationMain + 171
frame #14: 0x0000000106d4204a MyTestWorkProduct`main(argc=1, argv=0x00007fff590025d8) + 138 at main.m:16
frame #15: 0x000000010c3c592d libdyld.dylib`start + 1
frame #16: 0x000000010c3c592d libdyld.dylib`start + 1
(lldb) bt
* thread #1: tid = 0x3ae2, 0x0000000106ccc5a6 MyTestWorkProduct`-[SingletonViewController touchesBegan:withEvent:](self=0x00007fda32a8f9d0, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x00007fda307057b0) + 86 at SingletonViewController.m:61, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1
* frame #0: 0x0000000106ccc5a6 MyTestWorkProduct`-[SingletonViewController touchesBegan:withEvent:](self=0x00007fda32a8f9d0, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x00007fda307057b0) + 86 at SingletonViewController.m:61
frame #1: 0x0000000108972227 UIKit`forwardTouchMethod + 349
frame #2: 0x00000001089720b9 UIKit`-[UIResponder touchesBegan:withEvent:] + 49
frame #3: 0x00000001087d3790 UIKit`-[UIWindow _sendTouchesForEvent:] + 308
frame #4: 0x00000001087d46d4 UIKit`-[UIWindow sendEvent:] + 865
frame #5: 0x000000010877fdc6 UIKit`-[UIApplication sendEvent:] + 263
frame #6: 0x0000000106c15d73 MyTestWorkProduct`-[UIApplication(self=<unavailable>, _cmd=<unavailable>, event=<unavailable>) btg_swizzleSendEvent:] + 72 at UIApplication+BTGMethodSwizzler.m:27 [opt]
frame #7: 0x0000000108759553 UIKit`_UIApplicationHandleEventQueue + 6660
frame #8: 0x000000010b32f301 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
frame #9: 0x000000010b32522c CoreFoundation`__CFRunLoopDoSources0 + 556
frame #10: 0x000000010b3246e3 CoreFoundation`__CFRunLoopRun + 867
frame #11: 0x000000010b3240f8 CoreFoundation`CFRunLoopRunSpecific + 488
frame #12: 0x000000010da06ad2 GraphicsServices`GSEventRunModal + 161
frame #13: 0x000000010875ef09 UIKit`UIApplicationMain + 171
frame #14: 0x0000000106d4204a MyTestWorkProduct`main(argc=1, argv=0x00007fff590025d8) + 138 at main.m:16
frame #15: 0x000000010c3c592d libdyld.dylib`start + 1
frame #16: 0x000000010c3c592d libdyld.dylib`start + 1


B. thread return
Debug的時候,有時候會因為各種原因,不想讓程式碼沿著方法繼續執行了。要麼直接返回,要麼帶上一個值直接返回。
語法為:thread return [<expr>]
舉例如下:
(lldb) thread return
(lldb) thread return NO

C. c 、n 、s 、 finish
在lldb偵錯程式的做上角有個暫停按鈕,右邊三個順序為:單步執行、進入方法、從當前方法返回到上一層frame
這三個按鈕平時都是手動點選進行除錯,在lldb偵錯程式中,也有相應的命令可以操作
分別說明如下:
c/ continue/ thread continue: 這三個命令效果都表示程式繼續執行
n/ next/ thread step-over: 這三個命令效果都表示單步執行
s/ step/ thread step-in: 這三個命令效果都表示進入某個方法
finish/ step-out: 這兩個命令效果都表示直接走完當前方法,返回到上層frame

D. thread其他不常用的命令
thread list: 列出所有的執行緒
thread select: 選擇某個執行緒
thread info: 輸出當前執行緒的資訊
其他更多的命令可以使用命令help thread學習

5. frame(幀)
在第4項thread命令中,有個thread backtrace 命令,該命令會列印出當前執行緒的堆疊資訊,其中裡面的每一條就是一幀(frame)
舉例如下:
frame #0: 0x0000000106ccc5a6 MyTestWorkProduct`-[SingletonViewController touchesBegan:withEvent:](self=0x00007fda32a8f9d0, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x00007fda307057b0) + 86 at SingletonViewController.m:61
frame #1: 0x0000000108972227 UIKit`forwardTouchMethod + 349
frame #2: 0x00000001089720b9 UIKit`-[UIResponder touchesBegan:withEvent:] + 49

A. frame variable 列印當前frame中的變數資訊
舉例如下:
(lldb) frame variable
(SingletonViewController *) self = 0x00007fda32a8f9d0
(SEL) _cmd = "touchesBegan:withEvent:"
(__NSSetM *) touches = 0x00007fda32ad6a70 1 element
(UITouchesEvent *) event = 0x00007fda307057b0
(NSInteger) count1 = 10
(NSInteger) count2 = 20
(lldb) frame variable self->_count
(CGFloat) self->_count = 30

B. frame其他不常用命令
frame info: 檢視當前frame的資訊
frame select: 選擇某個frame

6. breakpoint 斷點命令,lldb中的斷點命令非常強大
A. breakpoint set 斷點設定,用於斷點設定的方式有多種 分別如下:
使用-n根據方法名設定斷點:
給所有類中的viewWillAppear:設定一個斷點:
(lldb) breakpoint set -n viewWillAppear:
Breakpoint 13: 33 locations.
使用-f指定檔案
只需要給ViewController.m檔案中的viewDidLoad設定斷點:
(lldb) breakpoint set -f ViewController.m -n viewDidLoad
Breakpoint 22: where = TLLDB`-[ViewController viewDidLoad] + 20 at ViewController.m:22, address = 0x000000010272a6f4
這裡需要注意,如果方法未寫在檔案中(比如寫在category檔案中,或者父類檔案中),指定檔案之後,將無法給這個方法設定斷點。
使用-l指定檔案某一行設定斷點
給ViewController.m第38行設定斷點
(lldb) breakpoint set -f ViewController.m -l 38
Breakpoint 23: where = TLLDB`-[ViewController text:] + 37 at ViewController.m:38, address = 0x000000010272a7d5
使用-c設定條件斷點
text:方法接受一個ret的引數,我們想讓ret == YES的時候程式中斷:
(lldb) breakpoint set -n text: -c ret == YES
Breakpoint 7: where = TLLDB`-[ViewController text:] + 30 at ViewController.m:37, address = 0x0000000105ef37ce
使用-o設定單次斷點
如果剛剛那個斷點我們只想讓他中斷一次:
(lldb) breakpoint set -n text: -o
'breakpoint 3': where = TLLDB`-[ViewController text:] + 30 at ViewController.m:37, address = 0x000000010b6f97ce

B. breakpoint command 命令 子命令
在程式走到斷點後,有時候需要執行一些命令,而這些命令是可以操作的。我們可以為斷點新增,刪除命令
B1. breakpoint command add 新增命令
(lldb) breakpoint command add -o "po self" 3
解釋:為斷點號為:3 的斷點新增一條命令。 -o完整寫法是--one-liner。
(lldb) breakpoint command add 3
Enter your debugger command(s). Type 'DONE' to end.
> frame variable
> continue
> DONE
解釋:為斷點ID為:3的斷點新增多個命令,這些命令以“ DONE”作為結束符。
B2. breakpoint command list 3
獲取斷點ID為:3的 斷點的命令列表
B3. breakpoint command delete 3
刪除斷點ID為:3的 斷點的所有命令

C. breakpoint list
獲取工程中所有斷點的列表

D. breakpoint disable/enable
斷點的失效/生效
舉例如下:
(lldb) breakpoint disable 1
1 breakpoints disabled.
(lldb) breakpoint disable 1
1 breakpoints disabled.

F. breakpoint delete
刪除斷點
(lldb) breakpoint delete 1
刪除斷點ID為:1的斷點
(lldb) breakpoint delete
About to delete all breakpoints, do you want to do that?: [Y/n] y
All breakpoints removed. (1 breakpoint)
刪除工程中所有的斷點
(lldb) breakpoint delete -f
All breakpoints removed. (1 breakpoint)
強制所有的斷點,忽略提示

7. watchpoint
breakpoint有一個孿生兄弟watchpoint。如果說breakpoint是對方法生效的斷點,watchpoint就是對地址生效的斷點
在開發過程中,平時我們簡稱一個屬性的變化通常是使用屬性的set方法,如果屬性沒有經過set方法,是直接通過self->屬性直接修改的話,用set方法就不行了,此時可以通過watchpoint命令監聽屬性的記憶體地址,一旦address的內容被修改,程式就會自動斷開。
A. watchpoint set
設定觀測點
watchpoint set variable <變數引數> 為變數設定觀測點
(lldb) watchpoint set variable self->_string
Watchpoint created: Watchpoint 1: addr = 0x7fcf3959c418 size = 8 state = enabled type = w
watchpoint spec = 'self->_string'
new value: 0x0000000000000000
watchpoint set expression <變數地址引數> 為變數地址設定觀測點
(lldb) p &_model
(Modek **) $3 = 0x00007fe0dbf23280
(lldb) watchpoint set expression 0x00007fe0dbf23280
Watchpoint created: Watchpoint 1: addr = 0x7fe0dbf23280 size = 8 state = enabled type = w
new value: 0

B. watchpoint command
跟breakpoint類似,在watchpoint中也可以新增命令

C. watchpoint command add
為觀測點新增命令
watchpoint command add -o 'po self' 1
為ID為:1的觀測點新增一條 命令
(lldb) watchpoint command add 1
Enter your debugger command(s). Type 'DONE' to end.
> po self
> continue
> DONE
為ID為:1的觀測點新增多條命令

D. watchpoint command list
查詢觀測點命令列表
(lldb) watchpoint command list 1
查詢觀測點ID為:1的觀測點,命令列表

F. watchpoint command delete
(lldb) watchpoint command delete 1
刪除觀測點ID為:1的觀測點的命令列表

E. watchpoint list
查詢工程中的所有觀測點列表

F. watchpoint disable
(lldb) watchpoint disable 1
設定觀測點ID為:1的觀測點為失效

G. watchpoint enable
(lldb) watchpoint enable 1
設定觀測點ID為:1的觀測點為生效

H. watchpoint delete
(lldb) watchpoint delete 1
刪除觀測點ID為:1的觀測點
(lldb) watchpoint delete
About to delete all watchpoints, do you want to do that?: [Y/n] y
All watchpoints removed. (2 watchpoints)
刪除工程中的觀測點

8. target 根據引數 列印出引數資訊對應在專案檔案中的位置
對於target這個命令,平時用的最多的是target modules lookup。由於LLDB給target modules取了個別名image,所以這個命令我們又可以寫成image lookup。
A.image lookup --address 根據所給的記憶體地址,找到符號所在專案檔案中的位置。簡寫為image lookup -a
當發生一個崩潰時,可以通過列印記憶體地址的方式定位到崩潰發生的地方。
舉例如下:
2017-04-14 21:49:30.784 MyTestWorkProduct[955:18657] -[__NSDictionaryM addObject:]: unrecognized selector sent to instance 0x7fc50b0e4e70
libc++abi.dylib: terminate_handler unexpectedly threw an exception
(lldb) image lookup -a 0x7fc50b0e4e70
B. image lookup --name 如果想查詢一個方法或符號的位置,可通過此命令查詢。 簡寫:image lookup -n
舉例如下:
lldb) image lookup -n didReceiveMemoryWarning
26 matches found in /Users/xuyefeng/Library/Developer/Xcode/DerivedData/MyTestWorkProduct-etxrcvqebgprfcfjyibavnioxbey/Build/Products/Debug-iphonesimulator/MyTestWorkProduct.app/MyTestWorkProduct:
Address: MyTestWorkProduct[0x000000010000b742] (MyTestWorkProduct.__TEXT.__text + 40370)
Summary: MyTestWorkProduct`-[BTGBaseViewController didReceiveMemoryWarning] at BTGBaseViewController.m:71 Address: MyTestWorkProduct[0x000000010005de80] (MyTestWorkProduct.__TEXT.__text + 378096)
Summary: MyTestWorkProduct`-[ModelDictionaryTransferVC didReceiveMemoryWarning] at ModelDictionaryTransferVC.m:22 Address: MyTestWorkProduct[0x000000010005df00] (MyTestWorkProduct.__TEXT.__text + 378224)
Summary: MyTestWorkProduct`-[ViewController didReceiveMemoryWarning] at ViewController.m:23 Address: MyTestWorkProduct[0x0000000100061510] (MyTestWorkProduct.__TEXT.__text + 392064)
C. image lookup --type 如果想查詢一個類的資訊可以用這個命令。簡寫為image lookup -t
舉例如下:
(lldb) image lookup -t Person
Best match found in /Users/xuyefeng/Library/Developer/Xcode/DerivedData/MyTestWorkProduct-etxrcvqebgprfcfjyibavnioxbey/Build/Products/Debug-iphonesimulator/MyTestWorkProduct.app/MyTestWorkProduct:
id = {0x000d796a}, name = "Person", byte-size = 40, decl = Person.h:13, compiler_type = "@interface Person : NSObject{
NSString * _name;
NSString * _adress;
NSNumber * _age;
NSInteger _ID;
}
@property ( getter = name,setter = setName:,readwrite,nonatomic ) NSString * name;
@property ( getter = adress,setter = setAdress:,readwrite,nonatomic ) NSString * adress;
@property ( getter = age,setter = setAge:,readwrite,nonatomic ) NSNumber * age;
@property ( getter = ID,setter = setID:,assign,readwrite,nonatomic ) NSInteger ID;
@end"

D. target stop-hook ,target stop-hook命令就是讓你可以在每次stop的時候去執行一些命令
target stop-hook add 、 display 新增命令的方式。
舉例如下:
(lldb) target stop-hook add -o "frame variable"
Stop hook #4 added.
註釋:-o的全稱是--one-liner

F. target stop-hook list
獲取全部命令列表
E. target stop-hook delete & undisplay
刪除列表
G. target stop-hook disable/enable
使命令失效

9. Extension
在偵錯程式中輸入 e @import UIKit 可以列印出view的frame
舉例如下:
(lldb) e @import UIKit
(lldb) po self.view.frame
(origin = (x = 0, y = 0), size = (width = 375, height = 667))


10. 更多
更多命令可以使用help 進行學習
apropos 命令可以模糊搜尋命令關鍵字。

 

參考來源:

小笨狼與LLDB的故事:http://www.jianshu.com/p/e89af3e9a8d7

 

相關文章