淺談LLDB調(diào)試器

[轉(zhuǎn)]淺談LLDB調(diào)試器
文章來(lái)源于:http://www.cocoachina.com/ios/20150126/11021.html

隨著Xcode 5的發(fā)布,LLDB調(diào)試器已經(jīng)取代了GDB,成為了Xcode工程中默認(rèn)的調(diào)試器。它與LLVM編譯器一起,帶給我們更豐富的流程控制和數(shù)據(jù)檢測(cè)的調(diào)試功能。LLDB為Xcode提供了底層調(diào)試環(huán)境,其中包括內(nèi)嵌在Xcode IDE中的位于調(diào)試區(qū)域的控制面板,在這里我們可以直接調(diào)用LLDB命令。如圖1所示:
圖1:位于Xcode調(diào)試區(qū)域的控制臺(tái)

1422244517581739.png

在本文中,我們主要整理一下LLDB調(diào)試器提供給我們的調(diào)試命令,更詳細(xì)的內(nèi)容可以查看The LLDB Debugger。
LLDB命令結(jié)構(gòu)
在使用LLDB前,我們需要了解一下LLDB的命令結(jié)構(gòu)及語(yǔ)法,這樣可以盡可能地挖掘LLDB的潛能,以幫助我們更充分地利用它。
LLDB命令的語(yǔ)法有其通用結(jié)構(gòu),通常是以下形式的:

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

其中:
**
(命令)和
**(子命令):LLDB調(diào)試命令的名稱。命令和子命令按層級(jí)結(jié)構(gòu)來(lái)排列:一個(gè)命令對(duì)象為跟隨其的子命令對(duì)象創(chuàng)建一個(gè)上下文,子命令又為其子命令創(chuàng)建一個(gè)上下文,依此類推。
**
**:我們想在前面的命令序列的上下文中執(zhí)行的一些操作。
**
**:行為修改器(action modifiers)。通常帶有一些值。
**
**:根據(jù)使用的命令的上下文來(lái)表示各種不同的東西。
LLBD命令行的解析操作在執(zhí)行命令之前完成。上面的這些元素之間通過(guò)空格來(lái)分割,如果某一元素自身含有空格,則可以使用雙引用。而如果元素中又包含雙引號(hào),則可以使用反斜杠;或者元素使用單引號(hào)。如下所示:
(lldb) command [subcommand] -option "some "quoted" string"(lldb) command [subcommand] -option 'some "quoted" string'
這種命令解析設(shè)計(jì)規(guī)范了LLDB命令語(yǔ)法,并對(duì)所有命令做了個(gè)統(tǒng)一。
命令選項(xiàng)
LLDB中的命令選項(xiàng)有規(guī)范形式和縮寫(xiě)形式兩種格式。以設(shè)置斷點(diǎn)的命令breakpoint set為例,以下列表了其部分選項(xiàng)的格式,其中括號(hào)中的是規(guī)范形式:
breakpoint set -M ( --method ) -S ( --selector ) -b ( --basename ) -f ( --file ) -l ( --line ) -n ( --name )…

