Advanced+Apple+Debugging(10)

來源:互聯網
上載者:User

標籤:lldb

到目前位置, 當執行JIT代碼的時候(例如:Objective-C, Swift, C等等. 代碼是通過你的Python指令碼執行的), 你用了一小部分API去執行代碼.
例如, 你使用了SBDebugger和SBCommandReturnObject的HandleCommand方法去執行指令碼.SBDebugger的HandleCommand直接輸出到stderr, 同時你可以控制SBCommandReturnObject的結果結束的位置. 一旦執行起來以後, 你不得不手動解析返回的輸出你感興趣的部分. 從JIT代碼的輸出中手動的搜尋
有點難看. 沒有人喜歡文字型的事情!
因此是時候介紹一下lldb Python模組中的一個新類, SBValue, 已經他如何讓解析JIT代碼的輸出變的簡單.
開啟本章中starter目錄下的Allocator項目.這是一個可以根據輸入框中的內容動態產生類的一個簡單的應用程式.

圖片.png

這是一個完成了的將輸入框的字串傳入NSClassFromString函數產生一個類. 如果返回了一個有效類, 就會使用古老的init方法初始化. 否則, 就會輸出一個錯誤.
iPhone 7 Plus模擬器上構建並運行這個應用程式. 你不需要對這個程式做任何修改, 而且你將會使用SBValue查看對象在記憶體裡的布局, 以及通過LLDB手動的指標.
記憶體布局的彎路

真的非常感謝強大的SBValue類, 你將會瀏覽Allocator應用程式中三個唯一對象的記憶體布局. 你將會以一個Objective-C類為開始, 然後瀏覽一個沒有父類的swift類, 最後瀏覽一個繼承自NSObject的swift類.
這三個類都有下面三個屬性:
? 一個叫做eyeColor的color屬性
? 一個叫做firstName的字串(String/NSString)屬性.
? 一個叫做lastName的字串(String/NSString)屬性.
它們中的每一個類都用同樣的初始化值. 它們是:
? eyeColor的值將是UIColor.brown或者[UIColor brownColor], 這取決語言.
? firstName的值將是"Derek"或者@"Derek"這也取決於語言.
? lastName的值將是"Selander"或者@"Selander"這也取決於語言.

Objective-C 記憶體布局

你首先會瀏覽Objective-C類, 因為它是這些對象在記憶體中如何布局的基礎. 跳到DSObjectiveCObject.h中然後觀察一下它. 這裡是給你的參考:

@interface DSObjectiveCObject : NSObject
@property (nonatomic, strong) UIColor eyeColor;
@property (nonatomic, strong) NSString
firstName;
@property (nonatomic, strong) NSString *lastName;br/>@end
跳到實現檔案DSObjectiveCObject.m裡, 然後看一下並理解一下Objective-C對象初始化的時候做了哪些事情:

@implementation DSObjectiveCObject

  • (instancetype)init
    {
    self = [super init];
    if (self) {
    self.eyeColor = [UIColor brownColor];
    self.firstName = @"Derek";
    self.lastName = @"Selander";
    }
    return self;br/>}
    @end
    這些代碼在編譯的時候, 這個Objective-C類看起來實際上像一個C結構體. 編譯器會建立一個類似下面的結構體的虛擬碼:

struct DSObjectiveCObject {
Class isa;
UIColor eyeColor;
NSString
firstName
NSString *lastName
}
注意一下這個類的作為第一個參數的isa變數. 這就是一個Objective-C 類被認為是一個Objective-C 類的背後的魔法.在對象執行個體的記憶體布局中isa總是第一個第一個值, 而且總是指向這個執行個體所屬的類. 然後, 屬性按照你再代碼中實現的順序被加到這個結構體上.
讓我們通過LLDB看看這些操作. 執行下面的步驟:

確保在UIPickerView中選中了DSObjectiveCObject.
在Allocate Class按鈕上點擊.
一旦引用地址輸出到了控制台上, 複製那個地址到你的剪貼簿上.
暫停執行然後提出LLDB控制台視窗.
圖片.png

一個DSObjectiveCObject的執行個體已經被建立了. 現在你將使用LLDB探索這個對象內容的位移.
從控制台的輸出中複製這個記憶體位址然後確保po這個記憶體位址以後給了你一個有效引用(例如. 當列印出這個地址的時候你沒有停在swift的棧幀上).
在我這裡, 我得到的指標是0x600000031f80. 正如往常一樣, 你的可能與我的有所不同. 通過LLDB列印出這個地址:
(lldb) po 0x600000031f80
你應該會得到下面這行期望的輸出:

<DSObjectiveCObject: 0x600000031f80>
因為這個可以作為一個C結構體使用, 你將開始探索這個指標內容的位移.
在LLDB控制台中, 輸入下面的內容(用你的指標替換下面的指標):

(lldb) po (id )(0x600000031f80)
這行代碼指明這個指標是id型然後解引用它. 這將會訪問這個對象的isa指標.
你應該會看到這些輸出:

DSObjectiveCObject
這就是這個類的描述, 跟我們期望的一樣.
讓我們用另外一種方式查看一下這個記憶體. 使用x命令(又名examine, 一個從GDB流傳下來的命令)來跳到起始指標的位置, 然後po它. 輸入下面內容:

(lldb) x/gx 0x600000031f80
這行命令做了下面的事情:
? 檢查這一塊記憶體(x)
? 列印出大端單詞的大小, (64 位元, 或者 8 位元組) (g)
? 最後, 用16進位格式化它 (x).
如果, 假設, 你只想查看二進位檔案中這個位置第一個位元組的內容, 你可以輸入x/bt 0x600000031f80替代. 這將會解釋為檢查 (x), 一個位元組 (b) 在二進位檔案中 (t). 這examine命令在瀏覽記憶體的時候是哪些好用的命令裡比較好的一個.
你將會看到下面的輸出(或者至少, 類似的輸出, 儘管你的值與我的肯能會有不同):

0x600000031f80: 0x0000000108b06568
這些輸出告訴你在記憶體位址0x600000031f80裡包含的值是0x0000000108b06568. 很好, 這就是我得到的值!
回到我們手裡的任務裡去, 取到x/gx命令列印出的地址然後用po命令列印出新地址.

(lldb) po 0x0000000108b06568
再一次, 這將列印出isa類, 也就是DSObjectiveCObject這個類. 這是一個列印出isa執行個體的備選方法, 這也許可以讓你一些更深刻的理解發生了什麼事. 然而, 它用兩個LLDB命令替代了一個, 因此你將會解引用這個指標而且不使用x/gx命令.
讓我們更深入eyeColor屬性一點. 在LLDB控制台中:

(lldb) po (id )(0x600000031f80 + 0x8)
這行指令是說"從0x600000031f80開始, 增加8位元組然後擷取這裡的指標指向的內容." 你將會得到下面的輸出:

UIExtendedSRGBColorSpace 0.6 0.4 0.2 1
怎麼知道我得到的數量是8呢? 在LLDB中試一下面的命令:

(lldb) po sizeof(Class)
isa變數是Class類型. 因此通過知曉一個類有多大, 你就可以知道這個它在這個結構體總佔了多大的空間, 因此你就可以知道eyeColor的位移.

注意:當我們使用64位架構工作的時候(x64 or ARM64), 所有NSObject子類的指標都是8位元組. 此外, Class這個類他自己是8位元組. 這就意味著在64位架構上, 要在不同的類之間切換隻需要移動8個位元組!
這裡有一些餓不同尺寸大小的類型, 例如int, short, bool 和其他基本類型, 而且在64位機器上編譯器可能會補足預定義的8位元組. 然而, 現在這裡不需要擔心這些, 因為DSObjectiveCObject只包含NSObject子類的指標, 沿著類對象可以拿到isa變數.
繼續進行. 在LLDB中將位移量再增加8個位元組:

(lldb) po (id )(0x600000031f80 + 0x10)
現在你增加了另外8個位元組, 用十六進位就是0x10(或者十進位的16).你得到的結果將是@"Derek", 這就是firstName屬性的值. 再增加8位來擷取lastName屬性的值:

(lldb) po (id )(0x600000031f80 + 0x18)
你將會得到@"Selander". 很酷, 對吧?
讓我們用一個鏈表圖看一下你剛才做的事情:

圖片.png

