iOS開發斷點調試高級技巧

關於LLDB調試,不少iOS開發者可能就是停留在會下簡單的斷點,使用最多命令也就是po。無可厚非,這些簡單的調試對於簡單的問題來講應該是遊刃有餘。可是若是稍微複雜一些的問題,好比我以前遇到過友盟SDK裏面的一個問題。我很想往裏面下一個斷點,但是對於的靜態庫來講,這根本不可能,最終仍是咱們組大牛使用命令的方式下了斷點解決了這個問題。感受這些知識頗有必要,我因而把LLDB的基本調試命令都學習了一下,並在此與你們分享。.a

雖然博客很長,不過耐心看完,而後動手實踐,必定會有很大幫助。html

breakpoint

給某個文件的某一行下斷點。可使用以下兩種方法,好比我想給Foo.m文件的26行下一個斷點。可使用以下的方法。git

(lldb) breakpoint set --file Foo.m --line 26

若是出現以下提示則說明設置斷點成功github

Breakpoint 2: where = BreakPointDemo`-[Foo foo] + 23 at Foo.m:26, address = 0x000000010b22e687

也可使用簡寫的形式以下。objective-c

(lldb) breakpoint set -f Foo.m -l 26

固然咱們也能夠直接給某個函數下斷點,可使用下面兩種方法shell

(lldb) breakpoint set --name foo
(lldb) breakpoint set -n foo

固然咱們也能夠在一次命令中爲下多個函數下斷點express

(lldb) breakpoint set --name foo --name bar

咱們也能夠更明確的指定是方法,若是是C的方法,可使用以下兩種的方法打斷點,第二種方法M須要大寫數組

(lldb) breakpoint set --method cplusFoo
(lldb) breakpoint set -M cplusFoo

若是是OC的方法,可使用如下兩種方式打斷點,第二種S須要大寫架構

(lldb) breakpoint set --selector foo

(lldb) breakpoint set -S foo

若是是C語言,仍是隻能使用上面介紹的--name的方式,不能直接指定對應的方法app

固然,還有一個必殺器,就是使用正則,匹配你要打斷點的函數。這個不限語言less

(lldb) breakpoint set -r cFoo
(lldb) breakpoint set -r foo

也能夠指定加載的動態庫

(lldb) breakpoint set --shlib foo.dylib --name foo 
(lldb) breakpoint set -s foo.dylib -n foo

咱們一樣能夠對命令進行簡寫。下面兩個命令的效果是同樣的

(lldb) breakpoint set -n "-[Foo foo]"
(lldb) br s -n "-[Foo foo]"

想要查看有多少斷點可使用

(lldb) breakpoint list

打印的結果以下

Current breakpoints:
1: file = '/Users/jianquan/Xcode/BreakPointDemo/BreakPointDemo/ViewController.m', line = 20, exact_match = 0, locations = 0 (pending)
2: file = '/Users/jianquan/Xcode/BreakPointDemo/BreakPointDemo/ViewController.mm', line = 33, exact_match = 0, locations = 1, resolved = 1, hit count = 0
  2.1: where = BreakPointDemo`::-[ViewController viewDidLoad]() + 186 at ViewController.mm:34, address = 0x0000000105f8362a, resolved, hit count = 0 
......

咱們能夠對斷點進行相關的操做,好比在執行到2.1斷點的時候打印追蹤軌跡。bt是

(lldb) breakpoint command add 2.1
Enter your debugger command(s).  Type 'DONE' to end.
> bt
> DONE

除了add,還要delete等命令,這些命令不須要死記硬背,可使用help命令。

(lldb) help break command
  
      add    -- Add LLDB commands to a breakpoint, to be executed whenever the
                breakpoint is hit.  If no breakpoint is specified, adds the
                commands to the last created breakpoint.
      delete -- Delete the set of commands from a breakpoint.
      list   -- List the script or set of commands to be executed when the
                breakpoint is hit.

要查看更詳細的命令用途,使用help <command> <subcommand>.好比查看add命令用法

(lldb) help break command add
......

Enter your Python command(s). Type 'DONE' to end.
> def breakpoint_output (bp_no):
>     out_string = "Hit breakpoint number " + repr (bp_no)
>     print out_string
>     return True
> breakpoint_output (1)
> DONE

