Current implementation - syscall abstractions

Current implementation - syscall abstractions

in

This post documents the current state of the malloc project, which intentionally differs from the fully interposed, cross-platform allocator described in the dynamic linking deep dives. This is a part of dynamic linking investigation series.

At present, the project prioritizes determinism, testability, and approachability by abstracting system calls behind a controllable interface rather than globally interposing on the platform allocator.

Why This Exists

The investigation pages describe what is ultimately required to own allocation across an entire process (ELF interposition on Linux, dyld interposition on macOS).

However, that model:

  • is invasive
  • depends on platform-specific linker behavior
  • complicates early experimentation
  • makes unit tests sensitive to runtime noise

To keep the allocator playable, hackable, and testable, the current mainline implementation deliberately avoids full interposition.

Syscall Wrapping Model

Instead of calling system interfaces directly, the allocator routes all OS interactions through project-defined wrappers.

Typical examples include:

  • mm_sbrk() instead of sbrk
  • mm_mmap() / mm_munmap() instead of mmap / munmap
  • mm_mremap()- instead of mremap

These wrappers serve two purposes:

  1. Indirection – the allocator never talks to the OS directly.
  2. Control – the behavior of the OS can be substituted during tests.

Mockable System Interface

When compiled in test mode:

  • syscall wrappers are backed by mock implementations
  • memory regions are carved out of preallocated buffers
  • address space layout is deterministic
  • growth direction and fragmentation is simulated (sbrk addresses are ascending, mmap addresses are descending on the same buffer, they grow towards each other)

This allows tests to:

  • emulate non-contiguous memory mappings
  • force edge cases (e.g. partial growth)
  • reason about allocator behavior without relying on the host kernel

Crucially, this avoids needing global allocator interposition just to test allocator logic.

Separation from glibc allocator symbols

In malloc.c, we define CALLOC/FREE/MALLOC/REALLOC macros. All calls inside this file after preprocessing become either:

  • mm_ appended versions of those symbols to separate from and not overwrite original symbols while testing,
  • real exported calloc/free/malloc/realloc when compiling into a library.

In this way, during testing, any other executable other than our tests call the original allocator procedures, while tests call our implementations using mock syscalls to isolate the behavior under testing.

#ifdef TESTING
#define CALLOC mm_calloc
#define FREE mm_free
#define MALLOC mm_malloc
#define REALLOC mm_realloc
#else
#define CALLOC calloc
#define FREE free
#define MALLOC malloc
#define REALLOC realloc
#endif

Relationship to the Interposition Work

The dynamic linking and interposition work answers the question:

How do we ensure that all allocations in a real process flow through our allocator?

The syscall abstraction answers a different question:

How do we develop and test allocator logic in isolation?

These approaches are complementary, not competing:

  • syscall abstraction is the development and testing model
  • interposition is the deployment and integration model

Branching Strategy

To make this distinction explicit:

  • the current default branch focuses on syscall abstraction and deterministic testing
  • the interposition branch contains the experimental, fully routed allocator discussed in the wiki

The interposition branch exists so readers can:

  • reproduce the dynamic linking investigation
  • experiment with real process-wide interposition
  • explore platform differences hands-on

Meanwhile, the mainline remains stable, debuggable, and suitable for iteration.

Design Principle

The project intentionally avoids premature global interposition.

Allocator correctness, invariants, and testability come first. Once those are solid, interposition becomes an integration concern rather than a development hazard.

Summary

  • The current malloc implementation wraps all system calls behind project-defined interfaces
  • These wrappers are mockable and deterministic under test
  • This simplifies allocator development and debugging
  • Full allocator interposition is explored separately in a dedicated branch

Both paths are documented so the reader can choose between learning, experimenting, or integrating the allocator depending on their goals.