LLDB是個開源的內置於XCode的具備REPL(read-eval-print-loop)特徵的Debugger,其能夠安裝C++或者Python插件。在平常的開發和調試過程當中給開發人員帶來了很是多的幫助。瞭解並熟練掌握LLDB的使用是很是有必要的。這篇文章將會帶着你們一塊兒瞭解在iOS開發中LLDB調試器的使用。express
LLDB的基本語法以下sass
<command> [<subcommand> [<subcommand>...]] <action> [-options [option-value]] [argument [argument...]]
舉個例子,假設咱們給main方法設置一個斷點,咱們使用下面的命令:
這個命令對應到上面的語法就是:markdown
1. command: breakpoint 表示斷點命令
app
2. action: set 表示設置斷點
3. option: -n 表示根據方法name設置斷點
4. arguement: mian 表示方法名爲mian
LLDB其中內置了很是多的功能,選擇去硬背每一條指令並非一個明智的選擇。咱們只須要記住一些經常使用的指令,在須要的時候經過help命令來查看相關的描述便可。dom
(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.) bugreport -- Commands for creating domain-specific bug reports. 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. gui -- Switch into the curses based GUI mode. help -- Show a list of all debugger commands, or give details about a specific command. ......
咱們要查看某一個命令改如何使用時,可使用 help <command> 來獲取對應命令的使用方法。工具
(lldb) help expression Evaluate an expression on the current thread. Displays any returned value with LLDB's default formatting. Expects 'raw' input (see 'help raw-input'.) Syntax: expression <cmd-options> -- <expr> Command Options Usage: expression [-AFLORTgp] [-f <format>] [-G <gdb-format>] [-a <boolean>] [-i <boolean>] [-t <unsigned-integer>] [-u <boolean>] [-l <source-language>] [-X <boolean>] [-v[<description-verbosity>]] [-j <boolean>] [-d <none>] [-S <boolean>] [-D <count>] [-P <count>] [-Y[<count>]] [-V <boolean>] [-Z <count>] -- <expr> expression [-AFLORTgp] [-a <boolean>] [-i <boolean>] [-t <unsigned-integer>] [-u <boolean>] [-l <source-language>] [-X <boolean>] [-j <boolean>] [-d <none>] [-S <boolean>] [-D <count>] [-P <count>] [-Y[<count>]] [-V <boolean>] [-Z <count>] -- <expr> expression [-r] -- <expr> expression <expr> -A ( --show-all-children ) Ignore the upper bound on the number of children to show. -D <count> ( --depth <count> ) Set the max recurse depth when dumping aggregate types (default is infinity). 。。。。。。 Examples: expr my_struct->a = my_array[3] expr -f bin -- (index * 8) + 5 expr unsigned int $foo = 5 expr char c[] = \"foo\"; c[0] Important Note: Because this command takes 'raw' input, if you use any command options you must use ' -- ' between the end of the command options and the beginning of the raw input.
expression命令的做用是執行一個表達式,並將表達式返回的結果輸出。expression的完整語法是這樣的 :oop
expression <cmd-options> -- <expr> //<cmd-options>:命令選項,通常狀況下使用默認的便可,不須要特別標明。 //--: 命令選項結束符,表示全部的命令選項已經設置完畢,若是沒有命令選項,--能夠省略 //<expr>: 要執行的表達式
說expression是LLDB裏面最重要的命令都不爲過。由於他能實現2個功能。ui
執行某個表達式。 咱們在代碼運行過程當中,能夠經過執行某個表達式來動態改變程序運行的軌跡。 假如咱們在運行過程當中,忽然想把self.view顏色改爲紅色,看看效果。咱們沒必要寫下代碼,從新run,只需暫停程序,用expression改變顏色,再刷新一下界面,就能看到效果this
// 改變顏色 (lldb) expression -- self.view.backgroundColor = [UIColor redColor] // 刷新界面 (lldb) expression -- (void)[CATransaction flush]
將返回值輸出。 也就是說咱們能夠經過expression來打印東西。 假如咱們想打印self.viewlua
(lldb) expression self.view (UIView *) $0 = 0x00007f8ed7418480 (lldb) expression -- self.view (UIView *) $1 = 0x00007f8ed7418480
通常狀況下,咱們直接用expression仍是用得比較少的,更多時候咱們用的是p、print、call。這三個命令其實都是 expression -- 的別名(--表示再也不接受命令選項,詳情見前面原始(raw)命令這一節):
print: 打印某個東西,能夠是變量和表達式
p: 能夠看作是print的簡寫
p
打印的是當前對象的地址而po
則會調用對象的description方法,作法和NSLog是一致的call: 調用某個方法
表面上看起來他們可能有不同的地方,實際都是執行某個表達式(變量也當作表達式),將執行的結果輸出到控制檯上。因此你能夠用p調用某個方法,也能夠用call打印東西 e.g: 下面代碼效果相同:
(lldb) expression -- self.view (UIView *) $5 = 0x00007fb2a40344a0 (lldb) p self.view (UIView *) $6 = 0x00007fb2a40344a0 (lldb) print self.view (UIView *) $7 = 0x00007fb2a40344a0 (lldb) call self.view (UIView *) $8 = 0x00007fb2a40344a0 (lldb) e self.view (UIView *) $9 = 0x00007fb2a40344a0
(lldb) expression -O -- self.view <UIView: 0x7fb2a40344a0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7fb2a4018c80>> (lldb) po self.view <UIView: 0x7fb2a40344a0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7fb2a4018c80>>
有時候咱們想要了解線程堆棧信息,可使用thread backtrace thread backtrace做用是將線程的堆棧打印出來。咱們來看看他的語法 :
thread backtrace [-c <count>] [-s <frame-index>] [-e <boolean>] /* * thread backtrace後面跟的都是命令選項,實際上這些命令選項咱們通常不須要使用。 -c:設置打印堆棧的幀數(frame) -s:設置從哪一個幀(frame)開始打印 -e:是否顯示額外的回溯 */
e.g: 當發生crash的時候,咱們可使用thread backtrace查看堆棧調用。從下面的結果中,咱們能夠看到crash發生在-[ViewController viewDidLoad]中的第23行,只需檢查這行代碼是否是幹了什麼非法的事兒就能夠了。
(lldb) thread backtrace * thread #1: tid = 0xdd42, 0x000000010afb380b libobjc.A.dylib`objc_msgSend + 11, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT) frame #0: 0x000000010afb380b libobjc.A.dylib`objc_msgSend + 11 * frame #1: 0x000000010aa9f75e TLLDB`-[ViewController viewDidLoad](self=0x00007fa270e1f440, _cmd="viewDidLoad") + 174 at ViewController.m:23 frame #2: 0x000000010ba67f98 UIKit`-[UIViewController loadViewIfRequired] + 1198 frame #3: 0x000000010ba682e7 UIKit`-[UIViewController view] + 27 frame #4: 0x000000010b93eab0 UIKit`-[UIWindow addRootViewControllerViewIfPossible] + 61 frame #5: 0x000000010b93f199 UIKit`-[UIWindow _setHidden:forced:] + 282 frame #6: 0x000000010b950c2e UIKit`-[UIWindow makeKeyAndVisible] + 42
此外,LLDB還爲backtrace專門定義了一個別名:bt,他的效果與thread backtrace相同,若是你不想寫那麼長一串字母,直接寫下bt便可
Debug的時候,也許會由於各類緣由,咱們不想讓代碼執行某個方法,或者要直接返回一個想要的值。這時候就該thread return上場了。thread return能夠接受一個表達式,調用命令以後直接從當前的frame返回表達式的值。
thread return [<expr>]
e.g: 咱們有一個someMethod方法,默認狀況下是返回YES。咱們想要讓他返回NO。咱們只需在方法的開始位置加一個斷點,當程序中斷的時候,輸入命令便可,效果至關於在斷點位置直接調用return NO;,不會執行斷點後面的代碼。
(lldb) thread return NO
thread 相關的還有其餘一些不經常使用的命令,這裏就簡單介紹一下便可,若是須要了解更多,可使用命令help thread查閱
thread jump: 直接讓程序跳到某一行。因爲ARC下編譯器實際插入了很多retain,release命令。跳過一些代碼不執行極可能會形成對象內存混亂髮生crash。
thread list: 列出全部的線程
thread select: 選擇某個線程
thread until: 傳入一個line的參數,讓程序執行到這行的時候暫停
thread info: 輸出當前線程的信息
通常在調試程序的時候,咱們常常用到下面這4個按鈕:
用觸摸板的孩子們可能會以爲點擊這4個按鈕比較費勁。其實LLDB命令也能夠完成上面的操做,並且若是不輸入命令,直接按Enter鍵,LLDB會自動執行上次的命令。按一下Enter就能達到咱們想要的效果,有木有頓時感受逼格滿滿的!!! 咱們來看看對應這4個按鈕的LLDB命令:
c/ continue/ thread continue: 這三個命令效果都等同於上圖中第一個按鈕的。表示程序繼續運行
n/ next/ thread step-over: 這三個命令效果等同於上圖第二個按鈕。表示單步運行
s/ step/ thread step-in: 這三個命令效果等同於上圖第三個按鈕。表示進入某個方法
finish/ step-out: 這兩個命令效果等同於第四個按鈕。表示直接走完當前方法,返回到上層frame
前面咱們提到過不少次frame(幀)。可能有的朋友對frame這個概念還不太瞭解。隨便打個斷點,咱們在控制檯上輸入命令bt,能夠打印出來全部的frame。若是仔細觀察,這些frame和左邊紅框裏的堆棧是一致的。平時咱們看到的左邊的堆棧就是frame。
(lldb) frame variable (ViewController *) self = 0x00007fa158526e60 (SEL) _cmd = "text:" (BOOL) ret = YES (int) a = 3
(lldb) frame variable self->_string
(NSString *) self->_string = nil
(lldb) frame info frame #0: 0x0000000101bf87d5 TLLDB`-[ViewController text:](self=0x00007fa158526e60, _cmd="text:", ret=YES) + 37 at ViewController.m:38
(lldb) frame select 1 frame #1: 0x0000000101bf872e TLLDB`-[ViewController viewDidLoad](self=0x00007fa158526e60, _cmd="viewDidLoad") + 78 at ViewController.m:23 - (void)viewDidLoad { [super viewDidLoad]; [self text:YES]; NSLog(@"1"); NSLog(@"2"); NSLog(@"3");
//咱們想給全部類中的viewWillAppear:設置一個斷點 (lldb) breakpoint set -n viewWillAppear: Breakpoint 13: 33 locations.
// 咱們只須要給ViewController.m文件中的viewDidLoad設置斷點 (lldb) breakpoint set -f ViewController.m -n viewDidLoad Breakpoint 22: where = TLLDB`-[ViewController viewDidLoad] + 20 at ViewController.m:22, address = 0x000000010272a6f4
//咱們想給ViewController.m第38行設置斷點 (lldb) breakpoint set -f ViewController.m -l 38 Breakpoint 23: where = TLLDB`-[ViewController text:] + 37 at ViewController.m:38, address = 0x000000010272a7d5
//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
//若是剛剛那個斷點咱們只想讓他中斷一次 (lldb) breakpoint set -n text: -o 'breakpoint 3': where = TLLDB`-[ViewController text:] + 30 at ViewController.m:37, address = 0x000000010b6f97ce
//假設咱們須要在ViewController的viewDidLoad中查看self.view的值 咱們首先給-[ViewController viewDidLoad]添加一個斷點 (lldb) breakpoint set -n "-[ViewController viewDidLoad]" 'breakpoint 3': where = TLLDB`-[ViewController viewDidLoad] + 20 at ViewController.m:23, address = 0x00000001055e6004 /* 能夠看到添加成功以後,這個breakpoint的id爲3,而後咱們給他增長一個命令:po self.view -o完整寫法是--one-liner,表示增長一條命令。3表示對id爲3的breakpoint增長命令。 添加完命令以後,每次程序執行到這個斷點就能夠自動打印出self.view的值了 */ (lldb) breakpoint command add -o "po self.view" 3 /* 若是咱們一會兒想增長多條命令,好比我想在viewDidLoad中打印當前frame的全部變量,可是咱們不想讓他中斷,也就是在打印完成以後,須要繼續執行。咱們能夠這樣玩 輸入breakpoint command add 3對斷點3增長命令。他會讓你輸入增長哪些命令,輸入’DONE’表示結束。這時候你就能夠輸入多條命令了 */ (lldb) breakpoint command add 3 Enter your debugger command(s). Type 'DONE' to end. > frame variable > continue > DONE
//咱們查看一下剛剛的斷點3已有的命令 (lldb) breakpoint command list 3 'breakpoint 3': Breakpoint commands: frame variable continue
//刪除斷點4 (lldb) breakpoint delete 4 1 breakpoints deleted; 0 breakpoint locations disabled. //若是咱們想刪除全部斷點,只須要不指定breakpoint delete參數便可 (lldb) breakpoint delete About to delete all breakpoints, do you want to do that?: [Y/n] y All breakpoints removed. (1 breakpoint) //刪除的時候他會提示你,是否是真的想刪除全部斷點,須要你再次輸入Y確認。若是想直接刪除,不須要他的提示,使用-f命令選項便可 (lldb) breakpoint delete -f All breakpoints removed. (1 breakpoint)
實際平時咱們真正使用breakpoint命令反而比較少,由於Xcode已經內置了斷點工具。咱們能夠直接在代碼上打斷點,能夠在斷點工具欄裏面查看編輯斷點,這比使用LLDB命令方便不少。不過了解LLDB相關命令可讓咱們對斷點理解更深入。 若是你想了解怎麼使用Xcode設置斷點,能夠閱讀這篇文章《Xcode中斷點的威力》
breakpoint有一個孿生兄弟watchpoint。若是說breakpoint是對方法生效的斷點,watchpoint就是對地址生效的斷點。若是咱們想要知道某個屬性何時被篡改了,咱們該怎麼辦呢?有人可能會說對setter方法打個斷點不就好了麼?可是若是更改的時候沒調用setter方法呢? 這時候最好的辦法就是用watchpoint。咱們能夠用他觀察這個屬性的地址。若是地址裏面的東西改變了,就讓程序中斷
(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:若是咱們想直接觀察某個地址,可使用watchpoint set expression
//咱們先拿到_model的地址,而後對地址設置一個watchpoint (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
watchpoint command add:和breakpoint同樣,給watchpoint添加命令
//設置一個watchpoint (lldb) watchpoint set variable _string Watchpoint created: Watchpoint 1: addr = 0x7fe4e1444760 size = 8 state = enabled type = w watchpoint spec = '_string' new value: 0x0000000000000000 //能夠看到這個watchpoint的id是1。咱們能夠用watchpoint command add -o添加單條命令 watchpoint command add -o 'bt' 1 //咱們也能夠一次添加多條命令 (lldb) watchpoint command add 1 Enter your debugger command(s). Type 'DONE' to end. > bt > continue > DONE
watchpoint command list:列出某個watchpoint全部的command
watchpoint command delete:刪除某個watchpoint全部的command
watchpoint list:查看當前全部watchpoint
watchpoint disable/enable:使某個watchpoint失效/生效
watchpoint delete:刪除watchpoint,刪除單個或多個,用法同breakpoint delete