浅谈在Swift中关于函数指针的实现

Swift没有什么?

苹果工程师给我建的唯一一堵墙是:在Swift中没有任何办法获得一个函数的指针:

注意,C函数指针不会导入到Swift中(来自“Using Swift with Cocoa and Objective-C“)

但是我们怎么知道这种情况下钩子的地址和跳到哪呢?让我们深入了解一下,并且看看Swift的func在字节码层面上的是什么。

当你给一个函数传递一个泛型参数时,Swift并没有直接传递它的地址,而是一个指向trampoline函数(见下文)并带有一些函数元数据信息的指针。并且trampoline自己是包装原始函数的结构的一部分。

这是什么意思?

让我们用它来举个例子:
 

代码如下:

func call_function(f : () -> Int) {
    let b = f()
}
 
func someFunction() -> Int {
    return 0
}

在Swift里我们只写 call_function(someFunction).
但是 Swift 编译器处理代码后,性能比调用call_function(&someFunction)好很多
 

代码如下:

struct swift_func_wrapper *wrapper =  ... /* configure wrapper for someFunction() */
struct swift_func_type_metadata *type_metadata = ... /* information about function's arguments and return type */
call_function(wrapper->trampoline, type_metadata);

一个包装器的结构如下:  
 

代码如下:

struct swift_func_wrapper {
    uint64_t **trampoline_ptr_ptr; // = &trampoline_ptr
    uint64_t *trampoline_ptr;
    struct swift_func_object *object;
}

什么是 swift_func_object类型? 为了创建对象,Swift 实时使用了一个全局的叫metadata[N]的的常量(每一个 function调用都是唯一的,似的你的func 作为一个泛型的参数,所以对于如下的代码:  
 

代码如下:

func callf(f: () -> ()) {
    f();
}
callf(someFunction);
callf(someFunction);

常量metadata和metadata2会被创建).

一个metadata[N]的结构有点儿像这样this:
 

代码如下:

struct metadata {
    uint64_t *destructor_func;
    uint64_t *unknown0;
    const char type:1; // I'm not sure about this and padding,
    char padding[7];   // maybe it's just a uint64_t too...
    uint64_t *self;
}

最初metadataN只有2个字段集合:destructor_func 和 type。前者是一个函数指针,将用作为使用swift_allocObject() 创建的对象分配内存。后者是对象类型识别器(函数或方法的0x40 或者 '@'),并且是(某种形式)被swift_allocObject() 用来创建一个正确的对象给我们的func: 
 
swift_allocObject(&metadata2->type, 0x20, 0x7);

一旦func 对象被创建,它拥有下面的结构:
 

代码如下:

struct swift_func_object {
    uint64_t *original_type_ptr;
    uint64_t *unknown0;
    uint64_t function_address;
    uint64_t *self;
}

第一个字段是一个指针,用来对应metadata[N]->type 的值,第二个字段似乎是 0x4 | 1 << 24(0x100000004) 并且暗示一些可能 (我不知道是什么)。  function_address 是我们实际挂钩感兴趣的地方,并且self 是 (立即) 自己的指针 (如果我们的对象表示一个普通的函数,这个字段是 NULL)。

好,那么这段我从框架开始如何?事实上,我不明白为什么Swift运行时需要它们,但不论如何,这就是它们原生态的样子:
 

代码如下:

void* someFunction_Trampoline(void *unknown, void *arg, struct swift_func_object *desc)
{
    void* target_function = (void *)desc->function_address;
    uint64_t *self = desc->self;
 
    swift_retain_noresult(desc->self); // yeah, retaining self is cool!
    swift_release(desc);
 
    _swift_Trampoline(unknown, arg, target_function, self);
    return unknown;
}
 
void *_swift_Trampoline(void *unknown, void *arg, void *target_function, void *self)
{
    target_function(arg, self);
    return unknown;
}

让我们创建它

想象一下,在你的Swift代码中有这些函数:

代码如下:

func takesFunc<T>(f : T) {
    ...
}
func someFunction() {
    ...
}

而且你想像这样生成它们:
 

代码如下:

takesFunc(someFunction)

这一行代码会转换成相当大的C程序:
 

代码如下:

struct swift_func_wrapper *wrapper = malloc(sizeof(*wrapper));
wrapper->trampoline_ptr     = &someFunction_Trampoline;
wrapper->trampoline_ptr_ptr = &(wrapper.trampoline);
wrapper->object = ({
    // let's say the metadata for this function is `metadata2`
    struct swift_func_object *object = swift_allocObject(&metadata2->type, 0x20, 0x7);
    object->function_address = &someFunction;
    object->self = NULL;
    object;
});
 
 
// global constant for the type of someFunction's arguments
const void *arg_type = &kSomeFunctionArgumentsTypeDescription;
// global constant for the return type of someFunction
const void *return_type = &kSomeFunctionReturnTypeDescription;
 
