swift 稠浊原理根究
条件
因为swift是一门静态言语, 其派发机制与OC不同,两者在构建时其MachO文件是有差异的.适用于OC代码层面的稠浊方案在swift中并不适用.要根究swift的稠浊方案,我们要搞清楚swift在构建时和OC的差异.关于面向方针的编程来说MachO文件可以简略看做是保存了全部类信息的文件,我们需求搞清楚这些类的信息是怎样保存的.
MachO是怎样存储OC中Class信息的
首要看一下MachO文件的全体结构, 我们将从__objc_classlist
下手,根究OC类的存储方法.MachO文件结构如下
__objc_classlist
中存储的类表,其实就是一系列地址,每个地址对应一个类.
其间赤色0xd408
指向_objc_data
分区中的某个地址,结构如下
struct class64
{
unsigned long long isa;
unsigned long long superClass;
unsigned long long cache;
unsigned long long vtable;
unsigned long long data;
};
其间赤色0xc048
为data指针 指向_objc_data
分区中的某个地址,结构如下
struct class64Info
{
unsigned int flags;//objc-runtime-new.h line:379~460
unsigned int instanceStart;
unsigned int instanceSize;
unsigned int reserved;
unsigned long long instanceVarLayout;
unsigned long long name;
unsigned long long baseMethods;
unsigned long long baseProtocols;
unsigned long long instanceVariables;
unsigned long long weakInstanceVariables;
unsigned long long baseProperties;
};
根据0xc048
往下数32个字节能找到name
地址0x7368
, 可以在__objc_classname
中找到该类名为ViewController
. \x56\x69\x65\x77\x43\x6f\x6e\x74\x72\x6f\x6c\x6c\x65\x72
是其Unicode编码
同理可知 0x6420
为其方法列表地址,其结构如下,首要是method64_list_t
随后跟着是relative_method_t
struct method64_list_t
{
unsigned int entsize; //hopper显现为flag
unsigned int count;
};
struct relative_method_t {
int32_t nameOffset; // SEL*
int32_t typesOffset; // const char *
int32_t impOffset; // IMP
};
0x6f78
表明方法名偏移.0x0fb0
为方法类型偏移. 0xfffffc60为
Imp偏移.
0x6428 + 0x6f78 = 0xd3a0
,0xd3a0
在 _objc_selrefs
分区
里面存放一个字符串的指针 再找到0x6500
位于 __objct_methodname
分区
可以看到方法名是viewDidLoad
. 同样的方法可以找到方法实现 00006430 + fffffc60 = 100006090
为Imp地址.
以上就是OC中Class的大致存储方法.总结一下
-
__objc_classlist
存放着Class
指针指向__objc_data
中的地址 -
_objc_data
存放着class64
结构体数据其间data
指向了class64Info
的地址 -
class64Info
中name
指向了__objc_classname
中的字符串,表明类名 -
class64Info
中baseMethods
指向了__objc_methodlist
中的结构体method64_list_t
,其间存放着方法信息 -
method64_list_t
中count
表明方法数量,在其随后的地址中以relative_method_t
表明 -
relative_method_t
中nameOffset
可以核算得出方法名的地址, 这一地址位于__objc_methodname
分区. -
relative_method_t
中impOffset
可以核算得出方法对应的函数地址,位于__text
分区.
类名和方法名都已字符串的方法存储在对应的分区__objc_classname
和__objc_methodname
中了. 这和我们的直觉是一起的,OC作为一门动态言语方法名一定会以某种形式保存在MachO文件中.
基于这个原因对OC代码的稠浊方案中会对方法名和类名进行替换然后到达稠浊的作用. 可是我们知道swift作为一门静态言语,其方法的派发机制和OC是不同的. 那么问题来了, 对swift进行方法称号的替换有相同有用吗? 下文将根究该问题.
Swift的派发机制
swift具有三种不同的派发机制
-
静态派发(直接派发)
-
函数表派发
-
动态派发
我们需求搞清楚,这些派发方法是否有方法名的参与.
他们的差异总结如下
这张图出自[Static vs Dynamic Dispatch in Swift: A decisive choice](https://medium.com/@bakshioye/static-vs-dynamic-dispatch-in-swift-a-decisive-choice-cece1e872d) 这篇文章, 不过在实践的检验中,有一些纤细的不同. Release形式下编译器尽可能的将函数表派发优化为静态派发乃至直接优化为内联函数, 因而为了调查派发的具体指令需求在Debug下进行.
- 静态派发会在编译或许静态链接时直接承认函数的地址,必定没有符号的参与.
- 动态派发就是oc的音讯机制, 需求方法名的参与,不赘述了.
下面探寻一下 函数表派发的细节
检验代码
其间 bl , blr 为跳转指令. 这以后是地址或许寄存器. 这儿iOS加载MachO时存在随机地址偏移(ASLR), 我们需求对地址进行转化得到其在MachO文件中的地址,
image list 打印内存中全部image , 找到SwiftConfuse这个Image,检查其地址 0x0000000102058000, 我们核算地址要减去这个数,来得到文件的偏移地址进行剖析.
下面逐行剖析这些指令做了啥
bl 0x10205db54
: 恢复地址 0x10205db54 - 0x012058000 = 0x5b54
, Hopper跳转到地址0x5b54
,
这是一个读取swift metadata的程序, 获取Foo的matedata的地址并回来.
mov x20 ,x0
: 将x0复制到x20, 前一条指令回来的地址保存到x0中,
bl 0x10205d664
: 恢复地址 0x10205d664 - 0x012058000 = 0x5664
, Hopper跳转检查,该程序用来初始化Foo方针并回来方针地址.
可以通过x0地址来验证 成功调用了Foo方针的bar方法.
mov x20 ,x0
: 将x0复制到x20
str x20, [sp, #0x10]
: 把寄存器x20的值保存到栈内存[sp + #0x10]
, 方针的赋值操作,我们不关心.
ldr x8 [x20]
: 这儿读取的是x20地址开端的8个字节的数据. x20中存储的是Foo方针的地址.
根据Swift方针地址布局. 方针的第一个8字节存储的就是方针metadata的地址, 对的,其实就是isa
指针. 所以这儿获取就是Foo的metadata信息
metadata 地址为 0x1020720c8
. 恢复地址 0x1020720c8 - 0x012058000 = 0x1a0c8
这个结构看着是不是很熟悉, 其实就是上文根究OC类存储时的 class64
, 说清楚OC和swift类的存储方法是一起的. 也可以解说为啥OC类的结构体中有vtable.
ldr x8 [x8, #0x50]
: 读取x8偏移50字节后地址的8个直接的数据 即 0x1a0c8 + 50 = 0x1a118
中的值,
这儿得到的就是0x10000053f4
就是bar方法的地址,通过hopper可以验证.
blr x8
: 实行bar方法.
留意: 这儿有2次获取matedata, 第一次是通过matedata accessor获取的, 第2次是通过方针地址获取的. 这是因为在存在方针的情况下,通过方针获取功率更高.
由上述流程可以看出Swift函数表派发寻址原理, swift class matedata 中存储了vtable, 函数表派发会先找到swift类的matedata, 再通过偏移量找到vtable中的地址. 这一进程并没有也不需求函数名的参与.
同OC根据matedata也可以灵敏定位出swift类名的位置, matedata->data->name
当然这个和OC有点差异, OC是保存在 __objc_classname
分区的, swift是保存在__cstring
分区的.
定论
关于swift而言
- 因为其大部分的方法是静态派发或许函数表派发,machO文件中不保存方法称号.对方法名进行稠浊只对
@objc dynamic
修饰的方法起作用, 大部分情况下是无用功, 市面上一些对swift进行方法名替换稠浊的东西从原理上来说就是行不通的. - 纯swift类 类名和特色名方法名, 包含方法名的字符串不都储存. 继承OC的类的类名和存储特色的字符串存储在 Text段的 __cstring 分区, @objc修饰的方法名 和 继承自OC类的方法名会储存