SwiftObject類: 彎路中的彎路
但是等一下, 你已經(jīng)大概看了一下SwiftObject類, 這個(gè)類是ASwiftClass
類的父類!讓我們用image lookup這個(gè)方法來(lái)提取這個(gè)類實(shí)現(xiàn)的方法.
在LLDB中輸入下面內(nèi)容:
(lldb) image lookup -rn SwiftObject
我不知道你的輸出內(nèi)容, 但是我實(shí)在厭倦了查看這么丑的格式的輸出. 要正確的讀這些內(nèi)容實(shí)在太難了. 既然你已經(jīng)讀這本書(shū)這么久, 你就知道你可以自由的做出任何必要的改變來(lái)讓你的生活更輕松.
使用lldb的Python API來(lái)創(chuàng)建一個(gè)更漂亮的查詢:
(lldb) script srch = lldb.target.FindGlobalFunctions('SwiftObject', 0,lldb.eMatchTypeRegex)
你聲明了一個(gè)SBSymbolContextList
類型的搜索查詢并且將它賦值給srch. 這是一些類似SBSymbolContext的Python數(shù)組的東西.
我要把通過(guò)gdocumentation中的SBTarget抓取FindGlobalFunctions文檔的工作留給你.
現(xiàn)在在LLDB中輸入下面的內(nèi)容:
(lldb) script print "\n".join(map(lambda a: str(a.symbol.name), srch))
這里用一個(gè)lambda
抓取函數(shù)名字然后將每一個(gè)對(duì)象通過(guò)換行符分割加到這個(gè)list中.
更好一點(diǎn)了. 也許對(duì)于接下來(lái)的章節(jié)來(lái)說(shuō)那是一個(gè)好的腳本. OK, 回到我們手里的目標(biāo)上. 你正在瀏覽
SwiftObject
類實(shí)現(xiàn)的方法. 注意SwiftObject
這個(gè)類實(shí)現(xiàn)的description和debugDescription方法.這個(gè)
Allocator
項(xiàng)目實(shí)際上交換了SwiftObject
的debugDescription去修改輸出并且顯示SwiftObject
子類的指針. 這個(gè)邏輯是在NSObject+DS_SwiftObject.m
文件里發(fā)現(xiàn)的. Swift語(yǔ)言和Swift LLDB 環(huán)境隱藏了這個(gè)指針, 這一點(diǎn)十分討厭.提取到的輸出意味著任何swift類仍然開(kāi)放了
swizzling
和Objective-C的運(yùn)行時(shí), 無(wú)論它是否繼承自NSObject, 或者是一個(gè)根本沒(méi)有父類的類.出于你應(yīng)用的安全的角度考慮將這些記在腦海里是好的.
以NSObject為父類的swift的內(nèi)存布局
最后一點(diǎn). 你知道這個(gè)訓(xùn)練, 因此我們將講的快一點(diǎn)而且會(huì)跳過(guò)實(shí)際調(diào)試會(huì)話.
檢查ASwiftNSObjectClass.swift的源代碼:
class ASwiftNSObjectClass: NSObject {
let eyeColor = UIColor.brown
let firstName = "Derek"
let lastName = "Selander"
required override init() { }
}
這是與ASwiftClass
同樣的內(nèi)容, 除了它繼承自NSObject而不是繼承自nothing之外.
那么這里生成的C結(jié)構(gòu)體的偽代碼是否有什么不同呢?
struct ASwiftNSObjectClass {
Class isa;
uintptr_t referenceCounts;
UIColor *eyeColor;
struct _StringCore {
uintptr_t _baseAddress;
uintptr_t _countAndFlags;
uintptr_t _owner;
} firstName;
struct _StringCore {
uintptr_t _baseAddress;
uintptr_t _countAndFlags;
uintptr_t _owner;
} firstName;
}
不是的!這里生成的偽代碼與ASwiftClass
的偽代碼比沒(méi)有什么不同的. 最大的不同是這個(gè)類有著更簡(jiǎn)單的內(nèi)觀因?yàn)镹SObject與SwiftObject相比實(shí)現(xiàn)了更多的方法.
讓我們跳過(guò)調(diào)試會(huì)話而且僅僅只討論當(dāng)你嘗試retain這個(gè)類的一個(gè)實(shí)例的時(shí)候會(huì)發(fā)生什么: refCounts變量不會(huì)被修改. 這個(gè)現(xiàn)象是因?yàn)镺bjective-C有它自己實(shí)現(xiàn)的 retain/release
那些與Swift實(shí)現(xiàn)的是不同的.
你終于可以學(xué)習(xí)SBValue類了我已經(jīng)迫不及待的想講給你聽(tīng)了!.
SBValue
耶!是時(shí)候討論一下這個(gè)讓人驚喜的類了.
SBValue負(fù)責(zé)從你的JIT代碼中解釋剖析表達(dá)式. 將SBValue想想成一個(gè)讓你瀏覽你對(duì)象的成員變量的代表, 僅僅在你做上面事情的時(shí)候, 但是沒(méi)有那些難看的解引用. 在SBValue實(shí)例內(nèi)部, 你可以輕松的訪問(wèn)你結(jié)構(gòu)體中的所有變量..., 我的意思是, 你的Objective-C 或者swift類.
在SBTarget和SBFrame類里, 這里有一個(gè)叫做EvaluateExpression的方法, 這個(gè)方法會(huì)將你的表達(dá)式作為一個(gè)Python str
然后返回一個(gè)SBValue實(shí)例. 此外, 這里有一個(gè)可選的第二個(gè)參數(shù)可以讓你指明你希望你的代碼怎樣被解析. 你剛開(kāi)始的時(shí)候不會(huì)用到可選的第二個(gè)參數(shù), 但是后面會(huì)用到.
回到LLDB控制臺(tái)中然后確保Allocator
項(xiàng)目仍然在運(yùn)行.確保LLDB控制臺(tái)在前臺(tái)(也就是說(shuō), 程序是暫停的), 清空控制臺(tái)并且輸入下面的內(nèi)容:
(lldb) po [DSObjectiveCObject new]
你將會(huì)得到一些類似下面的內(nèi)容:
<DSObjectiveCObject: 0x61800002eec0>
這確保你可以創(chuàng)建一個(gè)DSObjectiveCObject
的有效實(shí)例.
這些代碼是可以工作的, 因此你可以將它應(yīng)用到EvaluateExpression
方法上可以是全局的SBTarget
實(shí)例或者SBFrame實(shí)例:
(lldb) script lldb.frame.EvaluateExpression('[DSObjectiveCObject new]')
你將會(huì)得到這個(gè)類通常的神秘輸出但是沒(méi)有這些做的內(nèi)容的上下文描述:
<lldb.SBValue; proxy of <Swig Object of type 'lldb::SBValue *' at0x10ac78b10> >
你已經(jīng)使用print
獲取這些類的上下文:
(lldb) script print lldb.target.EvaluateExpression('[DSObjectiveCObjectnew]')
你將會(huì)得到你喜歡的你已經(jīng)變得習(xí)慣了的debugDescription
.
(DSObjectiveCObject *) $2 = 0x0000618000034280
注意: 如果你輸入錯(cuò)誤一些東西, 你將仍然得到一個(gè)`SBValue`實(shí)例, 因此確保它打印出了你期望的內(nèi)容.例如, 如果你輸入錯(cuò)誤了JIT代碼, 你將會(huì)從SBValue中得到一些類似`** = <could not resolvetype>**`的內(nèi)容.
你可以通過(guò)檢查你SBValue中的SBError實(shí)例確認(rèn)SBValue成功了.如果你的SBvalue叫做`sbval`, 你一個(gè)通過(guò)`sbval.GetError().Success()`, 或者更簡(jiǎn)單的`sbval.error.success. print`是一種開(kāi)蘇查看是否工作的方法.
修改這個(gè)命令以便你將這個(gè)值賦值給了Python上下文中的變量a:
(lldb) script a = lldb.target.EvaluateExpression('[DSObjectiveCObjectnew]')
現(xiàn)在將Python的print
函數(shù)應(yīng)用到a
變量上:
(lldb) script print a
再一次你將得到類似下面的輸出:
(DSObjectiveCObject *) $0 = 0x0000608000033260
太棒了!你有一個(gè)SBValue實(shí)例存儲(chǔ)在a
變量里而且已經(jīng)見(jiàn)識(shí)到了DSObjectiveCObject
的內(nèi)存布局.
你知道a
持有一個(gè)SBValue, 這個(gè)值指向DSObjectiveCObject
類.你可以抓取通過(guò)使用GetDescription()
抓取DSObjectiveCObject的description
, 或者更簡(jiǎn)單的SBValue的description屬性.
輸入下面的內(nèi)容:
(lldb) script print a.description
你將會(huì)看到類似下面的內(nèi)容:
<DSObjectiveCObject: 0x608000033260>
你也可以獲取value
屬性, 這個(gè)屬性返回一個(gè)PythonString
包含著這個(gè)實(shí)例的地址:
(lldb) script print a.value
這一次的值是:
0x0000608000033260
復(fù)制a.value
的輸出然后確保po
這個(gè)給你的原始的指針, 當(dāng)前引用:
(lldb) po 0x0000608000033260
是的:
<DSObjectiveCObject: 0x608000033260>
如果你想讓這個(gè)地址通過(guò)一個(gè)Python的number表達(dá)而不是一個(gè)Python的str
, 你可以使用signed
或者unsigned
屬性:
(lldb) script print a.signed
像這樣:
106102872289888
將這個(gè)數(shù)字格式化為16進(jìn)制將會(huì)產(chǎn)生這個(gè)DSObjectiveCObject
實(shí)例的指針:
(lldb) p/x 106102872289888
現(xiàn)在你就會(huì)到了之前的地方:
(long) $3 = 0x0000608000033260
通過(guò)SBValue的偏移瀏覽屬性
這些屬性是如何存儲(chǔ)在DSObjectiveCObject
實(shí)例里的呢?讓我們?yōu)g覽一下它們!
使用SBValue可用的GetNumChildren方法獲取它c(diǎn)hild的數(shù)量:
(lldb) script print a.GetNumChildren()4
你可以將children看成一個(gè)數(shù)組. 這里有一個(gè)特殊的API可以爬取一個(gè)類里的children, 這個(gè)API叫做GetChildAtIndex. 在LLDB中瀏覽children 0-3:
Child 0:
(lldb) script print a.GetChildAtIndex(0)
(NSObject) NSObject = {
isa = DSObjectiveCObject
}
Child 1:
(lldb) script print a.GetChildAtIndex(1)
(UICachedDeviceRGBColor *) _eyeColor = 0x0000608000070e00
Child 2:
(lldb) script print a.GetChildAtIndex(2)
(__NSCFConstantString *) _firstName = 0x000000010db83368 @"Derek"
Child 3:
(lldb) script print a.GetChildAtIndex(3)
(__NSCFConstantString *) _lastName = 0x000000010db83388 @"Selander"
它們中的每一個(gè)都會(huì)返回一個(gè)它自己內(nèi)部的SBValue, 因此你甚至可以進(jìn)一步瀏覽那個(gè)對(duì)象如果你渴望的話. 將firstName
屬性帶進(jìn)賬戶中. 輸入下面內(nèi)容來(lái)獲取description:
(lldb) script print a.GetChildAtIndex(2).description
Derek
記住Python變量a
是指向一個(gè)對(duì)象的指針是很重要的.輸入下面內(nèi)容:
(lldb) script a.size
8
這將會(huì)打印出一個(gè)值告訴我們a
是8字節(jié)長(zhǎng).但是你想要獲取a里面實(shí)際的內(nèi)容!幸運(yùn)的是, SBValue有一個(gè)deref屬性返回了另一個(gè)SBValue
.瀏覽size
屬性的輸出:
(lldb) script a.deref.size
這一次的返回值是32因?yàn)樗怯?code>isa, eyeColor
, firstName
和lastName
組成的, 他們中的每一個(gè)都是8字節(jié)長(zhǎng).
(lldb) script print a.type.name
你將會(huì)得到這些:
DSObjectiveCObject *
現(xiàn)在通過(guò)deref屬性做同樣的事情:
(lldb) script print a.deref.type.name
現(xiàn)在你將會(huì)這個(gè)普通的類:
DSObjectiveCObject
通過(guò)SBValue查看raw data (原始數(shù)據(jù))
你甚至可以通過(guò)SBValue中的data屬性提取出原始數(shù)據(jù)! 這是一個(gè)SBData的類, 你可以在空閑的時(shí)候自己檢查一下這個(gè)類.
打印出DSObjectiveCObject
的data
的指針:
(lldb) script print a.data
這將會(huì)打印出組成這個(gè)對(duì)象的物理字節(jié).再一次, 這是指向DSObjectiveCObject
的指針, 并不是這個(gè)對(duì)象本身.
60 32 03 00 80 60 00 00 `2...`..
記住, 十六進(jìn)制中的內(nèi)一個(gè)字節(jié)都代表兩個(gè)位.
你還記得第十一章“Assembly& Memory”中的小端格式嗎以及原始數(shù)據(jù)是如何被反轉(zhuǎn)的?
將這個(gè)值與SBValue的value屬性做一個(gè)比較.
(lldb) script print a.value
0x0000608000033260
注意這些值是如何排位的. 例如, 我的指針的原始數(shù)據(jù)中最后兩個(gè)16進(jìn)制位是一組. 在我這里, 原始數(shù)據(jù)中的
0x60
是第一個(gè)值, 與此同時(shí)指針中包含的0x60
作為最后一個(gè)值.使用deref屬性去抓取組成DSObjectiveCObject的所有字節(jié).
(lldb) script print a.deref.data f054b80d01000000000e070080600000 .T...........`.. 6833b80d010000008833b80d01000000 h3.......3......
這是另外一種可視化發(fā)生了什么的方式. 你每次跳過(guò)了8字節(jié)當(dāng)你用po *(id*)(0x0000608000033260 + multiple_of_8)
命令查看內(nèi)存的時(shí)候.
SBExpressionOptions
正如我們?cè)谟懻?code>EvaluateExpressionAPI的時(shí)候提到的那樣, 這里有一個(gè)可選的第二個(gè)參數(shù)將會(huì)帶入一個(gè)SBExpressionOptions實(shí)例. 你可以用這個(gè)選項(xiàng)為JIT的執(zhí)行傳入一個(gè)特殊的選項(xiàng).
在LLDB中, 清空屏幕, 輸入下面的內(nèi)容:
(lldb) script options = lldb.SBExpressionOptions()
上面的命令執(zhí)行成功之后不會(huì)有任何的輸出. 接下來(lái)輸入:
(lldb) script options.SetLanguage(lldb.eLanguageTypeSwift)
SBExpressionOptions有一個(gè)叫做SetLanguage的方法(當(dāng)有疑惑的時(shí)候, 使用gdocumentation SBExpressionOptions), 這個(gè)方法帶了一個(gè)LLDB模塊枚舉類型的參數(shù)lldb::LanguageType. LLDB的作者有一個(gè)約定在一個(gè)枚舉的前面粘貼一個(gè)"e", 這個(gè)枚舉的名字, 然后是唯一的值.
這設(shè)定了作為swift代碼執(zhí)行替換餓了默認(rèn)的類型, 基于SBFrame語(yǔ)言類型.
現(xiàn)在告訴options
變量翻譯JIT代碼作為ID型的a
(也就是說(shuō), po
取代了p
).
(lldb) script options.SetCoerceResultToId()
SetCoerceResultToId帶了一個(gè)可選的Boolean, 這個(gè)Boolean值決定了是否將它翻譯為id型. 默認(rèn)情況下, 這里設(shè)置的是True.
概括一下你再這里做的事情: 你設(shè)置了選項(xiàng)用Python API來(lái)解析這個(gè)表達(dá)式替代了通過(guò)表達(dá)式命令傳給我們的選項(xiàng).例如, 截至目前你已經(jīng)聲明的SBExpressionOptions
相當(dāng)與下面的表達(dá)式命令中的options
:
expression -lswift -O -- your_expression_here
接下來(lái), 僅僅使用表達(dá)式命令創(chuàng)建一個(gè)ASwiftClass
實(shí)例方法. 如果這是可行的, 你將會(huì)在EvaluateExpression
命令中嘗試同樣的命令. 在LLDB中輸入下面的內(nèi)容:
(lldb) e -lswift -O -- ASwiftClass()
你將會(huì)得到輸出內(nèi)容將會(huì)是一個(gè)有點(diǎn)難看的錯(cuò)誤...
error: <EXPR>:3:1: error: use of unresolved identifier 'ASwiftClass'
ASwiftClass()
^~~~~~~~~~~
歐耶, 你需要導(dǎo)入Allocator模塊來(lái)確保swift可以在調(diào)試器中很好的運(yùn)行.
在LLDB中:
(lldb) e -lswift -- import Allocator
注意:這是一個(gè)許多LLDB用戶抱怨的問(wèn)題: LLDB不能正確的執(zhí)行應(yīng)該能夠執(zhí)行的代碼. 添加這個(gè)導(dǎo)入邏輯將會(huì)將會(huì)修改LLDB的swift ** expression prefix**, 這個(gè)前綴是在執(zhí)行你的JIT代碼之前正確引用了的基本的頭文件的集合.
當(dāng)你停止在非swift調(diào)試環(huán)境的時(shí)候LLDB不能看見(jiàn)JIT代碼中的`ASwiftClass`. 這就意味著你需要附加屬于`Allocator`模塊的表達(dá)式前綴的頭部. 這里有一個(gè)來(lái)自LLDB的作者的關(guān)于這個(gè)問(wèn)題的非常好的解釋:[http://stackoverflow.com/questions/19339493/why-cant-lldb-evaluate-this-expression](http://stackoverflow.com/questions/19339493/why-cant-lldb-evaluate-this-expression).
再次執(zhí)行前一個(gè)命令. 按兩次上箭頭鍵然后回車:
(lldb) e -lswift -O -- ASwiftClass()
你將會(huì)得到一個(gè) ASwiftClass()
實(shí)例的引用.
現(xiàn)在你知道了這是可行的, 使用EvaluateExpression方法這一次將options作為第二個(gè)參數(shù)然后將輸出賦值給變量b, 像下面這樣:
(lldb) script b = lldb.target.EvaluateExpression('ASwiftClass()',options)
如果一切順利的話, Python變量b中將會(huì)存儲(chǔ)一個(gè)SBValue的引用.
注意: 需要指出的是SBValue的一些屬性在Swift中不能很好的運(yùn)行. 例如, 使用SBValue的`deref`或者`address_of`屬性解引用一個(gè)swift對(duì)象將不能恰當(dāng)?shù)墓ぷ? 你可以通過(guò)指明這個(gè)指針是一個(gè)SwiftObject強(qiáng)制將這個(gè)指針指向一個(gè)Objective-C引用, 然后一切就都可以照常工作了. 就像我說(shuō)的那樣, 當(dāng)你嘗試在swift中追逐的時(shí)候他們讓你為它做事!
通過(guò)名字引用SBValue中的變量
通過(guò)GetChildAtIndex從SBValue
中引用子SBValues來(lái)查看內(nèi)存中的對(duì)象是一個(gè)相當(dāng)無(wú)聊的方式. 如果這個(gè)類的作者在eyeColor
前添加了一個(gè)屬性那么當(dāng)你爬去這個(gè)SBValue的時(shí)候的總的偏移邏輯是什么呢?
幸運(yùn)的是, SBValue還有另外一個(gè)方法讓你通過(guò)名字替代偏移來(lái)引用實(shí)例變量: GetValueForExpressionPath.
跳回到LLDB中然后輸入下面內(nèi)容:
(lldb) script print b.GetValueForExpressionPath('.firstName')
如果你想的話你可以繼續(xù)練習(xí)進(jìn)入下面的child的自己的結(jié)構(gòu)體:
(lldb) script
b.GetValueForExpressionPath('.firstName._core._baseAddress._rawValue').va
lue
我如何或者那個(gè)瘋狂的_core._baseAddress._rawValue
部分呢?如果你沒(méi)有子SBValue的名字的線索, 你所需要做的就是使用GetIndexOfChildAPI獲取child, 然后使用那個(gè)子SBValue上的name屬性.
例如, 如果我不知道在bSBValue中找到的UIColor屬性的名字, 我可能做下面的事情:
lldb.value
最后一件你可以做的很酷的事情是創(chuàng)建一個(gè)包含SBValue屬性的Python引用作為Python對(duì)象的屬性(等一下...什么?).將這想象成一個(gè)你可以使用Python屬性來(lái)替代替代字符串來(lái)引用變量的對(duì)象.
回到控制臺(tái)中, 從你的bSBValue中創(chuàng)建一個(gè)新的value對(duì)象:
(lldb) script c = lldb.value(b)
這將會(huì)創(chuàng)建一個(gè)value類型的特殊Python對(duì)象. 現(xiàn)在你可以像使用一個(gè)普通的對(duì)象一樣引用他的實(shí)例變量!
在LLDB中輸入下面的內(nèi)容:
(lldb) script print c.firstName
當(dāng)然, 它自己的所有的child屬性你也可以用. 輸入下面的內(nèi)容:
(lldb) script print c.firstName._core._countAndFlags
你將會(huì)得到:
_countAndFlags = 5
你同樣可以將child對(duì)象解析到一個(gè)SBValue以便你可以查詢它或者將它應(yīng)用到一個(gè)for循環(huán)上, 像這樣:
(lldb) script print c.firstName._core._countAndFlags.sbvalue.signed
這將會(huì)在控制臺(tái)中輸出Python int
5.
再說(shuō)一次, 如果你不知道child SBValue的名字, 使用GetChildAtIndex
API獲取child然后從name屬性上獲取它的名字.
注意: 盡管`lldb.value `類很強(qiáng)大, 但是這里也有一個(gè)代價(jià). 它創(chuàng)建和訪問(wèn)屬性耗費(fèi)的性能相當(dāng)大. 如果你在剖析一個(gè)很大的數(shù)組, 使用這個(gè)類將會(huì)顯著的降低運(yùn)行速度. 練習(xí)使用這個(gè)類然后找到速度和性能的平衡點(diǎn).
我們?yōu)槭裁匆獙W(xué)習(xí)這些?
哇噻!... 這一章的內(nèi)容知識(shí)點(diǎn)多么稠密啊?幸運(yùn)的是你完整的看了下來(lái). 你可以使用你自定義命令中的options
動(dòng)態(tài)的生成你的JIT腳本代碼. 從你JIT代碼的返回值中, 你可以書(shū)寫(xiě)基于 EvaluateExpression
API解析后返回的SBValue的自定義邏輯腳本 .
這可以為你解鎖一些驚喜的腳本. 在任何你可以用LLDB附加的進(jìn)程中, 你可以返回你自己的自定義腳本然后處理你的Python腳本返回的的自定義值. 這樣不用處理簽名問(wèn)題或者加載frameworks的問(wèn)題或者其他類似的問(wèn)題.
這一部分剩下的章節(jié)將會(huì)集中與創(chuàng)作一些創(chuàng)造性的腳本以及這些腳本如何讓你的調(diào)試(或者逆向工程)生活變得更加簡(jiǎn)單.
理論時(shí)間已經(jīng)結(jié)束了. 是時(shí)候做一些有趣的事情了!