struct swift_func_type_metadata *type_metadata = swift_getFunctionTypeMetadata(arg_type, return_type);
 
takesFunc(wrapper->trampoline_ptr, type_metadata);

结构体“swift_func_type_metadata”很不透明,因此我也没太多可以说的。

回到函数指针

既然我们已经知道函数怎样作为一个泛型类型参数表示,让我们借助这个打到你的目的:获取一个真正指向函数的指针!

我们要做的只是需要注意,我们已经拥有一个作为第一个参数传递的trampoline_ptr指针域地址,所以object域的偏移量只是0x8。其他的所有都很容易组合:
 

代码如下:

uint64_t _rd_get_func_impl(void *trampoline_ptr)
{
    struct swift_func_object *obj = (struct swift_func_object *)*(uint64_t *)(trampoline_ptr + 0x8);
 
    return obj->function_address;
}

看起来是时候写写

代码如下:

rd_route(
    _rd_get_func_impl(firstFunction),
    _rd_get_func_impl(secondFunction),
    nil
)

但我们怎样从Swift中调用这些C函数呢?

为此,我们将使用Swift非公开的特性:允许我们提供给C函数一个Swift接口的@asmname属性。用法如下:
 

代码如下:

@asmname("_rd_get_func_impl")
    func rd_get_func_impl<Q>(Q) -> UInt64; 
 
@asmname("rd_route")
    func rd_route(UInt64, UInt64, CMutablePointer<UInt64>) -> CInt;

这就是我们在Swift中使用rd_route()需要的一切。

但是它不能处理任何函数!

也就是说,你不能用rd_route()钩住任何带有泛型参数的函数(这可能是Swift的bug,也可能不是,我还没弄清楚)。但是你可以使用extensions轻松的覆盖它们,直接指定参数的类型:
 

代码如下:

class DemoClass {
    class func template <T : CVarArg>(arg : T, _ num: Int) -> String {
        return "\(arg) and \(num)";
    }
}
 
DemoClass.template("Test", 5) // "Test and 5"
 
extension DemoClass {
    class func template(arg : String, _ num: Int) -> String {
        return "{String}";
    }
    class func template(arg : Int, _ num: Int) -> String {
        return "{Int}";
    }
}
 
-- Your extension's methods for String and Int will be preferred over the original ones */
DemoClass.template("Test", 5) -- "{String}"
DemoClass.template(42, 5) -- "{Int}"
-- But for other types `template(T, Int)` will be used
DemoClass.template(["Array", "Item"], 5) --- "[Array, Item] and 5"

SWRoute

为了在Swift里轻松地勾住函数,我创建了一个名为SWRoute的封装体—它只是一个小类和一个我们之前写过的C函数:

代码如下:

_rd_get_func_impl():
 
class SwiftRoute {
    class func replace<MethodT>(function targetMethod : MethodT, with replacement : MethodT) -> Int
    {
        return Int(rd_route(rd_get_func_impl(targetMethod), rd_get_func_impl(replacement), nil));
    }
}

注意,我们无偿进行类型检查因为Swift需要目标方法和替换具有相同的MethoT类型。

而且我们也无法使用一个复制的原始实现,因此我只能把nil作为另一个参数传给函数rd_route()。如果你对如何把这个指针集成到Swift代码有自己的看法,麻烦告诉我!

你可以在资源库中找到大量SWRoute的实例。

这就是所有的了。

(0)

