ClassDump源码解析

CDClassDump 这个文件是class-dump的一部分,用于检查Mach-O文件的Objective-C segment

getopt_long()类似于getopt()都是解析命令行参数函数,只是getopt用于处理单字母,而getopt_long用于处理长选项。解析出可执行路径之后设置到classDump.searchPathState.executablePath,然后将路径文件转成NSData,根据魔数判断可执行是否为fat文件,fat文件则需要根据CDDataCursor拆分出每一个CDFatArch架构文件加入到arches数组。否则为单一架构的CDMachOFile文件,根据魔数判断是大端还是小端以便确定后续解析规则。根据Mach Header4个字节的偏移和大小端可以将ncmds,flags等解析出来,接下来就是解析loadCommand:

- (void)_readLoadCommands:(CDMachOFileDataCursor *)cursor count:(uint32_t)count {
        for (uint32_t index = 0; index < count; index++) {
        //根据游标的首个32位确定coamnd的类型,每一个case对应一个类,这个类统一继承自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
                        //设置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与立即数:通过 byte & 0xF0 得到 opcode,byte & 0x0F 得到 immediate(立即数),根据操作数(opcode)进行分支处理,具体含义如下:
      for (CDLoadCommand *loadCommand in _loadCommands) {
        [loadCommand machOFileDidReadLoadCommands:self];
    }
}
0x00    REBASE_OPCODE_DONE
rebasing 结束标志
0x10    REBASE_OPCODE_SET_TYPE_IMM
立即数(immediate)设置为 type,分为以下类型:
REBASE_TYPE_POINTER 1
REBASE_TYPE_TEXT_ABSOLUTE32 2
REBASE_TYPE_TEXT_PCREL32 3
0x20    REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB
立即数(immediate)设置为当前上下文的指向 segment 索引,从而计算出当前 segment 首地址 segmentStartAddress
当前 byte 后的数据为 ULEB128 字节流的值,解码为相对 segmentStartAddress 的偏移,从而计算出操作地址 address
0x30    REBASE_OPCODE_ADD_ADDR_ULEB
操作地址 address 向后移动 ULEB128 数据对应的值,即 address += read_uleb128(p, end);
0x40    REBASE_OPCODE_ADD_ADDR_IMM_SCALED
操作地址 address 向后移动立即数(immediate)倍数的指针宽度,即 address += immediate*sizeof(uintptr_t);
0x50    REBASE_OPCODE_DO_REBASE_IMM_TIMES
将立即数(immediate)作为操作(循环)次数,依次将当前操作地址 address 对应的值进行 rebasing,即,将内部的值加上 slide 偏移
每次循环后操作地址 address 向后移动指针宽度的字节,进入下一个需要 rebase 的地址
0x60    REBASE_OPCODE_DO_REBASE_ULEB_TIMES
与上一个 0x50 值相似,唯一不同点就是立即数的值替换为 ULEB128 的值进行循环操作,这意为着需要 rebase 的地址超过了 4 位数能表示的最大值,即超过 16(0x0F)个.
0x70    REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB
根据上下文数据执行 rebase 操作
随后操作地址 address 向后移动,偏移值为 ULEB128 加一个指针宽度的值,即 address += read_uleb128(p, end) + sizeof(uintptr_t);
0x80    REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB
连续读取两个 ULEB128 值,依次为循环次数 count 和跳过的字节数 skip
执行循环,根据之前得出的上下文数据执行 rebasing
操作地址 address 向后移动 skip 加指针宽度的偏移量,即 address += skip + sizeof(uintptr_t);
0x00    BIND_OPCODE_DONE    
binding 结束标志
0x10     BIND_OPCODE_SET_DYLIB_ORDINAL_IMM
立即数(immediate)设置为依赖库索引 Ordinal,即 Load command 中的 LC_LOAD_DYLIB 按顺序排列的库,
0x20    BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB
将随后的 ULEB128 值设置为依赖库索引 Ordinal
0x30    BIND_OPCODE_SET_DYLIB_SPECIAL_IMM
根据立即数计算索引 Ordinal
0x0为self,0xf(-1)为main executable,0xe(-2)为flat lookup。
0x40    BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM
从 byte 后获取以 \0 结尾的符号名字符串
立即数作为符号的标志(flag)
0x50    BIND_OPCODE_SET_TYPE_IMM
立即数(immediate)设置为 type,分为以下类型:
BIND_TYPE_POINTER 1
BIND_TYPE_TEXT_ABSOLUTE32 2
BIND_TYPE_TEXT_PCREL32 3
0x60    BIND_OPCODE_SET_ADDEND_SLEB
设置上下文的加数(addend)为随后的 SLEB128 值
0x70    BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB
立即数(immediate)设置为当前上下文的 segment 索引,从而计算出当前 segment 首地址 segmentStartAddress
将随后的 ULEB128 字节流的值作为 segmentStartAddress 的偏移,从而计算出操作地址 address
0x80    BIND_OPCODE_ADD_ADDR_ULEB
操作地址 address 向后移动 ULEB128 数据对应的值,即 address += read_uleb128(p, end);
0x90    BIND_OPCODE_DO_BIND 
利用之前计算的上下文数据执行 binding
操作地址 address 向后移动一个指针宽度
0xA0    BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB
利用之前计算的上下文数据执行 binding
操作地址 address 向后移动 ULEB128 的值加一个指针宽度
0xB0    BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB
利用之前计算的上下文数据执行 binding
操作地址 address 向后移动立即数倍数的指针宽度(immediate*sizeof(intptr_t))再加一个指针宽度
0xC0    BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB
连续读取两个 ULEB128 值,先后为循环次数 count 和跳过的字节数 skip
执行循环,根据上下文数据执行 binding 操作
操作地址 address 向后移动 skip 加指针宽度的偏移量,即 address += skip + sizeof(uintptr_t);
- (void)logBindOps:(const uint8_t *)start end:(const uint8_t *)end isLazy:(BOOL)isLazy;
{
            //根据opcode的类型,如此处对binding的符号以符号地址作为key,符号名作为value,进行收集以便后面对dyld符号进行使用和设置
            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获取:

在loadCommandWithDataCursor方法中通过segment类型得到对应的子类实现,如0x19对应的LC_SEGMENT_64:CDLCSegment即为section header的获取:

- (id)initWithDataCursor:(CDMachOFileDataCursor *)cursor;
{
    //根据游标位置获取对应section header所在的偏移,然后依次设置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个数,依次设置__TEXT, __DATA等segment下的具体section header,原理相同,具体数据结构参考header的addr,size,offset等长度进行解析
        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对应的section header解析出来之后,和当前segment进行一个绑定,但此时真正的section data为空
        _sections = [sections copy];
    }
    return self;
}

经过这一步结束,那一个CDMachOFile可执行的结构已经搭建出来了,里面包含mach header对应的数据,load command数据,segment和对应section的头等。

处理OC数据:

- (void)processObjectiveCData;
{
    for (CDMachOFile *machOFile in self.machOFiles) {
        //processorClass首先会根据是否存在__objc_imageinfo section来决定使用不同的处理方式CDObjectiveC2Processor,__objc_imageinfo节可以看作是用于区分Objective-C 1.0与2.0,旧版是没有这个节的
        CDObjectiveCProcessor *processor = [[[machOFile processorClass] alloc] initWithMachOFile:machOFile];
        [processor process];
        [_objcProcessors addObject:processor];
    }
}

- (void)process;
{
    //根据是否存在CDLCEncryptionInfo加密段以及状态进行判断能否导出
    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,分为静态符号和动态符号两步,先看静态符号表的处理:

- (void)loadSymbols;
{
    for (CDLoadCommand *loadCommand in [self.machOFile loadCommands]) {
        if ([loadCommand isKindOfClass:[CDLCSegment class]]) {
            CDLCSegment *segment = (CDLCSegment *)loadCommand;
            //每一个 segment 的 VP (Virtual Page) 都根据 initprot 进行初始化,initprot 指定了如何通过读/写/执行位初始化页面的保护级别(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);
    
    //根据symtabCommand.stroff找到字符串表的起始位置([self.machOFile.data bytes] 即为可执行的起始偏移对应的指针地址)
    const char *strtab = (char *)[self.machOFile.data bytes] + _symtabCommand.stroff;
    
    void (^addSymbol)(NSString *, CDSymbol *) = ^(NSString *name, CDSymbol *symbol) {
        [symbols addObject:symbol];
        //根据_OBJC_CLASS_$_获取对应的className
        NSString *className = [CDSymbol classNameFromSymbolName:symbol.name];
        if (className != nil) {
            //根据地址是否存在判断符号是否为外部符号
            if (symbol.value != 0)
                classSymbols[className] = symbol;
            else
                externalClassSymbols[className] = symbol;
        }
    };

    if (![self.machOFile uses64BitABI]) {
        //XXX
    } else {
        /*
         遍历符号表读取nlist,符号的数据结构如下:
         struct nlist_64 {
             union {
                 uint32_t  n_strx; /在 string table 中的索引/
             } n_un;
             uint8_t n_type;        符号类型,8bit的复合字段
             uint8_t n_sect;        符号所在的 section index(内部符号有效值从 1 开始,最大为 255)
             uint16_t n_desc;       用来标识重定义符的特性,比如是否lazy bind
             uint64_t n_value;      符号的地址值(在链接过程中,会随着其 section 发生变化)
         };
         **/

        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即为字符串表的下标,那么拿到strtab加上所在的下标即为符号名
            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];
            //将字符串和符号对象进行绑定,回到上一步的block实现
            addSymbol(str, symbol);
        }

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

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

动态符号表解析,先看下dysymtab的定义:

//动态符号
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把符号表分为三个区域,ilocalsym 本地符号仅用于调试,iextdefsym可执行文件定义的符号,iundefsym可执行文件里没有定义的 
2.tocoff,目录偏移,该内容只有在动态分享库中存在。主要作用是把符号和定义它的模块对应起来。
3.modtaboff:为了支持“模块”(整个对象文件)的动态绑定,符号表必须知道文件创建的模块。该内容只有在动态分享库中存在。
4.extrefsymoff:为了支持动态绑定模块,每个模块都有一个引用符号表,符号表里存放着每个模块所引用的符号(定义的和没有定义的)。该表指针动态库中存在。
5.indirectsymoff:如果 section 中有符号指针或者桩(stub),section中的reserved1存放该表的下标。间接符号表,只是存放一些32位下标,这些下标执行符号表。
6.extreloff:每个模块都有一个重定位外部符号表。仅在动态库中存在
7.locreloff:重定位本地符号表,由于只是在调试中用,所以不必增加模块分组
//重定向实体:
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 的字节范围,其中r_address是相对于 section 的偏移量
r_pcrel表示地址值是 PC 相对地址值
r_extern标记该符号是否是外部符号
r_symbolnum,index 值,对于外部符号,它描述了符号在 symbol table 中的位置;如果是内部符号,它描述了符号所在的 section 的index
r_type,符号类型
- (void)loadSymbols;
{
    NSMutableArray *externalRelocationEntries = [[NSMutableArray alloc] init];
    //游标置位到外部重定向表的偏移位置
    CDMachOFileDataCursor *cursor = [[CDMachOFileDataCursor alloc] initWithFile:self.machOFile offset:_dysymtab.extreloff];
    //遍历外部重定向实体条数
    for (uint32_t index = 0; index < _dysymtab.nextrel; index++) {
        struct relocation_info rinfo;
        //读取重定向实体
        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];
}

加载协议loadProtocols,通过解析__objc_protolist section:

- (CDOCProtocol *)protocolAtAddress:(uint64_t)address;
{
    if (address == 0)
        return nil;
    //根据地址从字典中取出CDOCProtocol对象
    CDOCProtocol *protocol = [self.protocolUniquer protocolWithAddress:address];
    if (protocol == nil) {
        //不存在则设置进
        protocol = [[CDOCProtocol alloc] init];
        [self.protocolUniquer setProtocol:protocol withAddress:address];
        
        //从游标位置开始读取cd_objc2_protocol结构记录协议在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结构体的前10个属性大小,大于则表示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);
        
        //根据所在偏移地址拿到内容
        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都是指向一个cd_objc2_list_header结构体,这个结构体的count如果大于0,则表示存在cd_objc2_method具体方法,而这个结构体即方法name,type,imp的组合。然后根据地址拿到name,type在文件中的偏移创建CDOCMethod实例加入到数组倒序并返回
        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];
        //属性列表实现类似,不同的是根据cd_objc2_property结构体name,attributes创建CDOCProperty实例数组
        for (CDOCProperty *property in [self loadPropertiesAtAddress:objc2Protocol.instanceProperties])
            [protocol addProperty:property];
    }
    
    return protocol;
}

然后通过createUniquedProtocols方法对字典进行排序并更新到_uniqueProtocolsByAddress,并把协议中的实例方法和类方法以及属性进行抽取分类。

加载类,loadClasses方法通过读取__objc_protolist节:

- (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];
        //循环读取处理单个类,调用在下一个方法
        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结构体,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);
    
    //将游标置为data在可执行的偏移并构造cd_objc2_class_ro_t,查看可执行可得知具体的数据位于__objc_const节,而这一个实体的条目前2个偏移数据是一个cd_objc2_list_header结构体,内部包含2个i32的属性,表示size和count,通过其可以算出总共的方法数和占用的空间大小
    [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];
    
    //收集实例方法,通过cd_objc2_class_ro_t.baseMethods偏移取出cd_objc2_method创建CDOCMethod并加入集合
    for (CDOCMethod *method in [self loadMethodsAtAddress:objc2ClassData.baseMethods])
        [aClass addInstanceMethod:method];
    
    //根据ivars的偏移取出cd_objc2_ivar构造CDOCInstanceVariable并加入集合
    aClass.instanceVariables = [self loadIvarsAtAddress:objc2ClassData.ivars];
    
    {
        //根据类名从之前构造的符号映射表中取出符号进行设置
        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]) {
            //根据地址从dyldInfo中取出superclassname
            superClassName = [self.machOFile externalClassNameForAddress2:classNameAddress];
            //NSLog(@"class: got external class name (2): %@", [aClass superClassName]);
        } else if ([self.machOFile hasRelocationEntryForAddress:classNameAddress]) {
            //否则根据地址从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数据所在的偏移去重新检索类关系
            CDOCClass *sc = [self loadClassAtAddress:objc2Class.superclass];
            aClass.superClassRef = [[CDOCClassReference alloc] initWithClassObject:sc];
        }
        
        if (superClassName) {
            //如果父类存在,则找到其对应的符号进行引用关系绑定
            CDSymbol *superClassSymbol = [[self.machOFile symbolTable] symbolForExternalClassName:superClassName];
            if (superClassSymbol)
                aClass.superClassRef = [[CDOCClassReference alloc] initWithClassSymbol:superClassSymbol];
            else
                aClass.superClassRef = [[CDOCClassReference alloc] initWithClassName:superClassName];
        }
    }
    
    //收集类方法,规则是先通过isa收集到元类,然后cd_objc2_class.data --> cd_objc2_class_ro_t.baseMethods 最后拿到cd_objc2_list_header.count得到cd_objc2_method构造出CDOCMethod数组
    for (CDOCMethod *method in [self loadMethodsOfMetaClassAtAddress:objc2Class.isa])
        [aClass addClassMethod:method];
    
    // Process protocols 拿到cd_objc2_class_ro_t.baseProtocols在_uniqueProtocolsByAddress中收集到的协议
    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节然后进行loadCategoryAtAddress处理,重点在于cd_objc2_category结构体,按8字节解析之后可以获取到instanceMethods,classMethods等的偏移,然后得到对应的方法:

- (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;
};

最后通过创建Visitor然后遍历进行字符串的拼接输出到指定路径:

    CDMultiFileVisitor *multiFileVisitor = [[CDMultiFileVisitor alloc] init];
    multiFileVisitor.classDump = classDump;
    classDump.typeController.delegate = multiFileVisitor;
    multiFileVisitor.outputPath = outputPath;
    [classDump recursivelyVisit:multiFileVisitor];
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,290评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,399评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,021评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,034评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,412评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,651评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,902评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,605评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,339评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,586评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,076评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,400评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,060评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,851评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,685评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,595评论 2 270

推荐阅读更多精彩内容