LLDB詳解

Xcode po 是什么
你以前調試是不是這樣打印的?

NSLog(@"%@", whatIsInsideThisThing);

或者臨時寫一個臨時變量?

NSNumber *n = @7; 

或者專門寫個檢查器來判斷?

if (1 || theBooleanAtStake) { ... }

或者專門寫一個方法?

int calculateTheTrickyValue {
  return 9;

  /*
   先這樣
   ...
}

是不是每次都要重新運行程序,重新開始?

代碼已經夠多,你還要多寫幾句代碼,然后重新編譯運行看看結果對不對。如果老板給你的是低配電腦,那真是叫苦連天?。?。

但其實你不需要重新編譯運行,你完全可以使用調試器(也加控制臺),即使你已經會使用打個斷點,或打個全局斷點,但實際上調試器還可以做更多事情。

接下來我們來重新認識Xcode中的調試器吧!看看到底能做多少事情。

LLDB

LLDB是一個帶著REPL、C++特性,且?guī)в蠵ython插件的開源調試器。LLDB內置在了Xcode里面。也就是我們所見到的控制臺中。
LLDB 是一個有著 REPL 的特性和 C++ ,Python 插件的開源調試器。LLDB 綁定在 Xcode 內部,存在于主窗口底部的控制臺中(這里有一個關于調試器如何工作的總體的解釋。)

咱們以前使用調試器的時候,只是簡單的在Xcode中加一些斷點,然后通過
(lldb) po value
來打印值對不對?但提到上面的那些缺點,這已經不能滿足我們調試的要求了。
接下來我們做更酷的事!

基礎

先寫一個簡單的代碼。這里會打印字符串。然后我們在打印字符串的那行代碼加個斷點。相信這點大家都會,如果你的確是新手,那你點下圖中8那個數字,就會看到和圖中一樣的斷點標示。

簡單的小程序

我們運行一下,接下來編譯器運行到第8行的時候就停住了!
這時我們就可以使用調試器了 。先看看怎么用。
我們輸入: " help "

help

(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.
....太長就不復制了

然后調試器打印了一些文字,這都是調試器的命令和說明,先簡單的看一看。

print

我們平時使用最多的就是p了,其實它是print,輸入看一下:

打印值

實際上LLDB里面搞了很多別名,比如prin、prip等,都是print的別名,使用的效果和print是一樣的。但是你不能用pr,因為LLDB不能區(qū)分你輸入的pr是不是process。

圖中打印是不是還有個$0?,這個東西是用它來指向這個結果的,比如print $0 + 7也就是計算count+7的值,輸入后的結果是106。任何以美元符號開頭的東西都存在與LLDB的命名控件里面,它是為了幫助咱們調試而存在的。

expression

如果要改變一個值,你可用expression這個命令

改變一個值

你看,這個家伙它不僅改變了調試器中的值,它甚至還改變了程序中的值!牛逼吧。
它也有給別名e。

那么現在為了簡單一點,從現在開始,我們用 pe 來代替 printexpression。

什么是 print 命令

print就是打印的意思,比如想打印一個變量 print sum
這里有一個很有意思的事情,你輸入p count = 18expression count = 18,結果是一樣樣的!

輸入 help print,然后向下滾動,你會發(fā)現:

'print' is an abbreviation for 'expression --'.   
(print是 `expression --` 的縮寫) `--` 標示不需要參數

打印對象

嘗試輸入

p objects

控制臺輸出了

(NSString *) $7 = 0x0000000104da4040 @"red balloons"

如果打印復雜一點的的對象,你會發(fā)覺只打印了對象的指針地址

(lldb) p @[ @"foo", @"bar" ]

(NSArray *) $8 = 0x00007fdb9b71b3e0 @"2 objects" 

其實我只是想看看這個對象的 description 方法的結果。
那我就需要用-O(是字母O)這個參數了,我們輸入 e -O --$8,就打印了里面的值。

(lldb) e -O -- $8
<__NSArrayI 0x7fdb9b71b3e0>(
foo,
bar
)

其實 e -o -- 也有個別名,那就是 po (print object 的縮寫),咱們用它更簡單。看~

(lldb) po $8
<__NSArrayI 0x7fdb9b71b3e0>(
foo,
bar
)
(lldb) po @"lunar"
lunar
(lldb) p @"lunar"
(NSString *) $13 = 0x00007fdb9d0003b0 @"lunar"

還可以打印這些

我們可以設置打印格式,打印格式這么寫:print/<fmt>p/<fmt>
例子:
默認的格式

(lldb) p 16
16

十六進制:

(lldb) p/x 16
0x10

二進制 (t 代表 two):

(lldb) p/t 16
0b00000000000000000000000000010000
(lldb) p/t (char)16
0b00010000

更多格式點這里

變量

前面講完打印對象、又講完修改變量值,現在我們在講點東西。
我們可以用LLDB定義變量,是不是很酷!
咱們只需要記住,申明變量必須要以美元符開頭??蠢?/p>

(lldb) e int $a = 2
(lldb) p $a * 19
38
(lldb) e NSArray *$array = @[ @"Saturday", @"Sunday", @"Monday" ]
(lldb) p [$array count]
2
(lldb) po [[$array objectAtIndex:0] uppercaseString]
SATURDAY
(lldb) p [[$array objectAtIndex:$a] characterAtIndex:0]
error: no known method '-characterAtIndex:'; cast the message send to the method's return type
error: 1 errors parsing expression

最后一個怎么回事!打印不出來?
這其實是因為沒有確定返回的類型值。
解決辦法很簡單,前面加個類型,就像oc里面的類型強轉一樣。

(lldb) p (char)[[$array objectAtIndex:$a] characterAtIndex:0]
'M'
(lldb) p/d (char)[[$array objectAtIndex:$a] characterAtIndex:0]
77

搞定了。

流程控制

當你通過 Xcode 的源碼編輯器的側邊槽 (或者通過下面的方法) 插入一個斷點,程序到達斷點時會就會停止運行。

調試條上會出現四個你可以用來控制程序的執(zhí)行流程的按鈕。

[圖片上傳中...(image-653a78-1528639982303-8)]

從左到右,四個按鈕分別是:continue,step over,step into,step out。

第一個,continue 按鈕,會取消程序的暫停,允許程序正常執(zhí)行 (要么一直執(zhí)行下去,要么到達下一個斷點)。在 LLDB 中,你可以使用 process continue 命令來達到同樣的效果,它的別名為 continue,或者也可以縮寫為 c。

第二個,step over 按鈕,會以黑盒的方式執(zhí)行一行代碼。如果所在這行代碼是一個函數調用,那么就不會跳進這個函數,而是會執(zhí)行這個函數,然后繼續(xù)。LLDB 則可以使用 thread step-over,next,或者 n 命令。

如果你確實想跳進一個函數調用來調試或者檢查程序的執(zhí)行情況,那就用第三個按鈕,step in,或者在LLDB中使用 thread step instep,或者 s 命令。注意,當前行不是函數調用時,nextstep 效果是一樣的。

大多數人知道 c,ns,但是其實還有第四個按鈕,step out。如果你曾經不小心跳進一個函數,但實際上你想跳過它,常見的反應是重復的運行 n 直到函數返回。其實這種情況,step out 按鈕是你的救世主。它會繼續(xù)執(zhí)行到下一個返回語句 (直到一個堆棧幀結束) 然后再次停止。

例子

考慮下面一段程序:

[圖片上傳中...(image-fe03d7-1528639982303-7)]

假如我們運行程序,讓它停止在斷點,然后執(zhí)行下面一些列命令:

p i
n
s
p i
finish
p i
frame info

這里,frame info 會告訴你當前的行數和源碼文件,以及其他一些信息;查看 help framehelp threadhelp process 來獲得更多信息。這一串命令的結果會是什么?看答案之前請先想一想。

(lldb) p i
(int) $0 = 99
(lldb) n
2014-11-22 10:49:26.445 DebuggerDance[60182:4832768] 101 is odd!
(lldb) s
(lldb) p i
(int) $2 = 110
(lldb) finish
2014-11-22 10:49:35.978 DebuggerDance[60182:4832768] 110 is even!
(lldb) p i
(int) $4 = 99
(lldb) frame info
frame #0: 0x000000010a53bcd4 DebuggerDance`main + 68 at main.m:17

它始終在 17 行的原因是 finish 命令一直運行到 isEven() 函數的 return,然后立刻停止。注意即使它還在 17 行,其實這行已經被執(zhí)行過了。

Thread Return

調試時,還有一個很棒的函數可以用來控制程序流程:thread return 。它有一個可選參數,在執(zhí)行時它會把可選參數加載進返回寄存器里,然后立刻執(zhí)行返回命令,跳出當前棧幀。這意味這函數剩余的部分不會被執(zhí)行。這會給 ARC 的引用計數造成一些問題,或者會使函數內的清理部分失效。但是在函數的開頭執(zhí)行這個命令,是個非常好的隔離這個函數,偽造返回值的方式 。

讓我們稍微修改一下上面代碼段并運行:

p i
s
thread return NO
n
p even0
frame info

看答案前思考一下。下面是答案:

(lldb) p i
(int) $0 = 99
(lldb) s
(lldb) thread return NO
(lldb) n
(lldb) p even0
(BOOL) $2 = NO
(lldb) frame info
frame #0: 0x00000001009a5cc4 DebuggerDance`main + 52 at main.m:17

斷點

我們都把斷點作為一個停止程序運行,檢查當前狀態(tài),追蹤 bug 的方式。但是如果我們改變和斷點交互的方式,很多事情都變成可能。

斷點允許控制程序什么時候停止,然后允許命令的運行。

想象把斷點放在函數的開頭,然后用 thread return 命令重寫函數的行為,然后繼續(xù)。想象一下讓這個過程自動化,聽起來不錯,不是嗎?

管理斷點

Xcode 提供了一系列工具來創(chuàng)建和管理斷點。我們會一個個看過來并介紹 LLDB 中等價的命令 (是的,你可以在調試器內部添加斷點)。

在 Xcode 的左側面板,有一組按鈕。其中一個看起來像斷點。點擊它打開斷點導航,這是一個可以快速管理所有斷點的面板。

在這里你可以看到所有的斷點 - 在 LLDB 中通過 breakpoint list (或者 br li) 命令也做同樣的事兒。你也可以點擊單個斷點來開啟或關閉 - 在 LLDB 中使用 breakpoint enable <breakpointID>breakpoint disable <breakpointID>

(lldb) br li
Current breakpoints:
1: file = '/Users/arig/Desktop/DebuggerDance/DebuggerDance/main.m', line = 16, locations = 1, resolved = 1, hit count = 1

  1.1: where = DebuggerDance`main + 27 at main.m:16, address = 0x000000010a3f6cab, resolved, hit count = 1

(lldb) br dis 1
1 breakpoints disabled.
(lldb) br li
Current breakpoints:
1: file = '/Users/arig/Desktop/DebuggerDance/DebuggerDance/main.m', line = 16, locations = 1 Options: disabled

  1.1: where = DebuggerDance`main + 27 at main.m:16, address = 0x000000010a3f6cab, unresolved, hit count = 1

(lldb) br del 1
1 breakpoints deleted; 0 breakpoint locations disabled.
(lldb) br li
No breakpoints currently set.

創(chuàng)建斷點

在上面的例子中,我們通過在源碼頁面器的滾槽 16 上點擊來創(chuàng)建斷點。你可以通過把斷點拖拽出滾槽,然后釋放鼠標來刪除斷點 (消失時會有一個非常可愛的噗的一下的動畫)。你也可以在斷點導航頁選擇斷點,然后按下刪除鍵刪除。

要在調試器中創(chuàng)建斷點,可以使用 breakpoint set 命令。

(lldb) breakpoint set -f main.m -l 16
Breakpoint 1: where = DebuggerDance`main + 27 at main.m:16, address = 0x000000010a3f6cab

也可以使用縮寫形式 br。雖然 b 是一個完全不同的命令 (_regexp-break 的縮寫),但恰好也可以實現和上面同樣的效果。

(lldb) b main.m:17
Breakpoint 2: where = DebuggerDance`main + 52 at main.m:17, address = 0x000000010a3f6cc4

也可以在一個符號 (C 語言函數) 上創(chuàng)建斷點,而完全不用指定哪一行

(lldb) b isEven
Breakpoint 3: where = DebuggerDance`isEven + 16 at main.m:4, address = 0x000000010a3f6d00
(lldb) br s -F isEven
Breakpoint 4: where = DebuggerDance`isEven + 16 at main.m:4, address = 0x000000010a3f6d00

這些斷點會準確的停止在函數的開始。Objective-C 的方法也完全可以:

(lldb) breakpoint set -F "-[NSArray objectAtIndex:]"
Breakpoint 5: where = CoreFoundation`-[NSArray objectAtIndex:], address = 0x000000010ac7a950
(lldb) b -[NSArray objectAtIndex:]
Breakpoint 6: where = CoreFoundation`-[NSArray objectAtIndex:], address = 0x000000010ac7a950
(lldb) breakpoint set -F "+[NSSet setWithObject:]"
Breakpoint 7: where = CoreFoundation`+[NSSet setWithObject:], address = 0x000000010abd3820
(lldb) b +[NSSet setWithObject:]
Breakpoint 8: where = CoreFoundation`+[NSSet setWithObject:], address = 0x000000010abd3820

如果想在 Xcode 的UI上創(chuàng)建符號斷點,你可以點擊斷點欄左側的 + 按鈕。

[圖片上傳中...(image-7907e9-1528639982302-5)]

然后選擇第三個選項:

[圖片上傳中...(image-18611-1528639982302-4)]

這時會出現一個彈出框,你可以在里面添加例如 -[NSArray objectAtIndex:] 這樣的符號斷點。這樣每次調用這個函數的時候,程序都會停止,不管是你調用還是蘋果調用。

如果你 Xcode 的 UI 上右擊任意斷點,然后選擇 "Edit Breakpoint" 的話,會有一些非常誘人的選擇。

這里,斷點已經被修改為只有i99 的時候才會停止。你也可以使用 "ignore" 選項來告訴斷點最初的 n次調用 (并且條件為真的時候) 的時候不要停止。

接下來介紹 'Add Action' 按鈕...

斷點行為 (Action)

上面的例子中,你或許想知道每一次到達斷點的時候 i 的值。我們可以使用 p i 作為斷點行為。這樣每次到達斷點的時候,都會自動運行這個命令。

[圖片上傳中...(image-2ef2f1-1528639982302-2)]

你也可以添加多個行為,可以是調試器命令,shell 命令,也可以是更直接的打?。?/p>

[圖片上傳中...(image-14fb32-1528639982302-1)]

可以看到它打印 i,然后大聲念出那個句子,接著打印了自定義的表達式。

下面是在 LLDB 而不是 Xcode 的 UI 中做這些的時候,看起來的樣子。

(lldb) breakpoint set -F isEven
Breakpoint 1: where = DebuggerDance`isEven + 16 at main.m:4, address = 0x00000001083b5d00
(lldb) breakpoint modify -c 'i == 99' 1
(lldb) breakpoint command add 1
Enter your debugger command(s).  Type 'DONE' to end.
> p i
> DONE
(lldb) br li 1
1: name = 'isEven', locations = 1, resolved = 1, hit count = 0
    Breakpoint commands:
      p i

Condition: i == 99

  1.1: where = DebuggerDance`isEven + 16 at main.m:4, address = 0x00000001083b5d00, resolved, hit count = 0 

接下來說說自動化。

賦值后繼續(xù)運行

看編輯斷點彈出窗口的底部,你還會看到一個選項: "Automatically continue after evaluation actions." 。它僅僅是一個選擇框,但是卻很強大。選中它,調試器會運行你所有的命令,然后繼續(xù)運行??雌饋砭拖駴]有執(zhí)行任何斷點一樣 (除非斷點太多,運行需要一段時間,拖慢了你的程序)。

這個選項框的效果和讓最后斷點的最后一個行為是 continue 一樣。選框只是讓這個操作變得更簡單。調試器的輸出是:

(lldb) breakpoint set -F isEven
Breakpoint 1: where = DebuggerDance`isEven + 16 at main.m:4, address = 0x00000001083b5d00
(lldb) breakpoint command add 1
Enter your debugger command(s).  Type 'DONE' to end.
> continue
> DONE
(lldb) br li 1
1: name = 'isEven', locations = 1, resolved = 1, hit count = 0
    Breakpoint commands:
      continue

  1.1: where = DebuggerDance`isEven + 16 at main.m:4, address = 0x00000001083b5d00, resolved, hit count = 0

執(zhí)行斷點后自動繼續(xù)運行,允許你完全通過斷點來修改程序!你可以在某一行停止,運行一個 expression 命令來改變變量,然后繼續(xù)運行。

例子

想想所謂的"打印調試"技術吧,不要這么做:

NSLog(@"%@", whatIsInsideThisThing);

而是用個打印變量的斷點替換 log 語句,然后繼續(xù)運行。

也不要:

int calculateTheTrickyValue {
  return 9;

  /*
   Figure this out later.
   ...
}

而是加一個使用 thread return 9 命令的斷點,然后讓它繼續(xù)運行。

符號斷點加上 action 真的很強大。你也可以在你朋友的 Xcode 工程上添加一些斷點,并且加上大聲朗讀某些東西的 action??纯此麄円ǘ嗑貌拍芘靼装l(fā)生了什么。??

完全在調試器內運行

在開始舞蹈之前,還有一件事要看一看。實際上你可以在調試器中執(zhí)行任何 C/Objective-C/C++/Swift 的命令。唯一的缺點就是不能創(chuàng)建新函數... 這意味著不能創(chuàng)建新的類,block,函數,有虛擬函數的 C++ 類等等。除此之外,它都可以做。

我們可以申請分配一些字節(jié):

(lldb) e char *$str = (char *)malloc(8)
(lldb) e (void)strcpy($str, "munkeys")
(lldb) e $str[1] = 'o'
(char) $0 = 'o'
(lldb) p $str
(char *) $str = 0x00007fd04a900040 "monkeys"

我們可以查看內存 (使用 x 命令),來看看新數組中的四個字節(jié):

(lldb) x/4c $str
0x7fd04a900040: monk

我們也可以去掉 3 個字節(jié) (x 命令需要斜引號,因為它只有一個內存地址的參數,而不是表達式;使用 help x 來獲得更多信息):

(lldb) x/1w `$str + 3`
0x7fd04a900043: keys

做完了之后,一定不要忘了釋放內存,這樣才不會內存泄露。(哈,雖然這是調試器用到的內存):

(lldb) e (void)free($str)

讓我們起舞

現在我們已經知道基本的步調了,是時候開始跳舞并玩一些瘋狂的事情了。我曾經寫過一篇 NSArray 深度探究的博客。這篇博客用了很多 NSLog 語句,但實際上我的所有探索都是在調試器中完成的??纯茨隳懿荒芘靼自趺醋龅?,這會是一個有意思的練習。

不用斷點調試

程序運行時,Xcode 的調試條上會出現暫停按鈕,而不是繼續(xù)按鈕:

點擊按鈕會暫停 app (這會運行 process interrupt 命令,因為 LLDB 總是在背后運行)。這會讓你可以訪問調試器,但看起來可以做的事情不多,因為在當前作用域沒有變量,也沒有特定的代碼讓你看。

這就是有意思的地方。如果你正在運行 iOS app,你可以試試這個: (因為全局變量是可訪問的)

    (lldb) po [[[UIApplication sharedApplication] keyWindow] recursiveDescription]
<UIWindow: 0x7f82b1fa8140; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x7f82b1fa92d0>; layer = <UIWindowLayer: 0x7f82b1fa8400>>
   | <UIView: 0x7f82b1d01fd0; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x7f82b1e2e0a0>>

你可以看到整個層次。Chiselpviews 就是這么實現的。

更新UI

有了上面的輸出,我們可以獲取這個 view:

(lldb) e id $myView = (id)0x7f82b1d01fd0

然后在調試器中改變它的背景色:

(lldb) e (void)[$myView setBackgroundColor:[UIColor blueColor]]

但是只有程序繼續(xù)運行之后才會看到界面的變化。因為改變的內容必須被發(fā)送到渲染服務中,然后顯示才會被更新。

渲染服務實際上是一個另外的進程 (被稱作 backboardd)。這就是說即使我們正在調試的內容所在的進程被打斷了,backboardd 也還是繼續(xù)運行著的。

這意味著你可以運行下面的命令,而不用繼續(xù)運行程序:

(lldb) e (void)[CATransaction flush]

即使你仍然在調試器中,UI 也會在模擬器或者真機上實時更新。Chisel 為此提供了一個別名叫做 caflush,這個命令被用來實現其他的快捷命令,例如 hide <view>,show <view> 以及其他很多命令。所有 Chisel 的命令都有文檔,所以安裝后隨意運行 help show 來看更多信息。

Push 一個 View Controller

想象一個以 UINavigationController 為 root ViewController 的應用。你可以通過下面的命令,輕松地獲取它:

(lldb) e id $nvc = [[[UIApplication sharedApplication] keyWindow] rootViewController]

然后 push 一個 child view controller:

(lldb) e id $vc = [UIViewController new]
(lldb) e (void)[[$vc view] setBackgroundColor:[UIColor yellowColor]]
(lldb) e (void)[$vc setTitle:@"Yay!"]
(lldb) e (void)[$nvc pushViewContoller:$vc animated:YES]

最后運行下面的命令:

(lldb) caflush // e (void)[CATransaction flush]

navigation Controller 就會立刻就被 push 到你眼前。

查找按鈕的 target

想象你在調試器中有一個 $myButton 的變量,可以是創(chuàng)建出來的,也可以是從 UI 上抓取出來的,或者是你停止在斷點時的一個局部變量。你想知道,按鈕按下的時候誰會接收到按鈕發(fā)出的 action。非常簡單:

(lldb) po [$myButton allTargets]
{(
    <MagicEventListener: 0x7fb58bd2e240>
)}
(lldb) po [$myButton actionsForTarget:(id)0x7fb58bd2e240 forControlEvent:0]
<__NSArrayM 0x7fb58bd2aa40>(
_handleTap:
)

現在你或許想在它發(fā)生的時候加一個斷點。在 -[MagicEventListener _handleTap:] 設置一個符號斷點就可以了,在 Xcode 和 LLDB 中都可以,然后你就可以點擊按鈕并停在你所希望的地方了。

觀察實例變量的變化

假設你有一個 UIView,不知道為什么它的 _layer 實例變量被重寫了 (糟糕)。因為有可能并不涉及到方法,我們不能使用符號斷點。相反的,我們想監(jiān)視什么時候這個地址被寫入。

首先,我們需要找到 _layer 這個變量在對象上的相對位置:

(lldb) p (ptrdiff_t)ivar_getOffset((struct Ivar *)class_getInstanceVariable([MyView class], "_layer"))
(ptrdiff_t) $0 = 8

現在我們知道 ($myView + 8) 是被寫入的內存地址:

(lldb) watchpoint set expression -- (int *)$myView + 8
Watchpoint created: Watchpoint 3: addr = 0x7fa554231340 size = 8 state = enabled type = w
    new value: 0x0000000000000000

這被以 wivar $myView _layer 加入到 Chisel 中。

非重寫方法的符號斷點

假設你想知道 -[MyViewController viewDidAppear:] 什么時候被調用。如果這個方法并沒有在MyViewController 中實現,而是在其父類中實現的,該怎么辦呢?試著設置一個斷點,會出現以下結果:

(lldb) b -[MyViewController viewDidAppear:]
Breakpoint 1: no locations (pending).
WARNING:  Unable to resolve breakpoint to any actual locations.

因為 LLDB 會查找一個符號,但是實際在這個類上卻找不到,所以斷點也永遠不會觸發(fā)。你需要做的是為斷點設置一個條件 [self isKindOfClass:[MyViewController class]],然后把斷點放在 UIViewController上。正常情況下這樣設置一個條件可以正常工作。但是這里不會,因為我們沒有父類的實現。

viewDidAppear: 是蘋果實現的方法,因此沒有它的符號;在方法內沒有 self 。如果想在符號斷點上使用 self,你必須知道它在哪里 (它可能在寄存器上,也可能在棧上;在 x86 上,你可以在 $esp+4 找到它)。但是這是很痛苦的,因為現在你必須至少知道四種體系結構 (x86,x86-64,armv7,armv64)。想象你需要花多少時間去學習命令集以及它們每一個的調用約定,然后正確的寫一個在你的超類上設置斷點并且條件正確的命令。幸運的是,這個在 Chisel 被解決了。這被成為 bmessage

(lldb) bmessage -[MyViewController viewDidAppear:]
Setting a breakpoint at -[UIViewController viewDidAppear:] with condition (void*)object_getClass((id)$rdi) == 0x000000010e2f4d28
Breakpoint 1: where = UIKit`-[UIViewController viewDidAppear:], address = 0x000000010e11533c

LLDB 和 Python

LLDB 有內建的,完整的 Python 支持。在LLDB中輸入 script,會打開一個 Python REPL。你也可以輸入一行 python 語句作為 script 命令 的參數,這可以運行 python 語句而不進入REPL:

(lldb) script import os
(lldb) script os.system("open http://www.objc.io/")

這樣就允許你創(chuàng)造各種酷的命令。把下面的語句放到文件 ~/myCommands.py 中:

def caflushCommand(debugger, command, result, internal_dict):
  debugger.HandleCommand("e (void)[CATransaction flush]")

然后再 LLDB 中運行:

command script import ~/myCommands.py

或者把這行命令放在 /.lldbinit 里,這樣每次進入 LLDB 時都會自動運行。Chisel 其實就是一個 Python 腳本的集合,這些腳本拼接 (命令) 字符串 ,然后讓 LLDB 執(zhí)行。很簡單,不是嗎?

緊握調試器這一武器

LLDB 可以做的事情很多。大多數人習慣于使用 p,ponsc,但實際上除此之外,LLDB 可以做的還有很多。掌握所有的命令 (實際上并不是很多),會讓你在揭示代碼運行時的運行狀態(tài),尋找 bug,強制執(zhí)行特定的運行路徑時獲得更大的能力。你甚至可以構建簡單的交互原型 - 比如要是現在以 modal 方式彈出一個 View Controller 會怎么樣?使用調試器,一試便知。

這篇文章是為了想你展示 LLDB 的強大之處,并且鼓勵你多去探索在控制臺輸入命令。

打開 LLDB,輸入 help,看一看列舉的命令。你嘗試過多少?用了多少?

但愿 NSLog 看起來不再那么吸引你去用,每次編輯再運行并不有趣而且耗時。

調試愉快!

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

相關閱讀更多精彩內容

  • 你是否曾經苦惱于理解你的代碼,而去嘗試打印一個變量的值? NSLog(@"%@", whatIsInsideThi...
    木易林1閱讀 1,046評論 0 4
  • 轉載 與調試器共舞 - LLDB 的華爾茲: https://objccn.io/issue-19-2/ 推薦:i...
    F麥子閱讀 3,463評論 0 10
  • 你是否曾經苦惱于理解你的代碼,而去嘗試打印一個變量的值? NSLog(@"%@", whatIsInsideThi...
    paraneaeee閱讀 1,339評論 0 7
  • 你是否曾經苦惱于理解你的代碼,而去嘗試打印一個變量的值? NSLog(@"%@", whatIsInsideThi...
    我是啊梁閱讀 888評論 1 1
  • 與調試器共舞 - LLDB 的華爾茲 nangege 2014/12/19 你是否曾經苦惱于理解你的代碼,而去嘗試...
    McDan閱讀 960評論 0 0

友情鏈接更多精彩內容