在我的前一篇文章:iOS調(diào)試Block引用對(duì)象無法被釋放的一個(gè)小技巧 中有介紹一種顯示某個(gè)block對(duì)象的實(shí)現(xiàn)函數(shù)的方法,以及從Debug Memory Graph中查看某個(gè)對(duì)象被哪個(gè)block所引用的方法盈包,其實(shí)有更加簡單的兩個(gè)方法來查看持有某個(gè)對(duì)象的block的信息:
方法1:
在項(xiàng)目工程中打開Edit Scheme... 在出現(xiàn)的如下界面:
中勾選Malloc Stack。 這樣在Debug Memory Graph中就可以看到對(duì)象的內(nèi)存分配調(diào)用棧信息,以及某個(gè)block的實(shí)現(xiàn)函數(shù)代碼了傅蹂。
方法2:
在lldb控制臺(tái)中使用 po [xxx debugDescription] 這里面的xxx就是某個(gè)block對(duì)象或者block在內(nèi)存中的地址励稳。
既然從Debug Memory Graph中可以查看某個(gè)對(duì)象是被哪個(gè)具體的block所持有南蹂,那么反過來說是否有查看某個(gè)block中持有了哪些對(duì)象呢犬金?很明顯在Debug Memory Graph中是無能為力了。
block內(nèi)存布局簡介
要想實(shí)現(xiàn)這個(gè)能力,就需要從block對(duì)象的內(nèi)存布局說起晚顷,如果你查看開源庫 https://opensource.apple.com/source/libclosure/libclosure-73/ 中關(guān)于block內(nèi)部實(shí)現(xiàn)的定義就可以看出峰伙,在其中的Block_private.h文件中有關(guān)于block對(duì)象內(nèi)部布局的定義,每個(gè)block其實(shí)是一個(gè)如下形式的結(jié)構(gòu)體:
//block的描述信息
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
//可選的Block_descriptor_2或者Block_descriptor_3
};
//block的描述信息
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
BlockCopyFunction copy;
BlockDisposeFunction dispose;
};
//block的描述信息
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
//block的內(nèi)存布局結(jié)構(gòu)该默。
struct Block_layout {
void *isa; //block對(duì)象的類型
volatile int32_t flags; // block對(duì)象的一些特性和標(biāo)志
int32_t reserved; //保留未用
void *invoke; //block的實(shí)現(xiàn)函數(shù)地址
struct Block_descriptor_1 *descriptor; //block的描述信息
// imported variables 所引用的外部對(duì)象或者變量瞳氓。
};
之所以一個(gè)block的閉包函數(shù)能夠引用外部的一些對(duì)象或者變量,其根本的原因是每一個(gè)引用的外部對(duì)象或者變量都會(huì)在編譯運(yùn)行時(shí)添加到上面的imported variables部分作為block布局的擴(kuò)展成員數(shù)據(jù)栓袖。就比如下面的一個(gè)block實(shí)例代碼:
//假設(shè)是在TestViewController這個(gè)類的viewDidLoad中使用block對(duì)象顿膨。
-(void)viewDidLoad{
[super viewDidLoad];
id obj = [NSObject new];
int a = 0;
void (^blk)() = ^(){
NSLog("obj = %@ a=%d self = %@",obj, a, self);
};
}
當(dāng)上述的代碼被編譯運(yùn)行時(shí),blk對(duì)象的內(nèi)存布局除了基本的Block_layout外還有一些擴(kuò)展的數(shù)據(jù)成員其真實(shí)的結(jié)構(gòu)如下:
//blk對(duì)象的真實(shí)內(nèi)部布局結(jié)構(gòu)叽赊。
struct Block_layout_for_blk
{
void *isa; //block對(duì)象的類型
volatile int32_t flags; // block對(duì)象的一些特性和標(biāo)志
int32_t reserved; //保留未用
void *invoke; //block的實(shí)現(xiàn)函數(shù)地址
struct Block_descriptor_1 *descriptor; //block的描述信息
//下面部分就是使用的外部對(duì)象信息。擴(kuò)展布局部分的內(nèi)存信息必搞。
id obj;
TestViewController *self;
int a;
}
從上面的結(jié)構(gòu)中你應(yīng)該已經(jīng)了解到了一個(gè)block內(nèi)之所有能夠訪問外部變量的原因了吧必指!其實(shí)沒有什么秘密,就是系統(tǒng)在編譯block時(shí)會(huì)把所有訪問的外部變量都復(fù)制到block對(duì)象實(shí)例內(nèi)部而已恕洲。
我們知道在普通OC類中有一個(gè)ivar_layout數(shù)據(jù)成員來描述OC對(duì)象數(shù)據(jù)成員的布局信息塔橡。對(duì)于block而言要想獲取到對(duì)象的所有擴(kuò)展的成員數(shù)據(jù)則需要借助上述的flags數(shù)據(jù)成員以及descriptor中的信息來獲取。針對(duì)一個(gè)block中的flags可設(shè)置值可以是下面值的組合:
// Values for Block_layout->flags to describe block objects
enum {
BLOCK_DEALLOCATING = (0x0001), //runtime 標(biāo)志當(dāng)前block是否正在銷毀中霜第。這個(gè)值會(huì)在運(yùn)行時(shí)被修改
BLOCK_REFCOUNT_MASK = (0xfffe), //runtime block引用計(jì)數(shù)的掩碼葛家,flags中可以用來保存block的引用計(jì)數(shù)值。
BLOCK_NEEDS_FREE = (1 << 24), // runtime block需要被銷毀
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler block有XXX
BLOCK_HAS_CTOR = (1 << 26), // compiler block中有C++的代碼
BLOCK_IS_GC = (1 << 27), // runtime, 新版本中未用泌类。
BLOCK_IS_GLOBAL = (1 << 28), // compiler block是一個(gè)GlobalBlock
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
BLOCK_HAS_SIGNATURE = (1 << 30), // block的函數(shù)有簽名信息
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // block中有訪問外部變量和對(duì)象
};
可以看出當(dāng)一個(gè)block中有引用外部對(duì)象或變量時(shí)癞谒,其flags值中就會(huì)有BLOCK_HAS_EXTENDED_LAYOUT標(biāo)志。而當(dāng)有BLOCK_HAS_EXTENDED_LAYOUT標(biāo)志時(shí)就會(huì)在block的Block_layout結(jié)構(gòu)體中的descriptor中會(huì)有數(shù)據(jù)成員來描述所有引用的外部數(shù)據(jù)成員的擴(kuò)展描述信息刃榨。這個(gè)描述結(jié)構(gòu)體就是上面提到的:
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
針對(duì)layout部分的定義在Block_private.h文件頭中有明確描述:
// 擴(kuò)展布局信息編碼
// Extended layout encoding.
// Values for Block_descriptor_3->layout with BLOCK_HAS_EXTENDED_LAYOUT
// and for Block_byref_3->layout with BLOCK_BYREF_LAYOUT_EXTENDED
// If the layout field is less than 0x1000, then it is a compact encoding
// of the form 0xXYZ: X strong pointers, then Y byref pointers,
// then Z weak pointers.
// If the layout field is 0x1000 or greater, it points to a
// string of layout bytes. Each byte is of the form 0xPN.
// Operator P is from the list below. Value N is a parameter for the operator.
// Byte 0x00 terminates the layout; remaining block data is non-pointer bytes.
enum {
BLOCK_LAYOUT_ESCAPE = 0, // N=0 halt, rest is non-pointer. N!=0 reserved.
BLOCK_LAYOUT_NON_OBJECT_BYTES = 1, // N bytes non-objects
BLOCK_LAYOUT_NON_OBJECT_WORDS = 2, // N words non-objects
BLOCK_LAYOUT_STRONG = 3, // N words strong pointers
BLOCK_LAYOUT_BYREF = 4, // N words byref pointers
BLOCK_LAYOUT_WEAK = 5, // N words weak pointers
BLOCK_LAYOUT_UNRETAINED = 6, // N words unretained pointers
BLOCK_LAYOUT_UNKNOWN_WORDS_7 = 7, // N words, reserved
BLOCK_LAYOUT_UNKNOWN_WORDS_8 = 8, // N words, reserved
BLOCK_LAYOUT_UNKNOWN_WORDS_9 = 9, // N words, reserved
BLOCK_LAYOUT_UNKNOWN_WORDS_A = 0xA, // N words, reserved
BLOCK_LAYOUT_UNUSED_B = 0xB, // unspecified, reserved
BLOCK_LAYOUT_UNUSED_C = 0xC, // unspecified, reserved
BLOCK_LAYOUT_UNUSED_D = 0xD, // unspecified, reserved
BLOCK_LAYOUT_UNUSED_E = 0xE, // unspecified, reserved
BLOCK_LAYOUT_UNUSED_F = 0xF, // unspecified, reserved
};
上面文檔的解釋就是當(dāng)layout的值小于0x1000時(shí)弹砚,則是一個(gè)壓縮的擴(kuò)展布局描述,其格式是0xXYZ, 其中的X的值表示的是block中引用的外部被聲明為strong類型的對(duì)象數(shù)量枢希,Y值則是block中引用的外部被聲明為__block 類型的變量數(shù)量桌吃,而Z值則是block中引用的外部被聲明為__weak類型的對(duì)象數(shù)量。
如果當(dāng)layout的值大于等于0x1000時(shí)則是一個(gè)以0結(jié)束的字節(jié)串指針苞轿,字節(jié)串的每個(gè)字節(jié)的格式是0xPN茅诱,也就是每個(gè)字節(jié)中的高4位bit表示的是引用外部對(duì)象的類型,而低4位bit則是這種類型的數(shù)量搬卒。
上面的信息只是記錄了一個(gè)block對(duì)象引用了外部對(duì)象的布局信息描述瑟俭,對(duì)于普通的數(shù)據(jù)類型則不會(huì)記錄。并且系統(tǒng)總是會(huì)把引用的對(duì)象排列在前面秀睛,而引用的普通數(shù)據(jù)類型則排列在后面尔当。
打印一個(gè)block中引用的所有外部對(duì)象
通過對(duì)上述的介紹后,你是否了解到了一個(gè)block是如何持有和描述引用的外部對(duì)象的,那么回到本文主題椭迎,我們又如何去訪問或者查看這些引用的外部對(duì)象呢锐帜?我們可以根據(jù)上面對(duì)block對(duì)象的內(nèi)存布局描述來并下面的代碼來實(shí)現(xiàn)打印出一個(gè)block對(duì)象所引用的所有外部對(duì)象:
/*
* Copyright (c) 歐陽大哥2013. All rights reserved.
* github地址:https://github.com/youngsoft
*/
void showBlockExtendedLayout(id block)
{
static int32_t BLOCK_HAS_COPY_DISPOSE = (1 << 25); // compiler
static int32_t BLOCK_HAS_EXTENDED_LAYOUT = (1 << 31); // compiler
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
void *copy;
void *dispose;
};
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
void *invoke;
struct Block_descriptor_1 *descriptor;
// imported variables
};
//將一個(gè)block對(duì)象轉(zhuǎn)化為blockLayout結(jié)構(gòu)體指針
struct Block_layout *blockLayout = (__bridge struct Block_layout*)(block);
//如果沒有引用外部對(duì)象也就是沒有擴(kuò)展布局標(biāo)志的話則直接返回。
if (! (blockLayout->flags & BLOCK_HAS_EXTENDED_LAYOUT)) return;
//得到描述信息畜号,如果有BLOCK_HAS_COPY_DISPOSE則表示描述信息中有Block_descriptor_2中的內(nèi)容缴阎,因此需要加上這部分信息的偏移。這里有BLOCK_HAS_COPY_DISPOSE的原因是因?yàn)楫?dāng)block持有了外部對(duì)象時(shí)简软,需要負(fù)責(zé)對(duì)外部對(duì)象的聲明周期的管理蛮拔,也就是當(dāng)對(duì)block進(jìn)行賦值拷貝以及銷毀時(shí)都需要將引用的外部對(duì)象的引用計(jì)數(shù)進(jìn)行添加或者減少處理。
uint8_t *desc = (uint8_t *)blockLayout->descriptor;
desc += sizeof(struct Block_descriptor_1);
if (blockLayout->flags & BLOCK_HAS_COPY_DISPOSE) {
desc += sizeof(struct Block_descriptor_2);
}
//最終轉(zhuǎn)化為Block_descriptor_3中的結(jié)構(gòu)指針痹升。并且當(dāng)布局值為0時(shí)表明沒有引用外部對(duì)象建炫。
struct Block_descriptor_3 *desc3 = (struct Block_descriptor_3 *)desc;
if (desc3->layout == 0)
return;
//所支持的外部對(duì)象的類型。
static unsigned char BLOCK_LAYOUT_STRONG = 3; // N words strong pointers
static unsigned char BLOCK_LAYOUT_BYREF = 4; // N words byref pointers
static unsigned char BLOCK_LAYOUT_WEAK = 5; // N words weak pointers
static unsigned char BLOCK_LAYOUT_UNRETAINED = 6; // N words unretained pointers
const char *extlayoutstr = desc3->layout;
//處理壓縮布局描述的情況疼蛾。
if (extlayoutstr < (const char*)0x1000)
{
//當(dāng)擴(kuò)展布局的值小于0x1000時(shí)則是壓縮的布局描述肛跌,這里分別取出xyz部分的內(nèi)容進(jìn)行重新編碼。
char compactEncoding[4] = {0};
unsigned short xyz = (unsigned short)(extlayoutstr);
unsigned char x = (xyz >> 8) & 0xF;
unsigned char y = (xyz >> 4) & 0xF;
unsigned char z = (xyz >> 0) & 0xF;
int idx = 0;
if (x != 0)
{
x--;
compactEncoding[idx++] = (BLOCK_LAYOUT_STRONG<<4) | x;
}
if (y != 0)
{
y--;
compactEncoding[idx++] = (BLOCK_LAYOUT_BYREF<<4) | y;
}
if (z != 0)
{
z--;
compactEncoding[idx++] = (BLOCK_LAYOUT_WEAK<<4) | z;
}
compactEncoding[idx++] = 0;
extlayoutstr = compactEncoding;
}
unsigned char *blockmemoryAddr = (__bridge void*)block;
int refObjOffset = sizeof(struct Block_layout); //得到外部引用對(duì)象的開始偏移位置察郁。
for (int i = 0; i < strlen(extlayoutstr); i++)
{
//取出字節(jié)中所表示的類型和數(shù)量衍慎。
unsigned char PN = extlayoutstr[i];
int P = (PN >> 4) & 0xF; //P是高4位描述引用的類型。
int N = (PN & 0xF) + 1; //N是低4位描述對(duì)應(yīng)類型的數(shù)量皮钠,這里要加1是因?yàn)楦袷降臄?shù)量是從0個(gè)開始計(jì)算稳捆,也就是當(dāng)N為0時(shí)其實(shí)是代表有1個(gè)此類型的數(shù)量。
//這里只對(duì)類型為3麦轰,4乔夯,5,6四種類型進(jìn)行處理款侵。
if (P >= BLOCK_LAYOUT_STRONG && P <= BLOCK_LAYOUT_UNRETAINED)
{
for (int j = 0; j < N; j++)
{
//因?yàn)橐猛獠康腳_block類型不是一個(gè)OC對(duì)象驯嘱,因此這里跳過BLOCK_LAYOUT_BYREF,
//當(dāng)然如果你只想打印引用外部的BLOCK_LAYOUT_STRONG則可以修改具體的條件。
if (P != BLOCK_LAYOUT_BYREF)
{
//根據(jù)偏移得到引用外部對(duì)象的地址喳坠。并轉(zhuǎn)化為OC對(duì)象鞠评。
void *refObjAddr = *(void**)(blockmemoryAddr + refObjOffset);
id refObj = (__bridge id) refObjAddr;
//打印對(duì)象
NSLog(@"the refObj is:%@ type is:%d",refObj, P);
}
//因?yàn)椴季种斜4娴氖菍?duì)象的指針,所以偏移要加上一個(gè)指針的大小繼續(xù)獲取下一個(gè)偏移壕鹉。
refObjOffset += sizeof(void*);
}
}
}
}
通過上述的代碼我們就可以將一個(gè)block中所持有的所有外部OC對(duì)象都打印出來了剃幌。在實(shí)踐中我們可以將這部分代碼通過方法交換的形式來作為block對(duì)象的日志輸出,比如:
//description方法的實(shí)現(xiàn)
NSString *block_description(id obj, SEL _cmd)
{
showBlockExtendedLayout(obj);
return @"";
}
////////////////////
//針對(duì)NSBlock類型添加一個(gè)自定義的描述信息輸出函數(shù)晾浴。
Class blkcls = NSClassFromString(@"NSBlock");
BOOL bok = class_addMethod(blkcls, @selector(description), block_description, "@@:");
這樣我們就可以在控制臺(tái) 通過 po [xxx description] 的形式來展示一個(gè)block所持有的對(duì)象信息了负乡。
結(jié)尾
既然我們可以通過Xcode 的Debug Memory Graph來查看某個(gè)對(duì)象被哪個(gè)block所引用,而又可以通過文本介紹的方法來查看某個(gè)block對(duì)象引用了哪些對(duì)象脊凰。兩個(gè)方法雙管齊下抖棘,就可以更加愉快的調(diào)試block和內(nèi)存泄漏以及內(nèi)存引用的相關(guān)問題了茂腥。
兩個(gè)有趣的點(diǎn)
在筆者完成這篇文章時(shí),特意在網(wǎng)絡(luò)上搜索了一下是否有同類型或者已經(jīng)實(shí)現(xiàn)了的方法切省,果然有幾篇介紹block持有對(duì)象的文章最岗,內(nèi)心一陣慌亂。點(diǎn)進(jìn)去看后其實(shí)都是在介紹Facebook的FBRetainCycleDetector 是如何實(shí)現(xiàn)block強(qiáng)持有對(duì)象檢測的朝捆“愣桑看了看源代碼,發(fā)現(xiàn)實(shí)現(xiàn)的思路和本文完全不同芙盘,這才放下心來驯用。 總的來Facebook那套是用了一些巧勁來實(shí)現(xiàn)檢測的,而本文則算是比較官方的實(shí)現(xiàn)儒老,而且可檢測的持有對(duì)象類型更加寬泛和通用蝴乔。
在知道block有BLOCK_BYREF_LAYOUT_EXTENDED這么一個(gè)標(biāo)志前,我的一個(gè)老的實(shí)現(xiàn)方法是通過分析block描述中的copy函數(shù)的指令來判斷和獲取擴(kuò)展對(duì)象的偏移量的驮樊。因?yàn)槿绻硞€(gè)block持有了外部對(duì)象時(shí)就必然會(huì)實(shí)現(xiàn)一個(gè)copy函數(shù)來對(duì)所有外部對(duì)象進(jìn)行引用計(jì)數(shù)管理淘这。我當(dāng)時(shí)的方法就是通過分析copy函數(shù)的機(jī)器指令特征,然后通過解析特征指令中的常數(shù)部分來獲取對(duì)象的偏移量的巩剖。下面就是實(shí)現(xiàn)的代碼, 有興趣的讀者可以閱讀一下(需要注意的是下面的代碼只能在真機(jī)上運(yùn)行通過):
/*
* Copyright (c) 歐陽大哥2013. All rights reserved.
* github地址:https://github.com/youngsoft
*/
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
// requires BLOCK_HAS_COPY_DISPOSE
void* copy;
void* dispose;
};
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
void* invoke;
struct Block_descriptor_1 *descriptor;
// imported variables
};
//定義ldr指令結(jié)構(gòu)
struct arm64_ldr_immediate_unsignedoffset
{
uint32_t Rt:5; //目標(biāo)寄存器
uint32_t Rn:5; //源寄存編號(hào)
uint32_t imm12:12; //偏移 = imm12 << size;
uint32_t opc:8; //11100101
uint32_t size:2; //11
};
boolean_t is_arm64_ldr_immediate_unsignedoffset(uint32_t *ins)
{
struct arm64_ldr_immediate_unsignedoffset *vins = (struct arm64_ldr_immediate_unsignedoffset*)ins;
return vins->size == 0b11 && vins->opc == 0b11100101;
}
//定義add立即數(shù)指令結(jié)構(gòu)
struct arm64_add_immediate
{
uint32_t Rd:5; //目標(biāo)
uint32_t Rn:5;
uint32_t imm12:12;
uint32_t shift:2; //00
uint32_t opS:7; //0010001
uint32_t sf:1; //1
};
boolean_t is_arm64_add_immediate(uint32_t *ins)
{
struct arm64_add_immediate *vins = (struct arm64_add_immediate*)ins;
return vins->sf == 0b1 && vins->opS == 0b0010001 && vins->shift == 0b00;
}
//定義mov寄存器指令結(jié)構(gòu)
struct arm64_mov_register
{
uint32_t Rd:5; //目標(biāo)
uint32_t Rn:5; //11111
uint32_t imm6:6; //000000
uint32_t Rm:5; //源
uint32_t opc:10; //0101010000
uint32_t sf:1; //1
};
boolean_t is_arm64_mov_register(uint32_t *ins)
{
struct arm64_mov_register *vins = (struct arm64_mov_register*)ins;
return vins->sf == 0b1 && vins->opc == 0b0101010000 && vins->imm6 == 0b000000 && vins->Rn == 0b11111;
}
//定義函數(shù)調(diào)用指令
struct arm64_bl
{
uint32_t imm26:26;
uint32_t op:6; //100101
};
boolean_t is_arm64_bl(uint32_t *ins)
{
struct arm64_bl *vins = (struct arm64_bl*)ins;
return vins->op == 0b100101;
}
//定義跳轉(zhuǎn)指令
struct arm64_b
{
uint32_t imm26:26;
uint32_t op:6; //000101
};
boolean_t is_arm64_b(uint32_t *ins)
{
struct arm64_b *vins = (struct arm64_b*)ins;
return vins->op == 0b000101;
}
//定義函數(shù)返回指令。
struct arm64_ret
{
uint32_t op:32; //0xd65f03c0
};
boolean_t is_arm64_ret(uint32_t *ins)
{
struct arm64_ret *vins = (struct arm64_ret*)ins;
return vins->op == 0xd65f03c0;
}
//寄存器編號(hào)信息
typedef enum : unsigned char {
REG_X0,
REG_X1,
REG_X2,
REG_X3,
REG_X4,
REG_X5,
REG_X6,
REG_X7,
REG_X8,
REG_X9,
REG_X10,
REG_X11,
REG_X12,
REG_X13,
REG_X14,
REG_X15,
REG_X16,
REG_X17,
REG_X18,
REG_X19,
REG_X20,
REG_X21,
REG_X22,
REG_X23,
REG_X24,
REG_X25,
REG_X26,
REG_X27,
REG_X28,
REG_X29,
REG_X30,
REG_SP
} ARM64_REG;
void showBlockExtendedLayout(id block)
{
static int32_t BLOCK_HAS_COPY_DISPOSE = (1 << 25); // compiler
struct Block_layout *blockLayout = (__bridge struct Block_layout*)(block);
//如果沒有持有附加的對(duì)象則沒有BLOCK_HAS_COPY_DISPOSE這個(gè)特性
if ((blockLayout->flags & BLOCK_HAS_COPY_DISPOSE) != BLOCK_HAS_COPY_DISPOSE)
return;
//定義引用的外部對(duì)象的偏移位置和block的尺寸
//所有外部引用對(duì)象的偏移位置必須>=firstRefObjOffset 并且 < blockSize
int firstRefObjOffset = sizeof(struct Block_layout);
int blockSize = (int)blockLayout->descriptor->size;
//得到block的copy函數(shù)的地址钠怯,并讀取函數(shù)指令內(nèi)容佳魔。
uint32_t *copyfuncAddr = blockLayout->descriptor->copy;
if (copyfuncAddr == NULL)
return;
//讀取地址的內(nèi)容。
int validateRefObjOffsets[40];
int validateRefObjCount = 0;
int validateRefObjOffset = 0;
//定義一個(gè)映射表晦炊。鞠鲜。。key是寄存器断国,value是偏移贤姆。記錄可能候選的偏移量
NSMutableDictionary *regoffsetMap = [NSMutableDictionary new];
unsigned char *pcopyfuncAddr = copyfuncAddr;
while (true)
{
//這里讀取數(shù)據(jù)。然后解析稳衬。
if (is_arm64_ldr_immediate_unsignedoffset(pcopyfuncAddr))
{
//目標(biāo)可以不是x0,這個(gè)要和mov指令結(jié)合霞捡。
struct arm64_ldr_immediate_unsignedoffset *vins = (struct arm64_ldr_immediate_unsignedoffset*)pcopyfuncAddr;
int immediate = vins->imm12 << vins->size;
//必須是范圍內(nèi),并且源不是sp寄存器薄疚。
if (immediate >= firstRefObjOffset && immediate < blockSize && vins->Rn != REG_SP)
{
if (vins->Rt == REG_X0)
{
validateRefObjOffset = immediate;
}
else
{
regoffsetMap[@(vins->Rt)] = @(immediate);
}
}
}
else if (is_arm64_add_immediate(pcopyfuncAddr))
{
//確保目標(biāo)寄存器是x0
struct arm64_add_immediate *vins = (struct arm64_add_immediate*)pcopyfuncAddr;
int immediate = vins->imm12;
if (immediate >= firstRefObjOffset && immediate < blockSize && vins->Rn != REG_SP)
{
if (vins->Rd == REG_X0)
{
validateRefObjOffset = immediate;
}
else
{
regoffsetMap[@(vins->Rd)] = @(immediate);
}
}
}
else if (is_arm64_mov_register(pcopyfuncAddr))
{
//確保目標(biāo)寄存器是x0
struct arm64_mov_register *vins = (struct arm64_mov_register*)pcopyfuncAddr;
if (vins->Rd == REG_X0)
{
//確保源寄存器必須是上面ldr的目標(biāo)寄存器碧信。
NSNumber *num = regoffsetMap[@(vins->Rm)];
if (num != nil)
{
validateRefObjOffset = num.intValue;
}
}
}
else if (is_arm64_bl(pcopyfuncAddr))
{
if (validateRefObjOffset != 0)
{
validateRefObjOffsets[validateRefObjCount++] = validateRefObjOffset;
}
validateRefObjOffset = 0;
[regoffsetMap removeAllObjects];
}
else if (is_arm64_b(pcopyfuncAddr))
{
if (validateRefObjOffset != 0)
{
validateRefObjOffsets[validateRefObjCount++] = validateRefObjOffset;
}
validateRefObjOffset = 0;
[regoffsetMap removeAllObjects];
//當(dāng)末尾是b指令時(shí)也認(rèn)為是函數(shù)結(jié)束
break;
}
else if (is_arm64_ret(pcopyfuncAddr))
{
//函數(shù)結(jié)束,停止遍歷街夭。
break;
}
pcopyfuncAddr += 4;
}
if (validateRefObjCount > 0)
{
//分別打印每個(gè)對(duì)象砰碴。
for (int i = 0; i < validateRefObjCount; i++)
{
unsigned char *blockmemoryAddr = (__bridge void*)block;
void *refObjAddr = *(void**)(blockmemAddr + validateRefObjOffsets[i]);
id refObj = (__bridge id) refObjAddr;
NSLog(@"refObj is:%@ offset:%d",refObj, validateRefObjOffsets[i]);
}
}
}