天天看点

[iOS]原生swift的hotpatch可行性初探

最近在ios群里面看到某应用因为hotpatch审核被拒绝, 如果hotpatch全面被封禁, 那还不如全切swift, 又能提高性能, 又能减少编码中犯的错误. 仔细想想如果swift也有办法被hotpatch, 不就更加完美了?

hotpatch是无法被全面封禁的, 可爱的程序猿们总能有应对的办法

swift有四种方法调用方式:

inline method

static dispatch

dynamic dispatch

message send

<code>inline method</code>会在编译期间将被调用的方法直接内联到调用方法的方法体里面, 这种状况下方法调用将没有任何开销.

示例代码如下:

编译后的汇编代码是:

可以看出汇编代码超级简洁, 就只剩两次对clock方法的调用.

<code>static dispatch</code>会通过汇编指令<code>bl</code>跳转到被调用方法所在的地址, 地址在编译期就已经决定. 但因为多了一次<code>bl</code>指令, 会有少量的时间消耗.

对代码做不inline处理:

这里比面上多出来一条<code>bl</code>指令, 跳转到hehe1方法所在地址.

<code>dynamic dispatch</code>会生成一张类的方法表(在数据段), 在调用时通过<code>ldr</code>指令从类表取出方法所在的地址到寄存器, 再跳转到方法所在的地址. 这种方式需要3条指令及1次内存访问, 所耗费的时间更长.

代码同第一段代码, 把<code>工程build settings的swift comipler - code generation的optimization level调为none</code>, 反编译代码如下:

注: 此段代码汇编代码经过精简, 代码的解释见注释

<code>message send</code>就是objc_msgsend的流程, 这里暂不多做介绍.

inline的代码明显没戏, 连独立的方法都没有了, 更没有方法调用.

static的代码也没戏, 调用的方法地址在编译期就决定好了, 无法动态的做改变(代码在__text段, 代码加载到内存后无写权限, 无法更改).

message用oc那一套方案就可以了.

dynamic的代码中, 是通过加载class的元数据中存储的方法地址进行方法调用, 而class元数据位于__data段, __data段是可以读写的. 那不就意味着采用dynamic方式, 把class的元数据给篡改掉就可以patch了? 我们试试!

先来一段原始代码:

按照正常的执行流程, 我们会在调试窗口看到:

我想把hehe1这个方法patch到另一个方法的实现(一个c方法):

从之前提到的dynamic调用方式的修改class元数据的思路展开说, 我们首先要获取到aclass的class元数据的地址, 再获取到hehe1方法指针在元数据中的偏移量, 再获取patched_hehe1方法的指针, 再塞到class元数据中hehe1方法对应的位置.

开干!

下面实现了patch_hehe1方法, 将hehe1方法给patch成patched_hehe1方法:

调用patch_hehe1方法后, aclass的hehe1方法就被patch掉了! 运行程序看调试窗口的结果:

patch成功!

在前面提到, 为了实现让swift走dynamic dispatch, 将编译选项中的优化级别设为了none. 那如果将优化级别恢复为<code>fast</code>后情况会变成什么样呢?

[iOS]原生swift的hotpatch可行性初探

<code>hehe1方法被inline, patch无效, 输出hehe1</code>

那么问题来了! 你愿意牺牲性能换取动态性么?