原文:How To Create an Xcode Plugin: Part 2/3
原作者:Derek Selander
譯者:@yohunl
譯者注:原文使用的是xcode6.3.2,我翻譯的時候,使用的是xcode7.2.1,經過驗證,本部分中說的依然是有效的.在文中你可以學習到一系列的技能,非常值得一看.這些技能不單單只是用來創建插件,對你平時的調試等,也有非常大的幫助.
歡迎你來到創建xcode插件教程的第二部分.在第一部分中,你已經了解了怎麼通過NSNotification來窺探xcode的內部類,並且代碼注入了其私有類DVTBezelAlertPanel,並且,你還添加了一個NSMenuItem菜單到xcode的菜單欄裡,通過它來持久化是否開啟我們自定義的插件Rayrolling的功能
在這個教程中,你會在第一部分中創建的Rayrolling的基礎上繼續向前,如果你並沒有在這之前閱讀第一部分或者你想從本處開始,你可以下載第一部分完成的demo.本章中,你將會通過深入了解一些可用的工具來進一步探索Xcode,利用你將會學到的新知識,來修改Xcode的標題欄,使標題欄改為展示Ray的某首歌的歌詞。
開始啦
打開Xcode和Terminal(終端),並且將它倆都平鋪在桌面上,這樣你可以同時看到它們,方便後續的操作.(yohunl注:後文將會一面在Xcode上點擊,一面觀察終端控制台的輸出,一旦發現我們需要的輸出,就要立馬停止終端控制台,所以,平鋪在桌面上才便於你操作,否則,你就忙不過來啦)
因為從上一章,你已經學會了從xcode運行一個xcode實例來創建插件和調試插件,所以我們換一種新的方式:你將會直接通過終端命令來使用LLDB來探索Xcode,你不用再像第一部分那樣從一個Xcode啟動另一個調試用的Xcode實例來探索Xcode是怎樣工作的了.
LLDB的BFF和DTrace
DTrace是最好的探索Xcode的工具之一,它是一款相當優秀的調試工具,它是Instruments的基石.只要你了解怎麼使用它,你將會發現它是非常有用的工具.
首先,為了對其有個了解,我們需要一個使用DTrace的demo,就如同我們學習一門新語言時候,總是會通過一個Hello Worlddemo一樣.那麼開始吧… 創建一個腳本,它是用來保持所有以IDE開頭的類的運行計數,並在每次你調用該特定類方法或者實例方法時,增加計數.在你退出腳本時候,DTrace將會轉儲這些數據.
運行Xcode,然後,在Terminal的一個新窗口中,輸入如下的腳本:
sudo dtrace -n 'objc$target:IDE*::entry { @[probemod] = count(); }' -p `pgrep -xo Xcode`
雖然你鍵入命令後並不會看到任何的輸出,但是DTrace已經在後台默默的生成了所有方法調用的蹤跡(trace).回到Xcode,隨便嘗試一些操作.打開某些文件,隨便做一些點擊操作.回到Terminal,然後按Ctrl+C來結束腳本,腳本產生的內容將會輸出到Terminal上 (只有在你Ctrl + C終止腳本後,才能在Terminal上看到腳本輸出的內容)
相當酷吧:),其實使用Dtrace,你可以做到很多事,但是我們的教程不會覆蓋你想了解的DTrace的全部,相反的,我們提供一個快速的Dtrace語法的概述,它將會幫助你開始:
1.Probe Description(探頭說明,前序描述):由提供者(Provider),模塊(Module),功能(Function),名字(Name)組成,它們由冒號分隔.省略它們中的任何一個,將會使Probe Description包含所有的匹配項.你可以使用 * 和 ? 操作符來做模式匹配.
Provider:這個包含了一系列的類和方法,例如profile,fbt,或者io.在我們的教程中,主要使用objc來hookObjective-C的方法調用.
Module:在OC語言中,這部分指示的是你希望觀察的指定的類名
Function:指示你希望觀察的特定方法名
Name:雖然根據你的Provider的不同,有不同的name值可選,但是你在此只需要使用entry或者retrun,來匹配函數的開始或者結束.
你要了解,你可以使用$target關鍵字來匹配進程ID,也可以使用p或者c標志來指定target.
2.Predicate(謂詞部分): 可選的表達式,它是用來過濾哪些動作是滿足條件的.你可以把它當做一個if語句的條件部分.
3.Action(動作): 將要執行的操作.它可以只是簡單的輸出一些內容到控制台,也可以是執行一個高級功能.
就如同LLDB的命令image lookup -rn {Regex Query}一樣,你也可以使用l標志來轉儲(dump)特定進程中的類和方法.
為了演示這點,我們來看這樣一個簡單的例子,運行Safari,然後在終端輸入以下腳本
sudo dtrace -ln 'objc$target::-*ecret*:entry' -p `pgrep -xn Safari`
上述的DTrace腳本會打印出所有的名字中包含ecret字符串的實例方法.因為Probe Description部分 提供了Name參數entry,所有的方法都有entry和return,所以基本上忽略了所有的重復查詢請求.
注意:如果你想掌握更多的Dtarce知識,你可以參考Big Nerd的文章和 這本Dtrace書籍.如果並沒有理解所有的這篇快速介紹中介紹的內容,也不需要沮喪,因為Dtrace是一個相當復雜的工具.
現在你了解到Dtace的最基本的內容,我們可以開始使用它來尋找我們感興趣的NSViews了.因為Xcode含有大量的Views,你會發現,使用LLDB來試圖找出它們,讓你不堪重負.即使結合LLDB的條件斷點,調試一些應用程序都包含的共同東西,也是一個丑陋的過程.
幸運的是,使用Dtrace,將能帶給你對付此類問題的巨大的幫助.你將會使用DTrace來找出Xcode中組成標題欄的視圖(NSViews).(⊙o⊙)…….那麼該怎麼做呢?
這是一種方法:當鼠標停止在,或者點擊一個NSView,hitTest:方法將會被觸發,這個方法將會返回在這個鼠標點下最深的子視圖.你可以使用DTrace來順著hitTest:方法來確定視圖,那個你應該進一步探索其其父視圖和子視圖的視圖.
在終端中輸入以下腳本:
sudo dtrace -qn 'objc$target:NSView:-hitTest?:return /arg1 != 0/ { printf("UIView: 0x%x\n", arg1); }' -p `pgrep -xo Xcode`
一旦這段腳本運行,請確定你的Xcode是第一響應者(通過在Xcode上任何地方點擊一下即可成為第一響應者).在Xcode的窗口上移動你的鼠標指針,當你停止移動鼠標指針的時候,Dtrace將會輸出一個內存地址多次,這是因為 hitTest: 被在視圖層次上的多個視圖調用.
定位到Xcode的標題欄,點擊標題欄.選擇出現在終端中最近的地址,拷貝到你的粘貼板中
打開一個新的終端窗口標簽,運行LLDB,並將其附著到Xcode上,然後輸出從DTrace中拷貝出的地址,像以下這樣操作:
> lldb (lldb) pro attach -n Xcode ... (lldb) po {上一步拷貝到粘貼板中的對象地址,例如是0x7fcc2687cd40,則該命令是 po 0x7fcc2687cd40} ...
其中,pro 是 progress的縮寫,LLDB使用的是前序匹配原則,在不引起歧義的情況下 process,proc,proce都是可以的.在下面你還會看到 pro at -n Xcode這種寫法,都是因為這個原則,都是等效的. pro attach -n Xcode ,使用attach命令直接在LLDB中把一個正在運行的進程連接到LLDB中,以便於進行動態調試.也就是說這個命令直接可以將LLDB添加到任意的其它不是由它自己創建的進程中!!
你將會看到和以下相似的輸出:
注意:現在,Xcode被我們通過終端輸入的LLDB命令所暫停了.你可以在任何時候通過輸入process interrupt或者其縮寫pro i來暫停Xcode,從而能夠開始調試Xcode.另外,你也可以通過在任何時候輸入continue或者縮寫c來恢復Xcode的運行.請確定你總是了解附著於Xcode上的LLDB的狀態,你可以認為,當Xcode被你的調試LLDB所暫停時候,它是不能夠相應你的單擊等操作的(如同你在Xcode中打了斷點,調試的時候進入到斷點一樣)
取決於你在Xcode中點擊的地方,你可能會命中一系列的視圖.探索這些內存地址的父視圖(通過在lldb中 po [對象地址 supview])或者子視圖(po [對象地址 subvies]),直到你得到了視圖 IDEActivityView.
一旦你找到了IDEActivityView,怎樣確定這個視圖就是你所希望找尋的視圖呢?
通過在LLDB中輸入以下命令:
(lldb) po [{找到的IDEActivityView實例的地址} setHidden:YES] 在我的機子上是 po [0x7fcc2686b3c0 setHidden:YES] ...這裡的點表示我們省略了lldb的某些不重要的輸出. (lldb) c ...
如果是我們所期望的那個標題視圖,那麼現在Xcode的標題欄就被隱藏了,這就能證實它的確是我們所期望的標題欄的視圖啦.
通過如下命令來重新顯示它:
(lldb) pro i (lldb) po [{找到的IDEActivityView實例的地址} setHidden:NO] 比如在我的機子上是 po [0x7fcc2686b3c0 setHidden:NO] (lldb) c
以下這個圖是上述在LLDB中輸入命令的gif動畫,供你參考:
按照以往的經驗,你知道,當你Build或者Stop一個Xcode工程的時候,標題欄視圖的內容將會變化.你可以用DTrace來觀察相關的函數.IDEActivity前綴是一個獨特的命名約定,你可以觀察所有的以IDEActivity開頭的類,以此來觀察在這幕後的所有相聯系的事情.
回到終端,通過Control + C來停止DTrace,然後輸入以下命令到終端中:
sudo dtrace -qn 'objc$target:IDEActivity*::entry { printf("%c[%s %s]\n", probefunc[0], probemod, (string)&probefunc[1]); @num[probemod] = count(); }' -p `pgrep -xn Xcode`
這段腳本將會輸出所有類名前綴是IDEActivity的所有方法調用.一旦你停止這個腳本命令(Control+C),它還會輸出指定類方法的被調用次數.
yohunl備注: 當你輸入這個命令的時候,可能會遇到譯者遇到的這種情況
yohunldeMacBook-Pro:~ yohunl$ sudo dtrace -qn 'objc$target:IDEActivity*::entry { printf("%c[%s %s]\n", probefunc[0], probemod, (string)&probefunc[1]); @num[probemod] = count(); }' -p `pgrep -xn Xcode`
dtrace: invalid probe specifier objc$target:IDEActivity*::entry { printf("%c[%s %s]\n", probefunc[0], probemod, (string)&probefunc[1]); @num[probemod] = count(); }: probe description objc50360:IDEActivity*::entry does not match any probes
當遇到這種情況不要擔心,不要茫然,你只要完全退出Xcode,再重新啟動Xcode,再輸入這個命令就好了.
開始這個DTrace命令,回到Xcode中,隨便打開一個工程,編譯並運行,然後再停止.注意觀察Xcode的標題欄的內容變化,然後停止Dtrace命令,查看結果:
仔細分析上面輸出的信息.IDEActivityView和其它類之間是怎麼運轉的都在控制台的輸出信息中,但是也有大量的其它信息夾雜在其中(控制台的輸出信息中),不是麼?
幸運的是,你可以有選擇的限制展示給你的信息量.通過浏覽相關的類,來確定是否有值得你進一步選擇性探索的內容….,也許 IDEActivityReport* 就是一個不錯的候選類,因為以它開頭的類看起來就是有聯系的(在我們上述的操作中,最後在DTrace看到的很多都是它,所以我們有理由覺得它就是有聯系的).
修改Dtrace腳本,使他看起來如下:
sudo dtrace -qn 'objc$target:IDEActivityReport*::entry { printf("%c[%s %s]\n", probefunc[0], probemod, (string)&probefunc[1]); @num[probemod] = count(); }' -p `pgrep -xn Xcode`
再運行,停止Xcode的動作的同時,密切關注控制台的輸出信息.一旦停止Xcode,立馬Control+C停止掉Dtrace的腳本,是否出現了一些類,看起來是值得我們進一步去探索的呢?
IDEActivityReportStringSegment看起來比較值得探索.縮小我們的腳本的搜索范圍,聚焦到這個類上來,注意以下命令的變化,注意Probe的Function部分的變化(我們上面分析了Dtrace命令的組成,其中Probe部分有個Function組成部分)
sudo dtrace -qn 'objc$target:IDEActivityReportStringSegment::entry { printf("%c[%s %s]\n", probefunc[0], probemod, (string)&probefunc[1]); @num[probefunc] = count(); }' -p `pgrep -xn Xcode`
通過編譯,運行,停止Xcode工程;再一次停止Dtrace命令,觀察IDEActivityReportStringSegment類的實例的方法各自被調用的次數(當Control+C停止命令後,在控制台會輸出各個方法的調用次數).看起來,initWithString:priority:frontSeparator:backSeparator: 和 initWithString:priority:值得進一步分析!
打開一個新的LLDB會話(打開一個終端標簽頁面,lldb進入LLDB),然後再運行如下命令:
(lldb) pro attach -n Xcode (lldb) rb 'IDEActivityReportStringSegment\ initWithString' Breakpoint 9: 2 locations. (lldb) br command add Enter your debugger command(s). Type 'DONE' to end. > po NSLog(@"customidentifier %s %@", $rsi, $rdx) > c > DONE (lldb) c
在上面這段命令中,你添加了自定義的命令,當任何屬於IDEActivityReportStringSegment類的以initWithString方法開頭的方法被調用時,就會執行添加的自定義命令.它輸出IDEActivityReportStringSegment實例的方法的 Selector 和 self (還記得教程一中提到的各個寄存器中存放的內容了吧)到控制台.
另外,使用了customidentifier 來標識 NSLog的輸出信息,這樣你可以更容易的從控制台的輸出中容易的定位到這些Log.
回到終端,新建一個終端tab(使用快捷鍵 command + t ),建立一個customidentifier的grep尾搜索:
tail -f /var/log/system.log | grep customidentifier
譯者注,這句話輸入後,並沒有輸出,當你切換到Xcode中,編譯,運行,就產生輸出了
編譯,運行Xcode,以便讓Xcode產生IDEActivityReportStringSegment變化.下面的gif動畫,顯示了上面所說的所有你輸入到LLDB中的命令,看起來如下:
比較控制台的輸出和Xcode的標題欄的輸出,你可以明確的得到:它就是你要尋找的內容!!
到使用Swizzle的時候了
創建一個Objective-C的類NSObject的category,命名為Rayrolling IDEActivityReportStringSegment
添加下面的代碼到建立的文件 NSObject+Rayrolling_IDEActivityReportStringSegment.m:中:
#import "NSObject+Rayrolling_IDEActivityReportStringSegment.h" #import "NSObject+MethodSwizzler.h" #import "Rayrolling.h" @interface NSObject () // 1 - (id)initWithString:(id)arg1 priority:(double)arg2 frontSeparator:(id)arg3 backSeparator:(id)arg4; @end @implementation NSObject (Rayrolling_IDEActivityReportStringSegment) // 2 + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [NSClassFromString(@"IDEActivityReportStringSegment") swizzleWithOriginalSelector:@selector(initWithString:priority:frontSeparator:backSeparator:) swizzledSelector:@selector(Rayrolling_initWithString:priority:frontSeparator:backSeparator:) isClassMethod:NO]; }); } - (id)Rayrolling_initWithString:(NSString *)string priority:(double)priority frontSeparator:(id)frontSeparator backSeparator:(id)backSeparator { // 3 static NSArray *lyrics; // 4 static NSInteger index = 0; static dispatch_once_t onceToken; // 5 dispatch_once(&onceToken, ^{ lyrics = @[@"Never gonna live you up.", @"Never gonna set you down."]; }); // 6 index = index >= lyrics.count -1 ? 0 : index + 1; // 7 if ([Rayrolling isEnabled]) { return [self Rayrolling_initWithString:lyrics[index] priority:priority frontSeparator:frontSeparator backSeparator:backSeparator]; } return [self Rayrolling_initWithString:string priority:priority frontSeparator:frontSeparator backSeparator:backSeparator]; } @end
下面是對上面代碼的一些解釋:
你需要前向聲明私有類的初始化方法initWithString:priority:frontSeparator:backSeparator:,否則編譯器直接就讓你通過不了.
load是通常被用來初始化添加swizzle代碼的地方.
因為你使用一個category來交換方法,在category中建立實例變量是稍微有些復雜的.你可以通過associated objects給一個已存在的類添加實例變量,但這個是你自己應該掌握的內容.在這裡,我們直接使用一個static NSArray來保持一個對象繼續存在,即使它所在的方法已經執行完畢了.
您可以使用相同的static伎倆,使得index也如此。
使用dispatch_once初始化歌詞NSArray
遞增index,如果越界了,就重置它
檢測插件是否是可用狀態,如果是,那就更改string參數,否則,使用默認的參數
正如你所看到的,使用正確的工具可以幫助你快速定位你想要的東西.然而,要達到這個目的,卻有不止一種方法 - 你將會發現,通過堆(heap)分析照樣可以定位到你所感興趣的內容
通過堆(heap)來重新尋找IDEActivityView
在本節中,你將會重新搜尋Xcode的IDEActivityView和IDEActivityReportStringSegment,你將會使用一種略微不同的方式來達到這一目的.
到目前為止,你都是通過自上而下的方式來進行定位的:你先找到一個可能的class,然後以某種方式在內存中尋找它的一個實例對象,再查找它的成員方法和屬性.其實,以相反的方向來進行,似乎也是可行的,就是沿著參考鏈從下而上去尋找目標對象,直到找到它.
很幸運,Xcode中附帶了一些Python腳本,讓你能夠做到這點!
結束掉終端中所有的已存在的LLDB和Dtrace的會話,然後重啟Xcode.開始一個新的Xcode和LLDB會話,再一次執行LLDB附著到Xcode上的命令,然後,添加下面的命令腳本:
(lldb) pro at -n Xcode (lldb) command script import lldb.macosx.heap "malloc_info", "ptr_refs", "cstr_refs", and "objc_refs" commands have been installed, use the "--help" options on these commands for detailed help.
這個腳本加載了一系列非常有用的方法到LLDB進程中.
malloc_info: 展示當前堆得對象的詳細信息.尤其是結合MallocStackLogging環境變量的時候及其有用!
ptr_refs: 給它一個堆空間中的一個內存地址,它可以找到指向這個內存地址的其它對象.
cstr_refs: 參數類型是char * ,查找它在內存中出現的地方.
objc_refs: 找到內存中指定的類的實例
你可能想先了解了解最容易的objc_refs.
在LLDB中,通過以下腳本來找尋所有的IDEActivityView類的實例變量:
(lldb) objc_refs -o IDEActivityView Searching for all instances of classes or subclasses of "IDEActivityView" (isa=0x10fffe650) 0x00007faaee9cbf30: malloc( 304) -> 0x7faaee9cbf30 IDEActivityView.DVTLayerHostingView.NSView.NSResponder.NSObject.isa [IDEActivityView: 0x7faaee9cbf30](因識別問題,這裡將尖括號替換為方括號)
這個腳本輸出了內存中所有的IDEActivityView類的實例.其中的 -o 參數表示輸出對象,也就是會調用對象的 description 方法
通過這個簡單地額腳本,你就可以在內存中找屬於指定類的的實例對象.這招也適用於繼承於該對象的子類.
yohunl備注:很遺憾,在我電腦上,這句怎麼都查詢不到
(lldb) objc_refs -o IDEActivityView error: libarclite_macosx.a(arclite.o) failed to load objfile for /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc/libarclite_macosx.a error: libarclite_macosx.a(arclite.o) failed to load objfile for /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc/libarclite_macosx.a Searching for all instances of classes or subclasses of "IDEActivityView" (isa=0x10ada9328)
試了很多次,都沒能得到所期望的東西,但我相信這個方法是可以的
通過上一步 objc_refs發現的內存地址,你可以采用如下的腳本來展開一個IDEActivityView實例的成員變量:
(lldb) malloc_info -t 0x7faaee9cbf30 0x00007faaee9cbf30: malloc( 304) -> 0x7faaee9cbf30 IDEActivityView.DVTLayerHostingView.NSView.NSResponder.NSObject.isa (IDEActivityView) *addr = { DVTLayerHostingView = { NSView = { NSResponder = { ...
這還不是它能做到的全部!你甚至可以用它,通過指定的內存地址來獲得其它對象的引用:
(lldb) ptr_refs 0x7faaee9cbf30 0x00007faaeed9a308: malloc( 16) -> 0x7faaeed9a300 + 8 0x00007faaeedb4148: malloc( 176) -> 0x7faaeedb4140 + 8 IDEActivityViewBackgroundButton.NSButton.NSControl.NSView.NSResponder._nextResponder 0x00007faaeedb4190: malloc( 176) -> 0x7faaeedb4140 + 80 IDEActivityViewBackgroundButton.NSButton.NSControl._aux.NSObject.isa 0x00007faaeedb4928: malloc( 160) -> 0x7faaeedb4920 + 8 NSView.NSResponder._nextResponder ...
注意:這些命令,如果結合 im loo -rn {classmethod regex} 方式來查詢,甚至可以幫助你快速的定位到,並且探查到在內存中存活的該類的所有實例.當然了,嘗試之前,請確定你的LLDB還是暫停狀態,否則的話,這些命令是不會起作用的.
你設置可以更進一步,有一個環境變量MallocStackLogging,它可以在你初始化任何一個對象的時候用於棧(stack)回溯.雖然很多引用都可以指向同一個對象,但是它可以指出來,哪個才是這個對象的”最終擁有者”. 這段還要再分析一下
由於上文的LLDB已經附著(attach)在xcode上,所以你要先殺掉Xcode進程,再通過帶上MallocStackLogging環境參數的LLDB來重啟它:
(lldb) pro kill Process 65196 exited with status = 9 (0x00000009) (lldb) pro launch -v MallocStackLogging=1 -n Process 67920 launched: '/Applications/Xcode.app/Contents/MacOS/Xcode' (x86_64)
注意:如果你比較懶,不斷的殺掉LLDB/Xcode會話,launchctl是一個更加方便的啟用環境變量MallocStackLogging的方式:
launchctl setenv MallocStackLogging 1
這個命令將通過launch創建的每一個進程的MallocStackLogging都設置成了true.所以你要記得,通過如下的命令來移除它們:
launchctl unsetenv MallocStackLogging
為了測試上面的內容,首先,確保你的Xcode的Edit菜單下的菜單項是 如下圖所示的 Enable Rayrolling :
現在,打開任何一個Xcode工程,保證源碼編輯窗口是打開的,編譯,運行工程.伴隨著工程的運行,你將會看到在編譯完成和運行完成的時候,IDEActivityView(假裝你不知道此時此刻,誰調用它)改變它的內容.
暫停執行這個Xcode工程,如果需要,使用下面的命令來重新加載LLDB的heap(堆)方法集:
(lldb) pro i (lldb) command script import lldb.macosx.heap "malloc_info", "ptr_refs", "cstr_refs", and "objc_refs" commands have been installed, use the "--help" options on these commands for detailed help.
提醒:如果你對多次輸入這個命令感到厭煩,你可以去網上了解下怎麼在 ~/.lldbinit 方法中建立 command aliases (命令的別名,或者縮寫).這個在 ~/.lldbinit 文件的內容,在每次創建一個LLDB的會話實例的時候,都會被加載.
現在,使用cstr_ref方法,給它傳遞Xcode標題欄的字符串內容.舉個例子,如果這個工程的名字叫Rayrolling,那麼你的Xcode的標題欄上顯示的應該是諸如:”Running Rayrolling : Rayrolling:”此類的字符串:
(lldb) cstr_ref "Running Rayrolling : Rayrolling" 0x0000000118cb21d0: malloc( 32) -> 0x118cb21d0 0x000000012079ae21: malloc( 48) -> 0x12079ae10 + 17 __NSCFString1 bytes after __NSCFString 0x00000001207a3bb3: malloc( 64) -> 0x1207a3b90 + 35 __NSCFString19 bytes after __NSCFString 0x00000001223834d1: malloc( 48) -> 0x1223834c0 + 17 __NSCFString1 bytes after __NSCFString 0x000000011f9126a1: malloc( 48) -> 0x11f912690 + 17 __NSCFString1 bytes after __NSCFString
正如你所看到的那樣,主要的參考信息一般都是 NSString類型的參考,多數情況下,你並不能從字符串本身得到更多的信息.所以你可能想了解這樣字符串是怎樣被創建的?因為有MallocStackLogging,做到這點,相當容易!
(lldb) cstr_ref "Running Rayrolling : Rayrolling" -s
這個腳本輸出堆棧幀的信息,當這個char * 被創建的時候.
通過所有的堆棧信息去跟蹤實例變量,由於他們都是在內存中,內存地址的數量和其在內存中的相對位置都有可能不同,這些都取決於你和Xcode的互動方式的不同而不同.
通過閱讀分析堆棧的跟蹤信息,你將會獲得一組類,這些類就是負責Xcode中這方面的.
舉例來說,取決於你運行的這些LLDB查詢命令,你可能會看到這些class,如: IDEActivityScrollingTextLayer , IDERunOperation , IDEActivityView 等等諸如此類的class.
你還可以使用 ptr_ref 來作用於 cstr_ref 輸出的信息,來看那些對象保留了這個string對象的引用.
繁雜的隨機探索技巧
在Xcode中尋找正確的api是很復雜的,因為你是想在成千上萬的類和方法中尋找符合條件的幾個類而已.
使用LLDB的正則表達式,可以幫助你更好的找到它們.某些時候,最好的獲取信息的地方是代碼中使用的單例(Singleton),查看Xcode中存在的單例,看看是否值得進一步深入的單例.
當LLDB附著到Xcode,並且處於暫停狀態時候,輸入以下命令:
(lldb) i loo -rn "\+\[IDE.*\ shared"
這條命令將會查找以IDE開頭的類名,並且以”shared”開頭的類方法.這條命令等價於 target modules lookup -rn "\+\[IDE.*\ shared",-r是指後面的是正則表達式
另外一個吸引力的替代方案是:
(lldb) i loo -rn "\+\[[A-Za-z]*\ [A-Za-z]*\]"
它將會輸出所有的沒有參數的類方法.
正如你前面看到的,你可以很容易的找出你剛興趣的類,通過給一個名字給上面提到的Python腳本,就可以搜索到.另外還有一個流行的工具,可以用來搜索類和類方法,這個工具的名字叫class-dump,它可以在 這裡下載.它是LLDB命令im loo -rn {regex} 的另一種替代方式,因為它(Class-dump)可以輸出更加整潔,清晰的頭文件形式的輸出.
當然了,class-dump也有小缺點,它的缺點就是你必須限制你的搜索在指定的framework或者plugin中,而LLDB的image lookup 命令搜索的是整個的Xcode進程中的framework和plugin!也就是image lookup的搜索范圍更大,更廣.
下一步該做什麼?
這裡是第二部分最後完成的domo工程.
在這個教程中,你學習到了最基本的DTrace技能,並且學習和使用了一些你可以得到的高級LLDB方法.
如果你想了解更多的關於DTrace的知識,你可以閱讀另一篇obcj.io上的優秀文章,還有就是官方的 DTrace文檔
在本系列教程的第三部分,我們將再一次關注 匯編,你將會學到另一種靈巧的工具,它的名字叫 Cycript.