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

orangegoose 发布于1年前 阅读1451次
0 条评论

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;
 }


好,那么这段我从框架开始如何?事实上,我不明白为什么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的实例。

这就是所有的了。

需要 登录 后回复方可回复, 如果你还没有账号你可以 注册 一个帐号。