Advanced+Apple+Debugging(15)

來源:互聯網
上載者:User

標籤:lldb

什麼?你從來沒有聽說DTrace?!這真是太可怕了!DTrace是一個可以讓你用動態或靜態方式查看代碼的工具.
http://dtrace.org/guide/preface.html
你可以建立一個DTrace probes編譯到你的代碼中(靜態方式), 或者你可以檢查已經編譯並運行起來的代碼(動態方式).DTrace是一個多用途的工具, 它有多種功能:它可以成為剖析器, 一個分析器, 一個調試器或者你想要的任何東西. 當我沒有從哪裡開始的頭緒的時候, 我經常在我想要瀏覽的代碼中用DTrace指定一個寬泛的搜尋網.

天呐!到了學習Dtrace的時間了! Dtrace(可能)是你從來沒有聽說過的最酷的工具.用DTrace, 你可以使用一個叫做probe的東西攔截一個函數或者一組函數. 從這裡開始, 你可以執行自訂的操作來查詢指定進程的資訊, 或者你電腦上系統層級的資訊(並檢測多個使用者)!
如果你用過Instruments應用程式, 它那些讓你感到驚訝的強大的底層功能就是通過強大的DTrace實現的.
在本章中, 你會看到非常小的一個篇幅介紹DTrace在已經編譯過的應用程式中追蹤Objective-C代碼是如何強大.使用DTrace來觀察iOS架構(比如UIKit)可以讓你難以置信的洞察到在架構內部作者是如何設計他們的代碼的.

壞訊息

讓我們先說一下壞訊息, 因為從現在開始後面都是讓人興奮而又炫酷的功能. 關於DTrace這裡有幾件需要注意的事情:
? 為了正常使用DTrace你需要禁用Rootless模式. 你還記得很久以前在第一章中我提醒你為了實現某些功能需要禁用Rootless? 為了讓LLDB附加到你macOS電腦的任何進程上, 如果啟用了System Integrity Protection DTrace將不能正常使用. 如果你跳過了第一章, 那麼現在回去禁用Rootless.
? DTrace 不是為iOS裝置而做的. 儘管Instruments應用程式底層用DTrace實現了很多功能. 但是它不可以在你的iOS裝置商運行自訂的指令碼. 這就意味著你只能在你的iOS裝置商運行預設的有限的功能. 然兒, 你依然可以在iOS模擬器(或者你macOS電腦的其他應用程式)上運行你想啟動並執行指令碼無論你是否是擁有這些程式的代碼.
? DTrace 有著陡峭的學習曲線. DTrace需要你知道你再做什麼和你在找什麼. 它的文檔會假設你已經知道了DTrace組件最基本的術語. 在本章中你會學到最基本的概念, 如果要介紹DTrace的方方面面那麼已經可以單獨的寫一本書了, 這超出了我要教你的範圍.
事實上, 這些需要你提前知道, 如果你對DTrace感興趣, 那麼可以看看這本書http://www.brendangregg.com/dtracebook/index.html. 它關注一個寬泛的話題但有可能不適合apple 調試/逆向工程策略, 但是它會教你如何使用DTrace.
現在我已經將壞訊息告訴你了, 是時候來聊一些開心的話題了.

正確的跳轉

我不會用討厭的專業術語將你趕跑. 沒有人會將時間花費在那上面. 取而代之的是, 你首先會把你的手弄髒, 然後弄清楚稍後你會做什麼.
啟動iOS 7 Plus 模擬器. 在模擬器啟動之後, 建立一個新的終端視窗. 在終端中輸入下面的內容:

sudo dtrace -n ‘objc$target:*ViewController::entry‘ -p pgrep SpringBoard
不, 這不會秘密的毀壞你的電腦, 在這裡你需要使用sudo 因為DTrace極其強大並且可以查詢你電腦裡其他使用者的資訊. 這就意味著你需要root許可權去使用它.
這條DTrace命令有兩個選項, name 選項 (-n) 和 PID選項 (-p), 這兩個選項我們稍後討論. 確保你用單引號報過了查詢語句夠則是不起作用的. 注意pgrep SpringBoard前後用的是抑音符而不是單引號.
如果你的輸入是正確的, 在終端中你會看到類似下面的輸出:

dtrace: description ‘objc$target:ViewController::entry‘ matched 28076
probes
在模擬器中瀏覽同時注意觀察終端視窗.
這會輸出出每一個以"ViewController"做結尾的Objective-C類名. 因為我們將函數選項留空 (不要擔心 - 我們會在下一部分討論術語), 它會匹配每一個以ViewController結尾的Objective-C類中單獨的Objective-C方法.
如果你厭煩了查看突然出現的內容,按下Ctrl + C 鍵殺掉正在運行DTrace指令碼的終端視窗.
回到你的終端中, 輸入下面的內容:
這一次這裡有一些細微的改變:
? 查詢的
ViewController 已經被改成了UIViewController.
? 查詢語句中的-viewWillAppear呢? 已經被添加到了函數的位置. 再說一次, 我們會在後面討論專業術語. 但是現在, 你所需要知道就的是 替換了匹配包含"ViewController"字串的任何類的每一個方法, 新的DTrace指令碼只會匹配-[UIViewController viewWillAppear:]. ?在 DTrace標識萬用字元, 用來拆分viewWillAppear:中的:.
? 最後, 你為一個叫做ustack()的函數加上了一堆大括弧. -[UIViewController viewWillAppear:]每次觸發的都會調用這個邏輯. ustack() 是DTrace內部的函數在一個函數觸發的時候可以提取出使用者的堆棧記錄 (在這個例子中就是SpringBoard).
? 注意觀察從entry末尾移動到移動到大括弧末尾的那個單引號
如果你輸入的都是正確的, 你將會得到下面的輸出:

dtrace: description ‘objc$target:UIViewController:-viewWillAppear?:entry‘ matched 1 probe
瀏覽SpringBoard.向上滑, 向下滑, 滾動到最左邊點擊edit按鈕, 因為你需要觸發UIViewController的viewWillAppear:.
當UIViewController的viewWillAppear:觸發的時候, 堆棧記錄將會在終端中列印出來.
注意那些沒有實際函數名而只有模組和地址的的堆棧記錄.

圖片.png
這是在告訴我們沒有調試資訊或者 沒有直接引用這些函數名字的符號表.
如果你厭煩了瀏覽SpringBoard 進程中所有的viewWillAppear:的堆棧記錄, 再次殺掉DTrace指令碼.
現在... 你還記得objc_msgSend使用寄存器的完整理論以及 第一個參數將會是Objective-C某個類的一個執行個體(或者一個類)嗎?
例如, 當objc_msgSend 執行的時候, 函數的聲明看起來將會是這個樣子:

objc_msgSend(self_or_class, SEL, ...);
你可以在DTrace中用抓取arg0參數第一個參數(也就是UIViewController的一個執行個體). 不幸的是, 你只能得到指標的引用 - 你不能運行任何Objective-C的代碼, 比如[arg0 title].
將下面這行代碼添加到你的DTrace指令碼中ustack()函數的前面:

printf("\nUIViewcontroller is: 0x%p\n", arg0);
你 DTrace 單行指令看起來將會是下面這個樣子:

sudo dtrace -n ‘objc$target:UIViewController:-viewWillAppear?:entry
{ printf("\nUIViewcontroller is: 0x%p\n", arg0); ustack(); }‘ -p pgrep SpringBoard
在正確的列印出堆棧記錄之前, 你已經列印除了調用viewWillAppear:的UIViewController的引用.
如果你複製了DTrace輸出的指標的地址 並且將LLDB附加到了SpringBoard上, 你將會發現它志向了一個有效UIViewController (假如它還沒有被銷毀的話).

注意: 從arg0處擷取指標是很簡單的,但是擷取其他資訊 (比如類名) 是一個狡猾的過程. 你不能再屬於使用者進程(比如SpringBoard)的DTrace指令碼中執行任何Objective-C/ Swift代碼. 你所能做的只有用你已有的引用在記憶體中Z型前進. 在最後的章節中, 你將能夠從精簡過的二進位檔案中實際擷取Objective-C的調用的arg0參數中擷取類名, 全無調試資訊!
讓我們再做一個DTrace的例子.
殺掉所有的DTrace 指令碼然後建立一個匯聚所有獨特類的可以在你瀏覽SpringBoard時執行的指令碼:

sudo dtrace -n ‘objc$target:::entry { @[probemod] = count() }‘ -p pgrepSpringBoard
再次瀏覽SpringBoard. 你還得不到任何輸出, 但是當你用ctrl+c終端指令碼之後, 你會得到一個在這個時間內執行了一個方法的類的列表. 這叫做Aggregations是你稍後要學習的內容.

