How to Debug and Trace Runtime Calls in mulle-objc-runtime
The mulle-objc-runtime provides a built-in tracing subsystem controlled via MULLE_OBJC_TRACE_* environment variables that outputs detailed runtime activity to stderr without requiring recompilation.
The ability to debug and trace runtime calls is essential when diagnosing method dispatch issues or analyzing class loading behavior in Objective-C applications. The mulle-objc-runtime repository includes a comprehensive tracing infrastructure that logs universe initialization, method lookups, and individual call invocations through a centralized logging system. This guide explains how to activate and interpret these diagnostic features using environment variables, source code hooks, and debugger integration.
Core Tracing Architecture
The Universe Debug Structure
All tracing state lives inside the _mulle_objc_universe structure defined in src/mulle-objc-universe.h. This universe object holds the entire runtime state, including a nested debug sub-structure that stores every trace flag. When you set an environment variable like MULLE_OBJC_TRACE_METHOD_CALL, the initialization code in mulle_objc_universe_init populates these bitfields so that subsequent runtime operations can check them quickly.
Central Trace Functions
Every diagnostic line flows through mulle_objc_universe_trace (and its non-newline variant mulle_objc_universe_trace_nolf) implemented in src/mulle-objc-universe.c. These functions handle the actual formatting and output to stderr.
Before printing the user message, the runtime invokes mulle_objc_universe_trace_preamble, which emits the thread ID (t:#%2lu) and, if enabled, a high-resolution timestamp. This preamble makes it possible to correlate events when debugging concurrent applications.
Enabling Runtime Tracing via Environment Variables
During mulle_objc_universe_init (around line 360 in src/mulle-objc-universe.c), the runtime scans the environment using getenv_yes_no and mulle_objc_environment_get_int. Any non-empty value evaluates to "true" for Boolean flags.
| Variable | Purpose | Example |
|---|---|---|
MULLE_OBJC_TRACE_UNIVERSE |
Log universe creation and finalization events | 1 |
MULLE_OBJC_TRACE_CLASS_ADD |
Emit a line when a class is registered | 1 |
MULLE_OBJC_TRACE_METHOD_CALL |
Trace every method entry and exit (verbose) | 1 |
MULLE_OBJC_TRACE_METHOD_SEARCH |
Show method lookup attempts | 1 |
MULLE_OBJC_TRACE_THREAD |
Prefix each line with the thread identifier | 1 |
MULLE_OBJC_TRACE_TIMESTAMP |
Add a timestamp to each line | 1 |
MULLE_OBJC_TRACE_INSTANCE |
Control instance-level tracing granularity (0-2) | 2 |
MULLE_OBJC_WARN_HANG |
Force the process to spin in mulle_objc_hang() for debugger attachment |
1 |
MULLE_OBJC_WARN_CRASH |
Abort immediately on fatal errors instead of recovering | 1 |
How Tracing Works Internally
When you launch a program with tracing enabled, the following sequence occurs:
-
Setup –
mulle_objc_universe_initparses the environment and populates theuniverse->debugbitfields. Boolean flags usegetenv_yes_no, while integer flags likeMULLE_OBJC_TRACE_INSTANCEusemulle_objc_environment_get_int. -
Preamble Generation – Every call to
mulle_objc_universe_tracefirst invokesmulle_objc_universe_trace_preamble. It prints the thread ID (t:#%2lu) and, ifdebug.trace.timestampis set, a value derived fromtime(NULL). -
Message Formatting – The trace function accepts a
printf-style format string and arguments, builds the final message, and writes it tostderr. -
Method Call Path – When
debug.method_callcontainsMULLE_OBJC_UNIVERSE_CALL_TRACE_BIT, the dispatch path insrc/mulle-objc-call.c(line 134) inserts a trace line before the actual IMP is invoked. The same flag disables caching insrc/mulle-objc-fastmethodtable.c(line 67) to avoid hiding the call behind a fast-path.
Practical Debugging Examples
Selective Tracing from the Shell
To monitor class registration and method lookups while identifying which thread performs each action:
export MULLE_OBJC_TRACE_CLASS_ADD=1
export MULLE_OBJC_TRACE_METHOD_SEARCH=1
export MULLE_OBJC_TRACE_THREAD=1
./my-program
Typical output:
t:# 1 mulle_objc_universe traceuniverse "init begin"
t:# 2 mulle_objc_universe traceclass_add "+class_add %08lx \"MyClass\""
t:# 2 mulle_objc_universe tracemethod_search "lookup method %s on class %08lx"
Attaching GDB to a Hanging Process
When you need to inspect the runtime state before it executes any significant logic, use the hang mechanism:
export MULLE_OBJC_WARN_HANG=1
./my-program
The process prints Hanging for debugger to attach and spins in mulle_objc_hang() defined in src/mulle-objc-universe.c. In another terminal:
gdb -p $(pgrep my-program)
(gdb) break mulle_objc_universe_trace
(gdb) continue
Every trace line now triggers a breakpoint, allowing you to inspect the full universe state with p *universe.
Programmatic Trace Control
If you need to toggle tracing after the universe has initialized, modify the debug structure directly:
#include "mulle-objc-universe.h"
int main(void)
{
struct _mulle_objc_universe *universe = mulle_objc_universe_create(NULL);
// Enable thread-ID and timestamp tracing at runtime
universe->debug.trace.thread = 1;
universe->debug.trace.timestamp = 1;
// ... normal program execution ...
mulle_objc_universe_destroy(universe);
return 0;
}
Key Source Files for Debugging
Summary
- Environment-based configuration: Set
MULLE_OBJC_TRACE_*variables before launch to enable specific tracing domains without recompiling. - Centralized logging: All diagnostic output flows through
mulle_objc_universe_traceinsrc/mulle-objc-universe.c, ensuring consistent formatting with optional thread IDs and timestamps. - Method call visibility: When
MULLE_OBJC_TRACE_METHOD_CALLis active, the runtime disables the fast-path method cache insrc/mulle-objc-fastmethodtable.cto log every dispatch throughsrc/mulle-objc-call.c. - Debugger integration: Use
MULLE_OBJC_WARN_HANG=1to pause execution inmulle_objc_hang()for GDB attachment, or modifyuniverse->debug.tracefields programmatically for runtime control.
Frequently Asked Questions
Can I enable tracing after the program has already started?
No. According to the source code in src/mulle-objc-universe.c, trace flags are read once during mulle_objc_universe_init from environment variables. Once the universe is initialized, you cannot toggle tracing via environment variables without restarting the process. However, you can programmatically modify universe->debug.trace fields at runtime as shown in the Programmatic Trace Control example above.
Why does method call tracing slow down my application significantly?
Enabling MULLE_OBJC_TRACE_METHOD_CALL significantly impacts performance because it disables the fast-path method cache. As implemented in src/mulle-objc-fastmethodtable.c at line 67, when the trace bit is set, the runtime bypasses the cached IMP table to ensure every call is logged through mulle_objc_universe_trace in src/mulle-objc-call.c. Use this flag only for targeted debugging sessions, not in production.
Where does the trace output go by default?
All trace output is written to stderr via the mulle_objc_universe_trace and mulle_objc_universe_trace_nolf functions defined in src/mulle-objc-universe.c. The output format includes an optional thread ID prefix (when MULLE_OBJC_TRACE_THREAD is set) and optional timestamp, followed by the formatted message. You can redirect stderr to a file using standard shell redirection (2> trace.log) for later analysis.
How can I make the runtime wait for a debugger attachment?
Set the environment variable MULLE_OBJC_WARN_HANG=1 before launching your program. This triggers mulle_objc_hang() early in the universe initialization sequence (see src/mulle-objc-universe.c), causing the process to spin in a visible loop printing "Hanging for debugger to attach". You can then attach GDB or LLDB, set breakpoints (e.g., break mulle_objc_universe_trace), and continue execution.
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 →