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

0x0 引子

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

0x1 swift的方法调用方式

swift有四种方法调用方式:

  • inline method
  • static dispatch
  • dynamic dispatch
  • message send

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

示例代码如下:

public class aclass {
    public func hehe() {
        clock()
        hehe1()
    }

    public func hehe1() {
        clock()
    }
}

编译后的汇编代码是:

    0x1000e9af8 <+84>:  bl     0x1000ea6c4               ; symbol stub for: clock
    0x1000e9afc <+88>:  bl     0x1000ea6c4               ; symbol stub for: clock

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

static dispatch会通过汇编指令bl跳转到被调用方法所在的地址, 地址在编译期就已经决定. 但因为多了一次bl指令, 会有少量的时间消耗.
对代码做不inline处理:

public class aclass {
    public func hehe() {
        clock()
        hehe1()
    }

    @inline(never) public func hehe1() {
        clock()
    }
}

编译后的汇编代码是:

    0x100051b18 <+124>: bl     0x1000526ac               ; symbol stub for: clock
    0x100051b1c <+128>: bl     0x100052100               ; function signature specialization <Arg[0] = Dead> of testswift.aclass.hehe1 () -> () at ViewController.swift:40
; hehe1 方法的实现
    0x100052100 <+0>: b      0x1000526ac                 ; symbol stub for: clock

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

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

代码同第一段代码, 把工程Build Settings的Swift Comipler - Code Generation的Optimization Level调为None, 反编译代码如下:

    0x1000c1ad8 <+28>: ldr    x8, [x30]             ; 从对象中取出class元数据指针
    0x1000c1adc <+32>: ldr    x8, [x8, #88]         ; 从class元数据指针的偏移量88位置取出hehe1方法的地址放入x8
    0x1000c1ae8 <+44>: blr    x8                    ; 跳转到x8中保存的地址处的代码, 也就是hehe1方法
; hehe1 方法的实现
    0x1000c1b08 <+16>: bl     0x1000c2704           ; symbol stub for: clock

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

message send就是objc_msgSend的流程, 这里暂不多做介绍.

0x2 patch尝试

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

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

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

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

先来一段原始代码:

public class aclass {
    public func hehe() {
        hehe1()
    }

    public func hehe1() {
        print("hehe1")
    }
}

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

hehe1

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

void patched_hehe1() {
    printf("patched_hehe1");
}

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

开干!

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

void patched_hehe1() {
    printf("patched_hehe1");
}

void write_memory(void *ptr, void value) {
    *ptr = value;
}

void *get_patch_method_address() {
    return &patched_hehe1;
}

void *get_class_meta_address() {
    Class aclass = NSClassFromString(@"testswift.aclass");
    return (__bridge void *)aclass;
}

long get_method_offset(void *class) {
    void * raw_method_address = dlsym(RTLD_DEFAULT, "_TFC9testswift6aclass5hehe1fT_T_");
    for (long i=0; i<1024; i++) {
        if ((long )(class+i) == (long)raw_method_address) {
            return i;
        }
    }
    return -1;
}

void patch_hehe1() {
    void *method_address = get_patch_method_address();  // 获取patched_hehe1方法的地址
    void *class_meta_address = get_class_meta_address();  // 获取aclass metadata的地址
    long offset = get_method_offset(class_meta_address);  // 获取偏移量
    write_memory(class_meta_address+offset, method_address); // 篡改metadata中的方法指针
}

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

patched_hehe1

patch成功!

0x3 局限性

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

hehe1方法被inline, patch无效, 输出hehe1

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

0x4 参考

  1. Swift Method Dispatch: http://stackoverflow.com/questions/24014045/does-swift-have-dynamic-dispatch-and-virtual-methods?answertab=votes#tab-top
  2. Increasing Performance by Reducing Dynamic Dispatch: https://developer.apple.com/swift/blog/?id=27
时间: 2024-10-02 11:51:16

[iOS]原生swift的hotpatch可行性初探的相关文章

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

0x0 引子 之前在<原生swift的hotpatch可行性初探>对swift hotpatch的原理做一个简单的介绍和简单的示例, 但基础的原理分析并不能确定真实的可行性. 为此想通过这篇文章来做一个更复杂的例子. 0x1 先来一个简单的例子 来一个例子, 实现用js patch swift的方法, 功能包括: 在js中通过类名/方法名/替换的方法, 来替换swift的方法 在js中通过方法名来调用原有的swift方法 swift代码: public class ViewController

iOS原生如何加载HTML中img标签的图片

原文出自:iOS原生如何加载HTML中img标签的图片 前言 最近iOS App项目中使用Webview加载H5页面比较多,也有不少朋友经常问到这个问题,在这里我也学习学习如何通过iOS原生的方式来加载H5页面中的图片然后让webview显示图片. 相信有很多朋友也遇到过这样的问题,可是很多朋友都没有思路,不知如何入手.今天,刚好学习了一下,并写了一个简单的demo.下面让我们一起来学习一下吧! 本篇文章适合哪些人群阅读? 项目中有类似需求的,而又没有思路的 曾经做过类似需求的,可以参考两者的思

使用iOS原生sqlite3框架对sqlite数据库进行操作

使用iOS原生sqlite3框架对sqlite数据库进行操作 一.引言       sqlite数据库是一种小型数据库,由于其小巧与简洁,在移动开发领域应用深广,sqlite数据库有一套完备的sqlite语句进行管理操作,一些常用的语句和可视化的开发工具在上篇博客中有介绍,地址如下: sqlite数据库常用语句及可视化工具介绍:http://my.oschina.net/u/2340880/blog/600820.       在iOS的原生开发框架中可以对sqlite数据库进行很好的支持,这个

iOS原生地图开发指南再续——地图覆盖物的应用

iOS原生地图开发指南再续--地图覆盖物的应用 一.引言 在前两篇博客中,将iOS系统的地图框架MapKit中地图的设置与应用以及关于添加大头针和自定义大头针的相关操作做了详细的介绍.链接如下:http://my.oschina.net/u/2340880/blog/415360.http://my.oschina.net/u/2340880/blog/415441.这篇博客中将进一步讨论关于地图添加覆盖物的使用方法. 二.添加地图覆盖物的逻辑原理 地图覆盖物其实就是在地图上画一些东西,例如路径

iOS原生地图开发指南续——大头针与自定义标注

iOS原生地图开发指南续--大头针与自定义标注 在上一篇博客中http://my.oschina.net/u/2340880/blog/415360系统总结了iOS原生地图框架MapKit中主体地图的设置与应用.这篇是上一篇的一个后续,总结了系统的大头针视图以及自定义标注视图的方法. 一.先来认识一个协议MKAnnotation 官方文档告诉我们,所有标注的类必须遵守这个协议.所以可以了解,标注这个概念在逻辑属性和视图上是分开的.先来看下这个协议声明了哪些方法: ? 1 2 3 4 5 6 7

iOS原生地图开发进阶——使用导航和附近兴趣点检索

iOS原生地图开发进阶--使用导航和附近兴趣点检索 iOS中的mapKit框架对国际化的支持非常出色.在前些篇博客中,对这个地图框架的基础用法和标注与覆盖物的添加进行了详细的介绍,这篇博客将介绍两个更加实用的功能的开发:线路导航与兴趣点搜索.前几篇博客的链接如下: 地图基础用法详解:http://my.oschina.net/u/2340880/blog/415360. 添加大头针与自定义标注:http://my.oschina.net/u/2340880/blog/415441. 添加地图覆盖

ios 用swift语言的,然后链接数据库,sqlite。

问题描述 ios 用swift语言的,然后链接数据库,sqlite. ios开发,用是swift语言.如何链接SQLite数据库.求详细代码 解决方案 iOS数据库Sqliteios SQlite操作数据库iOS中操作SQLite数据库 解决方案二: 用fmdb. http://www.tuicool.com/articles/jA3yUfj

iOS原生地图开发指南

iOS原生地图开发详解 在上一篇博客中:http://my.oschina.net/u/2340880/blog/414760.对iOS中的定位服务进行了详细的介绍与参数说明,在开发中,地位服务往往与地图框架结合使用,这篇博客主要对iOS官方的地图框架MapKit.framework进行介绍. 一.初始化地图视图与相关属性方法介绍 1.初始化地图视图 地图视图的展示依赖于MKMapView这个类,这个类继承于UIView,因此和其他View的使用方法类似.在我们需要展现地图的地方: ? 1 2

iOS 8 Swift Programming Cookbook

iOS 8 Swift Programming Cookbook   资源地址 http://pan.baidu.com/s/1c0hn1Gc   书籍介绍   源码截图   书籍截图