导读
在分析linkMap文件的时候,遇到一个有趣的问题:获取类名可以用_objc_classname, 获取方法名可以用_objc_methname。可是怎么将方法名称和对象名称对应起来,程序是如何对应这两部分数据的。带着这个疑问研究了下macho的文件结构。
MACHO文件说明
macho文件是mac os或ios系统可执行文件的格式,系统通过加载这个格式来执行代码。
相关结构如图:

注:来源于:(http://www.jianshu.com/p/f1a61b53398f)
具体每部分的含义可以参考这个定义:
这里简单讲几个我比较关注的:
注:下面都是以64位做演示说明,cpu结构为arm64。
MachO Header的结构

数据结构为:
|
|
- 第一个四字节数叫做
magic number,可以得到使用的是64位还是32位系统 - 第二个字节和第三个字节是CPU类型
- 第四个字节是文件类型。
MH_EXECUTE表示可执行文件 - 第五个字节和第六个字节表示
load commands的个数和长度 - 第7个字节是加载的flag信息。具体参考
loader.h中的文件
MachO load command
程序检索完Header之后就开始加载和解析Load Commands了。
相关代码在mach_loader.c,通过递归调用加载命令。


load_comand的数据结构为:
|
|
每一个command都需要包含
- cmd:加载类型
- cmdsize:加载的大小
相关的最主要的解析源码在mach_loader.c里的parse_machfile方法里.最主要的代码如下:
|
|
其中几个比较重要的加载命令:
LC_SEGMENT(LC_SEGMENT_64),用于加载段(segment)的命令,有下面段用下面加载:__PAGEZERO、__TEXT、DATA、__LINKEDIT。其中__PAGEZERO程序保留区,用于处理NULL异常,__TEXT保存程序代码和字符,DATA保存程序使用的二进制数据,__LINKEDIT保存动态库需要原始数据如符号、字符串、重定位条目等。也保留了起始地址信息,后续的LC_SYMTAB和LC_DYSYMTAB也是基于起始地址来算出相关偏移的值LC_LOAD_DYLINKER,用来读取动态加载库路径,通常在usr/lib/dyld,然后使用这个命令加载后面的动态库(最终还是递归调用parse_machfile)。LC_MAIN,用来读取程序入口LC_CODE_SIGNATURE用来验证程序签名LC_DYSYMTAB加载Dynamic Symbol Table,保存了C Function相关的链接信息,通过数据偏移,可以查询LC_SYMTAB保存的C Function相关的信息,比如方法名和实现等。fishhook,利用这个机制可以找到C对应的方法实现,并动态替换成要hook的函数,具体参考我的fishHooker源码解析。
经过LoadCommand,程序正式被加载到内存中,最终运行起来。
MACHO Section
下面的主要是相关的节数据,主要有:
__TEXT段节名含义
|
|
__DATA段节名含义
|
|
这部分数据会在上一步LoadCommand命令时,加载到内存里。
解析__objc_classlist
在看linkMap的时候,很奇怪的是,获取类名可以用_objc_classname, 获取方法名可以用_objc_methname,但是两个数据怎么匹配起来的,根据查相关资料,是通过__objc_classlist来映射的。
在解析的时候需要两个工具:MachOView和Hopper,
加载可执行文件
选用真机编译,编译选项选择Build Active Architecture Only,这样只生成一个CPU类型的文件,方便后续分析,然后在工程的DerivedData/**/Build/Products/**-iphonesos/**.app中显示包内容,把和工程同名的文件copy到自己的目录下。
打开`MachOview,打开刚才的可执行文件。

解析__objc_class结构
直接看__objc_classlist节,

然后看下__objc_classlist数据结构,这个是个内存地址占用64位,
经过分析,__objc_classlist,保存的地址,映射的是__objc_data的地址,在MachOView中,对应的数据为:

使用Hopper打开可执行文件,按G,在搜索框里输入这个地址,比如输入0000000100009278

之后显示了一个数据结构。
这个数据对应的数据结构为:
|
|
第一个是64位指针,保存isa指针,指向了
MetaClass指针,对应的地址为00000001000092A0,在Hopper中搜索这个地址,得到的数据为:
第二个指向父类的指针,对应地址为
0000000000000000第5个指向
data,对应的地址为:00000001000082C8, 这个数据保存在__objc_const节,对应的数据结构为__objc_data
,在Hopper中搜索这个地址,得到的数据为:
对应的具体数据为:
``
解析__objc_data
对应的数据结构为:
|
|
主要的几个数据结构:
name保存的类名称。这个地址为:00000001000076B6,对应的数据在__objc_classname段里,用Hopper查看这个地址,对应的名称为ViewController
baseMethod,保存了类所有方法,这个地址为:0000000100008278, 对应数据在__objc_const,可以在这里找到对应的数据。
对应数据结构为__objc_method_list,在Hopper,查看:
解析__objc_method_list
对应的数据结构为:
|
|
使用到的数据主要是count,对应数据为00000003,对应10进制数为3,说明有3个方法。具体方法对应的数据结构为:
|
|
这个数据结构占用24(8*3)字节。objc_method_list结构体占用8字节,所以从0000000100008278开始,偏移8个字节,到0000000100008280就是第一个方法的起始位置,再偏移24个字节到0000000100008298,就是第二个方法起始地址位置,以此类推,最后一个方法占用地址为00000001000082b0 ~ 00000001000082c7。
先看第一个方法存储的数据为:

然后分别解析这些地址:
0000000100006924,在__objc_methname段里,对应方法名称。

000000010000770F,在__objc_methtype段里,对应方法签名,这里的值为v16@0:8,代表含义可以参考这里关于type encodings的理解–runtime programming guide
0000000100004A20,在__text节里,对应的数据为:
最终类需要的数据完全解析完成。
ps:想要知道数据结构是什么,可以在Hopper的右侧导航栏下,点击Manager type查看。