相关推荐

  • Swift中的指针操作和使用详细介绍

    Apple期望在Swift中指针能够尽量减少登场几率,因此在Swift中指针被映射为了一个泛型类型,并且还比较抽象.这在一定程度上造成了在Swift中指针使用的困难,特别是对那些并不熟悉指针,也没有多少指针操作经验的开发者(包括我自己也是)来说,在Swift中使用指针确实是一个挑战.在这篇文章里,我希望能从最基本的使用开始,总结一下在Swift中使用指针的一些常见方式和场景.这篇文章假定你至少知道指针是什么,如果对指针本身的概念不太清楚的话,可以先看看这篇五分钟C指针教程(或者它的中文版本),应

  • Swift教程之字符串和字符详解

    一个字符串String就是一个字符序列,像"hello,world","albatross"这样的.Swift中的字符串是用String关键词来定义的,同时它也是一些字符的集合,用Character定义. Swift的String和Character类型为代码提供了一个快速的,兼容Unicode的字符解决方案.String类型的初始化和使用都是可读的,并且和C中的strings类似.同时String也可以通过使用+运算符来组合,使用字符串就像使用Swift中的其他基

  • Swift中的可变参数函数介绍

    可变参数函数指的是可以接受任意多个参数的函数,我们最熟悉的可能就是 NSString 的 -stringWithFormat:方法了.在 Objective-C 中,我们使用这个方法生成字符串的写法是这样的: 复制代码 代码如下: NSString *name = @"Tom"; NSDate *date = [NSDate date]; NSString *string = [NSString stringWithFormat:                 @"Hell

  • Swift教程之枚举类型详解

    枚举定义了一个常用的具有相关性的一组数据,并在你的代码中以一个安全的方式使用它们. 如果你熟悉C语言,你就会知道,C语言中的枚举指定相关名称为一组整数值.在Swift中枚举更为灵活,不必为枚举的每个成员提供一个值.如果一个值(被称为"原始"的值)被提供给每个枚举成员,则该值可以是一个字符串,一个字符,或者任何整数或浮点类型的值. 另外,枚举成员可以指定任何类型,每个成员都可以存储的不同的相关值,就像其他语言中使用集合或变体.你还可以定义一组通用的相关成员为一个枚举,每一种都有不同的一组

  • Swift流程控制之循环语句和判断语句详解

    Swift提供了所有c类语言的控制流结构.包括for和while循环来执行一个任务多次:if和switch语句来执行确定的条件下不同的分支的代码:break和continue关键字能将运行流程转到你代码的另一个点上. 除了C语言传统的for-condition-increment循环,Swift加入了for-in循环,能更加容易的遍历arrays, dictionaries, ranges, strings等其他序列类型. Swift的switch语句也比C语言的要强大很多. Swift中swi

  • Swift的74个常用内置函数介绍

    Swift包含了74个内置函数,但在 The Swift Programming Langage 一书中只介绍了其中的7个,其它的都没有在文档中体现. 这篇文章列举出了所有的Swift库函数.文中所谓的 内置函数 是指无需引入任何模块(比如说Fundation等)即可以直接使用的函数. 下面先来看看7个在文档中提到的库函数: 下面列出一些很实用,但未在文档中体现的库函数: 复制代码 代码如下: //断言,参数如果为`true`则继续,否则抛出异常 //assert mentioned on pa

  • Swift中动态调用实例方法介绍

    在 Swift 中有一类很有意思的写法,可以让我们不直接使用实例来调用这个实例上的方法,而是通过类型取出这个类型的某个实例方法的签名,然后再通过传递实例来拿到实际需要调用的方法.比如我们有这样的定义: 复制代码 代码如下: class MyClass {     func method(number: Int) -> Int {         return number + 1     } } 想要调用 method 方法的话,最普通的使用方式是生成MyClass的实例,然后用.method来

  • Swift与C语言指针结合使用实例

    Objective-C和C的API常常会需要用到指针.Swift中的数据类型都原生支持基于指针的Cocoa API,不仅如此,Swift会自动处理部分最常用的将指针作为参数传递的情况.这篇文章中,我们将着眼于在Swift中让C语言指针与变量.数组和字符串共同工作. ####用以输入/输出的参数指针 C和Objective-C并不支持多返回值,所以Cocoa API中常常将指针作为一种在方法间传递额外数据的方式.Swift允许指针被当作inout参数使用,所以你可以用符号&将对一个变量的引用作为指

  • Swift中的指针操作详解

    前言 Objective-C和C语言经常需要使用到指针.Swift中的数据类型由于良好的设计,使其可以和基于指针的C语言API无缝混用.但是语法上有很大的差别. 默认情况下,Swift 是内存安全的,这意味着它禁止我们直接操作内存,并且确保所有的变量在使用前都已经被正确地初始化了.但是,Swift 也提供了我们使用指针直接操作内存的方法,直接操作内存是很危险的行为,很容易就出现错误,因此官方将直接操作内存称为 "unsafe 特性". 一旦我们开始直接操作内存,一切就得靠我们自己了,因

  • swift中的正则表达式小结

    作为一门先进的编程语言,Swift 可以说吸收了众多其他先进语言的优点,但是有一点却是让人略微失望的,就是 Swift 至今为止并没有在语言层面上支持正则表达式. 正则表达式的用处: 判断给定的字符串是否符合某一种规则(专门用于操作字符串) - 电话号码,电子邮箱,URL... - 可以直接百度别人写好的正则 - 别人真的写好了,而且测试过了,我们可以直接用 - 要写出没有漏洞正则判断,需要大量的测试,通常最终结果非常负责 过滤筛选字符串,网络爬虫 替换文字,QQ聊天,图文混排 语法规则 使用过

随机推荐