iOS中教你快速掌握LLDB調試技巧 – iPhone手機開發 iPhone軟體開發教學課程

摘要

LLDB是Xcode默認的調試器,它與LLVM編譯器一起,帶給我們更豐富的流程控制和數據檢測的調試功能。平時用Xcode運行程序,實際走的都是LLDB。熟練使用LLDB,可以讓你debug事半功倍。

LLDB控制臺

Xcode中內嵌瞭LLDB控制臺,在Xcode中代碼的下方,我們可以看到LLDB控制臺。

 

這裡寫圖片描述

 

LLDB控制臺平時會輸出一些log信息。如果我們想輸入命令調試,必須讓程序進入暫停狀態。讓程序進入暫停狀態的方式主要有2種:

1. 斷點或者watchpoint: 在代碼中設置一個斷點(watchpoint),當程序運行到斷點位置的時候,會進入stop狀態

2. 直接暫停,控制臺上方有一個暫停按鈕,上圖紅框已標出,點擊即可暫停程序

LLDB語法

在使用LLDB之前,我們來先看看LLDB的語法,瞭解語法可以幫助我們清晰的使用LLDB:


 

[ [...]] [-options [option-value]] [argument [argument...]]

一眼看上去可能比較迷茫,給大傢解釋一下:

1. 

 

(命令)和(子命令):LLDB調試命令的名稱。命令和子命令按層級結構來排列:一個命令對象為跟隨其的子命令對象創建一個上下文,子命令又為其子命令創建一個上下文,依此類推。 2. :執行命令的操作 3. :命令選項 4. :命令的參數 5. []:表示命令是可選的,可以有也可以沒有.

舉個例子,假設我們給main方法設置一個斷點,我們使用下面的命令:
這裡寫圖片描述vcnPw+a1xNPvt6i+zcrHo7o8L3A+DQo8cHJlIGNsYXNzPQ==”brush:java;”>
1. command: breakpoint 表示斷點命令
2. action: set 表示設置斷點
3. option: -n 表示根據方法name設置斷點
4. arguement: mian 表示方法名為mian

原始(raw)命令

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

e.g: 常用的expression就是raw命令,一般情況下我們使用expression打印一個東西是這樣的:

這裡寫圖片描述

當我們想打印一個對象的時候。需要使用-O命令選項,我們應該用–將命令選項和參數區分:

這裡寫圖片描述

唯一匹配原則

LLDB的命令遵循唯一匹配原則:假如根據前n個字母已經能唯一匹配到某個命令,則隻寫前n個字母等效於寫下完整的命令。
e.g: 前面提到我設置斷點的命令,我們可以使用唯一匹配原則簡寫,下面2條命令等效:

這裡寫圖片描述

~/.lldbinit

LLDB有瞭一個啟動時加載的文件~/.lldbinit,每次啟動都會加載。所以一些初始化的事兒,我們可以寫入~/.lldbinit中,比如給命令定義別名等。但是由於這時候程序還沒有真正運行,也有部分操作無法在裡面玩,比如設置斷點。

LLDB命令,expression

expression命令的作用是執行一個表達式,並將表達式返回的結果輸出。expression的完整語法是這樣的:

expression  -- 
1. :命令選項,一般情況下使用默認的即可,不需要特別標明。
--: 命令選項結束符,表示所有的命令選項已經設置完畢,如果沒有命令選項,--可以省略
2. : 要執行的表達式
說expression是LLDB裡面最重要的命令都不為過。因為他能實現2個功能。

1) 執行某個表達式。 我們在代碼運行過程中,可以通過執行某個表達式來動態改變程序運行的軌跡。 假如我們在運行過程中,突然想把self.view顏色改成紅色,看看效果。我們不必寫下代碼,重新run,隻需暫停程序,用expression改變顏色,再刷新一下界面,就能看到效果

// 改變顏色
這裡寫圖片描述
// 刷新界面
這裡寫圖片描述

