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.
Phase 1: Link-Time Dependency Resolution
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 -gto confirm__register_mulle_objc_universeexists in your binary. - Dependency chain requires
mulle-atinit,mulle-atexit, and the coreMulleObjCruntime to be linked alongsideMulleObjC-startup. - Version alignment between
MULLE_OBJC__STARTUP_VERSIONand the runtime prevents structure mismatches inMulleObjCBang(). - Diagnostic tracing via
-DMULLE_OBJC_TRACE=1reveals whether the universe initialization reachessrc/MulleObjC-startup.mor 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →