序:原文 Dancing in the Debugger — A Waltz with LLDB
聲明:譯文有一部分參考自:與調試器共舞 - LLDB 的華爾茲
前言
你是否曾經為試圖理解尼的代碼和打印一個變量的值而感到苦惱?
NSLog(@"%@", whatIsInsideThisThing);
或者跳過一個函數調用來簡化程序的行為?
NSNumber *n = @7; // theFunctionThatShouldReallyBeCalled();
或短路一個邏輯檢查?
if (1 || theBooleanAtStake) { ... }
抑或偽造一個函數的實現?
int calculateTheTrickyValue {
return 9;
/*
Figure this out later.
...
}
并且每次都必須重新編譯,重新運行嗎?
構建軟件是復雜的,并且bug總是會出現。常見的修復周期是修改代碼,編譯,再次運行,并希望變的最好。
其實并不需要這樣。您可以使用調試器哈!即使你已經知道如何使用調試器來檢查一個變量,可是調試器可以做的遠不止這些。
本文打算挑戰你對調試的認知,更詳細地解釋了一些你可能不知道的基本原理,然后給你展示一些有趣的例子。讓我們隨著舞曲開始旋轉起來,看看我們會以何種水平結束。
LLDB
LLDB 是一個有著 REPL
特性,并內置 C++
和 Python
插件的開源調試器。該調試器捆綁在Xcode內部,并內置于Xcode窗口底部的控制臺面板里。調試器允許您在程序運行的特定時刻暫停程序,來檢查變量的值,執行自定義的指令,然后按照你所認為合適的步驟來操作程序的進展。(這里 是調試器如何工作的大體介紹。)
之前尼很有可能使用過調試器,即使只是在Xcode窗口頁面添加斷點。但是有一些小竅門,可以使你有一些很漂亮而又酷爽的事情去做。GDB to LLDB 參考的是一個偉大的可用命令的鳥瞰圖,你還有可能想要安裝 Chisel ,一個可以使調試器更加有趣的 LLDB 插件的開源合輯。
與此同時,讓我們以 在調試器中如何打印一個變量的值 來開始這場華爾茲的旅程吧。
基礎知識
這里是一段打印一個字符串的簡單示例小程序。注意,在第16
行添加了一個斷點:
程序會在第16
行被暫停運行,并且控制臺會被打開,允許我們和調試器交互。那我們應該輸入些什么呢?
help
最簡單的命令是 help
,這一指令將會列出所有的命令。如果你忘記某條命令是做什么的或者想了解更多的命令,你可以使用 help
命令查看更多的細節,例如 help print
或 help thread
。如果你忘記了 help
命令是做什么的?你可以使用 help help
命令,但如果你知道的足夠多,也許你還沒有完全忘記命令是做什么的。
使用 print
命令打印一個值是很簡單的。
LLDB 實際上會做前綴匹配,因此尼也可以使用
prin
、 pri
、 p
命令,但是不可以使用 pr
,因為 LLDB 不能把它和 process
命令做區分(幸運的是,p
并沒有歧義)。你可能還會注意到,結果中有個
$0
。實際上你可以使用它來代指這個結果。試試 print $0 + 7
,你會看到 45
。任何以美元符開頭的東西都是存在于 LLDB 的命名空間的,它們是為了幫助你進行調試而存在的。
expression
如果想修改一個值怎么辦?這里使用 expression
命令。
這一命令不僅僅修改了調試器中的值,實際上還修改了程序中的值。如果你在這個基礎上繼續執行程序,將會打印
83 huang qimeng
,神奇吧!從現在開始,我們下面會使用這兩個命令的簡化形式
p
、 e
。
什么是 print 命令
這里有一個有趣的表達式:p count = 18
,如果我們執行這個命令并打印count
的值,我們會看到結果和這個表達式 expression count = 18
是一個吊樣的。
和expression
不同的是,print
不需要參數,比如e -h +17
到底是以-h
為標識,僅僅執行+17
呢,還是計算17
和h
的差值呢?連字符確實讓人困惑,你可能得不到尼想要的結果。
幸運的是解決方式很簡單,使用--
表示標識的結束,和輸入的開始,比如以-h
作為標識,就要用e -h -- +17
,如果想計算它們的差值,就使用e -- -h +17
,一般來說,不使用標識的情況比較普遍,所以e --
就有了一個簡寫的方式,即print
。
輸入help print
,然后向下滾動,就會發現:
'print' is an abbreviation for 'expression --'.
(print是 `expression --` 的簡寫)
打印對象
輸入:
p objects
然后輸出是一堆奇怪的東西:
(NSString *) $7 = 0x0000000104da4040 @"huang qimeng"
如果我們嘗試打印結構更復雜的對象,結果甚至會更糟:
(lldb) p @[ @"foo", @"bar" ]
(NSArray *) $8 = 0x00007fdb9b71b3e0 @"2 objects"
實際上,我們想看的是對象的 description
方法的結果。我么需要使用 -O
(注意
是字母 O,而不是數字 0) 標志告訴expression
命令以對象 (Object)
的方式來打印結果。
(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
指定不同的打印格式。他們都以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
你也可以使用p/c
打印字符,或者p/s
打印以空終止的字符串*char **,這里是輸出格式的完整說明。
變量
現在你已經可以打印對象和簡單類型的變量了,以及如何使用expression
命令在調試器中修改他們了。~~此處廢話不翻譯~~,不過為了能使用聲明的變量,變量必須以美元符號$
開頭。
(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
糟了~,LLDB無法分辨涉及的類型(注:返回的類型),這種事情經常出現,給個說明就好了:
(lldb) p (char)[[$array objectAtIndex:$a] characterAtIndex:0]
'M'
(lldb) p/d (char)[[$array objectAtIndex:$a] characterAtIndex:0]
77
變量使調試器的使用變得更容易了,你想不到吧???