2) 將返回值輸出。 也就是說我們可以通過expression來打印東西。 假如我們想打印self.view:
這裡寫圖片描述

p & print & call

一般情況下,我們直接用expression還是用得比較少的,更多時候我們用的是p、print、call。這三個命令其實都是expression –的別名(–表示不再接受命令選項,詳情見前面原始(raw)命令這一節)

1. print: 打印某個東西,可以是變量和表達式
2. p: 可以看做是print的簡寫
3. call: 調用某個方法。

表面上看起來他們可能有不一樣的地方,實際都是執行某個表達式(變量也當做表達式),將執行的結果輸出到控制臺上。所以你可以用p調用某個方法,也可以用call打印東西 e.g: 下面代碼效果相同:

這裡寫圖片描述

根據唯一匹配原則,如果你沒有自己添加特殊的命令別名。e也可以表示expression的意思。原始命令默認沒有命令選項,所以e也能帶給你同樣的效果.

po

我們知道,OC裡所有的對象都是用指針表示的,所以一般打印的時候,打印出來的是對象的指針,而不是對象本身。如果我們想打印對象。我們需要使用命令選項:-O。為瞭更方便的使用,LLDB為expression -O –定義瞭一個別名:po

這裡寫圖片描述

還有其他很多命令選項,不過我們一般用得比較少,所以我就不具體的一一介紹瞭,如果想瞭解,在LLDB控制臺上輸入:help expression即可查到expression所有的信息

thread之thread backtrace & bt

有時候我們想要瞭解線程堆棧信息,可以使用thread backtrace thread backtrace作用是將線程的堆棧打印出來。我們來看看他的語法

thread backtrace [-c ] [-s <frame-index>] [-e ]</frame-index>

thread backtrace後面跟的都是命令選項:

-c:設置打印堆棧的幀數(frame)
-s:設置從哪個幀(frame)開始打印
-e:是否顯示額外的回溯

實際上這些命令選項我們一般不需要使用。
e.g: 當發生crash的時候,我們可以使用thread backtrace查看堆棧調用
這裡寫圖片描述

我們可以看到crash發生在-[ViewController viewDidLoad]中的第23行,隻需檢查這行代碼是不是幹瞭什麼非法的事兒就可以瞭。

LLDB還為backtrace專門定義瞭一個別名:bt,他的效果與thread backtrace相同,如果你不想寫那麼長一串字母,直接寫下bt即可:

這裡寫圖片描述

thread return

Debug的時候,也許會因為各種原因,我們不想讓代碼執行某個方法,或者要直接返回一個想要的值。這時候就該thread return上場瞭。

thread return []

thread return可以接受一個表達式,調用命令之後直接從當前的frame返回表達式的值。

e.g: 我們有一個someMethod方法,默認情況下是返回YES。我們想要讓他返回NO
這裡寫圖片描述

我們隻需在方法的開始位置加一個斷點,當程序中斷的時候,輸入命令即可:
這裡寫圖片描述
效果相當於在斷點位置直接調用return NO;,不會執行斷點後面的代碼

c & n & s & finish

一般在調試程序的時候,我們經常用到下面這4個按鈕:
這裡寫圖片描述

用觸摸板的孩子們可能會覺得點擊這4個按鈕比較費勁。其實LLDB命令也可以完成上面的操作,而且如果不輸入命令,直接按Enter鍵,LLDB會自動執行上次的命令。按一下Enter就能達到我們想要的效果,有木有頓時感覺逼格滿滿的!!! 我們來看看對應這4個按鈕的LLDB命令:

1. c/ continue/ thread continue: 這三個命令效果都等同於上圖中第一個按鈕的。表示程序繼續運行
2. n/ next/ thread step-over: 這三個命令效果等同於上圖第二個按鈕。表示單步運行
3. s/ step/ thread step-in: 這三個命令效果等同於上圖第三個按鈕。表示進入某個方法
4. finish/ step-out: 這兩個命令效果等同於第四個按鈕。表示直接走完當前方法,返回到上層frame