能夠看到其實這裏面的命令大部分是Python腳本,不熟悉Python,暫時尚未仔細研究。

補充一點使用了以後如何刪除斷點呢,命令說明以下。

breakpoint delete [-Df] [<breakpt-id | breakpt-id-list>]

我如今用breakpoint list查個人進程

Current breakpoints:
1: file = '/Users/jianquan/Xcode/BreakPointDemo/BreakPointDemo/ViewController.m', line = 20, exact_match = 0, locations = 0 (pending)


2: file = '/Users/jianquan/Xcode/BreakPointDemo/BreakPointDemo/ViewController.mm', line = 29, exact_match = 0, locations = 1, resolved = 1, hit count = 1

  2.1: where = BreakPointDemo`::-[ViewController viewDidLoad]() + 105 at ViewController.mm:30, address = 0x00000001025b55c9, resolved, hit count = 1 

4: name = 'foo', locations = 1, resolved = 1, hit count = 0
  4.1: where = BreakPointDemo`-[Foo foo] + 23 at Foo.m:26, address = 0x00000001025b5517, resolved, hit count = 0 

5: regex = 'cFoo', locations = 2, resolved = 2, hit count = 0
  5.1: where = BreakPointDemo`cFoo + 15 at CFoo.c:13, address = 0x00000001025b591f, resolved, hit count = 0 
  5.2: where = libicucore.A.dylib`icu::MeasureUnit::createCubicFoot(UErrorCode&), address = 0x00000001051b808a, resolved, hit count = 0

若果我要刪除5.1斷點我就使用breakpoint delete 5.1,若是我要刪除5下面的全部斷點,使用breakpoint delete 5,這樣5.1和5.2都會刪除。

刪除全部的斷點使用

(lldb) breakpoint delete
About to delete all breakpoints, do you want to do that?: [Y/n] y
All breakpoints removed. (4 breakpoints)

watchpoint

這個主要是用於觀察變量值的具體變化

好比我須要觀察某個變量a的值變化,我可使用以下命令

(lldb) watchpoint set variable a

成功添加watchpoint後結果以下。

Watchpoint created: Watchpoint 1: addr = 0x7fff5913ca3c size = 4 state = enabled type = w
    declare @ '/Users/jianquan/Xcode/BreakPointDemo/BreakPointDemo/ViewController.mm:25'
    watchpoint spec = 'a'
    new value: 10

也能夠在這裏添加.

而後咱們能夠設置在a的值變化爲某個特定值以後觸。

(lldb) watchpoint modify -c '(a=100)'

咱們這個時候能夠看一下具體斷點的參數,使用watchpoint list命令

(lldb) watchpoint list
Number of supported hardware watchpoints: 4
Current watchpoints:
Watchpoint 1: addr = 0x7fff4fcb7a3c size = 4 state = enabled type = w
    declare @ '/Users/jianquan/Xcode/BreakPointDemo/BreakPointDemo/ViewController.mm:25'
    watchpoint spec = 'a'
    new value: 10
    condition = '(a=100)'

能夠看到咱們觀察的變量的地址,聲明變量的代碼在第幾行,已經具體的變量名是a,當前的值是10,觸發的條件是'(a=100)'

而後咱們執行以下命令,就能夠看到斷點到a的值變爲100的地方

(lldb) c
Process 16596 resuming
2017-02-09 11:12:14.693 BreakPointDemo[16596:6050498] foo is foo
2017-02-09 11:12:14.693 BreakPointDemo[16596:6050498] bar is bar

Watchpoint 1 hit:
old value: 10
new value: 100

能夠看到這個地方a的值已經發生改變。咱們能夠再使用watchpoint list命令看看具體值的變化

(lldb) watchpoint list
Number of supported hardware watchpoints: 4
Current watchpoints:
Watchpoint 1: addr = 0x7fff4fcb7a3c size = 4 state = enabled type = w
    declare @ '/Users/jianquan/Xcode/BreakPointDemo/BreakPointDemo/ViewController.mm:25'
    watchpoint spec = 'a'
    old value: 10
    new value: 100
    condition = '(a=100)'

固然,還有一個特別好用的命令就是bt命令咱們能夠用它來追蹤程序運行的過程。

