iOS——調試工具LLDB學習

1、前言

  LLDB是個開源的內置於XCode的具備REPL(read-eval-print-loop)特徵的Debugger,其能夠安裝C++或者Python插件。在平常的開發和調試過程當中給開發人員帶來了很是多的幫助。瞭解並熟練掌握LLDB的使用是很是有必要的。這篇文章將會帶着你們一塊兒瞭解在iOS開發中LLDB調試器的使用。express

2、LLDB基礎

2.1 LLDB基本語法

  LLDB的基本語法以下sass

<command> [<subcommand> [<subcommand>...]] <action> [-options [option-value]] [argument [argument...]]
  • <command>(命令)和<subcommand>(子命令):LLDB調試命令的名稱。命令和子命令按層級結構來排列:一個命令對象爲跟隨其的子命令對象建立一個上下文,子命令又爲其子命令建立一個上下文,依此類推。
  • <action>:執行命令的操做
  • <options>:命令選項
  • <arguement>:命令的參數
  • []:表示命令是可選的,能夠有也能夠沒有

  舉個例子,假設咱們給main方法設置一個斷點,咱們使用下面的命令:
這裏寫圖片描述
  這個命令對應到上面的語法就是:markdown

1. command: breakpoint 表示斷點命令 
2. action: set 表示設置斷點 
3. option: -n 表示根據方法name設置斷點 
4. arguement: mian 表示方法名爲mian
 
 app

2.2 LLDB的基本使用

 2.2.1 Help命令  

  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.

 2.2.2 expression命令

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

expression <cmd-options> -- <expr>

//<cmd-options>:命令選項,通常狀況下使用默認的便可,不須要特別標明。
//--: 命令選項結束符,表示全部的命令選項已經設置完畢,若是沒有命令選項,--能夠省略
//<expr>: 要執行的表達式

  說expression是LLDB裏面最重要的命令都不爲過。由於他能實現2個功能。ui

  1. 執行某個表達式。 咱們在代碼運行過程當中,能夠經過執行某個表達式來動態改變程序運行的軌跡。 假如咱們在運行過程當中,忽然想把self.view顏色改爲紅色,看看效果。咱們沒必要寫下代碼,從新run,只需暫停程序,用expression改變顏色,再刷新一下界面,就能看到效果this

    // 改變顏色
     (lldb) expression -- self.view.backgroundColor = [UIColor redColor]
     // 刷新界面
     (lldb) expression -- (void)[CATransaction flush]
  2. 將返回值輸出。 也就是說咱們能夠經過expression來打印東西。 假如咱們想打印self.viewlua

    (lldb) expression self.view
    (UIView *) $0 = 0x00007f8ed7418480
    (lldb) expression -- self.view
    (UIView *) $1 = 0x00007f8ed7418480

 2.2.3 p & print & call & po 命令

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

  • print: 打印某個東西,能夠是變量和表達式

  • p: 能夠看作是print的簡寫

  • po:OC裏全部的對象都是用指針表示的,因此通常打印的時候,打印出來的是對象的指針,而不是對象自己。若是咱們想打印對象。咱們須要使用命令選項:-O。爲了更方便的使用,LLDB爲expression -O –定義了一個別名:po。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>>

2.2.4 thread backtrace命令

  有時候咱們想要了解線程堆棧信息,可使用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便可

2.2.5 thread return命令

  Debug的時候,也許會由於各類緣由,咱們不想讓代碼執行某個方法,或者要直接返回一個想要的值。這時候就該thread return上場了。thread return能夠接受一個表達式,調用命令以後直接從當前的frame返回表達式的值。

thread return [<expr>]

  e.g: 咱們有一個someMethod方法,默認狀況下是返回YES。咱們想要讓他返回NO。咱們只需在方法的開始位置加一個斷點,當程序中斷的時候,輸入命令便可,效果至關於在斷點位置直接調用return NO;,不會執行斷點後面的代碼。