thread其他不常用的命令

thread 相關的還有其他一些不常用的命令,這裡就簡單介紹一下即可,如果需要瞭解更多,可以使用命令help thread查閱

1. thread jump: 直接讓程序跳到某一行。由於ARC下編譯器實際插入瞭不少retain,release命令。跳過一些代碼不執行很可能會造成對象內存混亂發生crash。
2. thread list: 列出所有的線程
3. thread select: 選擇某個線程
4. thread until: 傳入一個line的參數,讓程序執行到這行的時候暫停
5. thread info: 輸出當前線程的信息

frame

前面我們提到過很多次frame(幀)。可能有的朋友對frame這個概念還不太瞭解。隨便打個斷點

這裡寫圖片描述
我們在控制臺上輸入命令bt,可以打印出來所有的frame。如果仔細觀察,這些frame和左邊紅框裡的堆棧是一致的。平時我們看到的左邊的堆棧就是frame。

frame variable

平時Debug的時候我們經常做的事就是查看變量的值,通過frame variable命令,可以打印出當前frame的所有變量
這裡寫圖片描述
可以看到,他將self,_cmd,ret,a等本地變量都打印瞭出來

如果我們要需要打印指定變量,也可以給frame variable傳入參數:

這裡寫圖片描述

不過frame variable隻接受變量作為參數,不接受表達式,也就是說我們無法使用frame variable self.string,因為self.string是調用string的getter方法。所以一般打印指定變量,我更喜歡用p或者po。

其他不常用命令

一般frame variable打印所有變量用得比較多,frame還有2個不怎麼常用的命令:

frame info: 查看當前frame的信息

這裡寫圖片描述

frame select: 選擇某個frame

這裡寫圖片描述

當我們選擇frame 1的時候,他會把frame1的信息和代碼打印出來。不過一般我都是直接在Xcode左邊點擊某個frame,這樣更方便

breakpoint

調試過程中,我們用得最多的可能就是斷點瞭。LLDB中的斷點命令也非常強大

breakpoint set

breakpoint set命令用於設置斷點,LLDB提供瞭很多種設置斷點的方式:

使用-n根據方法名設置斷點:

e.g: 我們想給所有類中的viewWillAppear:設置一個斷點:
這裡寫圖片描述

使用-f指定文件

e.g: 我們隻需要給ViewController.m文件中的viewDidLoad設置斷點:

這裡寫圖片描述

這裡需要註意,如果方法未寫在文件中(比如寫在category文件中,或者父類文件中),指定文件之後,將無法給這個方法設置斷點。

使用-l指定文件某一行設置斷點

e.g: 我們想給ViewController.m第38行設置斷點
這裡寫圖片描述

使用-c設置條件斷點

e.g: text:方法接受一個ret的參數,我們想讓ret == YES的時候程序中斷:
這裡寫圖片描述

使用-o設置單次斷點

e.g: 如果剛剛那個斷點我們隻想讓他中斷一次:
這裡寫圖片描述

breakpoint command

有的時候我們可能需要給斷點添加一些命令,比如每次走到這個斷點的時候,我們都需要打印self對象。我們隻需要給斷點添加一個po self命令,就不用每次執行斷點再自己輸入po self瞭

breakpoint command add

breakpoint command add命令就是給斷點添加命令的命令。

e.g: 假設我們需要在ViewController的viewDidLoad中查看self.view的值 我們首先給-[ViewController viewDidLoad]添加一個斷點
這裡寫圖片描述

可以看到添加成功之後,這個breakpoint的id為3,然後我們給他增加一個命令:po self.view
這裡寫圖片描述

-o完整寫法是–one-liner,表示增加一條命令。3表示對id為3的breakpoint增加命令。 添加完命令之後,每次程序執行到這個斷點就可以自動打印出self.view的值瞭

如果我們一下子想增加多條命令,比如我想在viewDidLoad中打印當前frame的所有變量,但是我們不想讓他中斷,也就是在打印完成之後,需要繼續執行。我們可以這樣玩:
這裡寫圖片描述

