Advanced+Apple+Debugging(9)

來源:互聯網
上載者:User

標籤:lldb

在前面的章節中, 你已經學習了為命令建立別名並將它們儲存在lldbinit檔案中.不幸的是, 命令別名有一些局限性.
用這種方法建立的別名如果你用來執行靜態命令會表現的很好, 但是有時候你想在命令中輸入一些內容以便得到一些有用的輸出.
用命令別名的本質是用實際的命令替換了別名. 如果你想在命令的中間輸入一些東西, 比如一條擷取對象執行個體的類的命令, 提供需要輸入的對象?
一種極其拙劣的解決方案就是用命令別名做下面的事情(請永遠不要這樣做):

(lldb) po id $INPUT = @"input test";
(lldb) command alias getcls po -l objc -O -- [$INPUT class]
這在LLDB中建立了一個叫做$INPUT的臨時變數然後使用$INPUT擷取類.但是這種方式很拙劣. 你不得不每次都要重新聲明一個$INPUT, 這完全違背了使用快捷命令的初衷.
然而, 不要絕望 - 這裡有一種優雅的解決方案可以支援輸入.

命令正則

在LLDB中command regex命令實際上很像命令別名, 除非你可以為輸入的將會作為命令的一部分執行的內容提供一個Regex.
command regex帶來了一種輸入文法類似於下面的樣子:

s/<regex>/<subst>/
這是一個普通的Regex. 它是以s/開頭的, 這指定了一個流編輯器將輸入的內容作為替換命令.<regex>是將要被替代的部分, <subst>部分是用來替代的部分.

注意:這種文法衍生自sed終端命令. 知道這點是很重要的, 因為如果你嘗試使用進階模式, 你可以查看sed的首頁來看看在替換格式文法中可能是哪些內容.
到了看一個具體例子的時間了. 開啟Signals的Xcode項目. 構建並運行, 然後將應用程式暫停在調試器中. 一旦LLDB的控制台出現並準備接受輸入, 在LLDB中輸入下面的內容:

(lldb) command regex rlook ‘s/(.+)/image lookup -rn %1/‘
你輸入的這條命令會使你的 image正則搜尋更簡單. 你已經建立了一個叫做rlook的新命令. 這條新命令會擷取rlook後面的所有東西並用image lookup -rn作為它的首碼. 這條命令做了這樣一件事情, 用image lookup -rn %1替換可以匹配一個或多個字元的Regex(圓括弧) 內的所有內容. %1表示這匹配到的內容.
所以, 例如, 你輸入了這樣的內容:

rlook FOO
LLDB實際執行的的是下面的內容:

image lookup -rn FOO
現在你就可以通過僅僅輸入rlook 來替代輸入長長的image lookup -rn命令.
但是等一下, 現在變得好一點了!假如與rl字元沒有衝突, 你也可以用rl字元替代. 你可以指定任何命令, 可以是內建的或者你自己的, 只要是與其他命令沒有衝突的首碼.
這就意味著你可以使用更簡潔的輸入來搜尋viewDidLoad方法. 現在來試一下:

(lldb) rl viewDidLoad
這回提取出當前可執行檔中所有模組中的viewDidLoad的實現. 嘗試將搜尋範圍限定在viewDidLoadAPP中:

(lldb) rl viewDidLoad Signals
現在你已經確信了這條命令, 講下面這行代碼添加到~/.lldbinit檔案中:

command regex rlook ‘s/(.+)/image lookup -rn %1/‘
注意:實現一個正則命令的最佳方式是在程式運行中用LLDB實現. 這可以讓你在命令正則中重複聲明(在你對它不滿意的時候重新聲明)並且可以在不重啟LLDB的情況下測試它.如果你對命令很滿意, 那麼將它添加到~/.lldbinit檔案中以便LLDB每次啟動的時候都可以使用.
從現在開始rlook就變得可用了, 你再也不用輸入令人頭疼的image lookup -rn的完整命令了.太棒了!

執行複雜的邏輯

是時候把命令的Regex提升一個等級了.實際上你可以用一條命令的別名來執行多條指令. 現在LLDB應該依然在暫停狀態, 輸入下面這條新的命令:

(lldb) command regex -- tv ‘s/(.+)/expression -l objc -O -- @import
QuartzCore; [%1 setHidden:!(BOOL)[%1 isHidden]]; (void)[CATransaction
flush];/‘
這是一條複雜而有用的命令, 將會建立一個名字叫tv (toggle view)的命令, 可用與當調試器暫停時候在UIView (或者 NSView)之間切換.
這條命令由三行程式碼群組成:

@import QuartzCore, 在調試器的地址空間中匯入QuartzCore架構. 這是必要的因為只有在你聲明了代碼之後調試器才理解你在執行什麼. 你需要執行QuartzCore架構中的代碼, 之前還沒有匯入, 但是現在已經匯入了.
[%1 setHidden:!(BOOL)[%1 isHidden]];, 在視圖的可見於不可見之間切換, 這取決於之前的狀態是可見的還是不可見的. 注意isHidden不知道傳回值的類型, 因此你需要將它轉換為Objective-C中的Bool值.
最後一行代碼是, [CATransactionflush], 它會重新整理CATransaction隊列. 調試器中控制UI也就是通常所說的螢幕在調試器繼續執行之前不會反映出任何改變. 然而, 這個方法將會更新LLDB中執行的結果而不需要continue就能展示視覺上的改變.
注意:由於輸入參數的限制, 多行輸入是不允許的, 所以你不得不將所有的指令都放在一行代碼中. 在手工控制正則命令的時候這很難看但卻很有必要.然而, 如果你曾經在Objective-C/Swift的原始碼中做過這些, 那麼蘋果上帝可能用很多次的審核嚴厲的懲罰過你!
如果LLDB依然在暫停狀態, 執行新建立的tv命令:

(lldb) tv [[[UIApp keyWindow] rootViewController] view]
page100image13312.png

查看模擬器裡確認一下視圖已經消失了.
現在只需要簡單的在LLDB中按下Enter, LLDB就會重複你之前執行的最後一條指令.視圖會重新整理到之前的狀態.
現在你已經實現了tv的命令, 將它添加到~/.lldbinit檔案中.
command regex -- tv ‘s/(.+)/expression -l objc -O -- @import QuartzCore;
[%1 setHidden:!(BOOL)[%1 isHidden]]; (void)[CATransaction flush];/‘
連結正則輸入

這條命令之所以選擇使用怪誕的流編輯器輸入是有原因的:這種格式可以讓你輕鬆的在一條命令中指定多個動作. 當給出多個指令的時候, 這個Regex會試著匹配每一個輸入.如果輸入匹配到了, 匹配到的內容就會應用到指令的<subst>部分.如果輸入沒有匹配到指定的流, 他就會進入下一條命令並且看看Regex是否能匹配到那些輸入.
當我們在處理記憶體中或者寄存器中的對象的時候使用Objective-C環境是普遍必要的. 因為, 以方括弧[開始的任何語句或者@字元都是Objective-C裡面的.這是因為Swift在處理記憶體資料的時候很難, 並且它不允許你訪問寄存器, 同時在處理swift運算式的時候也不是以[或者@開始的.
你可以使用這些資訊自動檢測給定的輸入需要什麼樣的環境.
讓我們看一下你應該怎麼去構建一個擷取對象類資訊的一條指令,:
? 在 Objective-C中, 你應該使用[objcObject class].
? 在 Swift 中, 你應該輸入 (of: swiftObject).
在Xcode中, 在MasterViewController.viewDidLoad () - > ()處建立一個斷點(不要忘記中間的空格).

page101image17376.png

構建並運行, 等待斷點被觸發.像往常一樣, 轉到調試器裡.
首先, 構建出新命令getcls的Objective-C的實現.
(lldb) command regex getcls ‘s/(([0-9]|\$|\@|[).)/cpo [%1 class]/‘
哇, 這條Regex讓我們的眼睛都花了.我們來拆解一下:
首先, 這裡有一個內部分組說在起始部分可以匹配接下來的字元:
? [0-9]的意思是可以是0-9的數字.
? \$的意思是$的字面量將會被匹配
? \@的意思是@的字面量將會被匹配
? [的意思是[的字面量將會被匹配
以上面的內容為開始的字元都會產生一個匹配. 緊跟著的.
的意思是為一個或多個字元產生一個匹配.
整體看來,這句話的意思是一個數字或者$符號或者 @符號或者[符號, 後面跟著任何字元的內容都會作為命令匹配到的結果並且運行cpo [%1 class].再說一次, %1會被Regex中第一個匹配到的內容替換. 在這裡, 就是整條命令. 而內部匹配器(匹配一個數字, $, 等等)將會作為%2.
現在用一組命令來實驗一下getcls命令看看它是如何工作的:

(lldb) getcls @"hello world"
NSCFString
(lldb) getcls @[@"hello world"]
NSSingleObjectArrayI
(lldb) getcls [UIDevice currentDevice]
UIDevice
(lldb) cpo [UIDevice currentDevice]
<UIDevice: 0x60800002b520>
(lldb) getcls 0x60800002b520
UIDevice
很神奇吧!
然而, 這隻能處理Objective-C環境中用命令匹配到的引用.例如, 試一下下面的內容:

(lldb) getcls self
你會得到一個錯誤:

error: getcls
這是因為Regex沒有匹配到你輸入的內容. 讓我們在getcls中添加一個匹配其他形式的輸入. 現在在LLDB中輸入下面的內容:

(lldb) command regex getcls ‘s/(([0-9]|\$|\@|[).*)/cpo [%1 class]/‘ ‘s/
(.+)/expression -l swift -O -- type(of: %1)/‘
這看起來更複雜了, 但還不算太糟. 這條命令的第一部分與你在前面添加的一致. 但是現在你在末尾添加了另外一個Regex. 這一個Regex可以捕獲所有輸入, 就像你添加的rlook命令. 這裡捕獲到的所有內容的指令很簡單叫做type(of:)用輸入的內容作為參數.
用self再次執行這條命令:

(lldb) getcls self
你會得到期望中的Signals.MasterViewController輸出. 鑒於, 你是在Swift環境下匹配的內容, 你可以用另外一種有趣的方法使用這條命令.

(lldb) getcls self .title
注意中間有一個空格, 而且這條命令依然起作用. 這是因為你告訴swift環境擷取除了分行符號以外的所有字元.
一旦, 你完成了練習並改進了getcls命令, 別忘了把它加到~/.lldbinit檔案中.

命令正則的局限性

LLDB中的命令正則在創造性應用的數量上有些限制. LLDB的命令正則有一個你只能在<subst>上應用一個參數的限制. 這就意味著你那些支援多個參數的進階命令也逃不出這個限制.
幸運的是, LLDB有script bridging介面 - 一個完全由Python實現的在調試過程中用來實現進階LLDB指令的功能.在後面的章節中你會深入的學習script bridging.
但是現在, 簡單的使用command alias或者command regex來滿足你調試的需要.

我們為什麼要學習這些?

回到本章中你已經建立並添加了文法和協助文檔的Regex命令.
正如前面提到的, 你將會感謝你自己提供了這些協助文檔來提醒你之前建立的命令的功能.

Advanced+Apple+Debugging(9)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.