ClassDump源碼解析

CDClassDump 這個(gè)文件是class-dump的一部分,用于檢查Mach-O文件的Objective-C segment

getopt_long()類似于getopt()都是解析命令行參數(shù)函數(shù),只是getopt用于處理單字母,而getopt_long用于處理長選項(xiàng)。解析出可執(zhí)行路徑之后設(shè)置到classDump.searchPathState.executablePath,然后將路徑文件轉(zhuǎn)成NSData,根據(jù)魔數(shù)判斷可執(zhí)行是否為fat文件,fat文件則需要根據(jù)CDDataCursor拆分出每一個(gè)CDFatArch架構(gòu)文件加入到arches數(shù)組。否則為單一架構(gòu)的CDMachOFile文件,根據(jù)魔數(shù)判斷是大端還是小端以便確定后續(xù)解析規(guī)則。根據(jù)Mach Header4個(gè)字節(jié)的偏移和大小端可以將ncmds,flags等解析出來,接下來就是解析loadCommand:

- (void)_readLoadCommands:(CDMachOFileDataCursor *)cursor count:(uint32_t)count {
        for (uint32_t index = 0; index < count; index++) {
        //根據(jù)游標(biāo)的首個(gè)32位確定coamnd的類型,每一個(gè)case對應(yīng)一個(gè)類,這個(gè)類統(tǒng)一繼承自CDLoadCommand
        CDLoadCommand *loadCommand = [CDLoadCommand loadCommandWithDataCursor:cursor];
        if (loadCommand != nil) {
            [loadCommands addObject:loadCommand];
                        
            //收集部分屬性,如版本等
            if (loadCommand.cmd == LC_VERSION_MIN_MACOSX)                        self.minVersionMacOSX = (CDLCVersionMinimum *)loadCommand;
            if (loadCommand.cmd == LC_VERSION_MIN_IPHONEOS)                      self.minVersionIOS = (CDLCVersionMinimum *)loadCommand;
                        //XXX
                        //設(shè)置segments,符號表等
            if ([loadCommand isKindOfClass:[CDLCSourceVersion class]])           self.sourceVersion = (CDLCSourceVersion *)loadCommand;
            //XXX
            else if ([loadCommand isKindOfClass:[CDLCRunPath class]]) {
                [runPaths addObject:[(CDLCRunPath *)loadCommand resolvedRunPath]];
                [runPathCommands addObject:loadCommand];
            }
        }
    }
      //可以自定義讀取的操作,如輸出dyld bind階段的cocode與立即數(shù):通過 byte & 0xF0 得到 opcode,byte & 0x0F 得到 immediate(立即數(shù)),根據(jù)操作數(shù)(opcode)進(jìn)行分支處理,具體含義如下:
      for (CDLoadCommand *loadCommand in _loadCommands) {
        [loadCommand machOFileDidReadLoadCommands:self];
    }
}
0x00    REBASE_OPCODE_DONE
rebasing 結(jié)束標(biāo)志
0x10    REBASE_OPCODE_SET_TYPE_IMM
立即數(shù)(immediate)設(shè)置為 type,分為以下類型:
REBASE_TYPE_POINTER 1
REBASE_TYPE_TEXT_ABSOLUTE32 2
REBASE_TYPE_TEXT_PCREL32 3
0x20    REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB
立即數(shù)(immediate)設(shè)置為當(dāng)前上下文的指向 segment 索引,從而計(jì)算出當(dāng)前 segment 首地址 segmentStartAddress
當(dāng)前 byte 后的數(shù)據(jù)為 ULEB128 字節(jié)流的值,解碼為相對 segmentStartAddress 的偏移,從而計(jì)算出操作地址 address
0x30    REBASE_OPCODE_ADD_ADDR_ULEB
操作地址 address 向后移動(dòng) ULEB128 數(shù)據(jù)對應(yīng)的值,即 address += read_uleb128(p, end);
0x40    REBASE_OPCODE_ADD_ADDR_IMM_SCALED
操作地址 address 向后移動(dòng)立即數(shù)(immediate)倍數(shù)的指針寬度,即 address += immediate*sizeof(uintptr_t);
0x50    REBASE_OPCODE_DO_REBASE_IMM_TIMES
將立即數(shù)(immediate)作為操作(循環(huán))次數(shù),依次將當(dāng)前操作地址 address 對應(yīng)的值進(jìn)行 rebasing,即,將內(nèi)部的值加上 slide 偏移
每次循環(huán)后操作地址 address 向后移動(dòng)指針寬度的字節(jié),進(jìn)入下一個(gè)需要 rebase 的地址
0x60    REBASE_OPCODE_DO_REBASE_ULEB_TIMES
與上一個(gè) 0x50 值相似,唯一不同點(diǎn)就是立即數(shù)的值替換為 ULEB128 的值進(jìn)行循環(huán)操作,這意為著需要 rebase 的地址超過了 4 位數(shù)能表示的最大值,即超過 16(0x0F)個(gè).
0x70    REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB
根據(jù)上下文數(shù)據(jù)執(zhí)行 rebase 操作
隨后操作地址 address 向后移動(dòng),偏移值為 ULEB128 加一個(gè)指針寬度的值,即 address += read_uleb128(p, end) + sizeof(uintptr_t);
0x80    REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB
連續(xù)讀取兩個(gè) ULEB128 值,依次為循環(huán)次數(shù) count 和跳過的字節(jié)數(shù) skip
執(zhí)行循環(huán),根據(jù)之前得出的上下文數(shù)據(jù)執(zhí)行 rebasing
操作地址 address 向后移動(dòng) skip 加指針寬度的偏移量,即 address += skip + sizeof(uintptr_t);
0x00    BIND_OPCODE_DONE    
binding 結(jié)束標(biāo)志
0x10     BIND_OPCODE_SET_DYLIB_ORDINAL_IMM
立即數(shù)(immediate)設(shè)置為依賴庫索引 Ordinal,即 Load command 中的 LC_LOAD_DYLIB 按順序排列的庫,
0x20    BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB
將隨后的 ULEB128 值設(shè)置為依賴庫索引 Ordinal
0x30    BIND_OPCODE_SET_DYLIB_SPECIAL_IMM
根據(jù)立即數(shù)計(jì)算索引 Ordinal
0x0為self,0xf(-1)為main executable,0xe(-2)為flat lookup。
0x40    BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM
從 byte 后獲取以 \0 結(jié)尾的符號名字符串
立即數(shù)作為符號的標(biāo)志(flag)
0x50    BIND_OPCODE_SET_TYPE_IMM
立即數(shù)(immediate)設(shè)置為 type,分為以下類型:
BIND_TYPE_POINTER 1
BIND_TYPE_TEXT_ABSOLUTE32 2
BIND_TYPE_TEXT_PCREL32 3
0x60    BIND_OPCODE_SET_ADDEND_SLEB
設(shè)置上下文的加數(shù)(addend)為隨后的 SLEB128 值
0x70    BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB
立即數(shù)(immediate)設(shè)置為當(dāng)前上下文的 segment 索引,從而計(jì)算出當(dāng)前 segment 首地址 segmentStartAddress
將隨后的 ULEB128 字節(jié)流的值作為 segmentStartAddress 的偏移,從而計(jì)算出操作地址 address
0x80    BIND_OPCODE_ADD_ADDR_ULEB
操作地址 address 向后移動(dòng) ULEB128 數(shù)據(jù)對應(yīng)的值,即 address += read_uleb128(p, end);
0x90    BIND_OPCODE_DO_BIND 
利用之前計(jì)算的上下文數(shù)據(jù)執(zhí)行 binding
操作地址 address 向后移動(dòng)一個(gè)指針寬度
0xA0    BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB
利用之前計(jì)算的上下文數(shù)據(jù)執(zhí)行 binding
操作地址 address 向后移動(dòng) ULEB128 的值加一個(gè)指針寬度
0xB0    BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB
利用之前計(jì)算的上下文數(shù)據(jù)執(zhí)行 binding
操作地址 address 向后移動(dòng)立即數(shù)倍數(shù)的指針寬度(immediate*sizeof(intptr_t))再加一個(gè)指針寬度
0xC0    BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB
連續(xù)讀取兩個(gè) ULEB128 值,先后為循環(huán)次數(shù) count 和跳過的字節(jié)數(shù) skip
執(zhí)行循環(huán),根據(jù)上下文數(shù)據(jù)執(zhí)行 binding 操作
操作地址 address 向后移動(dòng) skip 加指針寬度的偏移量,即 address += skip + sizeof(uintptr_t);
- (void)logBindOps:(const uint8_t *)start end:(const uint8_t *)end isLazy:(BOOL)isLazy;
{
            //根據(jù)opcode的類型,如此處對binding的符號以符號地址作為key,符號名作為value,進(jìn)行收集以便后面對dyld符號進(jìn)行使用和設(shè)置
            case BIND_OPCODE_DO_BIND:
                if (debugBindOps) NSLog(@"BIND_OPCODE: DO_BIND");
                [self bindAddress:address type:type symbolName:symbolName flags:symbolFlags addend:addend libraryOrdinal:libraryOrdinal];
                address += _ptrSize;
                bindCount++;
                break;
}