各選項(xiàng)的順序是任意的。如果后面的參數(shù)是以”–“開(kāi)頭的,則在選項(xiàng)后面添加”—“作為選項(xiàng)的終止信號(hào),以告訴LLDB我們處理的選項(xiàng)的正確位置。如下命令所示:
(lldb) process launch --stop-at-entry -- -program_arg_1 value -program_arg_2 value
如上所示,命令的選項(xiàng)是—stop-at-entry,參數(shù)是-program_arg_1和-program_arg_2,我們使用”—“將選項(xiàng)與參數(shù)作一下區(qū)分。
原始命令
LLDB命令解析器支持”原始(raw)“命令,即沒(méi)有命令選項(xiàng),命令字符串的剩余部分未經(jīng)解析就傳遞給命令。例如,expression就是一個(gè)原始命令。
不過(guò)原始命令也可以有選項(xiàng),如果命令字符串中有虛線,則在命令名與命令字符串之間放置一個(gè)選項(xiàng)結(jié)束符(—)來(lái)表明沒(méi)有命令標(biāo)記。
我們可以通過(guò)help命令的輸出來(lái)查看一個(gè)命令是否是原始命令。
命令補(bǔ)全(Command Completion)
LLDB支持源文件名,符號(hào)名,文件名,等等的命令補(bǔ)全(Commmand Completion)。終端窗口中的補(bǔ)全是通過(guò)在命令行中輸入一個(gè)制表符來(lái)初始化的。Xcode控制臺(tái)中的補(bǔ)全與在源碼編輯器中的補(bǔ)全方式是一樣的:補(bǔ)全會(huì)在第三個(gè)字符被鍵入時(shí)自動(dòng)彈出,或者通過(guò)Esc鍵手動(dòng)彈出。
一個(gè)命令中的私有選項(xiàng)可以有不同的完成者(completers)。如breakpoint中的—file 選項(xiàng)作為源文件的完成者,—shlib 選項(xiàng)作為當(dāng)前加載的庫(kù)的完成者,等等。這些行為是特定的,例如,如果指定—shlib ,且以—file 結(jié)尾,則LLDB只會(huì)列出由—shlib 指定的共享類庫(kù)。
Python腳本
對(duì)于高級(jí)用戶來(lái)說(shuō),LLDB有一個(gè)內(nèi)置的Python解析器,可以通過(guò)腳本命令來(lái)訪問(wèn)。調(diào)試器中的所有特性在Python解析器中都可以作為類來(lái)訪問(wèn)。這樣,我們就可以使用LLDB-Python庫(kù)來(lái)寫(xiě)Python函數(shù),并通過(guò)腳本將其加載到運(yùn)行會(huì)話中,以執(zhí)行一些更復(fù)雜的調(diào)試操作。
在命令行中調(diào)試程序
通常我們都是在Xcode中直接使用LLDB調(diào)試器,Xcode會(huì)幫我們完成很多操作。當(dāng)然,如果我們想讓自己看著更Bigger,或者想了解下調(diào)試器具體的一些流程,就可以試試直接在終端使用LLDB命令來(lái)調(diào)試程序。在終端中使用LLDB調(diào)試器,我們需要了解以下內(nèi)容:
1.加載程序以備調(diào)試
2.將一個(gè)運(yùn)行的程序綁定到LLDB
3.設(shè)置斷點(diǎn)和觀察點(diǎn)
4.控制程序的執(zhí)行
5.在調(diào)試的程序中導(dǎo)航
6.檢查狀態(tài)和值的變量
7.執(zhí)行替代代碼
了解在終端中這些操作是如何進(jìn)行的,可以幫助我們更深入的了解調(diào)試器在Xcode中是如何運(yùn)作的。下面我們分步來(lái)介紹一下。
指定需要調(diào)試的程序
首先我們需要設(shè)置需要調(diào)試的程序。我們可以使用如下命令做到這一點(diǎn):
$ lldb /Projects/Sketch/build/Debug/Sketch.app Current executable set to '/Projects/Sketch/build/Debug/Sketch.app' (x86_64).
或者在運(yùn)行l(wèi)ldb后,使用file命令來(lái)處理,如下所示:
$ lldb (lldb) file /Projects/Sketch/build/Debug/Sketch.app Current executable set to '/Projects/Sketch/build/Debug/Sketch.app' (x86_64).
設(shè)置斷點(diǎn)
在設(shè)置完程序后,我們可能想設(shè)置一點(diǎn)斷點(diǎn)來(lái)調(diào)試程序。此時(shí)我們可以使用breakpoint set命令來(lái)設(shè)置斷點(diǎn),這個(gè)命令簡(jiǎn)單、直觀,且有智能補(bǔ)全,接下來(lái)我們看看它的具體操作。
如果想在某個(gè)文件中的某行設(shè)置一個(gè)斷點(diǎn),可使用以下命令:
(lldb) breakpoint set --file foo.c --line 12
如果想給某個(gè)函數(shù)設(shè)置斷點(diǎn),可使用以下命令:
(lldb) breakpoint set --name foo
如果想給C++中所有命名為foo的方法設(shè)置斷點(diǎn),可以使用以下命令:
(lldb) breakpoint set --method foo
如果想給Objective-C中所有命名為alignLeftEdges:的選擇器設(shè)置斷點(diǎn),則可以使用以下命令:
(lldb) breakpoint set --selector alignLeftEdges:
我們可以使用—shlib 來(lái)將斷點(diǎn)限定在一個(gè)特定的可執(zhí)行庫(kù)中:

(lldb) breakpoint set --shlib foo.dylib --name foo
看吧,斷點(diǎn)設(shè)置命令還是很強(qiáng)大的。
如果我們想查看程序中所有的斷點(diǎn),則可以使用breakpoint list命令,如下所示:
(lldb) breakpoint listCurrent breakpoints:1: name = 'alignLeftEdges:', locations = 1, resolved = 1 1.1: where = Sketch-[SKTGraphicView alignLeftEdges:] + 33 at /Projects/Sketch/SKTGraphicView.m:1405, address = 0x0000000100010d5b, resolved, hit count = 0 從上面的輸出結(jié)果可以看出,一個(gè)斷點(diǎn)一般有兩部分: **1.**斷點(diǎn)的邏輯規(guī)范,這一部分是用戶提供給breakpoint set命令的。 **2.**與規(guī)范匹配的斷點(diǎn)的位置。 如上所示,通過(guò)”breakpoint set —selector alignLeftEdges:“設(shè)置的斷點(diǎn),其信息中會(huì)顯示出所有alignLeftEdges:方法的位置。 breakpoint list命令輸出列表顯示每個(gè)邏輯斷點(diǎn)都有一個(gè)整數(shù)標(biāo)識(shí),如上所示斷點(diǎn)標(biāo)識(shí)為1。而每個(gè)位置也會(huì)有一個(gè)標(biāo)識(shí),如上所示的1.1。 輸出列表中另一個(gè)信息是斷點(diǎn)位置是否是已解析的(resolved)。這個(gè)標(biāo)識(shí)表示當(dāng)與之相關(guān)的文件地址被加載到程序進(jìn)行調(diào)試時(shí),其位置是已解析的。例如,如果在共享庫(kù)中設(shè)置的斷點(diǎn)之后被卸載了,則斷點(diǎn)的位置還會(huì)保留,但其不能再被解析。 不管是邏輯斷點(diǎn)產(chǎn)生的所有位置,還是邏輯斷點(diǎn)解析的任何特定位置,我們都可以使用斷點(diǎn)觸發(fā)命令來(lái)對(duì)其進(jìn)行刪除、禁用、設(shè)置條件或忽略計(jì)數(shù)操作。例如,如果我們想添加一個(gè)命令,以在LLDB命中斷點(diǎn)1.1時(shí)打印跟蹤棧,則可以執(zhí)行以下命令 (lldb) breakpoint command add 1.1Enter your debugger command(s). Type 'DONE' to end.> bt> DONE 如果想更詳細(xì)地了解”breakpoint command add”命令的使用,可以使用help幫助系統(tǒng)來(lái)查看。 **設(shè)置觀察點(diǎn)** 作為斷點(diǎn)的補(bǔ)充,LLDB支持觀察點(diǎn)以在不中斷程序運(yùn)行的情況下監(jiān)測(cè)一些變量。例如,我們可以使用以下命令來(lái)監(jiān)測(cè)名為global的變量的寫(xiě)操作,并在(global==5)為真時(shí)停止監(jiān)測(cè): (lldb) watch set var globalWatchpoint created: Watchpoint 1: addr = 0x100001018 size = 4 state = enabled type = w declare @ '/Volumes/data/lldb/svn/ToT/test/functionalities/watchpoint/watchpoint_commands/condition/main.cpp:12'(lldb) watch modify -c '(global==5)'(lldb) watch listCurrent watchpoints:Watchpoint 1: addr = 0x100001018 size = 4 state = enabled type = w declare @ '/Volumes/data/lldb/svn/ToT/test/functionalities/watchpoint/watchpoint_commands/condition/main.cpp:12' condition = '(global==5)'(lldb) cProcess 15562 resuming(lldb) about to write to 'global'...Process 15562 stopped and was programmatically restarted.Process 15562 stopped and was programmatically restarted.Process 15562 stopped and was programmatically restarted.Process 15562 stopped and was programmatically restarted.Process 15562 stopped* thread #1: tid = 0x1c03, 0x0000000100000ef5 a.outmodify + 21 at main.cpp:16, stop reason = watchpoint 1 frame #0: 0x0000000100000ef5 a.outmodify + 21 at main.cpp:16 13 14 static void modify(int32_t &var) { 15 ++var;-> 16 } 17 18 int main(int argc, char** argv) { 19 int local = 0;(lldb) bt* thread #1: tid = 0x1c03, 0x0000000100000ef5 a.outmodify + 21 at main.cpp:16, stop reason = watchpoint 1 frame #0: 0x0000000100000ef5 a.outmodify + 21 at main.cpp:16 frame #1: 0x0000000100000eac a.outmain + 108 at main.cpp:25 frame #2: 0x00007fff8ac9c7e1 libdyld.dylibstart + 1(lldb) frame var global(int32_t) global = 5(lldb) watch list -vCurrent watchpoints:Watchpoint 1: addr = 0x100001018 size = 4 state = enabled type = w declare @ '/Volumes/data/lldb/svn/ToT/test/functionalities/watchpoint/watchpoint_commands/condition/main.cpp:12' condition = '(global==5)' hw_index = 0 hit_count = 5 ignore_count = 0(lldb) 可以使用help watchpoint來(lái)查看該命令的使用。 **使用LLDB來(lái)啟動(dòng)程序** 一旦指定了調(diào)試哪個(gè)程序,并為其設(shè)置了一些斷點(diǎn)后,就可以開(kāi)始運(yùn)行程序了。我們可以使用以下命令來(lái)啟動(dòng)程序: (lldb) process launch(lldb) run(lldb) r 我們同樣可以使用進(jìn)程ID或進(jìn)程名來(lái)連接一個(gè)已經(jīng)運(yùn)行的程序。當(dāng)使用名稱來(lái)連接一個(gè)程序時(shí),LLDB支持—waitfor選項(xiàng)。這個(gè)選項(xiàng)告訴LLDB等待下一個(gè)名稱為指定名稱的程序出現(xiàn),然后連接它。例如,下面3個(gè)命令都是用于連接Sketch程序(假定其進(jìn)程ID為123): (lldb) process attach --pid 123(lldb) process attach --name Sketch(lldb) process attach --name Sketch --waitfor 啟動(dòng)或連接程序后,進(jìn)程可能由于某些原因而停止,如: (lldb) process attach -p 12345Process 46915 AttachingProcess 46915 Stopped1 of 3 threads stopped with reasons:* thread #1: tid = 0x2c03, 0x00007fff85cac76a, where = libSystem.B.dylib__getdirentries64 + 10,stop reason = signal = SIGSTOP, queue = com.apple.main-thread
注意“1 of 3 threads stopped with reasons:”及其下面一行。在多線程環(huán)境下,在內(nèi)核實(shí)際返回控制權(quán)給調(diào)試器前,可能會(huì)有多個(gè)線程命中同一個(gè)斷點(diǎn)。在這種情況下,我們可以在停止信息中看到所有因此而停止的線程。
控制程序
啟動(dòng)程序后,LLDB允許程序在到達(dá)斷點(diǎn)前繼續(xù)運(yùn)行。LLDB中流程控制的命令都在thread命令層級(jí)中。如下所示:
(lldb) thread continueResuming thread 0x2c03 in process 46915Resuming process 46915
另外,還有以下命令:
(lldb) thread step-in // The same as "step" or "s" in GDB.(lldb) thread step-over // The same as "next" or "n" in GDB.(lldb) thread step-out // The same as "finish" or "f" in GDB.(lldb) thread step-inst // The same as "stepi" / "si" in GDB.(lldb) thread step-over-inst // The same as "nexti" / "ni" in GDB.
LLDB還提供了run until line按步調(diào)度模式,如:
lldb) thread until 100
這條命令會(huì)運(yùn)行線程,直到當(dāng)前frame到達(dá)100行。如果代碼在運(yùn)行的過(guò)程中跳過(guò)了100行,則當(dāng)frame被彈出棧后終止執(zhí)行。
查看線程狀態(tài)
在進(jìn)程停止后,LLDB會(huì)選擇一個(gè)當(dāng)前線程和線程中當(dāng)前幀(frame)。很多檢測(cè)狀態(tài)的命令可以用于這個(gè)線程或幀。
為了檢測(cè)進(jìn)程的當(dāng)前狀態(tài),可以從以下命令開(kāi)始:
(lldb) thread listProcess 46915 state is Stopped* thread #1: tid = 0x2c03, 0x00007fff85cac76a, where = libSystem.B.dylib__getdirentries64 + 10, stop reason = signal = SIGSTOP, queue = com.apple.main-thread thread #2: tid = 0x2e03, 0x00007fff85cbb08a, where = libSystem.B.dylibkevent + 10, queue = com.apple.libdispatch-manager thread #3: tid = 0x2f03, 0x00007fff85cbbeaa, where = libSystem.B.dylib__workq_kernreturn + 10 星號(hào)(*)表示thread #1為當(dāng)前線程。為了獲取線程的跟蹤棧,可以使用以下命令: (lldb) thread backtracethread #1: tid = 0x2c03, stop reason = breakpoint 1.1, queue = com.apple.main-thread frame #0: 0x0000000100010d5b, where = Sketch-[SKTGraphicView alignLeftEdges:] + 33 at /Projects/Sketch/SKTGraphicView.m:1405 frame #1: 0x00007fff8602d152, where = AppKit-[NSApplication sendAction:to:from:] + 95 frame #2: 0x00007fff860516be, where = AppKit-[NSMenuItem _corePerformAction] + 365 frame #3: 0x00007fff86051428, where = AppKit-[NSCarbonMenuImpl performActionWithHighlightingForItemAtIndex:] + 121 frame #4: 0x00007fff860370c1, where = AppKit-[NSMenu performKeyEquivalent:] + 272 frame #5: 0x00007fff86035e69, where = AppKit-[NSApplication _handleKeyEquivalent:] + 559 frame #6: 0x00007fff85f06aa1, where = AppKit-[NSApplication sendEvent:] + 3630 frame #7: 0x00007fff85e9d922, where = AppKit-[NSApplication run] + 474 frame #8: 0x00007fff85e965f8, where = AppKitNSApplicationMain + 364 frame #9: 0x0000000100015ae3, where = Sketchmain + 33 at /Projects/Sketch/SKTMain.m:11 frame #10: 0x0000000100000f20, where = Sketchstart + 52
如果想查看所有線程的調(diào)用棧,則可以使用以下命令:
(lldb) thread backtrace all
查看調(diào)用棧狀態(tài)
檢查幀參數(shù)和本地變量的最簡(jiǎn)便的方式是使用frame variable命令:
(lldb) frame variableself = (SKTGraphicView *) 0x0000000100208b40_cmd = (struct objc_selector *) 0x000000010001bae1sender = (id) 0x00000001001264e0selection = (NSArray *) 0x00000001001264e0i = (NSUInteger) 0x00000001001264e0c = (NSUInteger) 0x00000001001253b0
如果沒(méi)有指定任何變量名,則會(huì)顯示所有參數(shù)和本地變量。如果指定參數(shù)名或變量名,則只打印指定的值。如:
(lldb) frame variable self(SKTGraphicView ) self = 0x0000000100208b40
frame variable命令不是一個(gè)完全的表達(dá)式解析器,但它支持一些簡(jiǎn)單的操作符,如&,
,–>,[]。這個(gè)數(shù)組括號(hào)可用于指針,以將指針作為數(shù)組處理。如下所示:
(lldb) frame variable *self(SKTGraphicView *) self = 0x0000000100208b40(NSView) NSView = {(NSResponder) NSResponder = {...(lldb) frame variable &self(SKTGraphicView *) &self = 0x0000000100304ab(lldb) frame variable argv[0](char const ) argv[0] = 0x00007fff5fbffaf8 "/Projects/Sketch/build/Debug/Sketch.app/Contents/MacOS/Sketch"
frame variable命令會(huì)在變量上執(zhí)行”對(duì)象打印”操作。目前,LLDB只支持Objective-C打印,使用的是對(duì)象的description方法。
如果想查看另外一幀,可以使用frame select命令,如下所示:
(lldb) frame select 9frame #9: 0x0000000100015ae3, where = Sketchfunction1 + 33 at /Projects/Sketch/SKTFunctions.m:11 **小結(jié)** 以上所介紹的命令可以讓我們?cè)诮K端中直接調(diào)試程序。當(dāng)然,很多命令也可以在Xcode中直接使用。這些命令可以讓我們了解程序運(yùn)行的狀態(tài),當(dāng)然有些狀態(tài)可以在Xcode中了解到。建議在調(diào)試過(guò)程中,可以多使用這些命令。 如果想了解這一過(guò)程中使用的各種命令,可以查看[蘋(píng)果的官方文檔](https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/gdb_to_lldb_transition_guide/document/lldb-command-examples.html#//apple_ref/doc/uid/TP40012917-CH3-SW5)。 **在Xcode中調(diào)試程序** 對(duì)于我們?nèi)粘5拈_(kāi)發(fā)工作來(lái)說(shuō),更多的時(shí)候是在Xcode中進(jìn)行調(diào)試工作。因此上面所描述的流程,其實(shí)Xcode已經(jīng)幫我們完成了大部分的工作,而且很多東西也可以在Xcode里面看到。因此,我們可以把精力都集中在代碼層面上。 在[蘋(píng)果的官方文檔](https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/gdb_to_lldb_transition_guide/document/lldb-command-examples.html#//apple_ref/doc/uid/TP40012917-CH3-SW5)中列出了我們?cè)谡{(diào)試中能用到的一些命令,我們?cè)谶@重點(diǎn)講一些常用的命令。 **打印** 打印變量的值可以使用print命令,該命令如果打印的是簡(jiǎn)單類型,則會(huì)列出簡(jiǎn)單類型的類型和值。如果是對(duì)象,還會(huì)打印出對(duì)象指針地址,如下所示: (lldb) print a(NSInteger) $0 = 0(lldb) print b(NSInteger) $1 = 0(lldb) print str(NSString *) $2 = 0x0000000100001048 @"abc"(lldb) print url(NSURL *) $3 = 0x0000000100206cc0 @"abc" 在輸出結(jié)果中我們還能看到類似于$0,$1這樣的符號(hào),我們可以將其看作是指向?qū)ο蟮囊粋€(gè)引用,我們?cè)诳刂泼姘逯锌梢灾苯邮褂眠@個(gè)符號(hào)來(lái)操作對(duì)應(yīng)的對(duì)象,這些東西存在于LLDB的全名空間中,目的是為了輔助調(diào)試。如下所示: (lldb) exp $0 = 100(NSInteger) $9 = 100(lldb) p a(NSInteger) $10 = 100 另外$后面的數(shù)值是遞增的,每打印一個(gè)與對(duì)象相關(guān)的命令,這個(gè)值都會(huì)加1。 上面的print命令會(huì)打印出對(duì)象的很多信息,如果我們只想查看對(duì)象的值的信息,則可以使用po(print object的縮寫(xiě))命令,如下所示: (lldb) po strabc 當(dāng)然,po命令是”exp -O —“命令的別名,使用”exp -O —”能達(dá)到同樣的效果。 對(duì)于簡(jiǎn)單類型,我們還可以為其指定不同的打印格式,其命令格式是print/,如下所示: (lldb) p/x a(NSInteger) $13 = 0x0000000000000064 格式的完整清單可以參考[Output Formats](https://sourceware.org/gdb/onlinedocs/gdb/Output-Formats.html)。 **expression** 在開(kāi)發(fā)中,我們經(jīng)常會(huì)遇到這樣一種情況:我們?cè)O(shè)置一個(gè)視圖的背景顏色,運(yùn)行后發(fā)現(xiàn)顏色不好看。嗯,好吧,在代碼里面修改一下,再編譯運(yùn)行一下,嗯,還是不好看,然后再修改吧~~這樣無(wú)形中浪費(fèi)了我們大把的時(shí)間。在這種情況下,expression命令強(qiáng)大的功能就能體現(xiàn)出來(lái)了,它不僅會(huì)改變調(diào)試器中的值,還改變了程序中的實(shí)際值。我們先來(lái)看看實(shí)際效果,如下所示: (lldb) exp a = 10(NSInteger) $0 = 10(lldb) exp b = 100(NSInteger) $1 = 1002015-01-25 14:00:41.313 test[18064:71466] a + b = 110, abc expression命令的功能不僅于此,正如上面的po命令,其實(shí)際也是”expression -O —“命令的別名。更詳細(xì)使用可以參考[Evaluating Expressions](https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/gdb_to_lldb_transition_guide/document/lldb-command-examples.html#//apple_ref/doc/uid/TP40012917-CH3-SW5)。 **image** image命令的用法也挺多,首先可以用它來(lái)查看工程中使用的庫(kù),如下所示: (lldb) image list[ 0] 432A6EBF-B9D2-3850-BCB2-821B9E62B1E0 0x0000000100000000 /Users/**/Library/Developer/Xcode/DerivedData/test-byjqwkhxixddxudlnvqhrfughkra/Build/Products/Debug/test [ 1] 65DCCB06-339C-3E25-9702-600A28291D0E 0x00007fff5fc00000 /usr/lib/dyld [ 2] E3746EDD-DFB1-3ECB-88ED-A91AC0EF3AAA 0x00007fff8d324000 /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation [ 3] 759E155D-BC42-3D4E-869B-6F57D477177C 0x00007fff8869f000 /usr/lib/libobjc.A.dylib [ 4] 5C161F1A-93BA-3221-A31D-F86222005B1B 0x00007fff8c75c000 /usr/lib/libSystem.B.dylib [ 5] CBD1591C-405E-376E-87E9-B264610EBF49 0x00007fff8df0d000 /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation [ 6] A260789B-D4D8-316A-9490-254767B8A5F1 0x00007fff8de36000 /usr/lib/libauto.dylib ...... 我們還可以用它來(lái)查找可執(zhí)行文件或共享庫(kù)的原始地址,這一點(diǎn)還是很有用的,當(dāng)我們的程序崩潰時(shí),我們可以使用這條命令來(lái)查找崩潰所在的具體位置,如下所示: NSArray *array = @[@1, @2];NSLog(@"item 3: %@", array[2]); 這段代碼在運(yùn)行后會(huì)拋出如下異常: 2015-01-25 14:12:01.007 test[18122:76474] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 2 beyond bounds [0 .. 1]'*** First throw call stack:( 0 CoreFoundation 0x00007fff8e06f66c __exceptionPreprocess + 172 1 libobjc.A.dylib 0x00007fff886ad76e objc_exception_throw + 43 2 CoreFoundation 0x00007fff8df487de -[__NSArrayI objectAtIndex:] + 190 3 test 0x0000000100000de0 main + 384 4 libdyld.dylib 0x00007fff8f1b65c9 start + 1)libc++abi.dylib: terminating with uncaught exception of type NSException 根據(jù)以上信息,我們可以判斷崩潰位置是在main.m文件中,要想知道具體在哪一行,可以使用以下命令: (lldb) image lookup --address 0x0000000100000de0 Address: test[0x0000000100000de0] (test.__TEXT.__text + 384) Summary: testmain + 384 at main.m:23
可以看到,最后定位到了main.m文件的第23行,正是我們代碼所在的位置。
我們還可以使用image lookup命令來(lái)查看具體的類型,如下所示:
(lldb) image lookup --type NSURLBest match found in /Users/
/Library/Developer/Xcode/DerivedData/test-byjqwkhxixddxudlnvqhrfughkra/Build/Products/Debug/test:id = {0x100000157}, name = "NSURL", byte-size = 40, decl = NSURL.h:17, clang_type = "@interface NSURL : NSObject{ NSString * _urlString; NSURL * _baseURL; void * _clients; void * _reserved;}@property ( readonly,getter = absoluteString,setter = ,nonatomic ) NSString * absoluteString;@property ( readonly,getter = relativeString,setter = ,nonatomic ) NSString * relativeString;@property ( readonly,getter = baseURL,setter = ,nonatomic ) NSURL * baseURL;@property ( readonly,getter = absoluteURL,setter = ,nonatomic ) NSURL * absoluteURL;@property ( readonly,getter = scheme,setter = ,nonatomic ) NSString * scheme;@property ( readonly,getter = resourceSpecifier,setter = ,nonatomic ) NSString * resourceSpecifier;@property ( readonly,getter = host,setter = ,nonatomic ) NSString * host;@property ( readonly,getter = port,setter = ,nonatomic ) NSNumber * port;@property ( readonly,getter = user,setter = ,nonatomic ) NSString * user;@property ( readonly,getter = password,setter = ,nonatomic ) NSString * password;@property ( readonly,getter = path,setter = ,nonatomic ) NSString * path;@property ( readonly,getter = fragment,setter = ,nonatomic ) NSString * fragment;@property ( readonly,getter = parameterString,setter = ,nonatomic ) NSString * parameterString;@property ( readonly,getter = query,setter = ,nonatomic ) NSString * query;@property ( readonly,getter = relativePath,setter = ,nonatomic ) NSString * relativePath;@property ( readonly,getter = fileSystemRepresentation,setter = ) const char * fileSystemRepresentation;@property ( readonly,getter = isFileURL,setter = ,readwrite ) BOOL fileURL;@property ( readonly,getter = standardizedURL,setter = ,nonatomic ) NSURL * standardizedURL;@property ( readonly,getter = filePathURL,setter = ,nonatomic ) NSURL * filePathURL;@end"

可以看到,輸出結(jié)果中列出了NSURL的一些成員變量及屬性信息。
image命令還有許多其它功能,具體可以參考Executable and Shared Library Query Commands。
流程控制
流程控制的命令實(shí)際上我們?cè)谏弦恍」?jié)已經(jīng)講過(guò)了,在Xcode的控制面板中同樣可以使用這些命令,在此不在重復(fù)。
命令別名及幫助系統(tǒng)
LLDB有兩個(gè)非常有用的特性,即命令別名及幫助。
命令別名
我們可以使用LLDB的別名機(jī)制來(lái)為常用的命令創(chuàng)建一個(gè)別名,以方便我們的使用,如下命令:
(lldb) breakpoint set --file foo.c --line 12
如果在我們的調(diào)試中需要經(jīng)常用到這條命令,則每次輸入這么一長(zhǎng)串的字符一定會(huì)很讓人抓狂。此時(shí),我們就可以為這條命令創(chuàng)建一個(gè)別名,如下所示:
(lldb) command alias bfl breakpoint set -f %1 -l %2
這樣,我們只需要按如下方式來(lái)使用它即可:
(lldb) bfl foo.c 12
是不是簡(jiǎn)單多了?
我們可以自由地創(chuàng)建LLDB命令的別名集合。LLDB在啟動(dòng)時(shí)會(huì)讀取~/.lldbinit文件。這個(gè)文件中存儲(chǔ)了command alias命令創(chuàng)建的別名。LLDB幫助系統(tǒng)會(huì)讀取這個(gè)初始化文件并會(huì)列出這些別名,以讓我們了解自己所設(shè)置的別名。我們可以使用”help -a”命令并在輸出的后面來(lái)查看這邊別名,其以下面這行開(kāi)始:
...The following is a list of your current command abbreviations (see 'help command alias' for more info): ...
如果我們不喜歡已有命令的別名,則可以使用以下命令來(lái)取消這個(gè)別名:
(lldb) command unalias b
幫助系統(tǒng)
LLDB幫助系統(tǒng)讓我們可以了解LLDB提供了哪些功能,并可以查看LLDB命令結(jié)構(gòu)的詳細(xì)信息。熟悉幫助系統(tǒng)可以讓我們?cè)L問(wèn)幫助系統(tǒng)中中命令文檔。
我們可以簡(jiǎn)單地調(diào)用help命令來(lái)列出LLDB所有的頂層命令。如下所示:
(lldb) helpThe following is a list of built-in, permanent debugger commands:_regexp-attach -- Attach to a process id if in decimal, otherwise treat the argument as a process name to attach to._regexp-break -- Set a breakpoint using a regular expression to specify the location, where is in decimal and is in hex._regexp-bt -- Show a backtrace. An optional argument is accepted; if that argument is a number, it specifies the number of frames to display. If that argument is 'all', full backtraces of all threads are displayed. … and so forth …