輸入breakpoint command add 3對斷點3增加命令。他會讓你輸入增加哪些命令,輸入’DONE’表示結束。這時候你就可以輸入多條命令瞭

註意:多次對同一個斷點添加命令,後面命令會將前面命令覆蓋

breakpoint command list

如果想查看某個斷點已有的命令,可以使用breakpoint command list。 e.g: 我們查看一下剛剛的斷點3已有的命令
這裡寫圖片描述
可以看到一共有2條命令,分別為frame variable和continue

breakpoint command delete

有增加就有刪除,breakpoint command delete可以讓我們刪除某個斷點的命令 e.g: 我們將斷點3中的命令刪除:

這裡寫圖片描述

可以看到刪除之後,斷點3就沒有命令瞭

breakpoint list

如果我們想查看已經設置瞭哪些斷點,可以使用breakpoint list e.g:

這裡寫圖片描述

我們可以看到當前隻有一個斷點,打在-[ViewController viewDidLoad]上,id是4

breakpoint disable/enable

有的時候我們可能暫時不想要某個斷點,可以使用breakpoint disable讓某個斷點暫時失效 e.g: 我們來讓剛剛的斷點4失效
這裡寫圖片描述
輸入完命令之後,顯示斷點已經失效

當我們又需要這個斷點的時候,可以使用breakpoint enable再次讓他生效 e.g: 重新啟用斷點4
這裡寫圖片描述

breakpoint delete

如果我們覺得這個斷點以後再也用不上瞭,可以用breakpoint delete直接刪除斷點. e.g: 刪除斷點4
這裡寫圖片描述

如果我們想刪除所有斷點,隻需要不指定breakpoint delete參數即可

這裡寫圖片描述

刪除的時候他會提示你,是不是真的想刪除所有斷點,需要你再次輸入Y確認。如果想直接刪除,不需要他的提示,使用-f命令選項即可
這裡寫圖片描述

實際平時我們真正使用breakpoint命令反而比較少,因為Xcode已經內置瞭斷點工具。我們可以直接在代碼上打斷點,可以在斷點工具欄裡面查看編輯斷點,這比使用LLDB命令方便很多。不過瞭解LLDB相關命令可以讓我們對斷點理解更深刻。

watchpoint

breakpoint有一個孿生兄弟watchpoint。如果說breakpoint是對方法生效的斷點,watchpoint就是對地址生效的斷點

如果我們想要知道某個屬性什麼時候被篡改瞭,我們該怎麼辦呢?有人可能會說對setter方法打個斷點不就行瞭麼?但是如果更改的時候沒調用setter方法呢? 這時候最好的辦法就是用watchpoint。我們可以用他觀察這個屬性的地址。如果地址裡面的東西改變瞭,就讓程序中斷

watchpoint set

watchpoint set命令用於添加一個watchpoint。隻要這個地址中的內容變化瞭,程序就會中斷。

watchpoint set variable

一般情況下,要觀察變量或者屬性,使用watchpoint set variable命令即可 e.g: 觀察self->_string
這裡寫圖片描述

watchpoint set variable傳入的是變量名。需要註意的是,這裡不接受方法,所以不能使用watchpoint set variable self.string,因為self.string調用的是string的getter方法

watchpoint set expression

如果我們想直接觀察某個地址,可以使用watchpoint set expression e.g: 我們先拿到_model的地址,然後對地址設置一個watchpoint
這裡寫圖片描述

watchpoint command

跟breakpoint類似,在watchpoint中也可以添加命令

watchpoint command add

我們來看看怎麼給watchpoint添加命令:
首先,我們設置一個watchpoint:
這裡寫圖片描述

可以看到這個watchpoint的id是1。我們可以用watchpoint command add -o添加單條命令
這裡寫圖片描述
我們在watchpoint停下來的時候,打印瞭他的線程信息。

