Skip to content

Instrumentation

Hugo edited this page Sep 25, 2026 · 2 revisions

Instrumentation

Instrumentation is applied on LLVM modules in src/compilerlib/instrumentation/. Every hook the passes insert is declared in include/coretrace/runtime_abi.h: the passes take the callee name and the LLVM function type from that header, so a pass and the runtime cannot drift apart silently. See Runtime Library.

Passes and runtime hooks

Pass Entry point Main inserted hooks
Allocation wrapAllocCalls() __ct_malloc, __ct_free, __ct_new, __ct_delete, __ct_realloc, __ct_mmap, …
Bounds instrumentMemoryAccesses() __ct_check_bounds, __ct_stack_push, __ct_stack_pop
Trace instrumentModule() __ct_trace_enter, typed exit hooks (void/i64/ptr/f64/unknown)
VTable instrumentVirtualCalls() __ct_vtable_dump, __ct_vcall_trace
Objective-C Apple targets only __ct_objc_track

Allocation pass

The pass rewrites allocator and deallocator calls from a rewrite table rather than from scattered per-name checks (alloc_names.cpp, with unit coverage in test/unit/alloc_names_test.cpp). It covers:

  • malloc, calloc, realloc, free;
  • C++ operator new/delete, scalar and array, as mangled by the Itanium C++ ABI (Linux, macOS) and the Microsoft C++ ABI (Windows x64 and ARM64 mangle them identically);
  • posix_memalign, aligned_alloc, mmap/munmap, sbrk/brk;
  • allocation sites whose result the program provably never uses, rewritten to the *_unreachable variant so the runtime can release them at once;
  • auto-free calls at function exit for allocations proven unreachable at compile time.

Which new/delete overloads are rewritten

Operator Rewritten
operator new, operator new[] plain and nothrow
aligned operator new never — the runtime allocates through the unaligned operator
operator delete, operator delete[] plain, sized, nothrow and destroying-delete
aligned operator delete Itanium only

Aligned operator delete is rewritten for Itanium alone, where aligned blocks come from aligned_alloc and any operator delete may release them. The Microsoft CRT allocates them with _aligned_malloc, which only the aligned operator delete may release, so rewriting it there would hand the block to the wrong deallocator.

Some toolchains prefix Itanium names with an extra underscore; the lookup drops it.

Allocations are tracked with an interned file:line:col site string, which is what the reports name.

Bounds pass

Instrumented accesses: loads, stores, atomic memory operations and llvm.mem* intrinsics. Each check receives the base pointer, the accessed pointer, the access size, the site string and the read/write mode.

Heap and stack

Checks cover heap blocks and the stack objects of running frames; a stack violation is reported as stack-buffer-overflow.

A local array or struct is registered with __ct_stack_push for the duration of its frame when:

  • its address escapes, for instance to a callee, or
  • an access to it cannot be proven in bounds at compile time.

Accesses proven in bounds are not checked at all. __ct_stack_pop restores the registry depth when the frame exits, so an unwind does not leave dead objects registered.

As for heap blocks, an access is checked when its base is the start of the object — or any address inside it with --ct-shadow-aggressive, which is what makes container_of-style patterns checkable.

Not tracked: variable-length arrays, and objects beyond the 512 innermost ones each thread registers (kCtStackObjectCapacity).

Bounds checks rely on allocation metadata. Enabling bounds while alloc is disabled sets bounds_without_alloc and emits a warning.

Trace pass

Inserts __ct_trace_enter at function entry and a typed exit hook chosen from the return type; non-user and runtime functions are skipped through shared filtering helpers. Objective-C methods are reported under their Objective-C name, such as -[Greeter greet:].

VTable pass

Instruments indirect virtual-style calls when it can recover a this pointer pattern:

  • dump_vtable → __ct_vtable_dump(this, site, static_type)
  • trace_calls → __ct_vcall_trace(this, callee, site, static_type)

The runtime side resolves the vtable against the Itanium ABI on POSIX and the MSVC ABI on Windows.

Objective-C

On Apple targets the allocation pass also emits __ct_objc_track for objects created with +alloc, +allocWithZone:, +new or [[X alloc] init]. See Objective-C Support.

RuntimeConfig

include/compilerlib/instrumentation/config.hpp:

Field Default
shadow_enabled false
shadow_aggressive false
bounds_no_abort false
trace_enabled true
alloc_enabled true
bounds_enabled true
vtable_enabled false
vcall_trace_enabled false
vtable_diag_enabled false
autofree_enabled false
alloc_trace_enabled true
bounds_without_alloc derived
optnone_enabled false

extractRuntimeConfig() reads the --ct-* flags out of the argument list; emitRuntimeConfigGlobals() writes the corresponding weak ODR globals into the module, which is how the runtime learns the build's configuration. See Configuration model.

Frontend optnone wrapper

frontend/OptNoneAction wraps frontend actions to force optnone/noinline on user functions. It is controlled by --ct-optnone and applies even without --instrument.

Clone this wiki locally