- (void)bindAddress:(uint64_t)address type:(uint8_t)type symbolName:(const char *)symbolName flags:(uint8_t)flags
             addend:(int64_t)addend libraryOrdinal:(int64_t)libraryOrdinal;
{
    NSNumber *key = [NSNumber numberWithUnsignedInteger:address]; // I don't think 32-bit will dump 64-bit stuff.
    NSString *str = [[NSString alloc] initWithUTF8String:symbolName];
    _symbolNamesByAddress[key] = str;
}

section獲?。?/p>

在loadCommandWithDataCursor方法中通過segment類型得到對應(yīng)的子類實(shí)現(xiàn),如0x19對應(yīng)的LC_SEGMENT_64:CDLCSegment即為section header的獲?。?/p>

- (id)initWithDataCursor:(CDMachOFileDataCursor *)cursor;
{
    //根據(jù)游標(biāo)位置獲取對應(yīng)section header所在的偏移,然后依次設(shè)置header信息
    if ((self = [super initWithDataCursor:cursor])) {
        _segmentCommand.cmd     = [cursor readInt32];
        _segmentCommand.cmdsize = [cursor readInt32];
        
        _name = [cursor readStringOfLength:16 encoding:NSASCIIStringEncoding];
        size_t nameLength = [_name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
        memcpy(_segmentCommand.segname, [_name UTF8String], MIN(sizeof(_segmentCommand.segname), nameLength));
        _segmentCommand.vmaddr   = [cursor readPtr];
        _segmentCommand.vmsize   = [cursor readPtr];
        _segmentCommand.fileoff  = [cursor readPtr];
        _segmentCommand.filesize = [cursor readPtr];
        _segmentCommand.maxprot  = [cursor readInt32];
        _segmentCommand.initprot = [cursor readInt32];
        _segmentCommand.nsects   = [cursor readInt32];
        _segmentCommand.flags    = [cursor readInt32];
        
        //讀取number of sections個(gè)數(shù),依次設(shè)置__TEXT, __DATA等segment下的具體section header,原理相同,具體數(shù)據(jù)結(jié)構(gòu)參考header的addr,size,offset等長度進(jìn)行解析
        NSMutableArray *sections = [[NSMutableArray alloc] init];
        for (NSUInteger index = 0; index < _segmentCommand.nsects; index++) {
            CDSection *section = [[CDSection alloc] initWithDataCursor:cursor segment:self];
            [sections addObject:section];
        }
        //把segment的nsects對應(yīng)的section header解析出來之后,和當(dāng)前segment進(jìn)行一個(gè)綁定,但此時(shí)真正的section data為空
        _sections = [sections copy];
    }
    return self;
}

經(jīng)過這一步結(jié)束,那一個(gè)CDMachOFile可執(zhí)行的結(jié)構(gòu)已經(jīng)搭建出來了,里面包含mach header對應(yīng)的數(shù)據(jù),load command數(shù)據(jù),segment和對應(yīng)section的頭等。

處理OC數(shù)據(jù):

- (void)processObjectiveCData;
{
    for (CDMachOFile *machOFile in self.machOFiles) {
        //processorClass首先會(huì)根據(jù)是否存在__objc_imageinfo section來決定使用不同的處理方式CDObjectiveC2Processor,__objc_imageinfo節(jié)可以看作是用于區(qū)分Objective-C 1.0與2.0,舊版是沒有這個(gè)節(jié)的
        CDObjectiveCProcessor *processor = [[[machOFile processorClass] alloc] initWithMachOFile:machOFile];
        [processor process];
        [_objcProcessors addObject:processor];
    }
}

- (void)process;
{
    //根據(jù)是否存在CDLCEncryptionInfo加密段以及狀態(tài)進(jìn)行判斷能否導(dǎo)出
    if (self.machOFile.isEncrypted == NO && self.machOFile.canDecryptAllSegments) {
        [self.machOFile.symbolTable loadSymbols];
        [self.machOFile.dynamicSymbolTable loadSymbols];

        [self loadProtocols];
        [self.protocolUniquer createUniquedProtocols];

        // Load classes before categories, so we can get a dictionary of classes by address.
        [self loadClasses];
        [self loadCategories];
    }
}

加載符號loadSymbols,分為靜態(tài)符號和動(dòng)態(tài)符號兩步,先看靜態(tài)符號表的處理:

- (void)loadSymbols;
{
    for (CDLoadCommand *loadCommand in [self.machOFile loadCommands]) {
        if ([loadCommand isKindOfClass:[CDLCSegment class]]) {
            CDLCSegment *segment = (CDLCSegment *)loadCommand;
            //每一個(gè) segment 的 VP (Virtual Page) 都根據(jù) initprot 進(jìn)行初始化,initprot 指定了如何通過讀/寫/執(zhí)行位初始化頁面的保護(hù)級別(4=r,2=w,1=x)
            if (([segment initprot] & CD_VM_PROT_RW) == CD_VM_PROT_RW) {
                //可讀可寫存在于__DATA段
                //NSLog(@"segment... initprot = %08x, addr= %016lx *** r/w", [segment initprot], [segment vmaddr]);
                _baseAddress = [segment vmaddr];
                _flags.didFindBaseAddress = YES;
                break;
            }
        }
    }
    
    NSMutableArray *symbols = [[NSMutableArray alloc] init];
    NSMutableDictionary *classSymbols = [[NSMutableDictionary alloc] init];
    NSMutableDictionary *externalClassSymbols = [[NSMutableDictionary alloc] init];

    CDMachOFileDataCursor *cursor = [[CDMachOFileDataCursor alloc] initWithFile:self.machOFile offset:_symtabCommand.symoff];
    //NSLog(@"offset= %lu", [cursor offset]);
    //NSLog(@"stroff=  %lu", symtabCommand.stroff);
    //NSLog(@"strsize= %lu", symtabCommand.strsize);
    
    //根據(jù)symtabCommand.stroff找到字符串表的起始位置([self.machOFile.data bytes] 即為可執(zhí)行的起始偏移對應(yīng)的指針地址)
    const char *strtab = (char *)[self.machOFile.data bytes] + _symtabCommand.stroff;
    
    void (^addSymbol)(NSString *, CDSymbol *) = ^(NSString *name, CDSymbol *symbol) {
        [symbols addObject:symbol];
        //根據(jù)_OBJC_CLASS_$_獲取對應(yīng)的className
        NSString *className = [CDSymbol classNameFromSymbolName:symbol.name];
        if (className != nil) {
            //根據(jù)地址是否存在判斷符號是否為外部符號
            if (symbol.value != 0)
                classSymbols[className] = symbol;
            else
                externalClassSymbols[className] = symbol;
        }
    };

    if (![self.machOFile uses64BitABI]) {
        //XXX
    } else {
        /*
         遍歷符號表讀取nlist,符號的數(shù)據(jù)結(jié)構(gòu)如下:
         struct nlist_64 {
             union {
                 uint32_t  n_strx; /在 string table 中的索引/
             } n_un;
             uint8_t n_type;        符號類型,8bit的復(fù)合字段
             uint8_t n_sect;        符號所在的 section index(內(nèi)部符號有效值從 1 開始,最大為 255)
             uint16_t n_desc;       用來標(biāo)識重定義符的特性,比如是否lazy bind
             uint64_t n_value;      符號的地址值(在鏈接過程中,會(huì)隨著其 section 發(fā)生變化)
         };
         **/

        for (uint32_t index = 0; index < _symtabCommand.nsyms; index++) {
            struct nlist_64 nlist;

            nlist.n_un.n_strx = [cursor readInt32];
            nlist.n_type      = [cursor readByte];
            nlist.n_sect      = [cursor readByte];
            nlist.n_desc      = [cursor readInt16];
            nlist.n_value     = [cursor readInt64];

            //nlist.n_un.n_strx即為字符串表的下標(biāo),那么拿到strtab加上所在的下標(biāo)即為符號名
            const char *ptr = strtab + nlist.n_un.n_strx;
            NSString *str = [[NSString alloc] initWithBytes:ptr length:strlen(ptr) encoding:NSASCIIStringEncoding];
            CDSymbol *symbol = [[CDSymbol alloc] initWithName:str machOFile:self.machOFile nlist64:nlist];
            //將字符串和符號對象進(jìn)行綁定,回到上一步的block實(shí)現(xiàn)
            addSymbol(str, symbol);
        }

        //NSLog(@"Loaded %lu 64-bit symbols", [symbols count]);
    }
    // 最后加入到符號數(shù)組中
    _symbols = [symbols copy];
    _classSymbols = [classSymbols copy];
    _externalClassSymbols = [externalClassSymbols copy];

    //NSLog(@"symbols: %@", _symbols);
}

動(dòng)態(tài)符號表解析,先看下dysymtab的定義:

//動(dòng)態(tài)符號
struct dysymtab_command {
    uint32_t cmd;   /* LC_DYSYMTAB */
    uint32_t cmdsize;   /* sizeof(struct dysymtab_command) */
    uint32_t ilocalsym; /* index to local symbols */
    uint32_t nlocalsym; /* number of local symbols */
    uint32_t iextdefsym;/* index to externally defined symbols */
    uint32_t nextdefsym;/* number of externally defined symbols */
    uint32_t iundefsym; /* index to undefined symbols */
    uint32_t nundefsym; /* number of undefined symbols */
    uint32_t tocoff;    /* file offset to table of contents */
    uint32_t ntoc;  /* number of entries in table of contents */
    uint32_t modtaboff; /* file offset to module table */
    uint32_t nmodtab;   /* number of module table entries */
    uint32_t extrefsymoff;  /* offset to referenced symbol table */
    uint32_t nextrefsyms;   /* number of referenced symbol table entries */
    uint32_t indirectsymoff; /* file offset to the indirect symbol table */
    uint32_t nindirectsyms;  /* number of indirect symbol table entries */
    uint32_t extreloff; /* offset to external relocation entries */
    uint32_t nextrel;   /* number of external relocation entries */
    uint32_t locreloff; /* offset to local relocation entries */
    uint32_t nlocrel;   /* number of local relocation entries */
}

1.ilocalsym、iextdefsym、iundefsym把符號表分為三個(gè)區(qū)域,ilocalsym 本地符號僅用于調(diào)試,iextdefsym可執(zhí)行文件定義的符號,iundefsym可執(zhí)行文件里沒有定義的 
2.tocoff,目錄偏移,該內(nèi)容只有在動(dòng)態(tài)分享庫中存在。主要作用是把符號和定義它的模塊對應(yīng)起來。
3.modtaboff:為了支持“模塊”(整個(gè)對象文件)的動(dòng)態(tài)綁定,符號表必須知道文件創(chuàng)建的模塊。該內(nèi)容只有在動(dòng)態(tài)分享庫中存在。
4.extrefsymoff:為了支持動(dòng)態(tài)綁定模塊,每個(gè)模塊都有一個(gè)引用符號表,符號表里存放著每個(gè)模塊所引用的符號(定義的和沒有定義的)。該表指針動(dòng)態(tài)庫中存在。
5.indirectsymoff:如果 section 中有符號指針或者樁(stub),section中的reserved1存放該表的下標(biāo)。間接符號表,只是存放一些32位下標(biāo),這些下標(biāo)執(zhí)行符號表。
6.extreloff:每個(gè)模塊都有一個(gè)重定位外部符號表。僅在動(dòng)態(tài)庫中存在
7.locreloff:重定位本地符號表,由于只是在調(diào)試中用,所以不必增加模塊分組
//重定向?qū)嶓w:
struct relocation_info {
   int32_t  r_address;  /* offset in the section to what is being
                   relocated */
   uint32_t     r_symbolnum:24, /* symbol index if r_extern == 1 or section
                   ordinal if r_extern == 0 */
        r_pcrel:1,  /* was relocated pc relative already */
        r_length:2, /* 0=byte, 1=word, 2=long, 3=quad */
        r_extern:1, /* does not include value of sym referenced */
        r_type:4;   /* if not 0, machine specific relocation type */
};

r_address和r_length字段描述了需要被 relocation 的字節(jié)范圍,其中r_address是相對于 section 的偏移量
r_pcrel表示地址值是 PC 相對地址值
r_extern標(biāo)記該符號是否是外部符號
r_symbolnum,index 值,對于外部符號,它描述了符號在 symbol table 中的位置;如果是內(nèi)部符號,它描述了符號所在的 section 的index
r_type,符號類型
- (void)loadSymbols;
{
    NSMutableArray *externalRelocationEntries = [[NSMutableArray alloc] init];
    //游標(biāo)置位到外部重定向表的偏移位置
    CDMachOFileDataCursor *cursor = [[CDMachOFileDataCursor alloc] initWithFile:self.machOFile offset:_dysymtab.extreloff];
    //遍歷外部重定向?qū)嶓w條數(shù)
    for (uint32_t index = 0; index < _dysymtab.nextrel; index++) {
        struct relocation_info rinfo;
        //讀取重定向?qū)嶓w
        rinfo.r_address = [cursor readInt32];
        uint32_t val    = [cursor readInt32];

        rinfo.r_symbolnum = val & 0x00ffffff;
        rinfo.r_pcrel     = (val & 0x01000000) >> 24;
        rinfo.r_length    = (val & 0x06000000) >> 25;
        rinfo.r_extern    = (val & 0x08000000) >> 27;
        rinfo.r_type      = (val & 0xf0000000) >> 28;
        CDRelocationInfo *ri = [[CDRelocationInfo alloc] initWithInfo:rinfo];
        [externalRelocationEntries addObject:ri];
    }
    //收集重定向表
    _externalRelocationEntries = [externalRelocationEntries copy];
}

加載協(xié)議loadProtocols,通過解析__objc_protolist section:

- (CDOCProtocol *)protocolAtAddress:(uint64_t)address;
{
    if (address == 0)
        return nil;
    //根據(jù)地址從字典中取出CDOCProtocol對象
    CDOCProtocol *protocol = [self.protocolUniquer protocolWithAddress:address];
    if (protocol == nil) {
        //不存在則設(shè)置進(jìn)
        protocol = [[CDOCProtocol alloc] init];
        [self.protocolUniquer setProtocol:protocol withAddress:address];
        
        //從游標(biāo)位置開始讀取cd_objc2_protocol結(jié)構(gòu)記錄協(xié)議在mach-o中的信息
        CDMachOFileDataCursor *cursor = [[CDMachOFileDataCursor alloc] initWithFile:self.machOFile address:address];
        NSParameterAssert([cursor offset] != 0);
        
        struct cd_objc2_protocol objc2Protocol;
        objc2Protocol.isa                     = [cursor readPtr];
        objc2Protocol.name                    = [cursor readPtr];
        objc2Protocol.protocols               = [cursor readPtr];
        objc2Protocol.instanceMethods         = [cursor readPtr];
        objc2Protocol.classMethods            = [cursor readPtr];
        objc2Protocol.optionalInstanceMethods = [cursor readPtr];
        objc2Protocol.optionalClassMethods    = [cursor readPtr];
        objc2Protocol.instanceProperties      = [cursor readPtr];
        objc2Protocol.size                    = [cursor readInt32];
        objc2Protocol.flags                   = [cursor readInt32];
        objc2Protocol.extendedMethodTypes     = 0;
        
        CDMachOFileDataCursor *extendedMethodTypesCursor = nil;
        //8 * ptr + 2 * i32 即cd_objc2_protocol結(jié)構(gòu)體的前10個(gè)屬性大小,大于則表示extendedMethodTypes存在
        BOOL hasExtendedMethodTypesField = objc2Protocol.size > 8 * [self.machOFile ptrSize] + 2 * sizeof(uint32_t);
        if (hasExtendedMethodTypesField) {
            objc2Protocol.extendedMethodTypes = [cursor readPtr];
            if (objc2Protocol.extendedMethodTypes != 0) {
                extendedMethodTypesCursor = [[CDMachOFileDataCursor alloc] initWithFile:self.machOFile address:objc2Protocol.extendedMethodTypes];
                NSParameterAssert([extendedMethodTypesCursor offset] != 0);
            }
        }
        
        //NSLog(@"----------------------------------------");
        //NSLog(@"%016lx %016lx %016lx %016lx", objc2Protocol.isa, objc2Protocol.name, objc2Protocol.protocols, objc2Protocol.instanceMethods);
        //NSLog(@"%016lx %016lx %016lx %016lx", objc2Protocol.classMethods, objc2Protocol.optionalInstanceMethods, objc2Protocol.optionalClassMethods, objc2Protocol.instanceProperties);
        
        //根據(jù)所在偏移地址拿到內(nèi)容
        NSString *str = [self.machOFile stringAtAddress:objc2Protocol.name];
        [protocol setName:str];
        
        if (objc2Protocol.protocols != 0) {
            [cursor setAddress:objc2Protocol.protocols];
            uint64_t count = [cursor readPtr];
            for (uint64_t index = 0; index < count; index++) {
                uint64_t val = [cursor readPtr];
                CDOCProtocol *anotherProtocol = [self protocolAtAddress:val];
                if (anotherProtocol != nil) {
                    [protocol addProtocol:anotherProtocol];
                } else {
                    NSLog(@"Note: another protocol was nil.");
                }
            }
        }
        
        //instanceMethods,classMethods,optionalInstanceMethods,optionalClassMethods都是指向一個(gè)cd_objc2_list_header結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體的count如果大于0,則表示存在cd_objc2_method具體方法,而這個(gè)結(jié)構(gòu)體即方法name,type,imp的組合。然后根據(jù)地址拿到name,type在文件中的偏移創(chuàng)建CDOCMethod實(shí)例加入到數(shù)組倒序并返回
        for (CDOCMethod *method in [self loadMethodsAtAddress:objc2Protocol.instanceMethods extendedMethodTypesCursor:extendedMethodTypesCursor])
            [protocol addInstanceMethod:method];
        
        for (CDOCMethod *method in [self loadMethodsAtAddress:objc2Protocol.classMethods extendedMethodTypesCursor:extendedMethodTypesCursor])
            [protocol addClassMethod:method];
        
        for (CDOCMethod *method in [self loadMethodsAtAddress:objc2Protocol.optionalInstanceMethods extendedMethodTypesCursor:extendedMethodTypesCursor])
            [protocol addOptionalInstanceMethod:method];
        
        for (CDOCMethod *method in [self loadMethodsAtAddress:objc2Protocol.optionalClassMethods extendedMethodTypesCursor:extendedMethodTypesCursor])
            [protocol addOptionalClassMethod:method];
        //屬性列表實(shí)現(xiàn)類似,不同的是根據(jù)cd_objc2_property結(jié)構(gòu)體name,attributes創(chuàng)建CDOCProperty實(shí)例數(shù)組
        for (CDOCProperty *property in [self loadPropertiesAtAddress:objc2Protocol.instanceProperties])
            [protocol addProperty:property];
    }
    
    return protocol;
}

然后通過createUniquedProtocols方法對字典進(jìn)行排序并更新到_uniqueProtocolsByAddress,并把協(xié)議中的實(shí)例方法和類方法以及屬性進(jìn)行抽取分類。

加載類,loadClasses方法通過讀取__objc_protolist節(jié):

- (void)loadClasses;
{
    CDSection *section = [[self.machOFile dataConstSegment] sectionWithName:@"__objc_classlist"];
    
    CDMachOFileDataCursor *cursor = [[CDMachOFileDataCursor alloc] initWithSection:section];
    while ([cursor isAtEnd] == NO) {
        uint64_t val = [cursor readPtr];
        //循環(huán)讀取處理單個(gè)類,調(diào)用在下一個(gè)方法
        CDOCClass *aClass = [self loadClassAtAddress:val];
        if (aClass != nil) {
            //緩存類信息
            [self addClass:aClass withAddress:val];
        }
    }
}

- (CDOCClass *)loadClassAtAddress:(uint64_t)address;
{
    if (address == 0)
        return nil;
    
    CDOCClass *class = [self classWithAddress:address];
    if (class)
        return class;
    
    //NSLog(@"%s, address=%016lx", __cmd, address);
    
    CDMachOFileDataCursor *cursor = [[CDMachOFileDataCursor alloc] initWithFile:self.machOFile address:address];
    NSParameterAssert([cursor offset] != 0);
    
    //讀取cd_objc2_class結(jié)構(gòu)體,data為指向class_ro_t的指針
    struct cd_objc2_class objc2Class;
    objc2Class.isa        = [cursor readPtr];
    objc2Class.superclass = [cursor readPtr];
    objc2Class.cache      = [cursor readPtr];
    objc2Class.vtable     = [cursor readPtr];

    uint64_t value        = [cursor readPtr];
    class.isSwiftClass    = (value & 0x1) != 0;
    objc2Class.data       = value & ~7;

    objc2Class.reserved1  = [cursor readPtr];
    objc2Class.reserved2  = [cursor readPtr];
    objc2Class.reserved3  = [cursor readPtr];
    //NSLog(@"%016lx %016lx %016lx %016lx", objc2Class.isa, objc2Class.superclass, objc2Class.cache, objc2Class.vtable);
    //NSLog(@"%016lx %016lx %016lx %016lx", objc2Class.data, objc2Class.reserved1, objc2Class.reserved2, objc2Class.reserved3);
    
    NSParameterAssert(objc2Class.data != 0);
    
    //將游標(biāo)置為data在可執(zhí)行的偏移并構(gòu)造cd_objc2_class_ro_t,查看可執(zhí)行可得知具體的數(shù)據(jù)位于__objc_const節(jié),而這一個(gè)實(shí)體的條目前2個(gè)偏移數(shù)據(jù)是一個(gè)cd_objc2_list_header結(jié)構(gòu)體,內(nèi)部包含2個(gè)i32的屬性,表示size和count,通過其可以算出總共的方法數(shù)和占用的空間大小
    [cursor setAddress:objc2Class.data];
    struct cd_objc2_class_ro_t objc2ClassData;
    objc2ClassData.flags         = [cursor readInt32];
    objc2ClassData.instanceStart = [cursor readInt32];
    objc2ClassData.instanceSize  = [cursor readInt32];
    if ([self.machOFile uses64BitABI])
        objc2ClassData.reserved  = [cursor readInt32];
    else
        objc2ClassData.reserved = 0;
    
    objc2ClassData.ivarLayout     = [cursor readPtr];
    objc2ClassData.name           = [cursor readPtr];
    objc2ClassData.baseMethods    = [cursor readPtr];
    objc2ClassData.baseProtocols  = [cursor readPtr];
    objc2ClassData.ivars          = [cursor readPtr];
    objc2ClassData.weakIvarLayout = [cursor readPtr];
    objc2ClassData.baseProperties = [cursor readPtr];
    
    //NSLog(@"%08x %08x %08x %08x", objc2ClassData.flags, objc2ClassData.instanceStart, objc2ClassData.instanceSize, objc2ClassData.reserved);
    
    //NSLog(@"%016lx %016lx %016lx %016lx", objc2ClassData.ivarLayout, objc2ClassData.name, objc2ClassData.baseMethods, objc2ClassData.baseProtocols);
    //NSLog(@"%016lx %016lx %016lx %016lx", objc2ClassData.ivars, objc2ClassData.weakIvarLayout, objc2ClassData.baseProperties);
    NSString *str = [self.machOFile stringAtAddress:objc2ClassData.name];
    //NSLog(@"name = %@", str);
    
    CDOCClass *aClass = [[CDOCClass alloc] init];
    [aClass setName:str];
    
    //收集實(shí)例方法,通過cd_objc2_class_ro_t.baseMethods偏移取出cd_objc2_method創(chuàng)建CDOCMethod并加入集合
    for (CDOCMethod *method in [self loadMethodsAtAddress:objc2ClassData.baseMethods])
        [aClass addInstanceMethod:method];
    
    //根據(jù)ivars的偏移取出cd_objc2_ivar構(gòu)造CDOCInstanceVariable并加入集合
    aClass.instanceVariables = [self loadIvarsAtAddress:objc2ClassData.ivars];
    
    {
        //根據(jù)類名從之前構(gòu)造的符號映射表中取出符號進(jìn)行設(shè)置
        CDSymbol *classSymbol = [[self.machOFile symbolTable] symbolForClassName:str];
        
        if (classSymbol != nil)
            aClass.isExported = [classSymbol isExternal];
    }
    
    {
        uint64_t classNameAddress = address + [self.machOFile ptrSize];
        
        NSString *superClassName = nil;
        if ([self.machOFile hasRelocationEntryForAddress2:classNameAddress]) {
            //根據(jù)地址從dyldInfo中取出superclassname
            superClassName = [self.machOFile externalClassNameForAddress2:classNameAddress];
            //NSLog(@"class: got external class name (2): %@", [aClass superClassName]);
        } else if ([self.machOFile hasRelocationEntryForAddress:classNameAddress]) {
            //否則根據(jù)地址從dynamicSymbolTable中取出superclassname
            superClassName = [self.machOFile externalClassNameForAddress:classNameAddress];
            //NSLog(@"class: got external class name (1): %@", [aClass superClassName]);
        } else if (objc2Class.superclass != 0) {
            //如果父類雀食存在而符號無法找到,則最后通過cd_objc2_class的superclass數(shù)據(jù)所在的偏移去重新檢索類關(guān)系
            CDOCClass *sc = [self loadClassAtAddress:objc2Class.superclass];
            aClass.superClassRef = [[CDOCClassReference alloc] initWithClassObject:sc];
        }
        
        if (superClassName) {
            //如果父類存在,則找到其對應(yīng)的符號進(jìn)行引用關(guān)系綁定
            CDSymbol *superClassSymbol = [[self.machOFile symbolTable] symbolForExternalClassName:superClassName];
            if (superClassSymbol)
                aClass.superClassRef = [[CDOCClassReference alloc] initWithClassSymbol:superClassSymbol];
            else
                aClass.superClassRef = [[CDOCClassReference alloc] initWithClassName:superClassName];
        }
    }
    
    //收集類方法,規(guī)則是先通過isa收集到元類,然后cd_objc2_class.data --> cd_objc2_class_ro_t.baseMethods 最后拿到cd_objc2_list_header.count得到cd_objc2_method構(gòu)造出CDOCMethod數(shù)組
    for (CDOCMethod *method in [self loadMethodsOfMetaClassAtAddress:objc2Class.isa])
        [aClass addClassMethod:method];
    
    // Process protocols 拿到cd_objc2_class_ro_t.baseProtocols在_uniqueProtocolsByAddress中收集到的協(xié)議
    for (CDOCProtocol *protocol in [self.protocolUniquer uniqueProtocolsAtAddresses:[self protocolAddressListAtAddress:objc2ClassData.baseProtocols]])
        [aClass addProtocol:protocol];
    
    //cd_objc2_class_ro_t.baseProperties -> cd_objc2_list_header : cd_objc2_property 到屬性列表
    for (CDOCProperty *property in [self loadPropertiesAtAddress:objc2ClassData.baseProperties])
        [aClass addProperty:property];
    
    return aClass;
}

loadCategories:幾乎與類方法的處理方式一致讀取__objc_catlist節(jié)然后進(jìn)行l(wèi)oadCategoryAtAddress處理,重點(diǎn)在于cd_objc2_category結(jié)構(gòu)體,按8字節(jié)解析之后可以獲取到instanceMethods,classMethods等的偏移,然后得到對應(yīng)的方法:

- (void)loadCategories;
{
    CDSection *section = [[self.machOFile dataConstSegment] sectionWithName:@"__objc_catlist"];
    
    CDMachOFileDataCursor *cursor = [[CDMachOFileDataCursor alloc] initWithSection:section];
    while ([cursor isAtEnd] == NO) {
        CDOCCategory *category = [self loadCategoryAtAddress:[cursor readPtr]];
        [self addCategory:category];
    }
}

struct cd_objc2_category {
    uint64_t name;
    uint64_t class;
    uint64_t instanceMethods;
    uint64_t classMethods;
    uint64_t protocols;
    uint64_t instanceProperties;
    uint64_t v7;
    uint64_t v8;
};

最后通過創(chuàng)建Visitor然后遍歷進(jìn)行字符串的拼接輸出到指定路徑:

    CDMultiFileVisitor *multiFileVisitor = [[CDMultiFileVisitor alloc] init];
    multiFileVisitor.classDump = classDump;
    classDump.typeController.delegate = multiFileVisitor;
    multiFileVisitor.outputPath = outputPath;
    [classDump recursivelyVisit:multiFileVisitor];
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容