我們也可以一次添加多條命令:

(lldb) watchpoint command add 1
Enter your debugger command(s).  Type 'DONE' to end.
> bt
> continue
> DONE

可以看到watchpoint的使用方法跟breakpoint幾乎一模一樣。

watchpoint command list

我們可以用watchpoint command list列出某個watchpoint所有的command

(lldb) watchpoint command list 1
Watchpoint 1:
    watchpoint commands:
      bt
      continue

watchpoint command delete

我們也可以用watchpoint command delete刪除某個watchpoint所有的command

(lldb) watchpoint command delete 1
(lldb) watchpoint command list 1
Watchpoint 1 does not have an associated command.

watchpoint list

如果我們想看當前所有watchpoint,可以使用watchpoint list:

(lldb) watchpoint list
Number of supported hardware watchpoints: 4
Current watchpoints:
Watchpoint 1: addr = 0x7fe9f9f28e30 size = 8 state = enabled type = w
    watchpoint spec = '_string'
    old value: 0x0000000000000000
    new value: 0x000000010128e0d0

可以看到,隻有一個watchpoint。

watchpoint disable

當我們不想讓某個watchpoint生效的時候,可以用watchpoint disable:

(lldb) watchpoint disable 1
1 watchpoints disabled.

再次查看這個watchpoint,可以看到他的state已經變為瞭disabled
這裡寫圖片描述

watchpoint enable

過瞭一會,我們又要用這個watchpoint瞭,這時候可以使用watchpoint enable:

(lldb) watchpoint enable 1
1 watchpoints enabled.

watchpoint delete

如果我們覺得再也用不著這個watchpoint瞭,可以用watchpoint delete將他刪除:

(lldb) watchpoint delete 1
1 watchpoints deleted.
(lldb) watchpoint list
Number of supported hardware watchpoints: 4
No watchpoints currently set.

刪除之後,我們可以看到watchpoint list裡面已經沒有watchpoint1瞭

如果有很多個watchpoint,我們想全都幹掉,隻需要不指定具體哪個watchpoint即可:

(lldb) watchpoint delete 
About to delete all watchpoints, do you want to do that?: [Y/n] y
All watchpoints removed. (2 watchpoints)

target

target modules lookup(image lookup)

對於target這個命令,我們用得最多的可能就是target modules lookup。由於LLDB給target modules取瞭個別名image,所以這個命令我們又可以寫成image lookup。

image lookup –address

當我們有一個地址,想查找這個地址具體對應的文件位置,可以使用image lookup –address,簡寫為image lookup -a e.g: 當我們發生一個crash