你從指向一個DSObjectiveCObject執行個體的基地址開始. 在這個例子中, 這個起始地址是0x600000031f80. 你開始解引用這個指標, 這可以給你提供isa變數, 然後你增加8個位元組的位移擷取下一個Objective-C屬性, 解引用位移以後的地址, 將它映射為id型然後將它輸出到控制台中.
探索記憶體是很有趣的而且指明了查看現象後面發生的事情的道路. 這讓你更加感激SBValue類. 但是現在還不是討論SBValue類的時候, 因為你還有兩個類要瀏覽. 第一個就是沒有父類的swift類, 第二個是繼承自NSObject的swift類. 你首先會瀏覽那個沒有父類的swift類.
沒有父類的swift類的記憶體布局

注意: 有一件事需要提前注意: swift的API仍然在變動. 這就意味著下面的資訊可能會改變在swift的ABI完成(穩定下來)之前.Xcode的新版本發布的時間可能會破壞下面的下面這一部分的內容.
到了瀏覽沒有父類的swift類的時間了!
在Allocator項目中, 跳到ASwiftClass.swift類裡然後看一下那裡的代碼.

class ASwiftClass {
let eyeColor = UIColor.brown
let firstName = "Derek"
let lastName = "Selander"
required init() { }
}
在這裡, 你有一個等同於DSObjectiveCObject的swift風格的對象.
在一次, 你可以將swift類想象成與Objective-C的C結構體類似但是有一些有趣的不同的一個C結構體. 查看下面的虛擬碼:

struct ASwiftClass {
Class isa;
uint64_t refCounts;
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;
}
相當有趣對吧? 你仍然有isa變數作為第一個參數. 在isa變數後面, 是一個8位的保留的refCounts變數. 這與典型的沒有沒有在這個位置包含這些引用計數器的Objective-C對象有點不同.注意uint64_t這種類型, 這種類型佔用8位元組記憶體--甚至在32位機器上也是這樣.這不同於uintptr_t類型, 這種類型即可以是32位也可以是64位這取決於機器的硬體.
接下來是普通的UIColor.
swift的String是一個非常有趣的對象. 事實上, 一個swift的String是一個結構體內部包含ASwiftClass結構體. 當你查看記憶體的時候每一個swift的String都包含三個參數:
? 這是string的字元資料的實際地址.
? 接下來是長度和標誌混合在一個參數裡;它即可以是Unichar, 也可以是ASCII, 或者是其他我不知道如何解釋的瘋狂的東西.
? 最後, 是一個它的持有人的引用.
因為你是在編譯時間聲明的這些string(用那些let聲明的), 這裡不需要持有人因為編譯器將會只需要應用string的實際位置的位移, 因為他們是不可改變的.
這個swiftString結構體實際上讓彙編呼叫慣例變得相當有趣. 如果你將一個String傳給一個函數, 他實際上會穿進三個參數(並且使用三個寄存器)來取代一個指向包含三個參數的結構體的指標(在一個寄存器裡). 不相信我?當這一章結束的自己檢查一下吧!
回到LLDB中並且跳轉到一個對象上.
用? + K清空控制台, 然後通過LLDB或者Xcode繼續運行應用程式.
你將會在ASwiftClass上做與DSObjectiveCObject上同樣的事情. 使用開發人員/設計者 "認可的"UIPickerView然後選擇Allocator.SwiftClass. 記住, 正確的引用一個swift類(例如, NSClassFromString和其它類似的類), 你需要將模組的名字作為類名的首碼並用一個句點將兩者分割開.
點擊Allocate Class按鈕並且複製控制台輸出的記憶體位址.

圖片.png

你將會得到一些類似下面的輸出:

<Allocator.ASwiftClass: 0x61800009d830>
通常情況下, swift隱藏了description和debugDescription的指標, 但是有一些鬼祟的東西被編譯進這個項目裡, 你稍後就會看到.
但是現在, 抓取記憶體位址並且將他複製到剪下板上.
首先用LLDB確保它是有效, po它一下:

(lldb) po 0x61800009d830
如果你得到了一些與下面的不同的輸出, 你可能會有點驚訝:

<Allocator.ASwiftClass: 0x61800009d830>
儘管這是一個純淨的swift對象, 你依然能夠在Objective-C環境中擷取他的動態description. 這就意味著你可以爬去他的繼承樹來看他的父類!

