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];