(lldb) bt
* thread #1: tid = 0x5c52c2, 0x000000010ff465fe BreakPointDemo`::-[ViewController viewDidLoad](self=0x00007f932cc07c50, _cmd="viewDidLoad") + 158 at ViewController.mm:36, queue = 'com.apple.main-thread', stop reason = watchpoint 1
  * frame #0: 0x000000010ff465fe BreakPointDemo`::-[ViewController viewDidLoad](self=0x00007f932cc07c50, _cmd="viewDidLoad") + 158 at ViewController.mm:36
    frame #1: 0x000000011112ba3d UIKit`-[UIViewController loadViewIfRequired] + 1258
 ......

咱們可使用frame命令查看變量a的具體值。

(lldb) frame variable a
(int) a = 100

最後補充一點watchpoint list的東西。這個命令包括了三個可選參數,咱們可使用help命令查看具體的值

(lldb) help watchpoint list

       -b ( --brief )
            Give a brief description of the watchpoint (no location info).

       -f ( --full )
            Give a full description of the watchpoint and its locations.

       -v ( --verbose )
            Explain everything we know about the watchpoint (for debugging
            debugger bugs).

-b是比較簡略的信息,-f是比較全面的信息,-v是完整的信息。通過個人實驗,若是使用watchpoint list,默認的是 watchpoint list -f

process

使用process命令也能夠作不少有趣的操做。具體能作什麼,咱們也可以使用help命令查看

(lldb) process help
  
      attach    -- Attach to a process.
      connect   -- Connect to a remote debug service.
      continue  -- Continue execution of all threads in the current process.
      detach    -- Detach from the current target process.
      handle    -- Manage LLDB handling of OS signals for the current target
    ......

查看更詳細的命令使用help <command> <subcommand>。好比

(lldb) help process attach

這些命令在我目前平常開發中其實不怎麼使用,可能我功力還不足吧。

thread

其實這個功能主要就是斷點調試裏面的以下這個功能。

咱們可使用thread命令來作一些斷點的操做,具體有那些命令咱們可使用thread help進行查看。

(lldb) thread help

      ......
      
      select         -- Change the currently selected thread.
      step-in        -- Source level single step, stepping into calls. 
                        Defaults to current thread unless specified.
      step-inst      -- Instruction level single step, stepping into calls. 
                        Defaults to current thread unless specified.
      step-inst-over -- Instruction level single step, stepping over calls. 
                        Defaults to current thread unless specified.
      step-out       -- Finish executing the current stack frame and stop after
                        returning.  Defaults to current thread unless
                        specified.
      step-over      -- Source level single step, stepping over calls. 
                        Defaults to current thread unless specified.
      step-scripted  -- Step as instructed by the script class passed in the -C
                        option.
      until          -- Continue until a line number or address is reached by
                        the current or specified thread.  Stops when returning
                        from the current function as a safety measure.

用得比較多的應該是 step-開頭的這幾個命令,使用起來很容易。我我的感受比用鼠標點擊斷點好用多了~

EXAMINING THREAD STATE

這個使用的也主要仍是thread命令,主要是使用如下幾個命令。

檢查當前進程的狀態,可使用以下命令。

lldb)  thread list
Process 22323 stopped
* thread #1: tid = 0x62d0d7, 0x00000001082185fe BreakPointDemo`::-[ViewController viewDidLoad](self=0x00007ff81b60ab20, _cmd="viewDidLoad") + 158 at ViewController.mm:36, queue = 'com.apple.main-thread', stop reason = step until
......

*代表的就是當前的線程,可使用以下的命令獲得線程的回溯,這個詞我也不肯定怎麼表達好,backtrace,也能夠說是追蹤。

