随着Xcode 5的釋出,LLDB調試器已經取代了GDB,成為了Xcode工程中預設的調試器。它與LLVM編譯器一起,帶給我們更豐富的流程控制和資料檢測的調試功能。LLDB為Xcode提供了底層調試環境,其中包括内嵌在Xcode IDE中的位于調試區域的控制台,在這裡我們可以直接調用LLDB指令。如圖1所示:
圖1:位于Xcode調試區域的控制台

LLDB指令結構
在使用LLDB前,我們需要了解一下LLDB的指令結構及文法,這樣可以盡可能地挖掘LLDB的潛能,以幫助我們更充分地利用它。
LLDB指令的文法有其通用結構,通常是以下形式的:
1
<code><command> [<subcommand> [<subcommand>...]] <action> [-options [option-value]] [argument [argument...]]</action></subcommand></subcommand></command></code>
其中:
(指令)和(子指令):LLDB調試指令的名稱。指令和子指令按層級結構來排列:一個指令對象為跟随其的子指令對象建立一個上下文,子指令又為其子指令建立一個上下文,依此類推。
:我們想在前面的指令序列的上下文中執行的一些操作。
:行為修改器(action modifiers)。通常帶有一些值。
:根據使用的指令的上下文來表示各種不同的東西。
LLBD指令行的解析操作在執行指令之前完成。上面的這些元素之間通過空格來分割,如果某一進制素自身含有空格,則可以使用雙引用。而如果元素中又包含雙引号,則可以使用反斜杠;或者元素使用單引号。如下所示:
2
<code>(lldb) command [subcommand] -option </code><code>"some \"quoted\" string"</code>
<code>(lldb) command [subcommand] -option </code><code>'some "quoted" string'</code>
這種指令解析設計規範了LLDB指令文法,并對所有指令做了個統一。
指令選項
LLDB中的指令選項有規範形式和縮寫形式兩種格式。以設定斷點的指令breakpoint set為例,以下清單了其部分選項的格式,其中括号中的是規範形式:
3
4
5
6
7
8
<code>breakpoint set</code>
<code> </code><code>-M <method> ( --method <method> )</code>
<code> </code><code>-S <selector> ( --selector <selector> )</code>
<code> </code><code>-b <</code><code>function</code><code>-name> ( --basename <</code><code>function</code><code>-name> )</code>
<code> </code><code>-f <filename> ( --file <filename> )</code>
<code> </code><code>-l <linenum> ( --line <linenum> )</code>
<code> </code><code>-n <</code><code>function</code><code>-name> ( --name <</code><code>function</code><code>-name> )</code>
<code>…</</code><code>function</code><code>-name></</code><code>function</code><code>-name></linenum></linenum></filename></filename></</code><code>function</code><code>-name></</code><code>function</code><code>-name></selector></selector></method></method></code>
各選項的順序是任意的。如果後面的參數是以”–“開頭的,則在選項後面添加”—“作為選項的終止信号,以告訴LLDB我們處理的選項的正确位置。如下指令所示:
<code>(lldb) process launch --stop-at-entry -- -program_arg_1 value -program_arg_2 value</code>
如上所示,指令的選項是—stop-at-entry,參數是-program_arg_1和-program_arg_2,我們使用”—“将選項與參數作一下區分。
原始指令
LLDB指令解析器支援”原始(raw)“指令,即沒有指令選項,指令字元串的剩餘部分未經解析就傳遞給指令。例如,expression就是一個原始指令。
不過原始指令也可以有選項,如果指令字元串中有虛線,則在指令名與指令字元串之間放置一個選項結束符(—)來表明沒有指令标記。
我們可以通過help指令的輸出來檢視一個指令是否是原始指令。
指令補全(Command Completion)
LLDB支援源檔案名,符号名,檔案名,等等的指令補全(Commmand Completion)。終端視窗中的補全是通過在指令行中輸入一個制表符來初始化的。Xcode控制台中的補全與在源碼編輯器中的補全方式是一樣的:補全會在第三個字元被鍵入時自動彈出,或者通過Esc鍵手動彈出。
一個指令中的私有選項可以有不同的完成者(completers)。如breakpoint中的—file 選項作為源檔案的完成者,—shlib 選項作為目前加載的庫的完成者,等等。這些行為是特定的,例如,如果指定—shlib ,且以—file 結尾,則LLDB隻會列出由—shlib 指定的共享類庫。
Python腳本
對于進階使用者來說,LLDB有一個内置的Python解析器,可以通過腳本指令來通路。調試器中的所有特性在Python解析器中都可以作為類來通路。這樣,我們就可以使用LLDB-Python庫來寫Python函數,并通過腳本将其加載到運作會話中,以執行一些更複雜的調試操作。
在指令行中調試程式
通常我們都是在Xcode中直接使用LLDB調試器,Xcode會幫我們完成很多操作。當然,如果我們想讓自己看着更Bigger,或者想了解下調試器具體的一些流程,就可以試試直接在終端使用LLDB指令來調試程式。在終端中使用LLDB調試器,我們需要了解以下内容:
1.加載程式以備調試
2.将一個運作的程式綁定到LLDB
3.設定斷點和觀察點
4.控制程式的執行
5.在調試的程式中導航
6.檢查狀态和值的變量
7.執行替代代碼
了解在終端中這些操作是如何進行的,可以幫助我們更深入的了解調試器在Xcode中是如何運作的。下面我們分步來介紹一下。
指定需要調試的程式
首先我們需要設定需要調試的程式。我們可以使用如下指令做到這一點:
<code>$ lldb /Projects/Sketch/build/Debug/Sketch.app </code>
<code>Current executable set to </code><code>'/Projects/Sketch/build/Debug/Sketch.app'</code> <code>(x86_64).</code>
或者在運作lldb後,使用file指令來處理,如下所示:
<code>$ lldb </code>
<code>(lldb) file /Projects/Sketch/build/Debug/Sketch.app </code>
設定斷點
在設定完程式後,我們可能想設定一點斷點來調試程式。此時我們可以使用breakpoint set指令來設定斷點,這個指令簡單、直覺,且有智能補全,接下來我們看看它的具體操作。
如果想在某個檔案中的某行設定一個斷點,可使用以下指令:
<code>(lldb) breakpoint set --file foo.c --line 12</code>
如果想給某個函數設定斷點,可使用以下指令:
<code>(lldb) breakpoint set --name foo</code>
如果想給C++中所有命名為foo的方法設定斷點,可以使用以下指令:
<code>(lldb) breakpoint set --method foo</code>
如果想給Objective-C中所有命名為alignLeftEdges:的選擇器設定斷點,則可以使用以下指令:
<code>(lldb) breakpoint set --selector alignLeftEdges:</code>
我們可以使用—shlib 來将斷點限定在一個特定的可執行庫中:
<code>(lldb) breakpoint set --shlib foo.dylib --name foo</code>
看吧,斷點設定指令還是很強大的。
如果我們想檢視程式中所有的斷點,則可以使用breakpoint list指令,如下所示:
<code>(lldb) breakpoint list</code>
<code>Current breakpoints:</code>
<code>1: name = </code><code>'alignLeftEdges:'</code><code>, locations = 1, resolved = 1</code>
<code> </code><code>1.1: where = Sketch`-[SKTGraphicView alignLeftEdges:] + 33 at /Projects/Sketch/SKTGraphicView.m:1405, address = 0x0000000100010d5b, resolved, hit count = 0</code>
從上面的輸出結果可以看出,一個斷點一般有兩部分:
1.斷點的邏輯規範,這一部分是使用者提供給breakpoint set指令的。
2.與規範比對的斷點的位置。
如上所示,通過”breakpoint set —selector alignLeftEdges:“設定的斷點,其資訊中會顯示出所有alignLeftEdges:方法的位置。
breakpoint list指令輸出清單顯示每個邏輯斷點都有一個整數辨別,如上所示斷點辨別為1。而每個位置也會有一個辨別,如上所示的1.1。
輸出清單中另一個資訊是斷點位置是否是已解析的(resolved)。這個辨別表示當與之相關的檔案位址被加載到程式進行調試時,其位置是已解析的。例如,如果在共享庫中設定的斷點之後被解除安裝了,則斷點的位置還會保留,但其不能再被解析。
不管是邏輯斷點産生的所有位置,還是邏輯斷點解析的任何特定位置,我們都可以使用斷點觸發指令來對其進行删除、禁用、設定條件或忽略計數操作。例如,如果我們想添加一個指令,以在LLDB命中斷點1.1時列印跟蹤棧,則可以執行以下指令
<code>(lldb) breakpoint command add 1.1</code>
<code>Enter your debugger command(s). Type </code><code>'DONE'</code> <code>to end.</code>
<code>> bt</code>
<code>> DONE</code>
如果想更詳細地了解”breakpoint command add”指令的使用,可以使用help幫助系統來檢視。
設定觀察點
作為斷點的補充,LLDB支援觀察點以在不中斷程式運作的情況下監測一些變量。例如,我們可以使用以下指令來監測名為global的變量的寫操作,并在(global==5)為真時停止監測:
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<code>(lldb) watch set </code><code>var</code> <code>global</code>
<code>Watchpoint created: Watchpoint 1: addr = 0x100001018 size = 4 state = enabled type = w</code>
<code> </code><code>declare @ </code><code>'/Volumes/data/lldb/svn/ToT/test/functionalities/watchpoint/watchpoint_commands/condition/main.cpp:12'</code>
<code>(lldb) watch modify -c </code><code>'(global==5)'</code>
<code>(lldb) watch list</code>
<code>Current watchpoints:</code>
<code>Watchpoint 1: addr = 0x100001018 size = 4 state = enabled type = w</code>
<code> </code><code>declare @ </code><code>'/Volumes/data/lldb/svn/ToT/test/functionalities/watchpoint/watchpoint_commands/condition/main.cpp:12'</code>
<code> </code><code>condition = </code><code>'(global==5)'</code>
<code>(lldb) c</code>
<code>Process 15562 resuming</code>
<code>(lldb) about to write to </code><code>'global'</code><code>...</code>
<code>Process 15562 stopped and was programmatically restarted.</code>
<code>Process 15562 stopped</code>
<code>* thread </code><code>#1: tid = 0x1c03, 0x0000000100000ef5 a.out`modify + 21 at main.cpp:16, stop reason = watchpoint 1</code>
<code> </code><code>frame </code><code>#0: 0x0000000100000ef5 a.out`modify + 21 at main.cpp:16</code>
<code> </code><code>13</code>
<code> </code><code>14 static void modify(int32_t &</code><code>var</code><code>) {</code>
<code> </code><code>15 ++</code><code>var</code><code>;</code>
<code>-> 16 }</code>
<code> </code><code>17</code>
<code> </code><code>18 int main(int argc, char** argv) {</code>
<code> </code><code>19 int local = 0;</code>
<code>(lldb) bt</code>
<code> </code><code>frame </code><code>#1: 0x0000000100000eac a.out`main + 108 at main.cpp:25</code>
<code> </code><code>frame </code><code>#2: 0x00007fff8ac9c7e1 libdyld.dylib`start + 1</code>
<code>(lldb) frame </code><code>var</code> <code>global</code>
<code>(int32_t) global = 5</code>
<code>(lldb) watch list -v</code>
<code> </code><code>hw_index = 0 hit_count = 5 ignore_count = 0</code>
<code>(lldb)</code>
可以使用help watchpoint來檢視該指令的使用。
使用LLDB來啟動程式
一旦指定了調試哪個程式,并為其設定了一些斷點後,就可以開始運作程式了。我們可以使用以下指令來啟動程式:
<code>(lldb) process launch</code>
<code>(lldb) run</code>
<code>(lldb) r</code>
我們同樣可以使用程序ID或程序名來連接配接一個已經運作的程式。當使用名稱來連接配接一個程式時,LLDB支援—waitfor選項。這個選項告訴LLDB等待下一個名稱為指定名稱的程式出現,然後連接配接它。例如,下面3個指令都是用于連接配接Sketch程式(假定其程序ID為123):
<code>(lldb) process attach --pid 123</code>
<code>(lldb) process attach --name Sketch</code>
<code>(lldb) process attach --name Sketch --waitfor</code>
啟動或連接配接程式後,程序可能由于某些原因而停止,如:
<code>(lldb) process attach -p 12345</code>
<code>Process 46915 Attaching</code>
<code>Process 46915 Stopped</code>
<code>1 of 3 threads stopped </code><code>with</code> <code>reasons:</code>
<code>* thread </code><code>#1: tid = 0x2c03, 0x00007fff85cac76a, where = libSystem.B.dylib`__getdirentries64 + 10,</code>
<code>stop reason = signal = SIGSTOP, queue = com.apple.main-thread</code>
注意“1 of 3 threads stopped with reasons:”及其下面一行。在多線程環境下,在核心實際傳回控制權給調試器前,可能會有多個線程命中同一個斷點。在這種情況下,我們可以在停止資訊中看到所有是以而停止的線程。
控制程式
啟動程式後,LLDB允許程式在到達斷點前繼續運作。LLDB中流程控制的指令都在thread指令層級中。如下所示:
<code>(lldb) thread </code><code>continue</code>
<code>Resuming thread 0x2c03 </code><code>in</code> <code>process 46915</code>
<code>Resuming process 46915</code>
另外,還有以下指令:
<code>(lldb) thread step-</code><code>in</code> <code>// The same as "step" or "s" in GDB.</code>
<code>(lldb) thread step-over </code><code>// The same as "next" or "n" in GDB.</code>
<code>(lldb) thread step-out </code><code>// The same as "finish" or "f" in GDB.</code>
<code>(lldb) thread step-inst </code><code>// The same as "stepi" / "si" in GDB.</code>
<code>(lldb) thread step-over-inst </code><code>// The same as "nexti" / "ni" in GDB.</code>
LLDB還提供了run until line按步排程模式,如:
<code>lldb) thread until 100</code>
這條指令會運作線程,直到目前frame到達100行。如果代碼在運作的過程中跳過了100行,則當frame被彈出棧後終止執行。
檢視線程狀态
在程序停止後,LLDB會選擇一個目前線程和線程中目前幀(frame)。很多檢測狀态的指令可以用于這個線程或幀。
為了檢測程序的目前狀态,可以從以下指令開始:
<code>(lldb) thread list</code>
<code>Process 46915 state is Stopped</code>
<code>* thread </code><code>#1: tid = 0x2c03, 0x00007fff85cac76a, where = libSystem.B.dylib`__getdirentries64 + 10, stop reason = signal = SIGSTOP, queue = com.apple.main-thread</code>
<code> </code><code>thread </code><code>#2: tid = 0x2e03, 0x00007fff85cbb08a, where = libSystem.B.dylib`kevent + 10, queue = com.apple.libdispatch-manager</code>
<code> </code><code>thread </code><code>#3: tid = 0x2f03, 0x00007fff85cbbeaa, where = libSystem.B.dylib`__workq_kernreturn + 10</code>
星号(*)表示thread #1為目前線程。為了擷取線程的跟蹤棧,可以使用以下指令:
<code>(lldb) thread backtrace</code>
<code>thread </code><code>#1: tid = 0x2c03, stop reason = breakpoint 1.1, queue = com.apple.main-thread</code>
<code> </code><code>frame </code><code>#0: 0x0000000100010d5b, where = Sketch`-[SKTGraphicView alignLeftEdges:] + 33 at /Projects/Sketch/SKTGraphicView.m:1405</code>
<code> </code><code>frame </code><code>#1: 0x00007fff8602d152, where = AppKit`-[NSApplication sendAction:to:from:] + 95</code>
<code> </code><code>frame </code><code>#2: 0x00007fff860516be, where = AppKit`-[NSMenuItem _corePerformAction] + 365</code>
<code> </code><code>frame </code><code>#3: 0x00007fff86051428, where = AppKit`-[NSCarbonMenuImpl performActionWithHighlightingForItemAtIndex:] + 121</code>
<code> </code><code>frame </code><code>#4: 0x00007fff860370c1, where = AppKit`-[NSMenu performKeyEquivalent:] + 272</code>
<code> </code><code>frame </code><code>#5: 0x00007fff86035e69, where = AppKit`-[NSApplication _handleKeyEquivalent:] + 559</code>
<code> </code><code>frame </code><code>#6: 0x00007fff85f06aa1, where = AppKit`-[NSApplication sendEvent:] + 3630</code>
<code> </code><code>frame </code><code>#7: 0x00007fff85e9d922, where = AppKit`-[NSApplication run] + 474</code>
<code> </code><code>frame </code><code>#8: 0x00007fff85e965f8, where = AppKit`NSApplicationMain + 364</code>
<code> </code><code>frame </code><code>#9: 0x0000000100015ae3, where = Sketch`main + 33 at /Projects/Sketch/SKTMain.m:11</code>
<code> </code><code>frame </code><code>#10: 0x0000000100000f20, where = Sketch`start + 52</code>
如果想檢視所有線程的調用棧,則可以使用以下指令:
<code>(lldb) thread backtrace all</code>
檢視調用棧狀态
檢查幀參數和本地變量的最簡便的方式是使用frame variable指令:
<code>(lldb) frame variable</code>
<code>self = (SKTGraphicView *) 0x0000000100208b40</code>
<code>_cmd = (struct objc_selector *) 0x000000010001bae1</code>
<code>sender = (id) 0x00000001001264e0</code>
<code>selection = (NSArray *) 0x00000001001264e0</code>
<code>i = (NSUInteger) 0x00000001001264e0</code>
<code>c = (NSUInteger) 0x00000001001253b0</code>
如果沒有指定任何變量名,則會顯示所有參數和本地變量。如果指定參數名或變量名,則隻列印指定的值。如:
<code>(lldb) frame variable self</code>
<code>(SKTGraphicView *) self = 0x0000000100208b40</code>
frame variable指令不是一個完全的表達式解析器,但它支援一些簡單的操作符,如&,*,–>,[]。這個數組括号可用于指針,以将指針作為數組處理。如下所示:
<code>(lldb) frame variable *self</code>
<code>(NSView) NSView = {</code>
<code>(NSResponder) NSResponder = {</code>
<code>...</code>
<code>(lldb) frame variable &self</code>
<code>(SKTGraphicView **) &self = 0x0000000100304ab</code>
<code>(lldb) frame variable argv[0]</code>
<code>(char const *) argv[0] = 0x00007fff5fbffaf8 </code><code>"/Projects/Sketch/build/Debug/Sketch.app/Contents/MacOS/Sketch"</code>
frame variable指令會在變量上執行”對象列印”操作。目前,LLDB隻支援Objective-C列印,使用的是對象的description方法。
如果想檢視另外一幀,可以使用frame select指令,如下所示:
<code>(lldb) frame select 9</code>
<code>frame </code><code>#9: 0x0000000100015ae3, where = Sketch`function1 + 33 at /Projects/Sketch/SKTFunctions.m:11</code>
小結
以上所介紹的指令可以讓我們在終端中直接調試程式。當然,很多指令也可以在Xcode中直接使用。這些指令可以讓我們了解程式運作的狀态,當然有些狀态可以在Xcode中了解到。建議在調試過程中,可以多使用這些指令。
在Xcode中調試程式
對于我們日常的開發工作來說,更多的時候是在Xcode中進行調試工作。是以上面所描述的流程,其實Xcode已經幫我們完成了大部分的工作,而且很多東西也可以在Xcode裡面看到。是以,我們可以把精力都集中在代碼層面上。
列印
列印變量的值可以使用print指令,該指令如果列印的是簡單類型,則會列出簡單類型的類型和值。如果是對象,還會列印出對象指針位址,如下所示:
<code>(lldb) print a</code>
<code>(NSInteger) $0 = 0</code>
<code>(lldb) print b</code>
<code>(NSInteger) $1 = 0</code>
<code>(lldb) print str</code>
<code>(NSString *) $2 = 0x0000000100001048 @</code><code>"abc"</code>
<code>(lldb) print url</code>
<code>(NSURL *) $3 = 0x0000000100206cc0 @</code><code>"abc"</code>
在輸出結果中我們還能看到類似于$0,$1這樣的符号,我們可以将其看作是指向對象的一個引用,我們在控制台中可以直接使用這個符号來操作對應的對象,這些東西存在于LLDB的全名空間中,目的是為了輔助調試。如下所示:
<code>(lldb) exp $0 = 100</code>
<code>(NSInteger) $9 = 100</code>
<code>(lldb) p a</code>
<code>(NSInteger) $10 = 100</code>
另外$後面的數值是遞增的,每列印一個與對象相關的指令,這個值都會加1。
上面的print指令會列印出對象的很多資訊,如果我們隻想檢視對象的值的資訊,則可以使用po(print object的縮寫)指令,如下所示:
<code>(lldb) po str</code>
<code>abc</code>
當然,po指令是”exp -O —“指令的别名,使用”exp -O —”能達到同樣的效果。
對于簡單類型,我們還可以為其指定不同的列印格式,其指令格式是print/,如下所示:
<code>(lldb) p/x a</code>
<code>(NSInteger) $13 = 0x0000000000000064</code>
expression
在開發中,我們經常會遇到這樣一種情況:我們設定一個視圖的背景顔色,運作後發現顔色不好看。嗯,好吧,在代碼裡面修改一下,再編譯運作一下,嗯,還是不好看,然後再修改吧~~這樣無形中浪費了我們大把的時間。在這種情況下,expression指令強大的功能就能展現出來了,它不僅會改變調試器中的值,還改變了程式中的實際值。我們先來看看實際效果,如下所示:
<code>(lldb) exp a = 10</code>
<code>(NSInteger) $0 = 10</code>
<code>(lldb) exp b = 100</code>
<code>(NSInteger) $1 = 100</code>
<code>2015-01-25 14:00:41.313 test[18064:71466] a + b = 110, abc</code>
image
image指令的用法也挺多,首先可以用它來檢視工程中使用的庫,如下所示:
<code>(lldb) image list</code>
<code>[ 0] 432A6EBF-B9D2-3850-BCB2-821B9E62B1E0 0x0000000100000000 /Users</code><code>/**/</code><code>Library/Developer/Xcode/DerivedData/test-byjqwkhxixddxudlnvqhrfughkra/Build/Products/Debug/test </code>
<code>[ 1] 65DCCB06-339C-3E25-9702-600A28291D0E 0x00007fff5fc00000 /usr/lib/dyld </code>
<code>[ 2] E3746EDD-DFB1-3ECB-88ED-A91AC0EF3AAA 0x00007fff8d324000 /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation </code>
<code>[ 3] 759E155D-BC42-3D4E-869B-6F57D477177C 0x00007fff8869f000 /usr/lib/libobjc.A.dylib </code>
<code>[ 4] 5C161F1A-93BA-3221-A31D-F86222005B1B 0x00007fff8c75c000 /usr/lib/libSystem.B.dylib </code>
<code>[ 5] CBD1591C-405E-376E-87E9-B264610EBF49 0x00007fff8df0d000 /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation </code>
<code>[ 6] A260789B-D4D8-316A-9490-254767B8A5F1 0x00007fff8de36000 /usr/lib/libauto.dylib </code>
<code>......</code>
我們還可以用它來查找可執行檔案或共享庫的原始位址,這一點還是很有用的,當我們的程式崩潰時,我們可以使用這條指令來查找崩潰所在的具體位置,如下所示:
<code>NSArray *array = @[@1, @2];</code>
<code>NSLog(@</code><code>"item 3: %@"</code><code>, array[2]);</code>
這段代碼在運作後會抛出如下異常:
<code>2015-01-25 14:12:01.007 test[18122:76474] *** Terminating app due to uncaught exception </code><code>'NSRangeException'</code><code>, reason: </code><code>'*** -[__NSArrayI objectAtIndex:]: index 2 beyond bounds [0 .. 1]'</code>
<code>*** First </code><code>throw</code> <code>call stack:</code>
<code>(</code>
<code> </code><code>0 CoreFoundation 0x00007fff8e06f66c __exceptionPreprocess + 172</code>
<code> </code><code>1 libobjc.A.dylib 0x00007fff886ad76e objc_exception_throw + 43</code>
<code> </code><code>2 CoreFoundation 0x00007fff8df487de -[__NSArrayI objectAtIndex:] + 190</code>
<code> </code><code>3 test 0x0000000100000de0 main + 384</code>
<code> </code><code>4 libdyld.dylib 0x00007fff8f1b65c9 start + 1</code>
<code>)</code>
<code>libc++abi.dylib: terminating </code><code>with</code> <code>uncaught exception of type NSException</code>
根據以上資訊,我們可以判斷崩潰位置是在main.m檔案中,要想知道具體在哪一行,可以使用以下指令:
<code>(lldb) image lookup --address 0x0000000100000de0</code>
<code> </code><code>Address: test[0x0000000100000de0] (test.__TEXT.__text + 384)</code>
<code> </code><code>Summary: test`main + 384 at main.m:23</code>
可以看到,最後定位到了main.m檔案的第23行,正是我們代碼所在的位置。
我們還可以使用image lookup指令來檢視具體的類型,如下所示:
<code>(lldb) image lookup --type NSURL</code>
<code>Best match found </code><code>in</code> <code>/Users</code><code>/**/</code><code>Library/Developer/Xcode/DerivedData/test-byjqwkhxixddxudlnvqhrfughkra/Build/Products/Debug/test:</code>
<code>id = {0x100000157}, name = </code><code>"NSURL"</code><code>, byte-size = 40, decl = NSURL.h:17, clang_type = "@interface NSURL : NSObject{</code>
<code> </code><code>NSString * _urlString;</code>
<code> </code><code>NSURL * _baseURL;</code>
<code> </code><code>void * _clients;</code>
<code> </code><code>void * _reserved;</code>
<code>}</code>
<code>@property ( readonly,getter = absoluteString,setter = <</code><code>null</code> <code>selector>,nonatomic ) NSString * absoluteString;</code>
<code>@property ( readonly,getter = relativeString,setter = <</code><code>null</code> <code>selector>,nonatomic ) NSString * relativeString;</code>
<code>@property ( readonly,getter = baseURL,setter = <</code><code>null</code> <code>selector>,nonatomic ) NSURL * baseURL;</code>
<code>@property ( readonly,getter = absoluteURL,setter = <</code><code>null</code> <code>selector>,nonatomic ) NSURL * absoluteURL;</code>
<code>@property ( readonly,getter = scheme,setter = <</code><code>null</code> <code>selector>,nonatomic ) NSString * scheme;</code>
<code>@property ( readonly,getter = resourceSpecifier,setter = <</code><code>null</code> <code>selector>,nonatomic ) NSString * resourceSpecifier;</code>
<code>@property ( readonly,getter = host,setter = <</code><code>null</code> <code>selector>,nonatomic ) NSString * host;</code>
<code>@property ( readonly,getter = port,setter = <</code><code>null</code> <code>selector>,nonatomic ) NSNumber * port;</code>
<code>@property ( readonly,getter = user,setter = <</code><code>null</code> <code>selector>,nonatomic ) NSString * user;</code>
<code>@property ( readonly,getter = password,setter = <</code><code>null</code> <code>selector>,nonatomic ) NSString * password;</code>
<code>@property ( readonly,getter = path,setter = <</code><code>null</code> <code>selector>,nonatomic ) NSString * path;</code>
<code>@property ( readonly,getter = fragment,setter = <</code><code>null</code> <code>selector>,nonatomic ) NSString * fragment;</code>
<code>@property ( readonly,getter = parameterString,setter = <</code><code>null</code> <code>selector>,nonatomic ) NSString * parameterString;</code>
<code>@property ( readonly,getter = query,setter = <</code><code>null</code> <code>selector>,nonatomic ) NSString * query;</code>
<code>@property ( readonly,getter = relativePath,setter = <</code><code>null</code> <code>selector>,nonatomic ) NSString * relativePath;</code>
<code>@property ( readonly,getter = fileSystemRepresentation,setter = <</code><code>null</code> <code>selector> ) const char * fileSystemRepresentation;</code>
<code>@property ( readonly,getter = isFileURL,setter = <</code><code>null</code> <code>selector>,readwrite ) BOOL fileURL;</code>
<code>@property ( readonly,getter = standardizedURL,setter = <</code><code>null</code> <code>selector>,nonatomic ) NSURL * standardizedURL;</code>
<code>@property ( readonly,getter = filePathURL,setter = <</code><code>null</code> <code>selector>,nonatomic ) NSURL * filePathURL;</code>
<code>@end"</</code><code>null</code> <code>selector></</code><code>null</code> <code>selector></</code><code>null</code> <code>selector></</code><code>null</code> <code>selector></</code><code>null</code> <code>selector></</code><code>null</code> <code>selector></</code><code>null</code> <code>selector></</code><code>null</code> <code>selector></</code><code>null</code> <code>selector></</code><code>null</code> <code>selector></</code><code>null</code> <code>selector></</code><code>null</code> <code>selector></</code><code>null</code> <code>selector></</code><code>null</code> <code>selector></</code><code>null</code> <code>selector></</code><code>null</code> <code>selector></</code><code>null</code> <code>selector></</code><code>null</code> <code>selector></</code><code>null</code> <code>selector></code>
可以看到,輸出結果中列出了NSURL的一些成員變量及屬性資訊。
流程控制
流程控制的指令實際上我們在上一小節已經講過了,在Xcode的控制台中同樣可以使用這些指令,在此不在重複。
指令别名及幫助系統
LLDB有兩個非常有用的特性,即指令别名及幫助。
指令别名
我們可以使用LLDB的别名機制來為常用的指令建立一個别名,以友善我們的使用,如下指令:
如果在我們的調試中需要經常用到這條指令,則每次輸入這麼一長串的字元一定會很讓人抓狂。此時,我們就可以為這條指令建立一個别名,如下所示:
<code>(lldb) command alias bfl breakpoint set -f %1 -l %2</code>
這樣,我們隻需要按如下方式來使用它即可:
<code>(lldb) bfl foo.c 12</code>
是不是簡單多了?
我們可以自由地建立LLDB指令的别名集合。LLDB在啟動時會讀取~/.lldbinit檔案。這個檔案中存儲了command alias指令建立的别名。LLDB幫助系統會讀取這個初始化檔案并會列出這些别名,以讓我們了解自己所設定的别名。我們可以使用”help -a”指令并在輸出的後面來檢視這邊别名,其以下面這行開始:
<code>The following is a list of your current command abbreviations (see </code><code>'help command alias'</code> <code>for</code> <code>more info): ...</code>
如果我們不喜歡已有指令的别名,則可以使用以下指令來取消這個别名:
<code>(lldb) command unalias b</code>
幫助系統
LLDB幫助系統讓我們可以了解LLDB提供了哪些功能,并可以檢視LLDB指令結構的詳細資訊。熟悉幫助系統可以讓我們通路幫助系統中中指令文檔。
我們可以簡單地調用help指令來列出LLDB所有的頂層指令。如下所示:
<code>(lldb) help</code>
<code>The following is a list of built-</code><code>in</code><code>, permanent debugger commands:</code>
<code>_regexp-attach -- Attach to a process id </code><code>if</code> <code>in</code> <code>decimal, otherwise treat the</code>
<code> </code><code>argument as a process name to attach to.</code>
<code>_regexp-</code><code>break</code> <code>-- Set a breakpoint using a regular expression to specify the</code>
<code> </code><code>location, where <linenum> is </code><code>in</code> <code>decimal and <address> is</code>
<code> </code><code>in</code> <code>hex.</code>
<code>_regexp-bt -- Show a backtrace. An optional argument is accepted; </code><code>if</code>
<code> </code><code>that argument is a number, it specifies the number of</code>
<code> </code><code>frames to display. If that argument is </code><code>'all'</code><code>, full</code>
<code> </code><code>backtraces of all threads are displayed.</code>
<code> </code><code>… and so forth …</address></linenum></code>
如果help後面跟着某個特定的指令,則會列出該指令相關的所有資訊,我們以breakpoint set為例,輸出資訊如下:
<code>(lldb) help breakpoint set</code>
<code> </code><code>Sets a breakpoint or set of breakpoints </code><code>in</code> <code>the executable.</code>
<code>Syntax: breakpoint set <cmd-options></code>
<code>Command Options Usage:</code>
<code> </code><code>breakpoint set [-Ho] -l <linenum> [-s <shlib-name>] [-i <count>] [-c <expr>] [-x <thread-index>] [-t <thread-id>] [-T <thread-name>] [-q <queue-name>] [-f <filename>] [-K <boolean>]</code>
<code> </code><code>breakpoint set [-Ho] -a <address-expression> [-s <shlib-name>] [-i <count>] [-c <expr>] [-x <thread-index>] [-t <thread-id>] [-T <thread-name>] [-q <queue-name>]</code>
<code> </code><code>breakpoint set [-Ho] -n <</code><code>function</code><code>-name> [-s <shlib-name>] [-i <count>] [-c <expr>] [-x <thread-index>] [-t <thread-id>] [-T <thread-name>] [-q <queue-name>] [-f <filename>] [-K <boolean>] [-L <language>]</code>
<code> </code><code>breakpoint set [-Ho] -F <fullname> [-s <shlib-name>] [-i <count>] [-c <expr>] [-x <thread-index>] [-t <thread-id>] [-T <thread-name>] [-q <queue-name>] [-f <filename>] [-K <boolean>]</code>
<code> </code><code>… and so forth …</boolean></filename></queue-name></thread-name></thread-id></thread-index></expr></count></shlib-name></fullname></language></boolean></filename></queue-name></thread-name></thread-id></thread-index></expr></count></shlib-name></</code><code>function</code><code>-name></queue-name></thread-name></thread-id></thread-index></expr></count></shlib-name></address-expression></boolean></filename></queue-name></thread-name></thread-id></thread-index></expr></count></shlib-name></linenum></cmd-options></code>
還有一種更直接的方式來檢視LLDB有哪些功能,即使用apropos指令:它會根據關鍵字來搜尋LLDB幫助文檔,并為每個指令選取一個幫助字元串,我們以apropos file為例,其輸出如下:
<code>(lldb) apropos file</code>
<code>The following commands may relate to </code><code>'file'</code><code>:</code>
<code>…</code>
<code>log enable -- Enable logging </code><code>for</code> <code>a single log channel.</code>
<code>memory read -- Read from the memory of the process being</code>
<code> </code><code>debugged.</code>
<code>memory write -- Write to the memory of the process being</code>
<code>platform process launch -- Launch a </code><code>new</code> <code>process on a remote platform.</code>
<code>platform select -- Create a platform </code><code>if</code> <code>needed and select it as</code>
<code> </code><code>the current platform.</code>
<code>plugin load -- Import a dylib that implements an LLDB</code>
<code> </code><code>plugin.</code>
<code>process launch -- Launch the executable </code><code>in</code> <code>the debugger.</code>
<code>process load -- Load a shared library into the current</code>
<code> </code><code>process.</code>
<code>source -- A set of commands </code><code>for</code> <code>accessing source file</code>
<code> </code><code>information</code>
<code>… and so forth …</code>
我們還可以使用help來了解一個指令别名的構成。如:
<code>(lldb) help b</code>
<code>'b'</code> <code>is an abbreviation </code><code>for</code> <code>'_regexp-break'</code>
help指令的另一個特性是可以檢視某個具體參數的使用,我們以”break command add”指令為例:
<code>(lldb) help </code><code>break</code> <code>command add</code>
<code>Add a set of commands to a breakpoint, to be executed whenever the breakpoint is hit.</code>
<code>Syntax: breakpoint command add <cmd-options> <breakpt-id></code>
<code>etc...</breakpt-id></cmd-options></code>
如果想了解以上輸出的參數的作用,我們可以在help後面直接指定這個參數(将其放在尖括号内)來查詢它的詳細資訊,如下所示:
<code>(lldb) help <breakpt-id></code>
<code><breakpt-id> -- Breakpoint IDs consist major and minor numbers; the major</code>
<code>etc...</breakpt-id></breakpt-id></code>
幫助系統能讓我們快速地了解一個LLDB指令的使用方法。經常使用它,可以讓我們更快地熟悉LLDB的各項功能,是以建議多使用它。
總結
LLDB帶給我們強大的調試功能,在調試過程中充分地利用它可以幫助我們極大地提高調試效率。我們可以不用寫那麼多的NSLog來列印一大堆的日志。是以建議在日常工作中多去使用它。當然,上面的指令隻是LLDB的冰山一角,更多的使用還需要大家自己去發掘,在此隻是抛磚引玉,做了一些整理。
參考
<a target="_blank" href="http://lldb.llvm.org/tutorial.html">The LLDB Debugger</a>
<a target="_blank" href="https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/gdb_to_lldb_transition_guide/document/Introduction.html">LLDB Quick Start Guide</a>
<a target="_blank" href="http://www.cocoachina.com/ios/20141219/10709.html">與調試器共舞 – LLDB 的華爾茲</a>
<a target="_blank" href="http://www.starfelix.com/blog/2014/03/17/lldbdiao-shi-ming-ling-chu-tan/">LLDB調試指令初探</a>
<a target="_blank" href="http://www.cocoachina.com/ios/20140530/8619.html">NSLog效率低下的原因及嘗試lldb斷點列印Log</a>