Fundamentals 16 min read

Uncovering Swift’s Binary Layout: From Dynamic Calls to Mach‑O Class Structures

This article explores Swift’s binary representation by comparing it with Objective‑C, demonstrating runtime dynamic method invocation, dissecting Mach‑O sections such as __objc_classlist and __swift5_types, and revealing how Swift classes store their metadata and VTables.

ITPUB
ITPUB
ITPUB
Uncovering Swift’s Binary Layout: From Dynamic Calls to Mach‑O Class Structures

Background

Swift ABI stabilized, enabling mixed Swift/Objective‑C binaries. The article analyses how Swift classes are stored in Mach‑O files and how to invoke their methods dynamically.

Dynamic Invocation Example

Define a Swift class:

class MyClass {
    var p: Int = 0
    init() { print("init") }
    func helloSwift() -> Int { print("helloSwift"); return 100 }
    func helloSwift1() -> Int { print("helloSwift1"); return 100 }
    func helloSwift2() -> Int { print("helloSwift2"); return 100 }
}

Equivalent Objective‑C implementation can be inspected with the runtime APIs class_copyMethodList and method_getImplementation, which list six methods (three defined plus property getter/setter) and allow direct IMP calls.

Why Objective‑C Can Find Classes at Runtime

Class metadata is stored in the __objc_classlist section. Each entry follows the class64 layout:

struct class64 {
    uint64_t isa;
    uint64_t superClass;
    uint64_t cache;
    uint64_t vtable;
    uint64_t data;
};

By converting a virtual address to a file offset (using segment vmaddr/fileoff), the fields can be read directly, e.g. at offset 0x11820.

Swift’s Storage

Swift classes also appear in __objc_classlist to preserve compatibility, but their method list is not stored in class64. Swift adds dedicated sections such as __swift5_types, __swift5_typeref, etc. In __swift5_types each entry is a 4‑byte relative offset; the absolute class address is obtained by adding the offset to the current file position.

The Swift class descriptor ( ClassDescriptor) has the following fields:

struct ClassDescriptor {
    uint32_t Flags;
    int32_t  Parent;
    int32_t  Name;
    int32_t  AccessFunction;
    int32_t  FieldDescriptor;
    int32_t  SuperclassType;
    uint32_t MetadataNegativeSizeInWords;
    uint32_t MetadataPositiveSizeInWords;
    uint32_t NumImmediateMembers;
    uint32_t NumFields;
};

Builder classes ( ContextDescriptorBuilderBase, TypeContextDescriptorBuilderBase, ClassContextDescriptorBuilder) add additional information such as VTable, OverrideTable, and resilience data.

The VTable is emitted by addVTable():

void addVTable() {
    B.addInt32(VTableEntries.size());   // 4‑byte entry count
    for (auto fn : VTableEntries) {
        emitMethodDescriptor(fn);
    }
}

Flags encode the presence of a VTable, OverrideTable, etc. For example, flag value 0x80000050 indicates a class (low 5 bits = 0x10) with a VTable (high bit 16 set).

Implementing Dynamic Calls in Swift

A demonstration repository (https://github.com/pilaf-king/SwiftMachODemo) shows how to locate a Swift class at runtime, enumerate its VTable entries, and invoke methods via the obtained IMPs. The demo also notes that the compiler generates additional functions that appear in the VTable, causing the counted method number to exceed the manually written ones.

Function flags are defined as:

/*
| ExtraDiscriminator(16bit) | … | isDynamic(1bit) | isInstance(1bit) | Kind(4bit) |
*/
enum class Kind { Method, Init, Getter, Setter, ModifyCoroutine, ReadCoroutine };

The OverrideTable (not implemented in the demo) would follow the VTable and contain entries for method overrides.

Key Structures for Swift Metadata

Relevant Swift metadata sections and their purpose: __swift5_types: 4‑byte relative offsets to type descriptors (class, struct, enum). __swift5_typeref: references between types. __swift5_proto, __swift5_fieldmd, etc.: additional reflection data.

The class descriptor flags are interpreted as:

// Flag layout (16‑bit TypeContextDescriptorFlags)
| TypeFlag(16) | version(8) | generic(1) | unique(1) | unknown(1) | Kind(5) |

Relevant enum values:

enum class ContextDescriptorKind : uint8_t {
    Module = 0,
    Extension = 1,
    Anonymous = 2,
    Protocol = 3,
    OpaqueType = 4,
    Type_First = 16,
    Class = Type_First,
    Struct = Type_First + 1,
    Enum = Type_First + 2,
    Type_Last = 31
};

enum class TypeContextDescriptorFlags : uint16_t {
    Class_HasVTable = 15,
    Class_HasOverrideTable = 14,
    Class_HasResilientSuperclass = 13,
    // ... other flags omitted for brevity
};

For MyClass the flag 0x80000050 resolves to:

Low 5 bits = 0x10 → Class kind.

High 16 bits set → Class_HasVTable flag.

How to Perform Dynamic Calls

Steps to invoke a Swift method at runtime:

Obtain the class object with NSClassFromString("ModuleName.MyClass").

Read the data field of the class64 entry to get the address of the Swift ClassDescriptor.

Parse the __swift5_types section to resolve the relative offset to the absolute class address.

Locate the VTable by checking the Class_HasVTable flag, then read the 4‑byte entry count followed by method descriptors.

For each method descriptor, extract the function pointer (IMP) and invoke it, e.g. via ((int (*)(id, SEL))imp)(instance, selector).

The demo repository implements these steps and prints the discovered method count and the result of invoking helloSwift.

Conclusion

The analysis shows that Swift retains the Objective‑C class layout in __objc_classlist for compatibility, while storing method tables in its own metadata sections. VTable presence is indicated by descriptor flags, and the VTable stores a count followed by method descriptors. Limitations include the inability to map method names directly without parsing descriptor tables and the fact that VTable indices change when the binary is recompiled.

Class flag example
Class flag example
Mach‑O class layout
Mach‑O class layout
Swift class descriptor
Swift class descriptor
VTable layout
VTable layout
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Mach-ORuntimeSwiftObjective‑Cbinary analysisDynamic Invocation
ITPUB
Written by

ITPUB

Official ITPUB account sharing technical insights, community news, and exciting events.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.