如果help后面跟著某個(gè)特定的命令,則會(huì)列出該命令相關(guān)的所有信息,我們以breakpoint set為例,輸出信息如下:
(lldb) help breakpoint set Sets a breakpoint or set of breakpoints in the executable.Syntax: breakpoint set Command Options Usage: breakpoint set [-Ho] -l [-s ] [-i ] [-c ] [-x ] [-t ] [-T ] [-q ] [-f ] [-K ] breakpoint set [-Ho] -a [-s ] [-i ] [-c ] [-x ] [-t ] [-T ] [-q ] breakpoint set [-Ho] -n [-s ] [-i ] [-c ] [-x ] [-t ] [-T ] [-q ] [-f ] [-K ] [-L ] breakpoint set [-Ho] -F [-s ] [-i ] [-c ] [-x ] [-t ] [-T ] [-q ] [-f ] [-K ] … and so forth …

還有一種更直接的方式來(lái)查看LLDB有哪些功能,即使用apropos命令:它會(huì)根據(jù)關(guān)鍵字來(lái)搜索LLDB幫助文檔,并為每個(gè)命令選取一個(gè)幫助字符串,我們以apropos file為例,其輸出如下:
(lldb) apropos fileThe following commands may relate to 'file':…log enable -- Enable logging for a single log channel.memory read -- Read from the memory of the process being debugged.memory write -- Write to the memory of the process being debugged.platform process launch -- Launch a new process on a remote platform.platform select -- Create a platform if needed and select it as the current platform.plugin load -- Import a dylib that implements an LLDB plugin.process launch -- Launch the executable in the debugger.process load -- Load a shared library into the current process.source -- A set of commands for accessing source file information… and so forth …
我們還可以使用help來(lái)了解一個(gè)命令別名的構(gòu)成。如:
(lldb) help b…'b' is an abbreviation for '_regexp-break'
help命令的另一個(gè)特性是可以查看某個(gè)具體參數(shù)的使用,我們以”break command add”命令為例:
(lldb) help break command addAdd a set of commands to a breakpoint, to be executed whenever the breakpoint is hit.Syntax: breakpoint command add etc...

如果想了解以上輸出的參數(shù)的作用,我們可以在help后面直接指定這個(gè)參數(shù)(將其放在尖括號(hào)內(nèi))來(lái)查詢它的詳細(xì)信息,如下所示:

(lldb) help -- Breakpoint IDs consist major and minor numbers; the majoretc...

幫助系統(tǒng)能讓我們快速地了解一個(gè)LLDB命令的使用方法。經(jīng)常使用它,可以讓我們更快地熟悉LLDB的各項(xiàng)功能,所以建議多使用它。
總結(jié)
LLDB帶給我們強(qiáng)大的調(diào)試功能,在調(diào)試過(guò)程中充分地利用它可以幫助我們極大地提高調(diào)試效率。我們可以不用寫(xiě)那么多的NSLog來(lái)打印一大堆的日志。所以建議在日常工作中多去使用它。當(dāng)然,上面的命令只是LLDB的冰山一角,更多的使用還需要大家自己去發(fā)掘,在此只是拋磚引玉,做了一些整理。
參考
The LLDB Debugger
LLDB Quick Start Guide
與調(diào)試器共舞 – LLDB 的華爾茲
LLDB調(diào)試命令初探
NSLog效率低下的原因及嘗試lldb斷點(diǎn)打印Log

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容