(lldb) po [0x61800009d830 superclass]
你將會得到一個有趣的類名的類:

SwiftObject
稍後你會查看這個類的更多資訊.現在, 開始跳到記憶體裡查看記憶體. 解引用這個地址的指標然後自己驗證一下第一個參數是isa類變數:

(lldb) po (id )0x61800009d830
你將會得到Allocator.ASwiftClass. 現在檢查一個引用計數器變數:

(lldb) po (id )(0x61800009d830 + 0x8)
你將會得到一些類似下面的輸出:

0x0000000200000004
很明顯這個地址不是一個 Objective-C 地址, 因為(id)0x0000000200000004將會指向一個類如果它是一個有效執行個體/類的話.取而代之的是, 這是swift類唯一的引用計數器. 讓我們看一下這些是怎麼工作的.
使用LLDB手動的retain這個類:

(lldb) po [0x61800009d830 retain]
按下向上的箭頭按鈕再次重新執行一下之前的命令:

(lldb) po (id )(0x61800009d830 + 0x8)
你將會得到一個稍微不同的數字:

0x0000000200000008
注意最後的非常重要的十六進位值向前跳4個位元組, 同時2^33 (是的, 那就是0x0000000200000000)就是同樣的值. 看一下release是否減少這個引用的計數:

(lldb) po [0x61800009d830 release]
同樣的, 按兩下上方向鍵, 然後斷行符號.

(lldb) po (id )(0x61800009d830 + 0x8)
你將會得到一個一個讓你很開心的值, 原始值:

0x0000000200000004
圖片.png

這一次嘗試retain這個對象兩次, 然後列印出這個引用計數值:
(lldb) po [0x61800009d830 retain]
(lldb) po [0x61800009d830 retain]
(lldb) po (id )(0x61800009d830 + 0x8)
你將會得到這樣一個值:

0x000000020000000c
每一次retain, 這個值都會增加4. 那看起來好像是加法, 但實際上, 它只是最低的2位沒有被使用而且保留的值每次增加1. 換句話說, 每次增加0x100.
最後, release這個對象兩次來平衡那兩個retains:

(lldb) po [0x61800009d830 retain]
(lldb) po [0x61800009d830 retain]
現在你已經看過了isa變數和refCounts變數是時候將你的注意力放在ASwiftClass執行個體上那些可愛的屬性上了.
清空螢幕重新整理一下, 在LLDB中增加你的位移量.

(lldb) po (id )(0x61800009d830 + 0x10)
你將會得到核心中代表UIColor’中brown色的值:

UIExtendedSRGBColorSpace 0.6 0.4 0.2 1
跳過另外8個位元組然後開始瀏覽firstName String 結構體:

(lldb) po (id )(0x61800009d830 + 0x18)
0x0000000101f850d0
正如你在虛擬碼結構體上看到的, 這是swift string類起始地址的實際基地址. 本質上這個基地址可以被看做一個C char或者一個C unichar(對所有的emojis字串很有用). 因此你所需要做的就是正確的指定它的類型. 因為swift string"Derek"
一定屬於ASCII範圍, 將這個地址指明為char*型來替代id:

(lldb) po (char *)(0x61800009d830 + 0x18)
"Derek"
現在看一下_countAndFlags位移. 將你的位移增加到0x20然後將指明的類型恢複到id然後繼續瀏覽. id是一個很好的預設類型, 因為它可以解決他能解決的Objective-C類型, 如果它不能解析會轉化為十六進位地址.

(lldb) po (id )(0x61800009d830 + 0x20)
你將會得到下面的輸出:

0x0000000000000005
再一次, 這代表著flags和length. 因為"Derek"的長度是5, 在這個十六進位數的最後位置你得到的是5. 其餘的0指示著這裡沒有應用flags(也就是說, 用unichar格式替代了char).
最後, 增加你的位移量然後越過swift string的_owner.

(lldb) po (id )(0x61800009d830 + 0x28)
這將會提取出nil因為這個Objective-C等同於0x0000000000000000.正如先前提到的, 這裡不需要"owner"因為這個字串是在編譯吃建立的.
不需要去看後面的lastName屬性. 你已經知道了這是如何工作的.

Advanced+Apple+Debugging(10)

相關文章

聯繫我們

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