2015-12-17 14:51:06.301 TLLDB[25086:246169] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArray0 objectAtIndex:]: index 1 beyond bounds for empty NSArray'
*** First throw call stack:
(
    0   CoreFoundation                      0x000000010accde65 __exceptionPreprocess + 165
    1   libobjc.A.dylib                     0x000000010a746deb objc_exception_throw + 48
    2   CoreFoundation                      0x000000010ac7c395 -[__NSArray0 objectAtIndex:] + 101
    3   TLLDB                               0x000000010a1c3e36 -[ViewController viewDidLoad] + 86
    4   UIKit                               0x000000010b210f98 -[UIViewController loadViewIfRequired] + 1198
    5   UIKit                               0x000000010b2112e7 -[UIViewController view] + 27

我們可以看到是由於-[__NSArray0 objectAtIndex:]:超出邊界而導致的crash,但是objectAtIndex:的代碼到底在哪兒呢?

(lldb) image lookup -a 0x000000010a1c3e36
      Address: TLLDB[0x0000000100000e36] (TLLDB.__TEXT.__text + 246)
      Summary: TLLDB`-[ViewController viewDidLoad] + 86 at ViewController.m:32

根據0x000000010a1c3e36 -[ViewController viewDidLoad]裡面的地址,使用image lookup –address查找,我們可以看到代碼位置在ViewController.m裡面的32行

image lookup –name

當我們想查找一個方法或者符號的信息,比如所在文件位置等。我們可以使用image lookup –name,簡寫為image lookup -n。

e.g: 剛剛遇到的真問題,某個第三方SDK用瞭一個我們項目裡原有的第三方庫,庫裡面對NSDictionary添加瞭category。也就是有2個class對NSDictionary添加瞭名字相同的category,項目中調用自己的category的地方實際走到瞭第三方SDK裡面去瞭。最大的問題是,這2個同名category方法行為並不一致,導致出現bug

現在問題來瞭,怎麼尋找到底是哪個第三方SDK?方法完全包在.a裡面。
其實隻需使用image lookup -n即可:

(lldb) image lookup -n dictionaryWithXMLString:
2 matches found in /Users/jiangliancheng/Library/Developer/Xcode/DerivedData/VideoIphone-aivsnqmlwjhxapdlvmdmrubbdxpq/Build/Products/Debug-iphoneos/BaiduIphoneVideo.app/BaiduIphoneVideo:
        Address: BaiduIphoneVideo[0x00533a7c] (BaiduIphoneVideo.__TEXT.__text + 5414908)
        Summary: BaiduIphoneVideo`+[NSDictionary(SAPIXmlDictionary) dictionaryWithXMLString:] at XmlDictionary.m
         Module: file = "/Users/jiangliancheng/Library/Developer/Xcode/DerivedData/VideoIphone-aivsnqmlwjhxapdlvmdmrubbdxpq/Build/Products/Debug-iphoneos/BaiduIphoneVideo.app/BaiduIphoneVideo", arch = "armv7"
    CompileUnit: id = {0x00000000}, file = "/Users/jiangliancheng/Development/Work/iOS_ShareLib/SharedLib/Srvcs/BDPassport4iOS/BDPassport4iOS/SAPI/Extensive/ThirdParty/XMLDictionary/XmlDictionary.m", language = "Objective-C"
       Function: id = {0x23500000756}, name = "+[NSDictionary(SAPIXmlDictionary) dictionaryWithXMLString:]", range = [0x005a6a7c-0x005a6b02)
       FuncType: id = {0x23500000756}, decl = XmlDictionary.m:189, clang_type = "NSDictionary *(NSString *)" 

東西有點多,我們隻需關註裡面的file這一行:

CompileUnit: id = {0x00000000}, file = "/Users/jiangliancheng/Development/Work/iOS_ShareLib/SharedLib/Srvcs/BDPassport4iOS/BDPassport4iOS/SAPI/Extensive/ThirdParty/XMLDictionary/XmlDictionary.m", language = "Objective-C"
CompileUnit: id = {0x00000000}, file = "/Users/wingle/Workspace/qqlive4iphone/iphone_4.0_fabu_20150601/Common_Proj/mobileTAD/VIDEO/Library/Third Party/XMLDictionary/XMLDictionary.m", language = "Objective-C"

可以清晰的看到,LLDB給我們找出來瞭這個方法的位置。 當然這個命令也可以找到方法的其他相關信息,比如參數等.

image lookup –type

當我們想查看一個類型的時候,可以使用image lookup –type,簡寫為image lookup -t:

e.g: 我們來看看Model的類型:

(lldb) image lookup -t Model

可以看到,LLDB把Model這個class的所有屬性和成員變量都打印瞭出來,當我們想瞭解某個類的時候,直接使用image lookup -t即可

target stop-hook

我們知道,用LLDB debug,大多數時候需要讓程序stop,不管用breakpoint還是用watchpoint。
target stop-hook命令就是讓你可以在每次stop的時候去執行一些命令

target stop-hook隻對breakpoint和watchpoint的程序stop生效,直接點擊Xcode上的pause或者debug view hierarchy不會生效
target stop-hook add & display

假如我們想在每次程序stop的時候,都用命令打印當前frame的所有變量。我們可以添加一個stop-hook

(lldb) target stop-hook add -o "frame variable"
Stop hook #4 added.

target stop-hook add表示添加stop-hook,-o的全稱是–one-liner,表示添加一條命令。

我們看一下,當執行到一個斷點的時候會發生什麼?

- Hook 1 (frame variable)
(ViewController *) self = 0x00007fd55b12e380
(SEL) _cmd = "viewDidLoad"
(NSMutableURLRequest *) request = 0x00007fd55b1010c0

在程序stop的時候,他會自動執行frame variable,打印出瞭所有的變量。

大多情況下,我們在stop的時候可能想要做的是打印一個東西。正常情況我們需要用target stop-hook add -o “p xxx”,LLDB提供瞭一個更簡便的命令display。

e.g: 下面2行代碼效果相同

(lldb) target stop-hook add -o "p self.view"
(lldb) display self.view

也可以用display來執行某一個命令。p,e,expression是等效的。

target stop-hook list

當添加完stop-hook之後,我們想看當前所有的stop-hook怎麼辦呢?使用stop-hook list

(lldb) target stop-hook list
Hook: 4
  State: enabled
  Commands: 
    frame variable

Hook: 5
  State: enabled
  Commands: 
    expression self.view

Hook: 6
  State: enabled
  Commands: 
    expr -- self.view

我們可以看到,我們添加瞭4個stop-hook,每個stop-hook都有一個id,他們分別是4,5,6

target stop-hook delete & undisplay

有添加的命令,當然也就有刪除的命令。使用target stop-hook delete可以刪除stop-hook,如果你覺得這個命令有點長,懶得敲。你也可以用undisplay

(lldb) target stop-hook delete 4
(lldb) undisplay 5

我們用target stop-hook delete和undisplay分別刪除瞭id為4和5的stop-hook

target stop-hook disable/enable

當我們暫時想讓某個stop-hook失效的時候,可以使用target stop-hook disable

(lldb) target stop-hook disable 8

如果我們想讓所有的stop-hook失效,隻需不傳入stop-hookid即可:

(lldb) target stop-hook disable

有disable就有enable,我們又想讓stop-hook生效瞭。可以使用target stop-hook enable

(lldb) target stop-hook enable 8

同理,不傳入參數表示讓所有stop-hook生效

(lldb) target stop-hook enable

Extension

(lldb) p self.view.frame
error: property 'frame' not found on object of type 'UIView *'
error: 1 errors parsing expression
(lldb) e @import UIKit
(lldb) p self.view.frame
(CGRect) $0 = (origin = (x = 0, y = 0), size = (width = 375, height = 667))

由於每次run Xcode,LLDB的東西都會被清空。所以每次run你都需要在LLDB中輸入e @import UIKit才能使用這個方便的功能,有點麻煩呀!

之後有人提出瞭比較方便的一個辦法。給UIApplicationMain設置一個斷點,在斷點中添加執行e @import UIKit。
這裡寫圖片描述

這種方法非常方便,不用自己輸入瞭,但是斷點我們可能會誤刪,而且斷點是對應工程的。換一個工程又得重新打一個這樣的斷點。還是有點麻煩。有沒有更簡便的方法呢?

我們首先想到的是LLDB在每次啟動的時候都會load ‘~/.lldbinit’文件。在這裡面執行e @import UIKit不就行瞭麼?不會被誤刪,對每個工程都有效!

然而想法是美好的,現實卻是殘酷的!因為UIKit這個庫是在target中。而load ‘~/.lldbinit’的時候target還沒創建。所以無法import UIKit。stackoverflow詳細解釋

這時候我們又想到,可不可以在’~/.lldbinit’中給UIApplicationMain設置一個斷點,在斷點中添加執行e @import UIKit呢?

答案是不行。原因跟前面一樣,load ‘~/.lldbinit’執行時間太早。斷點是依賴target的,target還未創建,斷點加不上去。好事多磨,道路坎坷呀~~~

後來我們又想到用stop-hook行不行呢?stop-hook不依賴target。一般我們p frame的時候,都需要先stop,理論上是可行的

事實證明stop-hook的方法完全ok。隻需要在’~/.lldbinit’中添加這2條命令即可:

display @import UIKit
target stop-hook add -o "target stop-hook disable"
命令1:使用display表示在stop的時候執行@import UIKit
命令2:由於我們隻需要執行一次@import UIKit,所以執行完成之後,執行target stop-hook disable,使原有的所有stop-hook失效

這個命令有個缺陷,直接點擊Xcode上的pause和debug view hierarchy,stop-hook不會生效。正在探索有沒有更好的辦法完成@import UIKit,如果你想到瞭,可以聯系我~

target symbols add(add-dsym)

說這個命令之前,先簡單解釋一下dSYM文件。程序運行的時候,都會編譯成二進制文件。因為計算機隻識別二進制文件,那為什麼我們還能在代碼上打斷點?

這主要是因為在編譯的時候Xcode會生成dSYM文件,dSYM文件記錄瞭哪行代碼對應著哪些二進制,這樣我們對代碼打斷點就會對應到二進制上。dSYM詳細資料

當Xcode找不著dSYM文件的時候,我們就無法對代碼打斷點,進行調試。target symbols add命令的作用就是讓我們可以手動的將dSYM文件添加上去。LLBD對這個命令起瞭一個別名: add-dsym

e.g: 當我們對接framework的時候,如果隻有framework代碼,沒有工程代碼,能不能debug呢?其實我們隻需要拿到工程的ipa和dSYM文件,就可以debug瞭,通過Attach to Process,使用命令add-dsym將dSYM文件加入target,即可隻debug framework,不需要工程的代碼

add-dsym ~/.../XXX.dSYM

help & apropos

LLDB的命令其實還有很多,很多命令我也沒玩過。就算玩過的命令,我們也非常容易忘記,下次可能就不記得是怎麼用的瞭。還好LLDB給我們提供瞭2個查找命令的命令:help & apropos

help

直接在LLDB中輸入help。可以查看所有的LLDB命令

(lldb) help
Debugger commands:

  apropos           -- Find a list of debugger commands related to a particular
                       word/subject.
  breakpoint        -- A set of commands for operating on breakpoints. Also see
                       _regexp-break.
  help              -- Show a list of all debugger commands, or give details
                       about specific commands.
  script            -- Pass an expression to the script interpreter for
                       evaluation and return the results. 

如果我們想看具體某一個命令的詳細用法,可以使用help e.g: 我們查看watchpoint命令

(lldb) help watchpoint
The following subcommands are supported:

      command -- A set of commands for adding, removing and examining bits of
                 code to be executed when the watchpoint is hit (watchpoint
                 'commmands').
      delete  -- Delete the specified watchpoint(s).  If no watchpoints are
                 specified, delete them all.

apropos

有的時候,我們可能並不能完全記得某個命令,如果隻記得命令中的某個關鍵字。這時候我們可以使用apropos搜索相關命令信息。

e.g: 我們想使用stop-hook的命令,但是已經不記得stop-hook命令是啥樣瞭

(lldb) apropos stop-hook
The following built-in commands may relate to 'stop-hook':
  _regexp-display          -- Add an expression evaluation stop-hook.
  _regexp-undisplay        -- Remove an expression evaluation stop-hook.
  target stop-hook         -- A set of commands for operating on debugger
                              target stop-hooks.

可以看到使用apropos stop-hook搜索一下,即可將所有stop-hook相關命令搜索出來

常用的Debug快捷鍵

debug的時候,使用快捷鍵是一個很好的習慣,我簡單列舉瞭幾個debug的快捷鍵

功能  命令
暫停/繼續   cmd + ctrl + Y
斷點失效/生效 cmd + Y
控制臺顯示/隱藏    cmd + shift + Y
光標切換到控制臺    cmd + shift + C
清空控制臺   cmd + K
step over   F6
step into   F7
step out    F8

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *