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的实例。
这就是所有的了。
本文向大家介绍浅谈关于Android路由的实现,包括了浅谈关于Android路由的实现的使用技巧和注意事项,需要的朋友参考一下 先说一下背景,目前有需求从外部包括其他应用和WEB跳转到我们自己的APP,就这么个简单的需求…… 要实现这种外部跳转的功能,我们可以理解为打算跳转的一方有多少方式通知到APP进行相对的响应行为。所以,如果是应用之间的跳转,则有多种,你可以直接通过包名和具体的类名去打开已经
本文向大家介绍浅谈C/C++中指针和数组的不同,包括了浅谈C/C++中指针和数组的不同的使用技巧和注意事项,需要的朋友参考一下 这边先简单介绍一下内存分区。 内存按照用途划分为五个区: 1.栈区:由系统控制分配和回收。 例如定义变量 int x = 0; int *p = NULL; 变量所占的内存都是分配在栈区的。 2.堆区:由程序员管理。 在C语言中由 malloc 申请的内存,或者在C++中
本文向大家介绍浅谈Shell中的函数,包括了浅谈Shell中的函数的使用技巧和注意事项,需要的朋友参考一下 函数可以让我们将一个复杂功能划分成若干模块,让程序结构更加清晰,代码重复利用率更高。像其他编程语言一样,Shell也支持函数。Shell函数必须先定义后使用。 1.Shell函数的定义格式 可以带function关键字使用function fun_name()来定义,也可以直接给出函数名fu
本文向大家介绍浅谈Mysql中类似于nvl()函数的ifnull()函数,包括了浅谈Mysql中类似于nvl()函数的ifnull()函数的使用技巧和注意事项,需要的朋友参考一下 IFNULL(expr1,expr2) 如果expr1不是NULL,IFNULL()返回expr1,否则它返回expr2。IFNULL()返回一个数字或字符串值,取决于它被使用的上下文环境。 如果expr1是TRUE(e
一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址,这和数组名非常类似。我们可以把函数的这个首地址(或称入口地址)赋予一个 指针变量,使指针变量指向函数所在的内存区域,然后通过指针变量就可以找到并调用该函数。这种指针就是 函数指针。 函数指针的定义形式为: returnType (*pointerName)(param list); returnType
本文向大家介绍浅谈pandas中shift和diff函数关系,包括了浅谈pandas中shift和diff函数关系的使用技巧和注意事项,需要的朋友参考一下 通过?pandas.DataFrame.shift命令查看帮助文档 该函数主要的功能就是使数据框中的数据移动,若freq=None时,根据axis的设置,行索引数据保持不变,列索引数据可以在行上上下移动或在列上左右移动;若行索引为时间序列,则可