# How the Linker Resolves the __register_mulle_objc_universe Symbol in MulleObjC

> Learn how the linker resolves __register_mulle_objc_universe in MulleObjC. Discover how dllexport macros and linker flags ensure runtime initialization.

- Repository: [mulle-objc/mulleobjc-startup](https://github.com/mulle-objc/mulleobjc-startup)
- Tags: internals
- Published: 2026-03-07

---

**TLDR:** The linker resolves `__register_mulle_objc_universe` by combining a source-level dllexport definition triggered by the `MULLE_OBJC_DEFINE__register_mulle_objc_universe` macro in `src/MulleObjC-startup.m` with explicit `-exported_symbol` linker flags in CMake, ensuring the symbol remains visible for automatic runtime initialization.

The `mulle-objc/mulleobjc-startup` repository provides the critical bootstrap sequence for the MulleObjC runtime. Understanding how the linker resolves the `__register_mulle_objc_universe` symbol is essential for debugging startup failures and integrating custom initialization code.

## Source-Level Symbol Definition and Export

The symbol originates in the startup source file through a macro-based system that ensures proper visibility attributes across platforms.

### Triggering the dllexport Definition in MulleObjC-startup.m

In `src/MulleObjC-startup.m`, the code explicitly defines a macro before including a private implementation header. According to the source code, line 50 contains the critical include:

```c
// src/MulleObjC-startup.m
// need MULLE_OBJC_DEFINE__register_mulle_objc_universe to get dllexport 
// for our symbol defined in <MulleObjC/mulle-objc-startup-private.inc>
#define MULLE_OBJC_DEFINE__register_mulle_objc_universe
#include <MulleObjC/mulle-objc-startup-private.inc>

```

When this macro is defined, the included header expands into a concrete function definition marked with `__attribute__((visibility("default")))`. This attribute instructs the compiler to emit the symbol into the object file’s dynamic symbol table, making it available as a **dllexport** for the linker to discover.

### Implementation via the Private Header

The actual implementation of `__register_mulle_objc_universe` resides in `include/MulleObjC/mulle-objc-startup-private.inc`. This header uses conditional compilation to provide the function body only when `MULLE_OBJC_DEFINE__register_mulle_objc_universe` is set, preventing multiple definition errors while guaranteeing the symbol carries the correct visibility flags.

## CMake Linker Configuration for Symbol Export

Even with source-level visibility attributes, static library linking typically discards symbols not explicitly referenced. The CMake build system forces retention through platform-specific linker directives.

### ExecutableObjC.cmake Linker Flags

In `cmake/share/ExecutableObjC.cmake` at line 49, the build system adds a linker option to force symbol export:

```cmake
target_link_options(${target}
   PUBLIC "SHELL:LINKER:-exported_symbol,___register_mulle_objc_universe")

```

Note the triple underscore (`___register_mulle_objc_universe`). This accounts for the leading underscore prefix added by the platform ABI on macOS and some other systems, where the C symbol `__register_mulle_objc_universe` becomes `___register_mulle_objc_universe` at the assembly level.

### Project-Wide Linker Options

Additionally, `cmake/share/PROJECT_MAKE-config.cmake.in` at line 68 provides a global linker configuration:

```cmake
add_link_options("SHELL:LINKER:-exported_symbol,___register_mulle_objc_universe")

```

This ensures that any target built within the MulleObjC project ecosystem automatically exports the symbol without requiring individual target configuration.

## How the Symbol Resolution Works at Link Time

When building an executable that links against the MulleObjC-startup library, the resolution occurs in two distinct phases:

1. **Compilation Phase:** The compiler processes `src/MulleObjC-startup.m`, expands the macro, and emits `__register_mulle_objc_universe` with default visibility into the object file.
2. **Link Phase:** The linker encounters the `-exported_symbol` directive and explicitly marks the symbol as global, preventing dead-stripping. The runtime startup code can then resolve and call this function before `main()` executes to bootstrap the MulleObjC universe.

## Complete Integration Example

When linking your application against the startup library, the symbol resolution happens automatically:

```cmake

# CMakeLists.txt

find_package(MulleObjC REQUIRED)
target_link_libraries(myapp PRIVATE MulleObjC::startup)

```

```c
// Your application code
// __register_mulle_objc_universe is called automatically by the runtime
// before main() executes
int main(int argc, char *argv[]) {
    // MulleObjC universe is already initialized and ready
    return 0;
}

```

## Summary

- **`src/MulleObjC-startup.m`** defines `MULLE_OBJC_DEFINE__register_mulle_objc_universe` to trigger the dllexport definition of the symbol.
- **`include/MulleObjC/mulle-objc-startup-private.inc`** provides the actual function implementation with `__attribute__((visibility("default")))`.
- **`cmake/share/ExecutableObjC.cmake`** (line 49) and **`cmake/share/PROJECT_MAKE-config.cmake.in`** (line 68) add the `-exported_symbol` linker flag to force visibility.
- The triple-underscore variant (`___register_mulle_objc_universe`) in CMake accounts for platform ABI naming conventions.
- Together, these mechanisms ensure the linker resolves the symbol for automatic pre-main runtime initialization.

## Frequently Asked Questions

### Why does the CMake linker flag use three underscores instead of two?

On macOS and platforms using the Mach-O object file format, C symbols receive a leading underscore in the symbol table. Consequently, the source-level identifier `__register_mulle_objc_universe` becomes `___register_mulle_objc_universe` at the linker level. The CMake configuration uses the triple-underscore form to match the actual symbol name expected by the platform linker.

### What happens if the -exported_symbol linker flag is omitted?

Without the explicit `-exported_symbol` directive, the linker treats the function as an internal symbol subject to dead-code stripping. This results in an "undefined symbol" error during final executable linking or runtime startup, preventing the MulleObjC universe from initializing properly.

### Can I provide my own implementation of __register_mulle_objc_universe?

While technically possible by defining a function with the same signature in your own translation unit, this is strongly discouraged. The official implementation in `mulle-objc-startup-private.inc` performs critical runtime initialization sequence steps; overriding it may lead to undefined behavior or runtime crashes.

### Is this mechanism specific to macOS?

The `-exported_symbol` flag is specific to macOS and the Mach-O linker (ld64). On Linux (ELF), the build system would typically use `--whole-archive` to preserve all symbols from the static archive, while Windows (PE/COFF) relies on `__declspec(dllexport)` declarations. However, the source-level `__attribute__((visibility("default")))` remains portable across all supported platforms.