lldb) thread backtrace
* thread #1: tid = 0x62d0d7, 0x00000001082185fe BreakPointDemo`::-[ViewController viewDidLoad](self=0x00007ff81b60ab20, _cmd="viewDidLoad") + 158 at ViewController.mm:36, queue = 'com.apple.main-thread', stop reason = step until
  * frame #0: 0x00000001082185fe BreakPointDemo`::-[ViewController viewDidLoad](self=0x00007ff81b60ab20, _cmd="viewDidLoad") + 158 at ViewController.mm:36
    frame #1: 0x00000001093fda3d UIKit`-[UIViewController loadViewIfRequired] + 1258
    frame #2: 0x00000001093fde70 UIKit`-[UIViewController view] + 27
    frame #3: 0x00000001092c74b5 UIKit`-[UIWindow addRootViewControllerViewIfPossible] + 71
    frame #4: 0x00000001092c7c06 UIKit`-[UIWindow _setHidden:forced:] + 293
    frame #5: 0x00000001092db519 UIKit`-[UIWindow makeKeyAndVisible] + 42
    frame #6: 0x0000000109253f8d UIKit`-[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 4818
    frame #7: 0x000000010925a0ed UIKit`-[UIApplication _runWithMainScene:transitionContext:completion:] + 1731
    frame #8: 0x000000010925726d UIKit`-[UIApplication workspaceDidEndTransaction:] + 188
    frame #9: 0x000000010c3886cb FrontBoardServices`__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 24
    frame #10: 0x000000010c388544 FrontBoardServices`-[FBSSerialQueue _performNext] + 189
    frame #11: 0x000000010c3888cd FrontBoardServices`-[FBSSerialQueue _performNextFromRunLoopSource] + 45
    frame #12: 0x0000000108ddc761 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    frame #13: 0x0000000108dc198c CoreFoundation`__CFRunLoopDoSources0 + 556
    frame #14: 0x0000000108dc0e76 CoreFoundation`__CFRunLoopRun + 918
    frame #15: 0x0000000108dc0884 CoreFoundation`CFRunLoopRunSpecific + 420
    frame #16: 0x0000000109255aea UIKit`-[UIApplication _run] + 434
    frame #17: 0x000000010925bc68 UIKit`UIApplicationMain + 159
    frame #18: 0x000000010821899f BreakPointDemo`main(argc=1, argv=0x00007fff579e7600) + 111 at main.m:14
    frame #19: 0x000000010bbee68d libdyld.dylib`start + 1

固然咱們若是想看全部線程的backtrace,可使用thread backtrace all命令。內容太多,我這裏就不演示log輸出了。

若是咱們想單獨查看某個線程,咱們能夠先使用thread select 2跳到某個具體的線程,而後再進行其餘操做,好比thread backtrace

EXAMINING STACK FRAME STATE

爲了方便的觀測架構參數和本地變量,咱們可使用 frame variable 命令

若是我什麼參數也不加,將會把全部的參數和本地變量到打印出來。

(lldb) frame variable 
(ViewController *) self = 0x00007ff81b60ab20
(SEL) _cmd = "viewDidLoad"
(int) a = 100
(Foo *) foo = 0x000061800000e820
(BreakPointDemoNameSpace::BreakPointClass *) cplusFoo = 0x3ff0000000000000

要打印某個變量須要在參數裏面指定,這個命令咱們在前面也使用過,好比要查看self

(lldb) frame variable self
(ViewController *) self = 0x00007ff81b60ab20

更進一步,咱們能夠查看一些子元素

(lldb) frame variable self->isa
(Class) self->isa = ViewController

命令雖然不是完整的表達式解釋器,當時能夠識別一些基本的操做 好比 &, *, ->, [],不是重載運算符,數組也可使用,由於數組自己也是指針。

(lldb) frame variable *self 

(ViewController) *self = {
  UIViewController = {
    UIResponder = {
      NSObject = {
        isa = ViewController
      }
    ......
}

和以前thread命令很相似,我可使用frame select去選擇另外的一個frame

(lldb) frame select 9

若是想看更復雜的數據,咱們可使用expression命令

(lldb) expression self
(ViewController *) $0 = 0x00007fefa4705110

更復雜一些,咱們能夠用來輸出一個表達式

(lldb) expr (int) printf ("I have a pointer 0x%llx.\n", self)
I have a pointer 0x7fefa4705110.
(int) $1 = 33

咱們能夠繼續以以前的命令來操做

(lldb) expr self = $0
(ViewController *) $2 = 0x00007fefa4705110

固然這個expr用途感受不大。

call

其實這個命令徹底可使用po進行替代,call通常能夠用來調用不須要返回值的調試命令,好比更改View的背景顏色,如下兩個命令均可以達到類似的做用,更改當前View的背景顏色值。

(lldb) po [self.view setBackgroundColor:[UIColor redColor]]
(lldb) call [self.view setBackgroundColor:[UIColor redColor]]

image

雖然只是一個簡單的命令,可是我仍是感受這是一個比較重要也比較實用的命令, 命令可用於尋址。比較實用的用法是用於尋找棧地址對應的代碼位置。 下面我寫了一段代碼

//測試image命令使用
    NSArray *arr=[[NSArray alloc] initWithObjects:@"1",@"2", nil];
    NSLog(@"%@",arr[2]);

能夠很明顯的看到數組越界了,而後咱們運行程序,能夠看到程序報以下錯誤

*** First throw call stack:
(
    0   CoreFoundation                      0x000000011039dd4b __exceptionPreprocess + 171
    1   libobjc.A.dylib                     0x000000010fd5421e objc_exception_throw + 48
    2   CoreFoundation                      0x00000001102d82bb -[__NSArrayI objectAtIndex:] + 155
    3   BreakPointDemo                      0x000000010f77d444 -[ViewController viewDidLoad] + 340
    4   UIKit                               0x0000000110963a3d -[UIViewController loadViewIfRequired] + 1258
    5   UIKit                               0x0000000110963e70 -[UIViewController view] + 27
    6   UIKit                               0x000000011082d4b5 -[UIWindow addRootViewControllerViewIfPossible] + 71
    7   UIKit                               0x000000011082dc06 -[UIWindow _setHidden:forced:] + 293
    8   UIKit                               0x0000000110841519 -[UIWindow makeKeyAndVisible] + 42
    9   UIKit                               0x00000001107b9f8d -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 4818
    10  UIKit                               0x00000001107c00ed -[UIApplication _runWithMainScene:transitionContext:completion:] + 1731
    11  UIKit                               0x00000001107bd26d -[UIApplication workspaceDidEndTransaction:] + 188
    12  FrontBoardServices                  0x00000001138ee6cb __FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 24
    13  FrontBoardServices                  0x00000001138ee544 -[FBSSerialQueue _performNext] + 189
    14  FrontBoardServices                  0x00000001138ee8cd -[FBSSerialQueue _performNextFromRunLoopSource] + 45
    15  CoreFoundation                      0x0000000110342761 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    16  CoreFoundation                      0x000000011032798c __CFRunLoopDoSources0 + 556
    17  CoreFoundation                      0x0000000110326e76 __CFRunLoopRun + 918
    18  CoreFoundation                      0x0000000110326884 CFRunLoopRunSpecific + 420
    19  UIKit                               0x00000001107bbaea -[UIApplication _run] + 434
    20  UIKit                               0x00000001107c1c68 UIApplicationMain + 159
    21  BreakPointDemo                      0x000000010f77d8ef main + 111
    22  libdyld.dylib                       0x000000011315468d start + 1
)

咱們大概能夠猜想程序是崩潰在第三行log,也就是地址爲0x0000000104147544的地方,怎麼來呢,瞎猜的,哈哈。其實原理很簡單,由於個人Demo名字叫BreakPointDemo。其餘的名字很明顯是系統的庫。雖然log的21行也有BreakPointDemo,可是通過觀察應該是main函數,不在考慮範圍以內。

咱們使用image  lookup命令,能夠很快的定位到具體的代碼行。

(lldb) image lookup --address 0x000000010f77d444
      Address: BreakPointDemo[0x000000010f77d444] (BreakPointDemo.__TEXT.__text + 644)
      Summary: BreakPointDemo`::-[ViewController viewDidLoad]() + 340 at ViewController.mm:46

看看咱們的Xcode文件的代碼。確實是46行

 
 
斷點位置

固然還有不少的命令咱們能夠探索,使用image help能夠查看,這些命令我暫時沒有接觸過,後續工做或者學習中使用到了我會更新上來。

爲命令設置別名

好比pframe variable的別名,p view其實是frame variable view。除了系統自建的LLDB別名,你也能夠自定義別名。好比下面這個命令。掌握了規律以後,任何的命令咱們均可以本身設置別名。

(lldb) command alias bfl breakpoint set -f %1 -l %2
(lldb) bfl Foo.m 12

若是想要撤銷別名使用

(lldb) command unalias bfl

固然還有一些LLDB的具體命令,咱們能夠在官網查看: The LLDB Debugger

Demo地址

總結

這麼長的文章,看到這裏真的不容易,不過我相信你應該有所收穫了。另外個人博客長期歡迎評論留言,相互探討,不足之處歡迎批准指正。

相關文章
相關標籤/搜索