第二十一章:SBValue和Memory橋接腳本(二)

SwiftObject類: 彎路中的彎路

但是等一下, 你已經(jīng)大概看了一下SwiftObject類, 這個(gè)類是ASwiftClass類的父類!讓我們用image lookup這個(gè)方法來(lái)提取這個(gè)類實(shí)現(xiàn)的方法.
在LLDB中輸入下面內(nèi)容:

(lldb) image lookup -rn SwiftObject
圖片.png

我不知道你的輸出內(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中.

圖片.png

更好一點(diǎn)了. 也許對(duì)于接下來(lái)的章節(jié)來(lái)說(shuō)那是一個(gè)好的腳本. OK, 回到我們手里的目標(biāo)上. 你正在瀏覽SwiftObject類實(shí)現(xiàn)的方法. 注意SwiftObject這個(gè)類實(shí)現(xiàn)的descriptiondebugDescription方法.
這個(gè)Allocator項(xiàng)目實(shí)際上交換了SwiftObjectdebugDescription去修改輸出并且顯示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, firstNamelastName組成的, 他們中的每一個(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è)類.
打印出DSObjectiveCObjectdata的指針:

(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

圖片.png

注意這些值是如何排位的. 例如, 我的指針的原始數(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ò)GetChildAtIndexSBValue中引用子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屬性的名字, 我可能做下面的事情:

圖片.png
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的名字, 使用GetChildAtIndexAPI獲取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ě)基于 EvaluateExpressionAPI解析后返回的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í)候做一些有趣的事情了!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容