一種查看Block中引用的所有外部對(duì)象的實(shí)現(xiàn)方法

在我的前一篇文章: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)

  1. 在筆者完成這篇文章時(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ì)象類型更加寬泛和通用蝴乔。

  2. 在知道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]);
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市板丽,隨后出現(xiàn)的幾起案子呈枉,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件猖辫,死亡現(xiàn)場離奇詭異酥泞,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)住册,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門婶博,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人荧飞,你說我怎么就攤上這事凡人。” “怎么了叹阔?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵挠轴,是天一觀的道長。 經(jīng)常有香客問我耳幢,道長岸晦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任睛藻,我火速辦了婚禮启上,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘店印。我一直安慰自己冈在,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布按摘。 她就那樣靜靜地躺著包券,像睡著了一般。 火紅的嫁衣襯著肌膚如雪炫贤。 梳的紋絲不亂的頭發(fā)上溅固,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音兰珍,去河邊找鬼侍郭。 笑死,一個(gè)胖子當(dāng)著我的面吹牛掠河,可吹牛的內(nèi)容都是我干的励幼。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼口柳,長吁一口氣:“原來是場噩夢啊……” “哼苹粟!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起跃闹,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤嵌削,失蹤者是張志新(化名)和其女友劉穎毛好,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體苛秕,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肌访,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了艇劫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吼驶。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖店煞,靈堂內(nèi)的尸體忽然破棺而出蟹演,到底是詐尸還是另有隱情,我是刑警寧澤顷蟀,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布酒请,位于F島的核電站,受9級(jí)特大地震影響鸣个,放射性物質(zhì)發(fā)生泄漏羞反。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一囤萤、第九天 我趴在偏房一處隱蔽的房頂上張望昼窗。 院中可真熱鬧,春花似錦涛舍、人聲如沸澄惊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至窘哈,卻和暖如春吹榴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背滚婉。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國打工图筹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人让腹。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓远剩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親骇窍。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瓜晤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

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