How to Troubleshoot Startup Crashes in MulleObjC Applications

Startup crashes in MulleObjC applications are almost always caused by missing or mismatched static library dependencies, specifically when the __register_mulle_objc_universe symbol fails to link or execute before main().

The mulle-objc/mulleobjc-startup repository provides the critical static library that initializes the Objective-C universe before your application’s entry point runs. When this startup sequence fails, your program crashes with segmentation faults or "unrecognized selector" errors before any of your own code executes. Understanding the exact startup flow and verification techniques will help you diagnose these early initialization failures quickly.

Understanding the MulleObjC Startup Flow

MulleObjC applications use a specific three-phase bootstrap process that occurs before main() is called.

The MulleObjC-startup static library automatically pulls in two required helper libraries:

  • mulle-atinit – Provides constructor functions that run before main()
  • mulle-atexit – Registers cleanup handlers for graceful shutdown

These dependencies are defined in the CMake configuration and must be present in the final binary.

Phase 2: Runtime Universe Registration

When the executable loads, the constructor generated by mulle-atinit calls the private function __register_mulle_objc_universe, defined in src/MulleObjC-startup.m. Inside this file, the helper function bang() creates a copy of the default universe configuration and invokes MulleObjCBang() to perform the actual universe initialization.

Phase 3: Post-Initialization Execution

After successful registration, the runtime is fully initialized and main() can safely use any MulleObjC class or the Foundation layer. If any step in this chain fails, the application crashes before reaching your code.

Verifying the Startup Library is Linked

The most common cause of startup crashes is a missing __register_mulle_objc_universe symbol. Verify its presence using the nm command:


# Show the symbols exported by the final executable

nm -g <your-executable> | grep __register_mulle_objc_universe

Expected output:


0000000000001234 T __register_mulle_objc_universe

If the symbol is absent, the MulleObjC-startup static library was not linked. Add it explicitly in your CMake target:

target_link_libraries(${PROJECT_NAME} PUBLIC MulleObjC-startup)

This corresponds to the Add section in the repository README, which specifies how to integrate the startup library into your build system.

Checking Required Dependencies

The startup library depends on three core libraries. Verify their objects are in the final binary:

nm -g <your-executable> | grep mulle_atinit
nm -g <your-executable> | grep mulle_atexit
nm -g <your-executable> | grep MulleObjC

Missing symbols indicate that the corresponding sub-projects were not added. Follow the Legacy adds or Add sources sections in the README to include mulle-atinit, mulle-atexit, and the core MulleObjC runtime.

Resolving Version Compatibility Issues

MulleObjC-startup is compiled against a specific MulleObjC version using the MULLE_OBJC__STARTUP_VERSION constant, defined in src/MulleObjC-startup.m. If you link a newer or older runtime, the structures used by MulleObjCBang() may differ, causing a crash.

Validate version compatibility by printing the startup version at runtime:

#include <stdio.h>
#include <MulleObjC-startup/MulleObjC-startup.h>

int main(void)
{
    printf("Startup version: %lu\n", MULLE_OBJC__STARTUP_VERSION);
    return 0;
}

If the printed version does not match the version reported by MulleObjC (MulleObjCGetStartupVersion()), rebuild all dependencies so they share the same tag.

Enabling Diagnostic Tracing

MulleObjC offers optional tracing facilities that reveal early-init problems. Compile with the trace flag:


# Add the define to your CFLAGS (or in CMake)

add_definitions(-DMULLE_OBJC_TRACE=1)

When enabled, the startup code prints messages from MulleObjCBang(). The tracing infrastructure is imported via MulleObjCExceptionHandler-Private.h in the startup file.

Typical output:


[MulleObjC] initializing universe …
[MulleObjC] default configuration copied
[MulleObjC] universe ready – class table size 1024

If the program aborts before these lines appear, the crash occurs before the universe is constructed, indicating a missing __register_mulle_objc_universe symbol or a failure in the mulle-atinit constructor chain.

Common Startup Crash Scenarios

Symptom Likely Cause Solution
Segmentation fault before main __register_mulle_objc_universe missing or mismatched Ensure MulleObjC-startup is linked; verify symbol with nm.
"Unrecognized selector" early in execution Wrong MulleObjC runtime version Re‑build all dependencies from the same tag.
No diagnostic output with MULLE_OBJC_TRACE enabled Startup library not loaded Add MulleObjC-startup to target_link_libraries.
Linker errors about mulle_atinit / mulle_atexit Dependency libraries not added Follow the Add sources steps in the README.

Minimal Reproducible Example

Use this minimal program to verify your startup configuration:

/* main.c – minimal program that uses MulleObjC-startup */
#import <MulleObjC-startup/MulleObjC-startup.h>
#import <MulleObjC/MulleObjC.h>

int main(void)
{
    // The universe is already registered by the startup library.
    if (!MulleObjCGetUniverse())
    {
        fprintf(stderr, "Error: MulleObjC universe not initialized\n");
        return 1;
    }

    printf("MulleObjC startup succeeded – universe %p\n",
           (void *)MulleObjCGetUniverse());
    return 0;
}

Build with CMake:

add_executable(myapp main.c)
target_link_libraries(myapp PUBLIC MulleObjC-startup)

Running myapp should print the success message. If it crashes, apply the diagnostic steps outlined above.

Summary

  • Link verification is the first step: use nm -g to confirm __register_mulle_objc_universe exists in your binary.
  • Dependency chain requires mulle-atinit, mulle-atexit, and the core MulleObjC runtime to be linked alongside MulleObjC-startup.
  • Version alignment between MULLE_OBJC__STARTUP_VERSION and the runtime prevents structure mismatches in MulleObjCBang().
  • Diagnostic tracing via -DMULLE_OBJC_TRACE=1 reveals whether the universe initialization reaches src/MulleObjC-startup.m or fails earlier in the constructor chain.

Frequently Asked Questions

Why does my MulleObjC application crash before reaching main()?

This occurs when the __register_mulle_objc_universe symbol is missing or when the constructor chain fails to execute. The startup library must be linked as a static library so that its constructor runs before main(). Verify the symbol is present using nm -g <executable> | grep __register_mulle_objc_universe.

How do I check if MulleObjC-startup is properly linked?

Run nm -g on your final executable and search for __register_mulle_objc_universe, mulle_atinit, and mulle_atexit. All three should appear as global text symbols (marked with T). If any are missing, add MulleObjC-startup to your CMake target_link_libraries and ensure mulle-atinit and mulle-atexit are included in your dependency tree.

What version compatibility issues should I watch for?

The MULLE_OBJC__STARTUP_VERSION constant defined in src/MulleObjC-startup.m must match the version expected by the core MulleObjC runtime. Mismatches cause MulleObjCBang() to access incompatible structure layouts, resulting in immediate crashes. Always build MulleObjC-startup and MulleObjC from the same release tag.

How can I enable debug output to trace the startup sequence?

Define MULLE_OBJC_TRACE=1 during compilation to activate diagnostic messages from MulleObjCBang(). In CMake, use add_definitions(-DMULLE_OBJC_TRACE=1). When enabled, the runtime prints initialization steps to stderr, allowing you to see whether the universe creation begins or fails before reaching src/MulleObjC-startup.m.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →