Swift has nothing?
The only wall that Apple engineers built for me is: There is no way to get a pointer to a function in Swift:
Note that C function pointers are not imported into Swift (from "Using Swift with Cocoa and Objective-C")
But how do we know where the hook address and jump to in this case? Let's take a look at what Swift's func is at the bytecode level.
When you pass a generic parameter to a function, Swift does not pass its address directly, but a pointer to the trampoline function (see below) with some function metadata information. And trampoline itself is part of the structure that wraps the original function.
What does this mean?
Let's use it as an example:
func call_function(f : () -> Int) {
let b = f()
}
func someFunction() -> Int {
return 0
}
In Swift we only write call_function(someFunction).
However, after the Swift compiler processes the code, the performance is much better than calling 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);
The structure of a wrapper is as follows:
struct swift_func_wrapper {
uint64_t **trampoline_ptr_ptr; // = &trampoline_ptr
uint64_t *trampoline_ptr;
struct swift_func_object *object;
}
What is the swift_func_object type? In order to create an object, Swift uses a global constant called metadata[N] in real time (every function call is unique, like your func as a generic parameter, so for the following code:
func callf(f: () -> ()) {
f();
}
callf(someFunction);
callf(someFunction);
Constants metadata and metadata2 will be created).
The structure of a metadata[N] is a bit like 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;
}
Initially metadataN had only 2 fields: destructor_func and type. The former is a function pointer that will be allocated memory as an object created with swift_allocObject(). The latter is an object type recognizer (0x40 of function or method or'@') and is (some form) used by swift_allocObject() to create a correct object to our func:
swift_allocObject(&metadata2->type, 0x20, 0x7);
Once the func object is created, it has the following structure:
struct swift_func_object {
uint64_t *original_type_ptr;
uint64_t *unknown0;
uint64_t function_address;
uint64_t *self;
}
The first field is a pointer to correspond to the value of metadata[N]->type , the second field seems to be 0x4 | 1 << 24(0x100000004) and suggests some possible (I don't know what it is). function_address is where we actually hook is interested, and self is (immediately) its own pointer (if our object represents a normal function, this field is NULL).
OK, so how do I start with the framework of this paragraph? In fact, I don't understand why Swift needs them when running, but no matter what, this is what they look like:
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;
}
Let's create it
Imagine having these functions in your Swift code:
func takesFunc<T>(f : T) {
...
}
func someFunction() {
...
}
And you want to generate them like this:
takesFunc(someFunction)
This line of code will be converted into a considerable C program:
struct swift_func_wrapper *wrapper = malloc(sizeof(*wrapper));
wrapper->trampoline_ptr = &someFunction_Trampoline;
wrapper->trampoline_ptr_ptr = &();
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);
The structure "swift_func_type_metadata" is very opaque, so I don't have much to say.
Back to function pointer
Now that we already know how a function is represented as a generic type parameter, let's use this to hit your purpose: get a pointer to the function!
All we have to do is note that we already have a trampoline_ptr pointer domain address passed as the first parameter, so the offset of the object domain is only 0x8. All the others are easy to combine:
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;
}
It seems it's time to write
rd_route(
_rd_get_func_impl(firstFunction),
_rd_get_func_impl(secondFunction),
nil
)
But how do we call these C functions from Swift?
To do this, we will use Swift's non-public feature: allowing us to provide a Swift interface to the C function. The usage is as follows:
@asmname("_rd_get_func_impl")
func rd_get_func_impl<Q>(Q) -> UInt64;
@asmname("rd_route")
func rd_route(UInt64, UInt64, CMutablePointer<UInt64>) -> CInt;
That's everything we need to use rd_route() in Swift.
But it can't handle any functions!
That is, you can't hook any function with generic parameters with rd_route() (this may be a bug in Swift, or it may not, I haven't figured it out yet). But you can easily override them using extensions and directly specify the type of the parameter:
class DemoClass {
class func template <T : CVarArg>(arg : T, _ num: Int) -> String {
return "\(arg) and \(num)";
}
}
("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 */
("Test", 5) -- "{String}"
(42, 5) -- "{Int}"
-- But for other types `template(T, Int)` will be used
(["Array", "Item"], 5) --- "[Array, Item] and 5"
SWRoute
To easily hook the function in Swift, I created an encapsulation called SWRoute—it's just a small class and a C function we've written before:
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));
}
}
Note that we do type checking for free because Swift requires target methods and replace with the same MethodT type.
And we can't use a copied original implementation, so I can only pass nil as another parameter to the function rd_route(). If you have your own opinion on how to integrate this pointer into Swift code, please let me know!
You can find a large number of instances of SWRoute in the library.
That's all.