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函數:
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的實例。
這就是所有的了。