Skip to content

Mocking: Darwin interposer

Example: share/doc/aceunit/examples/hello_world_mocked_interpose

macOS’s dynamic loader supports library interposition. main is still renamed ahead of time with objcopy:

Terminal window
objcopy --redefine-sym main=original_main hello.o mocked_hello.o

But puts is not renamed at all — instead, mock_puts.c declares a static interpose table in a special linker section that Darwin’s dynamic loader recognizes:

__attribute__((section("__DATA,__interpose")))
const interpose_t interposing_puts[] = {
{ (void *) mock_puts, (void *) puts },
};

That table is built into a small dynamic library and inserted into the process at load time:

Terminal window
cc -dynamiclib mock_puts.c -o libmock.dylib
DYLD_INSERT_LIBRARIES=libmock.dylib ./hello_test

The dynamic loader reads the interpose table and substitutes mock_puts for every call to puts in the process, no recompilation or relinking of hello.o needed for that part.

Only relevant on Darwin/macOS — this mechanism doesn’t exist on GNU/Linux linkers. If you need llvm-objcopy and it’s not found, and you installed LLVM via Homebrew, add it to your PATH:

Terminal window
export PATH=$(brew --prefix llvm)/bin:$PATH

See also: Link-time wrap for the equivalent GNU/Clang-on-Linux approach.