圖片.png
正如你再我的輸出中看到的, 在我執行DTrace單行命令期間SpringBoard 調用了 187075個NSObject實現的方法.
區分它們非常像NSObject的子類的某個執行個體調用了NSObject實現的方法這個事實是很重要的 (比如這個NSObject的子類沒有重寫這些方法中的任何一個).例如, 調用-[UIViewController class]將會作為NSObject通過向前調用記入被執行方法的總數中 因為 UIViewController 不會重寫Objective-C 方法, 類, 或者 UIViewController的父類, UIResponder.

DTrace 專業術語

現在你已經讓一些DTrace單行命令弄髒了手, 是時候學些專業術語了以便你實實在在的弄清楚這些指令碼做了哪些事情.
讓我們再看一個DTrace probe. 你可以將一個probe. These 看做一個query. probe即可以是DTrace檢測到的特定進程或者你電腦的全域進程的事件.
思考一下下面這行DTrace單行命令:

dtrace -n ‘objc$target:NSObject:-description:entry / arg0 = 0 /
{ @[probemod] = count(): }‘ -p pgrep SpringBoard
我將問這個指令碼中有用的問題, 這個例子將會檢測名字叫做SpringBoard的進程中NSObject實現的description方法.此外, 這就是說在description開始的時候,執行這個方法調用次數的邏輯.
這個DTrace單行命令可以進一步被拆解為下面的術語:
? Probe Description: 包裹著0或者更多探針的一組項目.這組成了每一個被冒號分開的provider, module, function, 和name. 最佳化任何冒號之間的項目將會導致探針描述包含所有的匹配. 你可以運算子為這些匹配使用 或者?運算子 . ?將會作為單個字元的萬用字元, 將會匹配任何內容.
? Provider: 將provider想象成一組代碼或者普通的功能.在本章總, 你主要會使用objc provider來追蹤Objective-C方法的調用. objc provider 將所有的Objective-C代碼分組. 你稍後就會瀏覽這些providers.

Note: 關鍵詞$target 是一個匹配提供給DTrace的PID的特殊關鍵字. 某些providers (比如 objc) 期望你能提供這些內容. 將$target 想象成一個在某個具體的進程中檢車Objective- C的實際PID的預留位置. 如果你引用了$target預留位置, 你必須在你的DTrace命令中通過-p 或者 -c選項指明目標進程的PID. 通常情況下這即可以在你知道的具體PID時通過-p PID實現, 也可以更多的使用-p `pgrep NameOFProcess`實現. 終端命令pgrep將會查看名字叫做NameOFProcess的進程的PID然後返回這個PID, 然後將它應用到$target變數中.
? Module: 在objc provider中, module 部分是你指明你想觀察的類名的地方. 使用objc provider在這種情境下有點獨一無二, 因為 通常情況下 module 通常會引用一個程式碼程式庫. 事實上, 在某些providers中, 沒有一點module的內容! 然而, objc provider的作者選擇使用module來引用Objective- C 的類名. 在這個例子中, module就是NSObject.
? Function: 是可以指明你希望觀察的函數名字的probe描述部分. 在這個例子中, 函數是- description. objc provider的作者使用 +或者- 來決定 Objective-C 函數是類方法還是執行個體方法 (正如你期望的那樣!). 如果你啊講函數改為+description, 它將會查詢任何使用+ [NSObject description]的probes替代.

? Name: 這通常指明了一個函數的探針的位置. 通常情況下, 這裡有函數入口 和 出口對應的的入口名和返回點的名字. 此外, 在objc provider內部, 你也可以指定任何彙編指令位移建立探針! 在這個例子中, 名字是函數入口, 或者是函數的起始位置.
? Predicate: 如果要執行的操作是可選的那就執行一個條件運算式. 將predicate想象成if語句中的條件. 操作只會在predicate為true的情況下才會執行. 如果你省略了條件句, 則操作每次都會在探針上執行. 在這個例子中, 條件句就是/ arg0 != 0 /, 這就意味著條件句後面的內容只有在arg0不為 nil的情況下才會執行.
? Action: 如果探針與探針的描述相符並且條件句為真則操作就會被執行. 動作要緊可能像列印一些東西一樣簡單, 或者執行更進階的函數. 在這個例子中, 操作是@[probemod] = count(); 這些代碼.
當所有這幾部分組合起來之後, DTrace就是DTraceclause(從句)的形式.這些可選的條件句和可選的侗族組成了probe description.
簡單的說, 一個DTrace從句應該像下面這個樣子:

provider:module:function:name / predicate / { action }
DTrace "one-liners" 可以由多個可以檢測不同項目的探針描述的從句組成, 在條件句中判斷不同的條件然後執行不同的邏輯操作.
下面是一個例子:

dtrace -n ‘objc$target:NSView:-init:entry‘ -p pgrep -x Xcode
我們有一個包含NSView模組的探針描述objc$target:NSView:-init
:entry, -init*a是函數部分 而entry是沒有條件句和可選動作的名字. DTrace 為追蹤到的資訊產生了預設的輸出 (就是可能被你忽略的-q 選項). 預設的輸出只顯示了函數和名字. 例如, 如果你正在追蹤-[NSObject init]而沒有忽略預設的DTrace操作,你的DTrace輸出的內容看起來應該是下面這個樣子:

dtrace: description ’objc$target:NSObject:-init:entry’ matched 1 probe
CPU ID FUNCTION:NAME
2 512130 -init:entry
2 512130 -init:entry
2 512130 -init:entry
2 512130 -init:entry
從輸出中可以看出,在進程被追蹤的過程中 -[NSObject init] 調用了四次. 你可以通過使用-q 選項告訴DTrace的print函數輸出不同的格式.
-n參數的含義是什麼呢? -n參數指明了provider:module:function:name, module:function:name 或者 function:name形式的DTrace名字. 此外, name選項可以帶一個可選的probe從句, w但是要用單引號包裹著這個單行命令指令碼傳遞給-n參數.
明白了嗎? 沒有? 我們將會用一個有用的DTrace選項來鞏固一下你剛才學習的的知識和術語.

學習監聽探針

在DTrace中有一個非常好用的選項-l, -l可以列出probe description匹配到的所有探針. 當你在使用-l選項的時候, DTrace僅僅會列出探針而不會執行任何操作, 無論你是否使用了操作DTrace都不會執行.
這讓-l成為了學習 哪些是行得通哪些是行不通的好工具.
讓我們再來看一下在編譯DTrace指令碼時的probe description 並指定它的範圍. 思考下面的指令, 但是不要執行:

sudo dtrace -ln ’objc$target:::’ -p pgrep -x Finder
這會在Finder中的每一個Objective-C類的每一個方法和每一條彙編指令處建立一個probe description. 這對於DTrace指令碼是一個非常壞的注意並且可能無法在你的電腦上運行因為觸發的次數實在太多.

注意: 我已經將-x選項應用到pgrep因為我在使用pgrep查詢的時候可能得到多個PIDs, 這回搞砸$target參數. -x 選項的意思是只給我與名字Finder完全符合的. 如果有一個進程的多個執行個體. 你可以在pgrep中使用-o 或者-n選項擷取最開始的那個進程或者最後的那個進程.如果這些聽起來讓你覺得很混亂, 在終端中在沒有 DTrace的情況下練習使用pgrep命令來理解它是如何工作的.
不要執行上面的指令碼因為它會花費很長時間. 然而, 執行下面指令碼以便你理解發生了什麼.
讓我們將這個命令稍微精簡一下. 在終端中, 輸入下面命令:

sudo dtrace -ln ‘objc$target:NSView::‘ -p pgrep -x Finder
按下斷行符號鍵, 然後輸入你的密碼.
這將會列出NSView實現的所有方法中的每一個單獨的方法的probe以及每一個方法的彙編指令. 仍然是一個可怕的方法, 至少這一次過幾秒之後真的會列印出內容來.
有多少probes? 你可以將輸出嫁接到wc命令擷取答案:

sudo dtrace -ln ‘objc$target:NSView::‘ -p pgrep -x Finder | grep wc -l
在我的macOS 機器上是 10.12.4系統 (在我寫這本書的時候), 在finder進程中我得到了46k 個與NSView相關的Objective-C DTrace probes. 哇.
進一步過濾這些probe description:

sudo dtrace -ln ‘objc$target:NSView:-initWithFrame?:‘ -p pgrep -xFinder
這會過濾出執行-[NSView initWithFrame:]方法時除了入口和返回點以外的每一個彙編指令的probe description.
注意我是如何使用?代替冒號指明Objective-C selector(帶有參數)的?. 這是因為如果我使用了冒號,DTrace將會錯誤的認為我已經完成了函數部分並且已經開始指明DTrace probe名字了. 函數描述開頭的- 指明了這是一個執行個體方法.
這裡已然有太多的輸出, 我只想設定一個能夠檢測到-[NSView initWithFrame:] 方法起始位置的探針而不需要其他探針.

