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

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

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后情况会变成什么样呢?

screenshot

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
目录
相关文章
|
3月前
|
Swift iOS开发
iOS Swift使用Alamofire请求本地服务器报错-1002
iOS Swift使用Alamofire请求本地服务器报错-1002
104 1
|
4月前
|
Unix 调度 Swift
苹果iOS新手开发之Swift 中获取时间戳有哪些方式?
在Swift中获取时间戳有四种常见方式:1) 使用`Date`对象获取秒级或毫秒级时间戳;2) 通过`CFAbsoluteTimeGetCurrent`获取Core Foundation的秒数,需转换为Unix时间戳;3) 使用`DispatchTime.now()`获取纳秒级精度的调度时间点;4) `ProcessInfo`提供设备启动后的秒数,不表示绝对时间。不同方法适用于不同的精度和场景需求。
156 3
|
14天前
|
安全 数据处理 Swift
深入探索iOS开发中的Swift语言特性
本文旨在为开发者提供对Swift语言在iOS平台开发的深度理解,涵盖从基础语法到高级特性的全面分析。通过具体案例和代码示例,揭示Swift如何简化编程过程、提高代码效率,并促进iOS应用的创新。文章不仅适合初学者作为入门指南,也适合有经验的开发者深化对Swift语言的认识。
35 9
|
27天前
|
安全 API Swift
探索iOS开发中的Swift语言之美
【10月更文挑战第23天】在数字时代的浪潮中,iOS开发如同一艘航船,而Swift语言则是推动这艘船前进的风帆。本文将带你领略Swift的独特魅力,从语法到设计哲学,再到实际应用案例,我们将一步步深入这个现代编程语言的世界。你将发现,Swift不仅仅是一种编程语言,它是苹果生态系统中的一个创新工具,它让iOS开发变得更加高效、安全和有趣。让我们一起启航,探索Swift的奥秘,感受编程的乐趣。
|
1月前
|
安全 Swift iOS开发
探索iOS开发中的Swift语言之美
在数字时代的浪潮中,移动应用已成为日常生活的延伸。本文将深入探讨iOS平台上的Swift编程语言,揭示其背后的设计哲学、语法特性以及如何利用Swift进行高效开发。我们将通过实际代码示例,展示Swift语言的强大功能和优雅简洁的编程风格,引导读者理解并运用Swift解决实际问题。
|
2月前
|
安全 Swift iOS开发
探索iOS开发之旅:Swift语言的魅力与挑战
【9月更文挑战第21天】在这篇文章中,我们将一起潜入iOS开发的海洋,探索Swift这门现代编程语言的独特之处。从简洁的语法到强大的功能,Swift旨在让开发者能够以更高效、更安全的方式构建应用程序。通过实际代码示例,我们会深入了解Swift如何简化复杂任务,并讨论它面临的挑战和未来的发展方向。无论你是初学者还是有经验的开发者,这篇文章都将为你提供新的视角和知识。
46 4
|
2月前
|
安全 编译器 Swift
探索iOS开发之旅:Swift编程语言的魅力与挑战
【9月更文挑战第5天】在iOS应用开发的广阔天地中,Swift作为苹果官方推荐的编程语言,以其简洁、高效和安全的特点,成为了开发者的新宠。本文将带领你领略Swift语言的独特魅力,同时探讨在实际开发过程中可能遇到的挑战,以及如何克服这些挑战,成为一名优秀的iOS开发者。
|
2月前
|
设计模式 前端开发 Swift
探索iOS开发:Swift与Objective-C的较量
在这篇文章中,我们将深入探讨iOS开发的两大编程语言——Swift与Objective-C。我们将分析这两种语言的特性、优势和局限性,并讨论它们在现代iOS开发中的应用。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的见解和建议。
57 3
|
3月前
|
API Swift C语言
探索iOS开发:Swift中的异步编程与GCD应用
【8月更文挑战第4天】在iOS开发的海洋中,掌握Swift语言的航向是至关重要的。本文将引领你深入理解Swift中的异步编程概念,并借助Grand Central Dispatch(GCD)这一强大的工具,来简化并发编程的复杂性。我们将通过实际代码示例,展现如何在iOS应用中高效地管理后台任务和提升用户界面的响应性。
80 3
|
3月前
|
测试技术 Swift iOS开发
探索iOS自动化测试:使用Swift编写UI测试
【8月更文挑战第31天】在软件开发的海洋中,自动化测试是保证船只不偏离航线的灯塔。本文将带领读者启航,深入探索iOS应用的自动化UI测试。我们将通过Swift语言,点亮代码的灯塔,照亮测试的道路。文章不仅会展示如何搭建测试环境,还会提供实用的代码示例,让理论知识在实践中生根发芽。无论你是新手还是有经验的开发者,这篇文章都将是你技能提升之旅的宝贵指南。