高级语言swift开发者了解源码,如何着手呢?
下载源码那是肯定的,官网链接
本文通过简单的demo实例 + 少量汇编代码查看 + swift编译器swiftc生成swift中间代码 sil语法分析来探究,最后通过 MachO进行验证
Swift编译器
OC是通过clang编译器,编译成IR,然后生成可执行文件.o(机器码)
Swift则是通过Swift编译器编译生成IR,然后生成可执行文件
必要汇编指令
mov 将某一寄存器的值复制到另一寄存器
mov x1, x0. 将寄存器x0的值复制到 寄存器x1中
add 将两个寄存器的值相加,结果保存在一个寄存器中
add x0, x1, x2. 将寄存器 x1 与 x2的值相加,结果保存在x0中
sub 与add相反,就是相减
str 将寄存器中的值写入到内存中
str x0, [x0, x8]. 将寄存器x0中的值 保存到 栈内存 (x1存储的基地址 + x8存储的偏移地址 = 目标内存地址)
ldr 将内存中的值读取到寄存器中
ldr x0, [x1, x2]. 将 内存地址(x1存储的基地址 + x8存储的偏移地址 = 目标内存地址)中的值 放入到寄存器 x0中
bl 跳转到目标内存地址 无返回
bl x8 跳转到 x8寄存器中存储的地址
blr 跳转到目标内存地址 有返回
ret 子程序 (函数调用) 返回指令, 返回地址已默认保存在寄存器 lr (x30) 中
Swift编译器生成sil脚本
MachOView 打开 工程 - Products - xxxx.app - 展示包内容 - 黑色可执行文件
由于过程比较繁琐,为了对swift类结构探究过程有个初步认识,先对验证结果有个整体认知,避免看不下去了
方法调用断点
打开汇编
分析汇编
由此 x8寄存器中存储的就是 方法调用地址
通过脚本生成的sil文件 对照 MachO data部分 swift5_types, 正好对应5个方法
以 test1为例,0x7ABC + 0xFFFFFB80(小端模式) = 0x10000763C
其中 0x100000000 为 MachO生成的虚拟内存的偏移基址
0x10000763C - 虚拟内存基址 = 0x763C
TEXT.const 为 初始化过的常量, 可以理解为 类结构初始化后的记录在此会找到记录
至于为什么偏移48字节,由于结果先行,具体结构还未知,后面会有结构分析说明
偏移48字节后,地址为 0x766C,开始的4字节表示结构大小
继续偏移4字节,得到 test1 test2 test3
image list 得到 ASLR,可自行查看科普
ASLR (0x0000000102b6c000) + 0x3B44 = 0x102B6FB44
对比前面读取的寄存器中 值
相等,验证完成
通过查看汇编过程中,发现x8先获取到的是 type metadata的类型
如果分析过oc的类结构,就会知道获取方法是根据基址基础上偏移得到的,因此可以猜测 swift类结构属于 metadata类型
再者,swift可以兼容oc使用,会否metadata可以解析为 objc_class 或者 swift结构类型
源码全局搜索 metadata,耐心过滤,找到一个 Metadata.h文件
有一段注释 /// The common structure of all type metadata.
struct TargetMetadata
乍一看的话,还是不太符合oc底层源码的习惯,代码表面达意不像oc那么明显
从 TargetMetadata c++基本数据结构 可以看出
TargetMetadata 的构造部分 出现了isa
基本上就坐实了 TargetMetadata 是个复合结构,兼容 oc isa结构的
多看一步
Metadata.h中全局搜索 getKind()
Metadata.h中全局搜索 getKind()
直到 出现有意义的搜索结果
如果是 type metadata 类型的话,得到 Description 结构
由此,descriptor是个重要的关键字,可能蕴含着swift类的关键信息
同时锁定了另一个关键字 TargetClassMetadata
阅读 struct TargetClassMetadata上面的两段注释
所有的类metadata结构 - 该数据结构直接嵌入到类的 heap metadata结构中, 同时该结构内存布局与oc内存布局兼容
再次查看截图中的 TargetClassMetadata 构造部分,看参数
base - 可能是个基址 据此偏移 找到结构中的各个属性
ivarDestroyer - 应该就是变量的属性信息了
size - 大小 alignMask - 对齐
等等,这些在加载内存中构建类结构过程中 都会用到的关键参数信息
ClassMetadata {
var Flags: ClassFlags; // uint32_t
var InstanceAddressPoint: uint32_t
var InstanceSize: uint32_t
var InstanceAlignMask: uint16_t
var Reserved: uint16_t
var ClassSize: uint32_t
var ClassAddressPoint: uint32_t // class object 偏移
var Description: RawPointer // 元数据指针
var IVarDestroyer: RawPointer // 元数据指针
}
TargetClassMetadata 继承自 TargetAnyClassMetadata
进一步填充 ClassMetadata 结构
TargetAnyClassMetadata {
var Superclass: RawPointer
var CacheData:
var Data: StoredSize // Int32
}
整合
Metadata {
var Kind: StoredPointer // Int32
var Superclass: RawPointer
var CacheData:
var Data: StoredSize // Int32
var Flags: ClassFlags; // uint32_t
var InstanceAddressPoint: uint32_t
var InstanceSize: uint32_t
var InstanceAlignMask: uint16_t
var Reserved: uint16_t
var ClassSize: uint32_t
var ClassAddressPoint: uint32_t // class object 偏移
var Description: RawPointer // 元数据指针
var IVarDestroyer: RawPointer // 元数据指针
}
至此,Metadata 有了一个相对明朗的可观结构,但 Description 分析上又会受阻,原因在于直接通过源码关联是没有任何结果的,关联不到内容
因为这个结构类型是 元指针,也就是 通过指针转换成了 整型标识的 一个指针,直接关联是没有任何意义的
此时,有两种方式可以并进去考虑
一种是从源码层面 结合上下文 及 注释去分析递进; 一种是通过前面的sil代码 结合之前的源码结构分析去考虑
注释上看,Description是swift标识类型描述的越界信息
struct内存结构是通过内存偏移来一个一个标识出来的,也就是说 struct块偏移完内部描述结构之后,紧挨着的一段内存是不在struct描述块之内的,
但是可以通过继续偏移内存得到这样的 一个 Description块结构
继续分析 元数据指针 蕴含的 TargetClassDescriptor
篇幅有限,swift-类结构源码探寻(二), 继续 TargetClassDescriptor分析