標籤:lldb
這一章將更多的將會作為Dtrace的抓包原理, 毀滅動作(耶!), 以及如何用Swift使用Dtrace. 在進入理論之前我首先會告訴你一興奮的東西.我將會首先講解如何用Swift使用Dtrace然後進到讓你眼淚汪汪想要入睡的概念中. 相信我, 這會很有趣!
在這一章節, 你將會學些DTrace剖析代碼的其他方式, 以及如何在不動可執行檔一根手指頭的情況下增強已經存在的代碼.
神奇吧!
開始
我們沒有在Ray Wenderlich上摘抄. 本章節中包含的還有另外一個用穿插有Ray的名字源自電影主題靈感的項目.
開啟starter目錄下的Finding Ray程式.不需要做任何特殊的設定.只需要用iPhone 7Plus模擬器構建並運行就可以了.
這個項目主要是用swift寫的, 儘管swift的許多子類繼承自NSObject.因為它用到了很多UIKit裡的組件, 而UIKit裡面仍然有很多Objective-C代碼.
Dtrace不知道swift代碼繼承自哪個類, 因為在Dtrace看來他們都是一樣的. 你仍然可以通過swift代碼剖析Objective-C代碼的代碼, 因為它們都通過objc$target繼承自NSObject. 在底層看來只是swift的類有沒有實現新的方法或者重寫了父類的方法, 你在任何Objective-C的探針中都看不到他們.
Dtrace和Swift的理論
讓我們討論一下如何使用DTrace剖析Swfit的代碼. 網上有些不錯的意見值得考慮一下.
首先, 好訊息是: swift可以很好的相容DTrace模組!也就是說 可以輕鬆的過濾出特定模組中的swift代碼. 這個模組可能會成為你Xcode中包含swift代碼的的target的名字(除非你再Xcode的構建設定中改變了target的名字).
這就意味著你可以在SomeTarget模組中過濾出下面的swift代碼的實現.
pid$target:SomeTarget::entry
這會在SomeTarget模組中每一個函數實現署名開始的地方建立一個探針.因為pid$target在所有非Objective-C代碼的後面, 所以這些探針也同樣會捕獲到C或C++的代碼, 但是在第二部分你會看到我們可以用設計的很好的查詢語句很輕鬆的過濾掉.
現在來說一下壞訊息. 因為關於模組的資訊已經拿掉了, 所以swift方法的類名和函數名都在Dtrace section(也就是probefunc)裡. 這就意味著在你需要在DTrace查詢代碼裡更有創造性一點.
此外, swift函數的名字是被打亂了的. 這就意味著你需要將Dtrace的輸出轉接到一個可以將被打亂的swift函數名重新整理的命令上以便更容易理解一點.
幸運的是, swift中有一個非常好的終端命令叫做swift-demangle, 我們可以在這裡找到它:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift-demangle
通過終端設定下面的符號連結以便你不需要每次使用這個命令重組swift函數名的時候都將這個path輸入一遍.
ln -s /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift-demangle /usr/local/bin/
將路徑添加到/usr/local/bin/, 你只需要輸入swift-demangle就可以輕鬆的使用這個命令:
注意: 你也可以通過xcrun swift-demangle運行這個命令, 但是在用多個swift探針剖析Dtrace代碼的時候效能開銷就需要特別注意下. 在通過swift使用Dtrace的時候這個符號連結就可以最大程度的解決這個問題.
我們來看一個通過swift使用Dtrace探針的例子.
想象一下你有一個UIViewController的子類ViewController, 它只重寫了viewDidLoad方法. 就像下面這樣:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
如果你想在這個方法上建立一個斷點, 這個斷點全名應該是下面這個樣子:
SomeTarget.ViewController.viewDidLoad () -> ()
不要驚訝. 你已經將第一部分的概念擊敗了. 但是被打亂之後的函數名是什麼樣子的呢? 這就是高效的c函數返回的swift函數. 他看起來就像下面這個樣子:
_TToFC10SomeTarget14ViewController11viewDidLoadfTT
如果你不相信我, 你可以在終端中試一下下面的代碼:
$ echo "_TToFC10SomeTarget14ViewController11viewDidLoadfTT" | xcrunswift-demangle
你將會得到下面的輸出:
@objc SomeTarget.ViewController.viewDidLoad () -> ()
意外吧!再次得到了打亂之前的函數名.
打亂之後的名字就是DTrace在swift探針中看到名字.如果你想搜尋SomeTargettarget(易記的名字, 對吧?)中每一個用swift實現的viewDidLoad(), 你可以建立一個像下面這樣的探針:
pid$target:SomeTarget:SomeTargetviewDidLoad*:entry
這句話很高效, 它的意思是說"只要SomeTarget和viewDidLoad在函數區, 就給我這個探針."
是時候在Finding Ray程式中將理論付諸實踐了.
Dtrace和Swift 練習
如果Finding Ray應用程式還沒有運行起來, 那就用iPhone 7 Plus模擬器讓它跑起來.
新開一個終端視窗然後輸入下面命令:
sudo dtrace -n ‘pid$target:Finding?Ray::entry‘ -p pgrep "Finding Ray"
我有意選擇了一個名字中包含空格的項目. 以便提醒你需要在使用Dtrace指令碼的時候需要解決Xcode target中的空格問題.probemod部分使用了一個?作為一個空格的萬用字元.此外, 在pgrep進程名字的時候你需要用符號包裹一下語句, 否則句子是不生效的.
在你輸入你的密碼之後, 在Finding Ray模組中你總共會得到240個非Objective-C函數的探針.
在模擬器上點擊和拖拽然後注意觀察終端中捕獲到的方法.
圖片.png
可惡!名字沒打亂了. 幸運的是你可以用前面看到的swift-demangle命令輕鬆的解決這個問題.
用Ctrl + C殺掉Dtrace指令碼, 讓後在swift-demangle後面接上一個管道, 看起來應該是下面這個樣子:
sudo dtrace -n ‘pid$target:Finding?Ray::entry‘ -p pgrep "Finding Ray"
|swift-demangle -simplified
確保這個新的Drace指令碼是可以啟動並執行, 然後在模擬器中拖拽Ray Wenderlich並且注意查看終端中的輸出.
殺掉Dtrace指令碼然後用下面的命令替代它:
sudo dtrace -qn ‘pid$target:Finding?Ray::entry { printf("%s\n",probefunc); } ‘ -p pgrep "Finding Ray"
| swift-demangle -simplified
它是很狡猾的, 但是你已經添加了-q(或者 --quiet)選項. 這將會告訴DTrace不要顯示你發現的探針的序號, 當一個探針被處罰的時候也不要顯示預設輸出. 幸運的是, 你也添加了一個printf語句來手動的輸出probefunc.
等待Dtrace啟動完成, 然後再次拖拽.
很有趣吧. 不幸的是, 你仍然會得到一些我沒有寫出來的swift編譯器產生的方法. 你不想看見swift編譯器產生的任何方法.你只想看見我在swfit類中寫的代碼.
殺掉之前的DTrace指令碼然後將探針的描述補充為只包含你實現的代碼, 而不包含swfit編譯器的代碼:
sudo dtrace -qn ‘pid$target:Finding?Ray:Finding?Ray:entry
{ printf("%s\n", probefunc); } ‘ -p pgrep "Finding Ray"
| swift-
demangle -simplified
編譯運行然後拖轉Ray. 注意到了不同之處嗎?
這裡你已經為函數添加了Finding?Ray.正如你在斷點中知道的, 這個模組中包含所有的swift函數.這裡過濾掉了任何一個編譯器產生的swift方法, 過濾的非常非常乾淨.
根據我們之前的設想, 你已經將之前設想的對象的指標輸出了出來, 正如在前面的章節中tobjectivec.py所做的那樣.
在終端中殺掉這個指令碼, 然後輸入下面的內容:
sudo dtrace -qn ‘pid$target:Finding?Ray:Finding?Ray:entry
{ printf("0x%p %s\n", arg0, probefunc); } ‘ -p pgrep "Finding Ray"
|
swift-demangle -simplified
運行它然後拖拽Ray. 你將會得到一些類似下面的輸出:
0x7f8b17d02e40 MotionView.transformAmount.getter
0x7f8b17d02e40 MotionView.motionAmount.getter
0x7f8b17d02280 type metadata accessor for MotionView
如果你通過LLDB暫停執行Finding Ray可執行檔並且複製, 粘貼, 然後po其中的一個地址, 你將會範縣這個地址引用著一個有效對象.
最後加一點. 將指令碼中列印指標的代碼移除掉, 用追蹤Swift函數入口和出口來替換, 然後使用DTrace的flowindent在顯示出一個函數相對於其他函數執行的位置 :
sudo dtrace -qFn ‘pid$target:Finding?Ray:Finding?Ray:r
{ printf("%s\n", probefunc); } ‘ -p pgrep "Finding Ray"
| swift-
demangle -simplified
在這裡有幾點需要注意的. 你已經為flowindent添加了-F選項. 檢查一下探針描述中的名字部分, r.這是幹什麼的呢?
從DTrace的角度來看, 進程中的大部分函數都有入口, 返回點和每一條彙編指令的函數位移. 這些位移是以十六進位的形式給出的.這就是說"給我所有包含r字母的名字." 這回返回探針描述的名字中的入口和返回點, 但是會最佳化任何函數位移因為彙編僅僅從f開始.很聰明, 對吧?
有了每一個可用的swift函數的入口和返回點, 你可以清除的看到什麼函數被執行了以及它們是從哪裡執行的. 等待DTrace開始, 然後為著Ray Wenderlich的臉部拖拽.你將會得到想下面這樣完美的輸出:
圖片.png
額.....你應該會踢出其中的一個!
DTrace變數和控制流程
現在你會學到一些在本章餘下部分需要使用的理論.
DTrace有幾種方法可以在你的指令碼中建立和引用變數. 在使用DTrace的時候它們在速度和便利性上各有利弊.
scalar variable (標量變數)
第一種建立變數的方法是使用scalar variable. 這些簡單的變數只能帶一些固定大小的資料.你不需要聲明scalar variables的類型, 或者你DTrace指令碼中其他變數的類型.我更傾向於在DTrace指令碼中將scalar variable作為一個Boolean值使用, 這是由於DTrace條件陳述式邏輯的限制--你只能用判斷句和三元運算子來真正的將邏輯分開.
例如, 這裡有一個運用scalar variable的典型例子:
#!/usr/sbin/dtrace -s
#pragma D option quiet
dtrace:::BEGIN
{
isSet = 0;
object = 0; }
objc$target:NSObject:-init:return / isSet == 0 /
{object = arg1;
isSet = 1; }
objc$target:::entry / isSet && object == arg0 /
{
printf("0x%p %c[%s %s]\n", arg0, probefunc[0], probemod,
(string)&probefunc[1]);
}
這個指令碼聲明了兩個scalar variables: isSet這個scalar variables會檢查和判斷object scalar variables是否被賦值. 如果沒有, 這個指令碼會將下一個object賦值給object變數. 這個指令碼將會追蹤所有使用object變數的Objective-C方法.
局部從句變數(Clause-local variables)
下一步是局部從句變數. 它們用this->將變數名放在右邊來表示並且可一直想任意類型, 包括char*類型. 局部從句變數在經過同樣探針的時候可以倖存下來.如果你嘗試通過一個不同的探針引用他們, 那是行不通的.例如, 看一下下面的代碼:
pid$target::objc_msgSend:entry
{
this->object = arg0;
}
pid$target::objc_msgSend:entry / this->object != 0 / {
/ Do some logic here /
}
obc$target:::entry {
this-f = this->object; / Won‘t work since different probe /
}
我傾向於盡我所能的粘貼從句局部變數, 因為這樣速度很快而且我不需要手動釋放他們, 就像我對下一種類型的變數所做的那樣...
線程-局部變數(Thread-local variables)
線程局部變數在運行速度上提供了很大的靈活性. 此外, 你還需要手動釋放它們, 不然會產生記憶體泄露.線程局部變數可以通過在變數名前面加上self->來訪問和使用.
線程局部變數的一個優點是他們可以被不同的探針使用, 就像下面這樣:
objc$target:NSObject:init:entry {
self->a = arg0;
}
objc$target::-dealloc:entry / arg0 == self->a / {
self->a = 0;
}
這會將初始化的對象賦值給self->a.當這個對象被釋放的時候, 你同樣需要通過將a設定為0手動的釋放剛才賦值給a的對象.
討論DTrace中的變數已經偏離了我們的主題, 讓我們來說一說如何用變數來執行條件陳述式.
DTrace 條件
DTrace內部的條件句極其有限. 在DTrace中沒有像if-else這樣的語句. 這是一個明智的選擇, 因為DTrace 指令碼設計的初衷就是方便快捷.
然而, 這並不代表著你想在特定的探針或者探針中包含的資訊上執行條件陳述式的時候會遇到問題. 圍繞這個問題, 這裡有兩個主要的方法可以讓你執行條件陳述式.
第一種方法是使用三元運算子.
看一下在Objective-C中的邏輯:
int b = 10;
int a = 0;
if (b == 10) {
a = 5;
} else {
a = 6; }
這個邏輯在DTrace中可以被重寫為:
b = 10;
a = 0;
a = b == 10 ? 5 : 6
這裡還有一個沒有else語句的例子:
int b = 10;
int a = 0;
if (b == 10) {
a++; }
在DTrace中的形式, 看起來就像下面這樣:
b = 10;
a = 0;
a = b == 10 ? a + 1 : a
另外一種解決方案是用多條DTrace語句來判斷一種情況.第一條語句會為第二條語句設定必要的資訊, 這要看第二條語句在判斷句中是否要執行一個動作.
我知道你已經忘記了這些DTrace組件中所有的術語因此我們來看一個例子吧.
例如, 如果你想追蹤一個函數中從開始到結束的每一個調用.通常情況下, 我會推薦你設定一個DTrace指令碼來捕獲每一次調用然後用LLDB來執行命令. 但是哪些是你只能在DTrace中做的事情呢?
在這個特定的例子中, 你想用下面的DTrace指令碼來追蹤所有-[UIViewController initWithNibName:bundle:]被執行時調用的方法:
#!/usr/sbin/dtrace -s
#pragma D option quiet
dtrace:::BEGIN
{
trace = 0;
}
objc$target:target:UIViewController:-initWithNibName?bundle?:entry {
trace = 1 }
objc$target:target:::entry / trace / {
printf("%s\n", probefunc);
}
objc$target:target:UIViewController:-initWithNibName?bundle?:return {
trace = 0 }
initWithNibName:bundle:一旦執行完畢, 追蹤變數就被設定了. 從此刻開始, 每個單一的Objective-C方法都會在initWithNibName:bundle:返回之後顯示.
在寫DTrace指令碼的時候首先不能使用煩人的迴圈語句和條件陳述式, 但是想一下你已經變成了習慣於腦筋急轉彎不依賴普通文法習慣的人.
到了頭討論另一個大問題的時候了:在你的DTrace指令碼中檢查進程的記憶體.
檢查進程記憶體
它也許是作為一個驚喜而來的, 但是你之前寫的DTrace指令碼是在它自己的核心中執行的.這就是為什麼他們的速度如此之快也是為什麼你在已經編譯過的程式中執行動態追蹤的時候不需要改變任何代碼的原因. 都是直接存取核心的!
DTrace有的探針遍布你的電腦. 這其中有核心中的探針, 也有使用者區的探針, 甚至還有使用fbt提供的用來描述核心和使用者區銜接的探針.
這裡有一幅圖形象的描述了你電腦中非常非常小的一部分探針.
圖片.png
將你的經理集中在成千上萬探針中的兩個一個是系統調用的open, 另一個是系統調用的open_nocancel.這兩函數都是是現在核心中的而且是用來負責開啟任何類型的檔案的,無論它是唯讀唯寫的還是既可以讀也可以寫的.
系統中open函數的聲明是下面這個樣子:
int open(const char *path, int oflag, ...);
本質上, open函數有時候會調用open_nocancel函數, open_nocancel函數的聲明是這樣的:
int open_nocancel(const char path, int flags, mode_t mode);
這兩個函數的第一個參數都是char型. 前面在DTrace的探針中你已經用arg0和arg1從函數中抓取到了參數. 你還沒有做的就是解引用這些指標來查看它們的資料. 就像前面的SBValue章節, 你可以用DTrace來查看記憶體, 甚至可以擷取系統調用的open函數中第一個參數的字串.
雖然你已經明白了. DTrace指令碼是在核心中執行的.argX參數已經給到你了, 但是這些是程式記憶體空間中值的指標.然而, DTrace是在核心中執行的. 因此你需要手動複製你在核心的記憶體空間中讀到的任何資料.
這是通過copyin和copyinstr函數實現的. copyin 帶了一個你想要讀取的資料的數量的地址, 同時copyinstr會複製一個char*.
就系統調用的open家族的函數來說, 你可以用下面的DTrace語句將第一個參數以string的形式讀取出來:
sudo dtrace -n ‘syscall::open:entry { printf("%s", copyinstr(arg0)); }‘
例如, 如果一個PID是12345的進程試圖開啟/Applications/SomeApp.app/, DTrace就可以使用copyinstr(arg0)讀取第一個參數.
圖片.png
在這個例子中, DTrace會讀到arg0中, 在這個例子中等同於0x7fff58034300. 在copyinstr函數中, 記憶體位址0x7fff58034300將會被解引用然後抓取到代表pathname的char*, "/Applications/SomeApp.app/".
open syscalls的運用
用你檢查進程記憶體的知識, 建立一個檢測系統調用open家族函數的DTrace指令碼. 在終端中輸入下面內容:
sudo dtrace -qn ‘syscall::open:entry { printf("%s opened %s\n",
execname, copyinstr(arg0)); ustack(); }‘
這將會順著程式在使用者區的堆棧記錄中調用的系統的open列印出open(或者open_nocancel)的內容.
DTrace很強大對吧?
將系統調用的open家族的函數集中在Finding Ray進程上.
sudo dtrace -qn ‘syscall::open:entry / execname == "Finding Ray" /
{ printf("%s opened %s\n", execname, copyinstr(arg0)); ustack(); }‘
注意: 你在終端中用DTrace執行的動作有時候會產生一些錯誤到stderr
. 根據這些錯誤, 你可用建立的DTrace判斷句檢查輸入的內容是否恰當來繞過這些錯誤, 或者你可以用少量的查詢探針來過濾你的探針描述.提示一點, 忽略所有DTrace通過2>/dev/null
添加的單命令列程式產生的錯誤. 這可以高效的告訴你的DTrace單命令列程式輸出的任何stderr
內容都會被忽略.我經常用這種方法解決那些容易出錯的探針, 但是忽略我追蹤時產生的任何錯誤.
重新構建並運行程式.
堆棧記錄現在將會只顯示在Finding Ray應用中的系統調用的任何open函數. 在模擬器中運行這個程式然後看一下你是否能讓他輸出一個內容!
通過paths過濾open syscalls
在Finding Ray項目中, 我記得我用Ray.png圖片做了一些事情, 但是我不記得是在那個位置了. 好訊息是我用DTrace沿著grep找到了Ray.png被開啟的位置.
殺掉你當前的DTrace指令碼然後添加一個grep查詢, 就像下面這個樣子:
sudo dtrace -qn ‘syscall::open*:entry / execname == "Finding Ray" /
{ printf("%s opened %s\n", execname, copyinstr(arg0)); ustack(); }‘ |
grep Ray.png -A40
這將所有的輸出嫁接到grep並且搜尋任何Ray.png圖片的引用.如果搜尋到了, 則列印出接下來的40行代碼.
注意:在你的電腦中的/usr/bin/
下有一個相當可怕的叫做opensnoop
的DTrace指令碼, 它有很多選項去檢測系統調用的open
家族的函數並且與寫的指令碼比起來用起來更簡單. 但是如果你只用現成的東西那麼你就學不到任何東西. 對吧?在你有空的時候你可以看一下這個指令碼. 它能做的事情不會讓你失望的.
這裡有一個更優雅的不需要管道(好吧, 至少在我看來是更優雅)的方案來實現這個功能. 你可以使用DTrace語句中的判斷句來搜尋使用者區輸入的char* 類型的Ray.png字串.
你將會使用DTrace的strstr函數來做這個檢查. 這個函數帶了兩個參數並且返回了一個第二個字串首次出現在第一個字串中指標. 如果在第一個字串中沒有找到第二個字串, 它將會返回NULL. 這就意味著你可以通過檢查它在判斷句中的傳回值是否是NULL來搜尋包含Ray.png的路徑!
完善後的DTrace指令碼變得越來越複雜越來越難看了, 就像下面這樣:
sudo dtrace -qn ‘syscall::open*:entry / execname == "Finding Ray" &&
strstr(copyinstr(arg0), "Ray.png") != NULL / { printf("%s opened %s\n",
execname, copyinstr(arg0)); ustack(); }‘
構建並重新運行這個程式.
你丟掉了grep管道並且用一個用一個條件句來判斷FindingRay進程中×××的檔案路徑中是否包含Ray.png.
此外, 你可以輕鬆的精確找出負責開啟Ray.png圖片的堆棧記錄.
DTrace和毀滅性的操作
注意: 我將要向你展示的是非常危險的.我再重複一遍: 接下來的操作是非常危險的.
如果你搞砸了一個命令你可能失去一些你最愛的映像. 只在你自己的硬碟上!
事實上, 是安全的, 請關閉所有正在使用映像的應用程式(比如, Photos, PhotoShop, 等等). 我和Razeware團隊對你電腦上發生的任何事情都不負任何法律責任.
我們已經警告過你了!
呃...我打賭上面的法律聲明肯定讓你緊張了!
你將會使用DTrace來執行一個破壞性的操作. 那就是, 通常DTrace只會檢測你的電腦, 但是現在你將會實際修改你程式的邏輯. 你將會檢測到被Finding RayAPP執行的系統調用的open家族的函數. 如果有一個系統調用的open函數第得一個參數包含單詞.png(也就是它開啟的char*類型的路徑參數), 你將會用一個不同的PNG圖片替換那個參數.
這些可以通過copyout和copyoutstr 這兩個DTrace命令實現. 在這個例子中你將明確使用copyoutstr. 你將注意到類似copyin和copyinstr的名字. 在這個上下文中的in和out指明了你拷貝的資料的目錄, 既有內部DTrace可以讀取資料的目錄, 也有外部process可以讀取它的目錄.
在這個projects目錄中, 有一個叫做troll.png的圖片.用 ? + N建立一個新的Finder視窗, 然後按下? + Shift + H進到你的home目錄下. 將troll.png拖拽到這個目錄下(當本章結束的時候你就可以刪掉了). 這裡有一個瘋狂的只有我能承受的方法來做這件事! 為什麼你需要做呢?你將在一個令人興奮的程式裡改寫記憶體.在這個程式的記憶體裡為這個字串分配的只有有限的空間. 這有可能是一個很長的字串因為你再iPhone模擬器中並且你的進程(極有可能)是在他自己的沙箱中讀取圖片.
你還記得搜尋Ray.png? 下面是我電腦上的完整路徑. 你的路徑與我的肯定不同:
/Users/derekselander/Library/Developer/CoreSimulator/Devices/
97F8BE2C-4547-470C-955F-3654A8347C41/data/Containers/Bundle/Application/
102BDE66-79CB-453C-BA71-4062B2BC5297/Finding Ray.app/Ray.png
我們的計劃是用一個更短的路徑來使用DTrace讀取一個圖片, 在程式的記憶體中看起來應該是這個樣子:
/Users/derekselander/troll.png\0veloper/CoreSimulator/Devices/
97F8BE2C-4547-470C-955F-3654A8347C41/data/Containers/Bundle/Application/
102BDE66-79CB-453C-BA71-4062B2BC5297/Finding Ray.app/Ray.png
你看到這裡的\0了嗎?這是char*的終結符. 因此本質上這個字串僅僅只是:
/Users/derekselander/troll.png
因為那就是一個字串是如何用NULL結尾的!
擷取你的路徑長度
在寫完資料之後, 你需要弄清楚troll.png圖片的完成路徑有多少個chars. 我知道我的路徑的長度, 不幸的是, 我不知道你的路徑的長度.
在終端中輸入:
echo ~/troll.png
你將會擷取到troll.png圖片的完整路徑. 待會兒你會將這個路徑粘貼到你的指令碼中.還需要在終端中找出這個字串的長度.
echo ~/troll.png | wc -m
在我這裡, /Users/derekselander/troll.png是31個字元. 但是很明顯: 你需要將結束符null算進來.這就意味著我需要插入的新字串的長度是32或者更多.
open*函數中的arg0參數指向記憶體中的一些資料. 如果你將比這個字串更長的字串寫到這個位置, 然後這回破壞記憶體並退出程式.很顯然, 你不希望這樣的事情發生, 因此你需要將troll.png複製到一個路徑字元數量較短的目錄中.
你同樣要用DTrace的判斷句來檢查一下, 確保你有足夠的控制項儲存這個字串.拜託, 你是一個嚴謹而勤奮的程式員, 對吧?
在終端中輸入下面的內容, 用你的值替換/Users/derekselander和32:
sudo dtrace -wn ‘syscall::open*:entry / execname == "Finding Ray" && arg0
0xfffffffe && strstr(copyinstr(arg0), ".png") != NULL &&
strlen(copyinstr(arg0)) >= 32 / { this->a = "/Users/derekselander/
troll.png"; copyoutstr(this->a, arg0, 32); }‘
在新的DTrace指令碼啟用之後重新構建並運行Finding Ray應用程式.
假如你的執行是順利的, 那麼當Finding Ray進程每次嘗試開啟一個包含.png字元的檔案的時候, 你都會返回一個troll.png來替代.
圖片.png
其他毀滅性的操作
除了copyoutstr和copyout之外, DTrace還有其他一些破壞性的操作需要注意:
? stop(void): 這將會凍結當前正在啟動並執行使用者進程(通過內部的pid參數給定的進程). 這是一個完美的方式如果你想停止執行一個使用者進程, 並將LLDB附加附加到進程上並進一步瀏覽的時候.
? raise(int signal): 這負責為探針增加一個訊號到進程上.
? system(string program, ...): 這可以讓你想在終端中一樣執行一個命令.它有一個額外的好處可以你訪問所有DTrace內部的變數, 例如execname和probemod, 用於printf風格的格式化.
我鼓勵你查看這個破壞性的操作(尤其是stop()操作), 在你閒置時候. 但是要小心的使用這些系統函數. 如果使用的不正確你真的很容易就造成大梁破壞.
我們為什麼要學這些?
在你的macOS機器裡有許多強大的DTrace指令碼. 你可以通過man -k dtrace捕獲它們, 然後慢慢弄明白這些指令碼的作用. 此外, 你可以在它們的代碼裡學到許多知識. 記住, 它們是指令碼, 不是編譯過的可執行檔, 所以原始碼是公平的遊戲.
同樣, 在執行破壞性操作的時候必須要小心一點. 也就是說, 你可以將ayWenderlich放到你電腦裡的任何地方.
圖片.png
這不正式你想要的效果嗎?
嚴肅的說, 你可以在你的電腦上做一些十分瘋狂的操作並且可以用DTrace洞察許多資訊.
Advanced+Apple+Debugging(16)