swift 稠浊原理根究

条件

因为swift是一门静态言语, 其派发机制与OC不同,两者在构建时其MachO文件是有差异的.适用于OC代码层面的稠浊方案在swift中并不适用.要根究swift的稠浊方案,我们要搞清楚swift在构建时和OC的差异.关于面向方针的编程来说MachO文件可以简略看做是保存了全部类信息的文件,我们需求搞清楚这些类的信息是怎样保存的.

MachO是怎样存储OC中Class信息的

首要看一下MachO文件的全体结构, 我们将从__objc_classlist下手,根究OC类的存储方法.MachO文件结构如下

swift 稠浊原理探求

__objc_classlist 中存储的类表,其实就是一系列地址,每个地址对应一个类.

swift 稠浊原理探求

其间赤色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;
};

swift 稠浊原理探求

其间赤色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编码

swift 稠浊原理探求

swift 稠浊原理探求

同理可知 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
};
​

swift 稠浊原理探求

0x6f78表明方法名偏移.0x0fb0为方法类型偏移. 0xfffffc60为Imp偏移.

0x6428 + 0x6f78 = 0xd3a0,0xd3a0_objc_selrefs分区

swift 稠浊原理探求

里面存放一个字符串的指针 再找到0x6500位于 __objct_methodname分区

swift 稠浊原理探求

可以看到方法名是viewDidLoad. 同样的方法可以找到方法实现 00006430 + fffffc60 = 100006090为Imp地址.

swift 稠浊原理探求

以上就是OC中Class的大致存储方法.总结一下

  • __objc_classlist存放着Class指针指向__objc_data中的地址
  • _objc_data存放着class64结构体数据其间data指向了class64Info的地址
  • class64Infoname指向了__objc_classname中的字符串,表明类名
  • class64InfobaseMethods指向了__objc_methodlist中的结构体method64_list_t,其间存放着方法信息
  • method64_list_tcount表明方法数量,在其随后的地址中以relative_method_t表明
  • relative_method_tnameOffset可以核算得出方法名的地址, 这一地址位于__objc_methodname分区.
  • relative_method_timpOffset可以核算得出方法对应的函数地址,位于__text分区.

类名和方法名都已字符串的方法存储在对应的分区__objc_classname__objc_methodname中了. 这和我们的直觉是一起的,OC作为一门动态言语方法名一定会以某种形式保存在MachO文件中.

基于这个原因对OC代码的稠浊方案中会对方法名和类名进行替换然后到达稠浊的作用. 可是我们知道swift作为一门静态言语,其方法的派发机制和OC是不同的. 那么问题来了, 对swift进行方法称号的替换有相同有用吗? 下文将根究该问题.

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的音讯机制, 需求方法名的参与,不赘述了.

下面探寻一下 函数表派发的细节

检验代码

swift 稠浊原理探求

swift 稠浊原理探求

swift 稠浊原理探求
其间 bl , blr 为跳转指令. 这以后是地址或许寄存器. 这儿iOS加载MachO时存在随机地址偏移(ASLR), 我们需求对地址进行转化得到其在MachO文件中的地址,

image list 打印内存中全部image , 找到SwiftConfuse这个Image,检查其地址 0x0000000102058000, 我们核算地址要减去这个数,来得到文件的偏移地址进行剖析.

swift 稠浊原理探求
下面逐行剖析这些指令做了啥

bl 0x10205db54: 恢复地址 0x10205db54 - 0x012058000 = 0x5b54, Hopper跳转到地址0x5b54,

swift 稠浊原理探求

这是一个读取swift metadata的程序, 获取Foo的matedata的地址并回来.

mov x20 ,x0: 将x0复制到x20, 前一条指令回来的地址保存到x0中,

bl 0x10205d664 : 恢复地址 0x10205d664 - 0x012058000 = 0x5664 , Hopper跳转检查,该程序用来初始化Foo方针并回来方针地址.

swift 稠浊原理探求
可以通过x0地址来验证
swift 稠浊原理探求
成功调用了Foo方针的bar方法.

mov x20 ,x0: 将x0复制到x20

str x20, [sp, #0x10] : 把寄存器x20的值保存到栈内存[sp + #0x10], 方针的赋值操作,我们不关心.

ldr x8 [x20] : 这儿读取的是x20地址开端的8个字节的数据. x20中存储的是Foo方针的地址.

swift 稠浊原理探求
根据Swift方针地址布局. 方针的第一个8字节存储的就是方针metadata的地址, 对的,其实就是isa指针. 所以这儿获取就是Foo的metadata信息

metadata 地址为 0x1020720c8. 恢复地址 0x1020720c8 - 0x012058000 = 0x1a0c8

swift 稠浊原理探求
这个结构看着是不是很熟悉, 其实就是上文根究OC类存储时的 class64, 说清楚OC和swift类的存储方法是一起的. 也可以解说为啥OC类的结构体中有vtable.

ldr x8 [x8, #0x50] : 读取x8偏移50字节后地址的8个直接的数据 即 0x1a0c8 + 50 = 0x1a118 中的值,

swift 稠浊原理探求

这儿得到的就是0x10000053f4 就是bar方法的地址,通过hopper可以验证.

swift 稠浊原理探求
blr x8: 实行bar方法.

留意: 这儿有2次获取matedata, 第一次是通过matedata accessor获取的, 第2次是通过方针地址获取的. 这是因为在存在方针的情况下,通过方针获取功率更高.

由上述流程可以看出Swift函数表派发寻址原理, swift class matedata 中存储了vtable, 函数表派发会先找到swift类的matedata, 再通过偏移量找到vtable中的地址. 这一进程并没有也不需求函数名的参与.

同OC根据matedata也可以灵敏定位出swift类名的位置, matedata->data->name

swift 稠浊原理探求
当然这个和OC有点差异, OC是保存在 __objc_classname分区的, swift是保存在__cstring分区的.

定论

关于swift而言

  • 因为其大部分的方法是静态派发或许函数表派发,machO文件中不保存方法称号.对方法名进行稠浊只对@objc dynamic修饰的方法起作用, 大部分情况下是无用功, 市面上一些对swift进行方法名替换稠浊的东西从原理上来说就是行不通的.
  • 纯swift类 类名和特色名方法名, 包含方法名的字符串不都储存. 继承OC的类的类名和存储特色的字符串存储在 Text段的 __cstring 分区, @objc修饰的方法名 和 继承自OC类的方法名会储存