做包巨细优化的时分,我们可以通过分析mach-o中的__objc_classlist(工程中所有类调集,包括OC和Swift类)和__objc_classrefs(用到的OC类调集)做差集得到oc的无用类。 今日就来说一下怎样样拿到mach-o中的类和方法。
准备工作:
先编译一个自己的.app包:在自己的demo工程中编译完成之后,在Products中showInFinder拿到xxx.app
方法1:通过otool指令进行过滤,拿出有用信息
首要履行
otool -arch arm64 -ov xxx.app > all.txt
拿到mach-o文件的全体描绘输出到all.txt(这个文件后边会屡次用到)中,打开all.txt:
发现它在__DATA_CONST中,然后再去拿__objc_classlist:
otool -arch arm64 -v -s __DATA_CONST __objc_classlist xxx.app > class.txt
class.txt中便是列的所有工程中的类,可是都是地址,可以通过仿制对应的地址到all.txt查找来查看到底是哪一个类,比如00000001000925c8中对应的类是_ViewController:
可以看出,我们可以通过otool指令拿到我们想要的数据,详细可看这篇文章zhuanlan.zhihu.com/p/428084059…
方法2:通过代码分析mach-o
我们今日首要讲的是第二种方法。
在这之前,我们先来说一下mach-o的结构: 通过MachOView东西可查看mach-o文件
- mach-header;//文件格局、文件类型、CPU构架等
- load commands;//程序怎样加载到内存的一些描绘信息(文件布局、链接信息、符号表方位等)
- segment and section;//真正存储数据或代码的当地 //segment 中的数据都会被印射到虚拟内存中,所以 segment 是按页对齐的。即:segment 中的数据在虚拟内存 中占得巨细要比在磁盘中所占巨细更大。
把.app转为data,再通过偏移和size就能拿到mach-header,load commands和segment and section。 从MachOView中可以看到__objc_classlist在LC_SEGMENT_64(__DATA_CONST)中:
通过遍历data,得到__objc_classlist的偏移: 首要先拿到mach-header:
mach_header_64 mhHeader;
[self.fileData getBytes:&mhHeader range:NSMakeRange(0, sizeof(mach_header_64))];
if (mhHeader.filetype != MH_EXECUTE && mhHeader.filetype != MH_DYLIB) {
NSLog(@"当时文件不是可履行文件");
return;
}
mhHeader便是macho头,LoadCommonds在mhHeader的下面,所以LoadCommond的偏移就刚好是mhHeader的巨细:
//当时load_command在data中的方位
unsigned long long currentLocation = sizeof(mach_header_64);
拿到_classList和_classRefList:
mhHeader中的ncmds特色标明LoadCommonds中的个数,遍历:
section_64 _classList = {0};
section_64 _classRefList = {0};
...
for (int i = 0; i < ncmds; i ++) {
load_command *cmd = (load_command *)malloc(sizeof(load_command));
[fileData getBytes:cmd range:NSMakeRange(currentLocation, sizeof(load_command))];
if (cmd->cmd == LC_SEGMENT_64) { //只取LC_SEGMENT_64的数据
segment_command_64 segmentCommand;
[fileData getBytes:&segmentCommand range:NSMakeRange(currentLocation, sizeof(segment_command_64))];
NSString *segName = [NSString stringWithFormat:@"%s",segmentCommand.segname];
//取__TEXT、__DATA_CONST、__DATA中的值
if ([segName isEqualToString:@"__TEXT"] ||
[segName isEqualToString:@"__DATA_CONST"] ||
[segName isEqualToString:@"__DATA"]) {
unsigned long long secLocation = currentLocation + sizeof(segment_command_64);
//segmentCommand中segment的数量
for (int j = 0; j < segmentCommand.nsects; j ++) {
section_64 section;
[fileData getBytes:§ion range:NSMakeRange(secLocation, sizeof(section_64))];
NSString *secName = [[NSString alloc] initWithUTF8String:section.sectname];
secLocation += sizeof(section_64);
if ([secName isEqualToString:@"__objc_classlist__DATA"] ||
[secName isEqualToString:@"__objc_classlist__DATA_CONST"]) {
_classList = section;
}
if ([secName isEqualToString:@"__objc_classrefs__DATA"] ||
[secName isEqualToString:@"__objc_classrefs__DATA_CONST"]) {
_classRefList = section;
}
if ([secName isEqualToString:@"__objc_nlclslist__DATA"] ||
[secName isEqualToString:@"__objc_nlclslist__DATA_CONST"]) {
_nlclsList = section;
}
if ([secName isEqualToString:@"__objc_nlcatlist__DATA"] ||
[secName isEqualToString:@"__objc_nlcatlist__DATA_CONST"]) {
_nlcatList = section;
}
if ([secName isEqualToString:@"__cstring"]) {
_cfstringList = section;
}
if ([secName isEqualToString:@"__text"]) {
_textList = section;
}
if ([secName isEqualToString:@"__swift5_types"]) {
_swift5Types = section;
}
}
}
}
currentLocation += cmd->cmdsize;
free(cmd);
}
这样就拿到了_classList和_classRefList,他们都是存的class的地址(8位),再通过遍历拿到类:
NSUInteger addressSize = 8;
NSRange range = NSMakeRange(classList.offset, 0);
for (int i = 0; i < _classList.size / addressSize; i ++) {
//读出类的地址
unsigned long long classAddress;
NSData *data = [self.class readBytes:range length:addressSize fromFile:fileData];
[data getBytes:&classAddress range:NSMakeRange(0, addressSize)];
...
}
拿到类的地址之后,指向了类的结构,再来看下方法1中通过otool东西得到的all.txt文件中类的结构:
而类的详细信息,如method,ivar,protocol,properties等都是在类的data中:
由此可以写出来类的结构:
struct class64 {
unsigned long long isa;
unsigned long long superclass;
unsigned long long cache;
unsigned long long vtable;
unsigned long long data; //指向class64_data结构地址
};
struct class64_data {
unsigned int flags;
unsigned int instanceStart;
unsigned int instanceSize;
unsigned int reserved;
unsigned long long ivarLayout;
unsigned long long name; //类名地址
unsigned long long baseMethods;
unsigned long long baseProtocols;
unsigned long long ivars;
unsigned long long weakIvarLayout;
unsigned long long baseProperties;
};
上一步遍历拿到类的地址之后,可以通过类的地址拿到class64和class64_data信息
- (class64)getClass64Address:(unsigned long long)address{
unsigned long long classOffset = [self.class getOffsetFromVmAddress:address fileData:self.fileData]; //得到类的地址偏移
class64 targetClass = {0};
NSRange targetClassRange = NSMakeRange(classOffset, 0);
NSData *data = [self.class readBytes:targetClassRange length:sizeof(class64) fromFile:self.fileData];
[data getBytes:&targetClass length:sizeof(class64)];
return targetClass;
}
- (class64_data)getClass64InfoWithAddress:(unsigned long long)address{
class64_data targetClassInfo = {0};
unsigned long long targetClassInfoOffset = [self.class getOffsetFromVmAddress:address fileData:self.fileData];
targetClassInfoOffset = (targetClassInfoOffset / 8) * 8;
NSRange targetClassInfoRange = NSMakeRange(targetClassInfoOffset, 0);
NSData *data = [self.class readBytes:targetClassInfoRange length:sizeof(class64_data) fromFile:self.fileData];
[data getBytes:&targetClassInfo length:sizeof(class64_data)];
//或许直接这么取
// [self.fileData getBytes:&targetClassInfo range:NSMakeRange(classInfoOffset, sizeof(class64_data))];
return targetClassInfo;
}
获取类名:
//通过类的地址得到类的结构体
class64 targetClass = [self getClass64Address:classAddress];
//得到类的详细信息的结构体
class64_data classInfo = [self getClass64InfoWithAddress:targetClass.data];
//通过name的地址拿到name的偏移,再从data中拿到类名
unsigned long long stringOffset = [self.class getOffsetFromVmAddress:classInfo.name fileData:self.fileData];
NSString *className = @((char *)[self.fileData bytes] + stringOffset);
获取方法列表:
再从all.txt中可以看出methods的结构:
前两个是方法列表的描绘,entsize标明方法中的name,types,imp中存的是偏移仍是地址,12:偏移,24:地址; count标明方法的个数。而后边用半圆标出来的部分代码每个方法。于是又可以拿到method的结构:
struct method64_list {
unsigned int entsize;
unsigned int count;
};
//方法中特色偏移
struct relative_method_t {
int32_t nameOffset;
int32_t typesOffset;
int32_t impOffset;
};
//方法中特色地址
struct method64 {
unsigned long long name;
unsigned long long types;
unsigned long long imp;
};
通过classInfo中baseMethods中的地址拿到method64_list:
- (NSArray *)getClass64Address:(unsigned long long)methodsAddress{
unsigned long long address = methodsAddress;
if (address == 0) {
return @[];
}
NSData *fileData = self.fileData;
unsigned long long max = [fileData length];
//拿到方法列表偏移
unsigned long long offset = [self.class getOffsetFromVmAddress:address fileData:self.fileData];
method64_list method = {0};
NSRange methodRange = NSMakeRange(offset, 0);
NSData *data = [self.class readBytes:methodRange length:sizeof(method64_list) fromFile:self.fileData];
[data getBytes:&method length:sizeof(method64_list)];
NSMutableArray *methods = [NSMutableArray array];
offset += sizeof(method64_list);
for (int i = 0; i < method.count; i ++) {
MachoMethod *methodObj = [[MachoMethod alloc] init];
unsigned long long methodSize;
unsigned long long methodNameOffset;
unsigned long long methodTypeOffset;
if ((method.entsize & 0x80000000) !=0) {
relative_method_t methodInfo;
[self.fileData getBytes:&methodInfo range:NSMakeRange(offset, sizeof(relative_method_t))];
methodNameOffset = offset + methodInfo.nameOffset; //存的是方法姓名地址的偏移
methodTypeOffset = offset + methodInfo.typesOffset + sizeof(int32_t); //直接存的字符串
unsigned long long imp = offset + methodInfo.impOffset + 8;
methodSize = sizeof(relative_method_t);
//方法名
unsigned long long methodNameAddress; //得到方法地址
[fileData getBytes:&methodNameAddress range:NSMakeRange(methodNameOffset, 8)];
NSString *methodname = [self getStringWithAddress:methodNameAddress];
methodObj.name = methodname;
methodObj.types = @((char *)[fileData bytes] + (methodTypeOffset & ChainFixUpsRawvalueMask));
}else {
method64 methodInfo;
[self.fileData getBytes:&methodInfo range:NSMakeRange(offset, sizeof(method64))];
methodNameOffset = [self.class getOffsetFromVmAddress:methodInfo.name fileData:fileData];
methodTypeOffset = [self.class getOffsetFromVmAddress:methodInfo.types fileData:fileData];
methodSize = sizeof(method64);
//方法名
if (methodNameOffset > 0 && methodNameOffset < max) {
const char *classmethodname = (char *)[fileData bytes] + methodNameOffset;
methodObj.name = @(classmethodname);
}
//方法参数类型
if (methodTypeOffset > 0 && methodTypeOffset < max) {
const char *types = (char *)[fileData bytes] + (methodTypeOffset & ChainFixUpsRawvalueMask);
methodObj.types = @(types);
}
}
offset += methodSize;
[methods addObject:methodObj];
}
NSLog(@"%@", methods);
return methods;
}
类的方法包括实例方法和类方法,类方法在metaClass中:
//通过类的地址得到类的结构体
class64 targetClass = [self getClass64Address:classAddress];
//得到类的详细信息的结构体
class64_data classInfo = [self getClass64InfoWithAddress:targetClass.data];
//meta class
class64 metaClass = [self getClass64Address:targetClass.isa];
class64_data metaClassInfo = [self getClass64InfoWithAddress:metaClass.data];
obj.classMethods = [self getMethodsWithAddress:metaClassInfo.baseMethods];
再查看特色的结构同理可以获取到类的特色:
着手自己试试吧。
58同城的WBBlades东西便是通过分析Mach-o来检测无用类,欢迎运用和start