Swift Funtime

We love the Objective-C runtime for three main reasons: dynamic introspection, behavior changing, and the ability to analyze private APIs. In this talk, Boris Bügling asks, to what extent does this runtime functionality remain in Swift? Just enough, perhaps, to still do interesting things.


What is a SwiftObject? (0:26)

We all love Swift, but very likely we still have lots of questions about it. For example, what is a Swift object? The answer to that depends. If you inherit from NSObject, you will get an object which behaves like a regular NSObject. All of its variables will be properties, and it is fully interoperable with Objective-C. For these objects, we can just import the Objective-C.runtime and work with it.

If we don’t inherit from anything, we have an implicit superclass which is called SwiftObject. In terms of the runtime, all of the instance variables are only ivars without any type encoding. Thus, we cannot actually inspect their values. All the methods are not Objective-C methods — they are implemented completely differently. SwiftObjects are not at all interoperable with Objective-C.

// Inherit from NSObject
class MyObject : NSObject {
}
important ObjectiveC.runtime

// No inheriting
class MyObject {
}

We can still inspect those types. If we look at a SwiftObject, it looks a bit different from an NSObject. It has one ivar called magic, an isa pointer to the metaclass, and refCount. It implements the NSObject protocol. If you have an NSObject, you have the isa pointer for the metaclass, as well as the implemented NSObject protocol.

How Does Bridging Work? (2:01)

We might ask ourselves, “How does bridging work if there are two completely different kinds of objects?” The answer is that it doesn’t. What does that mean?

Let’s use the example of an array. If we don’t import Foundation, we cannot cast that array to AnyObject. It will not compile. If we look at its class, we see that it is a Swift array, which is actually a struct. Once we import Foundation into our code, we can then cast that array to an AnyObject. This will give us an array of type _NSSwiftArrayImpl that is a subclass of NSArray.

let array = [0, 1, 2] // 'as AnyObject' => !
info(array) // is a Swift.Array

import Foundation

let objc_array: AnyObject = [0, 1, 2] as AnyObject
info(objc_array) // is a Swift._NSSwiftArrayImpl

// comparing different array types => compiler error as well
//let equal = objc_array == array

This means that bridging relies on type inference. At some point, if you bridge to Objective-C, you have a different kind of array. That’s how bridging works for all of the Standard Library types, like Strings or Dictionaries. If you use these types in an Objective-C context, you will get a subclass of the Objective-C type.

Objective-C Runtime (3:16)

There were three things we loved about the Objective-C runtime that we can still do when we use Swift. The first thing is dynamic introspection. We could change behaviour as we wanted; as Rubyists would call it, “monkey patching”. Finally, we could analyze private APIs.

Dynamic Introspection (3:52)

Firstly, dynamic introspection. If we inherit from NSObject, or have used any kind of Cocoa frameworks, we can still use the runtime to inspect properties. After we import Objective-C.runtime, we can walk through all the properties to get their names.

var propertyCount : UInt32 = 0
var properties : UnsafeMutablePointer<objc_property_t> =
    class_copyPropertyList(myClass, &propertyCount)

for i in 0 ..< propertyCount {
    println("Property: " +
    String.fromCString(property_getName(properties[Int(i)]))!)
}

In pure Swift, not so much. But there is hope! If we look at the Swift Standard LIbrary, we see code that is private, but not documented anywhere else. One example is MirrorType, which is a reflection mechanism that Xcode uses to bring you support for Swift.

// Excerpt from the Swift Standard Library
/// How children of this value should be presented in the IDE.
enum MirrorDisposition {
    case Struct
    case Class
    case Enum
    [...]
}

/// A protocol that provides a reflection interface to an underlying value.
protocol MirrorType {
    [...]
}

With that, we can import something like KVO. We have a custom operator for that, which gets an object and a key. We use the reflect method from the Standard Library to get this mirror object for our instance. We can then walk through its children. If childKey matches the key that we want, we return the value. If we try to use this technique on a struct which has two floats, we can get its values. So, there are still some ways to do introspection, albeit in a more private manner.

infix operator --> {}
func --> (instance: Any, key: String) -> Any? {
    let mirror = reflect(instance)

    for index in 0 ..< mirror.count {
        let (childKey, childMirror) = mirror[index]
        if childKey == key {
            return childMirror.value
        }
    }
    return nil
}

Change Behavior (5:38)

Again, if we use any kind of inheritance from NSObject, we can still use the runtime as we did before. It’s a bit more cumbersome, because there is actually a difference between having a Swift closure and an Objective-C block. The attribute @objc_block can be used to convert a Swift closure into an Objective-C block. However, the signature of imp_implementationWithBlock in the runtime API takes an AnyObject, so we also need to use unsafeBitCast. Then we can simply set the implementation to our block. In the following example, we use this technique to override the description of a string. Once we do so, we get the string back instead of the actual description.

let myString = "foobar" as NSString
println(myString.description) // foobar

let myBlock : @objc_block (AnyObject!) -> String = { (sself : AnyObject!) -> (String) in
    "✋"
}

let myIMP = imp_implementationWithBlock(unsafeBitCast(myBlock, AnyObject.self))
let method = class_getInstanceMethod(myString.dynamicType, "description")
             method_setImplementation(method, myIMP)

println(myString.description) // ✋

NSInvocation (6:40)

One thing that doesn’t work is NSInvocation. That is completely off-limits, no matter what you try to do, whether you use objects directly from NSObject or not.

What about pure Swift? The Swift library SWRoute is a proof of concept for function hooking in Swift. It uses rd_route, a Mach specific injection library for C. This library allows you to swizzle C on platforms using Max OS X or iOS. To use it with Swift, the author essentially looked at the memory layout of the swift_func_object and implemented a struct containing the function address of the function being called. Using that, you can write some C to get to the function address of the function object. Once you have that, you can also change where it points to.

#include <stdint.h>
#define kObjectFieldOffset sizeof(uintptr_t)

struct swift_func_object {
    uintptr_t *original_type_ptr;
#if defined(__x86_64__)
    uintptr_t *unknown0;
#else
    uintptr_t *unknown0, *unknown1;
#endif
    uintptr_t function_address;
    uintptr_t *self;
};

uintptr_t _rd_get_func_impl(void *func) {
    struct swift_func_object *obj = (struct swift_func_object *)
        *(uintptr_t *)(func + kObjectFieldOffset);

    return obj->function_address;
}

Purely in Swift (7:59)

Can we do this without C? I actually wrote this before I found that library. Let’s take a step back — how do we find out about these things? Mike Ash wrote a memory dumper that allows you to dump the memory of any Swift object that you may have. Using that, I dumped a function object. It starts with an eight byte pointer to something called a “partial apply forwarder for reabstraction thunk helper”. This is basically a trampoline function that allows you to always have a level of indirection when you call the Swift function. It contains a pointer to a struct. There is also a pointer to _TF6memory3addFTSiSi_Si, which is actually a function pointer that we need. We can define some structs in Swift to get to the function pointer.

struct f_trampoline {
    var trampoline_ptr: COpaquePointer
    var function_obj_ptr: UnsafeMutablePointer<function_obj>
}

struct function_obj {
    var some_ptr_0: COpaquePointer
    var some_ptr_1: COpaquePointer
    var function_ptr: COpaquePointer
}

Now, we’re trying to dynamically load a function. We can do this statically with @asmname and get the function from C without any kind of bridging header. After this attribute, you give the name of the function and the declaration in order to call it.

@asmname("floor") func my_floor(dbl: Double) -> Double
println(my_floor(6.7))

let handle = dlopen(nil, RTLD_NOW)
let pointer = COpaquePointer(dlsym(handle, "ceil"))

typealias FunctionType = (Double) -> Double

We can also call it dynamically using dlopen and dlsym. We get to the ceil function from libm, and we also define a FunctionType. After pulling in our structs from earlier, we can unsafeBitCast our function object to this f_trampoline structure. The trampoline struct also has an initializer that takes a struct, copies, and changes the function pointer within it. We use that to get a new function object pointing to the ceil function. We can unsafeBitCast it back to our FunctionType, and finally, we can call it.

struct f_trampoline { [...] }
struct function_obj { [...] }

let orig = unsafeBitCast(my_floor, f_trampoline.self)
let new = f_trampoline(prototype: orig, new_fp: pointer)
let my_ceil = unsafeBitCast(new, FunctionType.self)
println(my_ceil(6.7))

If we run this, we see where we can actually call both functions, one statically and the other dynamically. Swift is really keen on inlining when you optimize. Mangling with function pointers in the internal structures does not work well when you optimize, so this isn’t something you can really use in practice.

Go in Reverse (11:32)

However, we can use this technique the other way around! We can take a Swift function and pass it to some C code as a function pointer. That can be useful for calling Legacy APIs, or any kind of C APIs that deal with function pointers as callbacks.

In this example, we declare a C function taking a FunctionPointer. We can pull this into our Swift program using @asmname. Then, let’s define a function, greeting, that prints to the screen. We can again use unsafeBitCast to get a more accessible function object, and we can get to the FunctionPointer. Cast that to a CFunctionPointer type and pass it to executeFunction. This actually works!

void executeFunction(void(*f)(void)) {
    f();
}
@asmname("executeFunction") func
executeFunction(fp: CFunctionPointer<()->()>)

func greeting() {
    println("Hello from Swift")
}

let t = unsafeBitCast(greeting, f_trampoline.self)
let fp = CFunctionPointer<()->()>
    (t.function_obj_ptr.memory.function_ptr)
executeFunction(fp)

This doesn’t depend on optimization because the FunctionPointer needs to actually work. So, this might be something that you can apply in practice if you interface with any C APIs.

Analyse Private API (13:03)

To analyze a private API, let’s look at this example myClass and pretend that it is private. We have a variable someVar and a function someFuncWithAReallyLongNameLol. When I compile it, I have a proof of concept that I wrote called swift-dump, which works like class-dump does for Objective-C. It takes a binary and returns all the classes declared within. But class-dump uses, of course, the Objective-C runtime to do so. How can we do it for Swift?

Well, if we look at the Swift binary, we can see a lot of symbols with mangled names. Swift uses name-mangling to generate unique identifiers for all the things you use inside your Swift programs, whether it be classes, methods, or variables. Inside Xcode, we have a tool called swift-demangle. We can pass it a mangled name to get back a somewhat readable name. Using that, we can demangle the contents of a binary to reconstruct what exists within.

As many of you know, we can now have emoji identifiers, but how are they encoded in the binary? One interesting tidbit about name-mangling might be how emoji are actually encoded. If we define a class using the “thumbs-up” emoji, we can compile it using nm to get to the global symbols. Once we demangle the name, we can see both the Swift and the Punycode representations for the Unicode.

Recap (16:49)

So with that, let’s recap what we can still do in Swift, in terms of runtime functionality. If we use Objective-C to derive things, we can just import objects to runtime. Introspection exists, somewhat. Changing behavior is really hard, mostly because of the optimization the Swift compiler does. It is not really feasible to poke around in the internal structures. However, reverse engineering is still fine. We can look at a binary and see exactly what is inside, whether that be functions or classes.

With that, thank you!

Resources (16:58)



Boris Bügling

Boris Bügling

Boris is a Cocoa developer from Berlin, who currently works on the iOS SDK at Contentful. A Java developer in another life, with many iOS apps under his belt, he is also a strong open source contributor, building plugins to tame Xcode, and bashing bugs as the CocoaPods Senior VP of Evil