(lldb) thread return NO

 2.2.6 thread其餘不經常使用的命令

  thread 相關的還有其餘一些不經常使用的命令,這裏就簡單介紹一下便可,若是須要了解更多,可使用命令help thread查閱

  • thread jump: 直接讓程序跳到某一行。因爲ARC下編譯器實際插入了很多retain,release命令。跳過一些代碼不執行極可能會形成對象內存混亂髮生crash。

  • thread list: 列出全部的線程

  • thread select: 選擇某個線程

  • thread until: 傳入一個line的參數,讓程序執行到這行的時候暫停

  • thread info: 輸出當前線程的信息

2.2.7 c & n & s & finish命令

  通常在調試程序的時候,咱們常常用到下面這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

 2.2.8 frame命令

  前面咱們提到過不少次frame(幀)。可能有的朋友對frame這個概念還不太瞭解。隨便打個斷點,咱們在控制檯上輸入命令bt,能夠打印出來全部的frame。若是仔細觀察,這些frame和左邊紅框裏的堆棧是一致的。平時咱們看到的左邊的堆棧就是frame。

  

  • frame variable:平時Debug的時候咱們常常作的事就是查看變量的值,經過frame variable命令,能夠打印出當前frame的全部變量
    (lldb) frame variable
    (ViewController *) self = 0x00007fa158526e60
    (SEL) _cmd = "text:"
    (BOOL) ret = YES
    (int) a = 3
  • frame variable 參數:若是咱們要須要打印指定變量,也能夠給frame variable傳入參數。不過frame variable只接受變量做爲參數,不接受表達式,也就是說咱們沒法使用frame variable self.string,由於self.string是調用string的getter方法。因此通常打印指定變量,我更喜歡用p或者po。
    (lldb) frame variable self->_string
    (NSString *) self->_string = nil
  • frame info: 查看當前frame的信息
    (lldb) frame info
    frame #0: 0x0000000101bf87d5 TLLDB`-[ViewController text:](self=0x00007fa158526e60, _cmd="text:", ret=YES) + 37 at ViewController.m:38
  • frame select: 選擇某個frame,當咱們選擇frame 1的時候,他會把frame1的信息和代碼打印出來。不過通常我都是直接在Xcode左邊點擊某個frame,這樣更方便
    (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");

2.2.9 breakpoint命令

  • breakpoint set:設置斷點,LLDB提供了不少種設置斷點的方式
    • 使用-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
    • 使用-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 
  • breakpoint command add:就是給斷點添加命令的命令。屢次對同一個斷點添加命令,後面命令會將前面命令覆蓋
     //假設咱們須要在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
  • breakpoint command list:查看某個斷點已有的命令
    //咱們查看一下剛剛的斷點3已有的命令
    (lldb) breakpoint command list 3
    'breakpoint 3':
        Breakpoint commands:
          frame variable
          continue
  • breakpoint command delete:有增長就有刪除,breakpoint command delete可讓咱們刪除某個斷點的命令
  • breakpoint list:查看已經設置了哪些斷點
  • breakpoint disable/enable:有的時候咱們可能暫時不想要某個斷點,可使用breakpoint disable讓某個斷點暫時失效,使用breakpoint enable再次讓他生效。
  • breakpoint delete:若是咱們以爲這個斷點之後再也用不上了,能夠用breakpoint delete直接刪除斷點
    //刪除斷點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中斷點的威力》

2.2.10 watchpoint命令

  breakpoint有一個孿生兄弟watchpoint。若是說breakpoint是對方法生效的斷點,watchpoint就是對地址生效的斷點。若是咱們想要知道某個屬性何時被篡改了,咱們該怎麼辦呢?有人可能會說對setter方法打個斷點不就好了麼?可是若是更改的時候沒調用setter方法呢? 這時候最好的辦法就是用watchpoint。咱們能夠用他觀察這個屬性的地址。若是地址裏面的東西改變了,就讓程序中斷

  • watchpoint set:用於添加一個watchpoint。只要這個地址中的內容變化了,程序就會中斷。
  • watchpoint set variable:通常狀況下,要觀察變量或者屬性,使用watchpoint set variable命令便可。watchpoint set variable傳入的是變量名。須要注意的是,這裏不接受方法,因此不能使用watchpoint set variable self.string,由於self.string調用的是string的getter方法
    (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 

相關文章
相關標籤/搜索