做包巨细优化的时分,我们可以通过分析mach-o中的__objc_classlist(工程中所有类调集,包括OC和Swift类)和__objc_classrefs(用到的OC类调集)做差集得到oc的无用类。 今日就来说一下怎样样拿到mach-o中的类和方法。

准备工作:

先编译一个自己的.app包:在自己的demo工程中编译完成之后,在Products中showInFinder拿到xxx.app

通过代码获取mach-o中所有的类,方法

方法1:通过otool指令进行过滤,拿出有用信息

首要履行

otool -arch arm64 -ov xxx.app > all.txt

拿到mach-o文件的全体描绘输出到all.txt(这个文件后边会屡次用到)中,打开all.txt:

通过代码获取mach-o中所有的类,方法

发现它在__DATA_CONST中,然后再去拿__objc_classlist:

otool -arch arm64 -v -s __DATA_CONST __objc_classlist xxx.app > class.txt

class.txt中便是列的所有工程中的类,可是都是地址,可以通过仿制对应的地址到all.txt查找来查看到底是哪一个类,比如00000001000925c8中对应的类是_ViewController:

通过代码获取mach-o中所有的类,方法

可以看出,我们可以通过otool指令拿到我们想要的数据,详细可看这篇文章zhuanlan.zhihu.com/p/428084059…

方法2:通过代码分析mach-o

我们今日首要讲的是第二种方法。

在这之前,我们先来说一下mach-o的结构: 通过MachOView东西可查看mach-o文件

通过代码获取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)中:

通过代码获取mach-o中所有的类,方法

通过遍历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:&section 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文件中类的结构:

通过代码获取mach-o中所有的类,方法
而类的详细信息,如method,ivar,protocol,properties等都是在类的data中:

通过代码获取mach-o中所有的类,方法
由此可以写出来类的结构:

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的结构:

通过代码获取mach-o中所有的类,方法
前两个是方法列表的描绘,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];

再查看特色的结构同理可以获取到类的特色:

通过代码获取mach-o中所有的类,方法

着手自己试试吧。

58同城的WBBlades东西便是通过分析Mach-o来检测无用类,欢迎运用和start