sudo dtrace -ln ‘objc$target:NSView:-initWithFrame?:entry‘ -p pgrep -xFinder
這條指令的意思是說只在-[NSView initWithFrame:]的起始位置設定一個探針 並且不在這個方法的其他位置設定探針.
使用-l 選項是學習在你的DTrace操作中指定探針範圍的好方法. 我推薦你在學習DTrace的時候多使用(重用) -l 選項.

一個建立DTrace指令碼的指令碼

在使用DTrace的時候, 你不僅需要處理陡峭的學習曲線, 你還要處理在構建時或者運行時出現的一些隱式錯誤(耶, 它們的隱秘程度和那些swfit編譯器的錯誤處在同一層級).
在學習DTrace的時候為了緩解那些構建是的問題 , 我建立了一個叫做tobjectivec.py的小指令碼 (跟蹤 Objective-C), 它是可以為你產生自訂 DTrace 指令碼的LLDB Python 指令碼.

注意: 歐耶, 現在是提醒你建立DTrace 指令碼以及 DTrace 單行命令的好時候了. 隨著你DTrace指令碼中邏輯複雜度的增加, 使用指令碼成為了一個更好的選擇. 簡單的DTrace查詢, 可以使用單行命令.
你可以在本章的starter目錄下找到tobjectivec.py 指令碼.我假設你已經看過了第22章, “SB Examples, Improved Lookup” 並且已經安裝了lldbinit.py 指令碼 並且已經將它放在了~/lldb 檔案夾下. 假設你已經做過了, 你所需要做的就是把tobjectivec.py 指令碼複製/粘貼到你的~/lldb 目錄下 並且它會在LLDB下次啟動並執行時候啟動.
如果你還沒有做, 返回到第22章中 並按照指令安裝lldbinit.py檔案. 提示一下, 如果你非常固執, 我猜想你可以通過拓展~/.lldbinit檔案手動安裝`tobjectivec.py.

通過tobjectivec.py瀏覽 DTrace

是時候開啟在Objective-C代碼中瀏覽DTrace的旋風之旅了.
在starter 檔案夾中有可迴圈利用的Allocator項目. 開啟那個項目, 構建, 運行, 然後暫停調試器.
當你把Allocator項目暫停在調試器中之後, 進入LLDB 控制台然後輸入下面的內容:

(lldb) tobjectivec -g
通常情況下, tobjectivec 指令碼會在你電腦的/tmp/目錄下產生一個指令碼. 然而, -g 選項的意思是你正在調試你的指令碼 用將輸出顯示到LLDB中來代替在/tmp/displays the output 目錄下產生一個指令碼. 使用-g (or -- debug) 選項, 你當前的指令碼會顯示在控制台中.
這就僅僅只是在沒有任何參數的情況下運行tobjectivec.py指令碼, 這將會產生一下輸出:

#!/usr/sbin/dtrace -s / 1 /
#pragma D option quiet / 2 /
dtrace:::BEGIN { printf("Starting... use Ctrl + c to stop\n"); } / 3 /
dtrace:::END { printf("Ending...\n" ); }
/ Script content below /
objc$target:::entry / 5 /
{
printf("0x%016p %c[%s %s]\n", arg0, probefunc[0], probemod,
(string)&probefunc[1]); / 6 /
}
讓我們來拆解一下:

當執行一個DTrace指令碼的時候, 第一行需要是#!/usr/sbin/dtrace -s指令碼可能無法正確運行.
這一行代碼的意思是當一個探針觸發的時候既不要列出探針的數量也不要執行預設的DTrace操作. 而是執行你指定給DTrace的操作.
這hi這個指令碼中DTrace語句的三分之一.這是一個 DTrace中用於 檢測某種事件的實際探針... 比如當一個DTrace指令碼啟動的時候. 這也就是說, DTrace 一啟動, 就列印出"Starting... use Ctrl + c to stop" 字串.
這裡還有另外一個當DTrace指令碼一結束就列印"Ending..."的DTrace語句.
這是一個有趣的DTrace probe description.這句話的意思是說跟蹤你應用到指令碼上的進程ID中的所有的Objective-C 代碼 .
這個語句中列印出觸發探針的Objective-C執行個體的操作, 後面跟著Objective-C 風格的輸出. 在這裡, 你可以看到這裡使用了代表函數char的probefunc 和模組char的probemod. DTrace有幾個你可以用的內部變數, probefunc 和probemod 只是其中兩個. 你還有probeprov 和probename 可以使用. 記住模組將會代表類名同時函數代表Objective-C 方法. 這裡使用了probemod和probefunc組合 並且 完美的顯示在了你熟悉的Objective-C文法中.
現在你已經理解了這個指令碼, 移除-g 選項以便你不再使用調試選項. 在LLDB中輸入:
(lldb) tobjectivec
這一次你會得到一個不同的輸出:
Copied script to clipboard... paste in Terminal
你剪下板上的內容已經被改變了. 回到你的終端中, 然後粘貼你粘貼板上的內容. 下面是我粘貼板上的內容, 但是你的可能會有點不容:

sudo /tmp/lldb_dtrace_profile_objc.d -p 95129 2>/dev/null
你之前看到的內容都被放到了/tmp/ lldb_dtrace_profile_objc.d檔案中.如果你固執的想知道這個指令碼做了什麼, 我推薦你首先cat 它然後確保你知道它做了什麼.
這個指令碼提供了LLDB附加的進程的標識(因此你不需要輸入pgrep Allocator).
如果你看到了輸入密碼的提示, 輸入你的密碼來擷取root 許可權:

$ sudo /tmp/lldb_dtrace_profile_objc.d -p 95129 2>/dev/null
Password:
Starting... use Ctrl + c to stop
等待直到DTrace指令碼提示你它已經開始運行了.
確保Xcode 和 Terminal 都是可見的, 在控制台中輸入po [NSObject class]. 檢查這個方法輸出的大量的Objective-C 訊息.

圖片.png
這回讓你為即將到來的事情做好準備. 用LLDB繼續執行, 就像這樣:

(lldb) continue
在iOS模擬器中瀏覽Allocator app (在視圖上點擊, 按下? + Y調出來電狀態列)同時注意觀察DTrace終端視窗.
很可怕, 對吧?
這裡輸出了太多內容. 讓我們通過在模組修飾符上新增內容來過濾掉一些幹擾資訊.
回到Xcode中, 暫停執行Allocator進程並進到LLDB中.
產生一個新的只關注 Objective-C 類名中包含StatusBar的指令碼. 在LLDB中輸入下面內容:

(lldb) tobjectivec -m StatusBar -g
這會運行然後輸出下面精簡後的內容:

objc$target:StatusBar::entry
{
printf("0x%016p %c[%s %s]\n", arg0, probefunc[0], probemod,
(string)&probefunc[1]);
}
注意模組部分的probe是如何被改變的. 可以當做你在Regex中最愛用的.. 這意味著我們在查詢一個區分大小寫包含StatusBar的Objective-C類的探針.
在 LLDB中, 移除-g選項以便將指令碼複製到剪下板, 然後重新運行這個命令.
跳到你的終端視窗中. 通過按下Ctrl + C殺掉之前的DTrace執行個體, 然後粘貼你的新指令碼.

(lldb) tobjectivec -m StatusBar
回到Xcode中繼續執行.
回到模擬器中並且按下? + Y 調出來電狀態列或者使用? + ← 或者 ? + →旋轉模擬器同時注意觀察DTrace終端視窗.
你會再次得到一些輸出.
在你需要的時候你可以使用DTrace在代碼中用最小額效能指定一個寬網來觸發並快速練習.

跟蹤調試命令

在我執行簡單的調試命令的時候我經常會洞察出在現象後面發生了什麼 和在他們後面為我工作的代碼.
觀察一下調用了多少 帶了一個簡單的的NSString做參數的Objective-C方法.
回到LLDB中, 輸入下面內容:

(lldb) tobjectivec
將內容粘貼到終端視窗中, 但是不要繼續在LLDB中執行. 取而代之的是, 只需要輸入下面的內容:

(lldb) po @"hi this is a long string to avoid tagged pointers"
在你按下斷行符號鍵的時候, 檢查DTrace終端視窗中的內容 並且 查看一下輸出了什麼內容. 你將會得到一些類似下面的輸出:

圖片.png

我們只是輸出了簡單的NSString 並且查看調用了多少Objective-C方法!
這裡輸出的都是swift風格的代碼.
用? + K清空終端中的內容, 確保終端中的DTrace指令碼仍然在運行. 回到LLDB中並且輸入下面的內容:

(lldb) expression -l swift -O -- class b { }; b()
你再用Swift 的調試環境建立一個純正的swift類並初始化它. 當這個類被建立的時候觀察調用的swift代碼.
DTrace 將會提取出:

0x00000001087541b8 +[SwiftObject class]
0x0000000119149778 +[SwiftObject initialize]
0x0000000119149778 +[SwiftObject class]
如果你複製任何一個DTrace輸出的地址然後po它, 你就會看到這個Swift 類調用的大量的Objective-C方法.
並不是像你想想的那樣純正, 對吧?

跟蹤一個對象

你可以使用DTrace輕鬆的追蹤一個特定的引用調用的方法. 按下Ctrl + C移除之前的DTrace指令碼.
同時應用程式處於暫停狀態, 用 LLDB擷取UIApplication的應用. 確保你再Objective-C的棧幀當中.

(lldb) po UIApp
你將會得到類似下面的輸出:

<UIApplication: 0x7fa774600f90>
複製這個引用然後用這個引用建立一個只在這個引用是arg0參數的時候才會停下的判斷句, objc_msgSend的第一個參數是一個類的執行個體或者是這個類本身.

(lldb) tobjectivec -g -p ‘arg0 == 0x7fa774600f90‘
你的指令碼運行之後在控制台中你將會得到類似下面的輸出:

#!/usr/sbin/dtrace -s
#pragma D option quiet
dtrace:::BEGIN { printf("Starting... use Ctrl + c to stop\n"); }
dtrace:::END { printf("Ending...\n" ); }
/ Script content below /
objc$target:::entry / arg0 == 0x7fa774600f90 /
{
printf("0x%016p %c[%s %s]\n", arg0, probefunc[0], probemod,
(string)&probefunc[1]);
}
看起來不錯! 再次執行將-g 選項移除後的這個命令:

(lldb) tobjectivec -p ‘arg0 == 0x7fa774600f90‘
繼續在LLDB中執行, 然後將你的指令碼粘貼在終端中.
在模擬器中按下home鍵(? + Shift + H) 或者來電狀態列 (? + Y).
這會提取出[UIApplication sharedApplication]執行個體調用的每一個Objective-C 方法.
哦, 輸出的內容看起來太多了是吧? 然後統計一下他們的數量!
回到Xcode中, 暫停執行並回到LLDB中:

(lldb) tobjectivec -g -p ‘arg0 == 0x7fa774600f90‘ -a ‘@[probefunc] =
count()‘
這將會產生下面的指令碼:

#!/usr/sbin/dtrace -s
#pragma D option quiet
dtrace:::BEGIN { printf("Starting... use Ctrl + c to stop\n"); }
dtrace:::END { printf("Ending...\n" ); }
/ Script content below /
objc$target:::entry / arg0 == 0x7fa774600f90 /
{
@[probefunc] = count()
}
你懂這個練習.重新運行上面的沒有-g選項的tobjectivec 命令, 然後將你剪下板的內容粘貼到終端中然後在LLDB中繼續執行.終端中還不會顯示任何內容. 但是 DTrace 暗中統計了UIApplication執行個體調用的每一個方法.
在模擬器中移動 以擷取UIApplication調用的大量方法 . 當你用Ctrl + c殺掉DTrace指令碼的時候, DTrace將會提取出UIApplication執行個體調用的所有的Objective-C方法.

其他的DTrace方法

這裡有一些其他方法在你閒置時候可以嘗試一下:
追蹤所有對象的所有初始化方法:

(lldb) tobjectivec -f ?init*
檢測進程中通訊相關的邏輯(比如, Webviews, keyboards, 等等):

(lldb) tobjectivec -m NSXPC*
列印出在你iOS裝置上處理開始觸摸事件的UIControl 的子類 :

(lldb) tobjectivec -m UIControl -f -touchesBegan?withEvent?
我們為什麼要學習這些?

這裡所講的只是DTrace的冰山一角. DTrace還能做許多事情.
我推薦你看一下下面的網站它們有許多學習DTrace非常好的資源.
? https://www.bignerdranch.com/blog/hooked-on-dtrace-part-1/
? https://www.objc.io/issues/19-debugging/dtrace/
在下一章中, 你會深入學習DTrace 以及瀏覽和剖析Swift代碼.

Advanced+Apple+Debugging(15)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.