Using ASan from inside C - ProkopHapala/FireCore GitHub Wiki

Using AddressSanitizer instrumentation from C++: a quick primer

AddressSanitizer (ASan) is best known for runtime detection of memory errors when you compile with -fsanitize=address, but its C/C++ runtime also exposes a small “instrumentation” API. You can call these hooks directly when you want extra diagnostics (e.g., the logging we added around sw_register_buffers). This note walks through the basics.


1. Enabling ASan

  1. Compile and link with ASan on every target that participates in your run (executables, shared libs):

    clang++ -g -O1 -fsanitize=address -fno-omit-frame-pointer \
            -o myapp src/*.cpp

    GCC supports the same switches. When mixing static/shared libraries, make sure every library that you want ASan-instrumented is rebuilt.

  2. Set runtime options via environment variables when needed (examples):

    export ASAN_OPTIONS=halt_on_error=1:detect_leaks=0:abort_on_error=1
    export LSAN_OPTIONS=detect_leaks=0

2. Importing the ASan interface

ASan ships with an optional header that declares helper functions. Wrap the include in an __has_include guard so builds still succeed when ASan is disabled:

#if __has_include(<sanitizer/asan_interface.h>)
#  include <sanitizer/asan_interface.h>
#  define HAVE_ASAN_INTERFACE 1
#else
#  define HAVE_ASAN_INTERFACE 0
#endif

Use HAVE_ASAN_INTERFACE to compile ASan-only code paths.


3. What you can query/control

Function Purpose Notes
int __asan_address_is_poisoned(void const *addr) Returns non-zero if ASan considers addr poisoned. Useful for logging whether a pointer is already invalid before you touch it.
void __asan_poison_memory_region(void *addr, size_t size) Marks a region as poisoned so future accesses fault. Helpful for custom allocators or when guarding manual buffers.
void __asan_unpoison_memory_region(void *addr, size_t size) Clears poison so ASan stops trapping. Use after you hand memory back to valid code paths.
void __asan_set_error_report_callback(void (*cb)(char const *msg)) Installs a custom crash report hook. Optional, stick to logging unless you need to intercept reports.
void __asan_handle_no_return(void) Inform ASan a function does not return. Rare outside low-level signal handlers.

The interface also exposes lower-level allocator APIs and stack-trace helpers, but the functions above cover most diagnostic needs for “postmortem” logging.


4. Typical usage patterns

A. Logging “is this pointer already dead?”

This is exactly what we used around the prefix buffer:

#if HAVE_ASAN_INTERFACE
if (__asan_address_is_poisoned(prefix)) {
    printf("prefix %p already poisoned!\n", prefix);
}
#endif

It tells you whether ASan already marked that address as invalid (red zones, freed memory, etc.). If you call this before and after the code you suspect, a change from 0 → 1 means that code poisoned it.

B. Protecting sentinels or guard regions

For a scratch buffer that must never be touched after a certain point:

char buf[256];
process(buf, sizeof(buf));
#if HAVE_ASAN_INTERFACE
__asan_poison_memory_region(buf, sizeof(buf));
#endif

Any accidental reuse raises an immediate ASan error. When you legitimately need it again:

#if HAVE_ASAN_INTERFACE
__asan_unpoison_memory_region(buf, sizeof(buf));
#endif

C. Guarding custom allocators/FIFO pools

If you manage raw memory yourself, poison pages you hand out and unpoison just before returning real data:

void* MyPool::alloc() {
    Block* b = freelist.pop();
#if HAVE_ASAN_INTERFACE
    __asan_unpoison_memory_region(b, blockSize);
#endif
    return b;
}

void MyPool::free(void* p) {
#if HAVE_ASAN_INTERFACE
    __asan_poison_memory_region(p, blockSize);
#endif
    freelist.push(static_cast<Block*>(p));
}

5. Workflow tips for mixed Python/C++ setups

Task Recommendation
Toggle ASan at runtime Keep a wrapper (e.g., st.set_debug) so Python can flip verbosity without rebuilding.
Leave hooks compiled in Wrap them in #if HAVE_ASAN_INTERFACE so the same code runs clean when ASan is disabled.
Keep logging light Print only when verbosity or similar debug level is high to avoid noisy output in normal runs.

6. References


7. Minimal checklist for new investigations

  1. Rebuild everything with -fsanitize=address -fno-omit-frame-pointer.
  2. Wrap suspicious bytes in ASan logging or poison/unpoison calls.
  3. Turn on detailed logging only when you’re instrumenting (verbosity flags help).
  4. Keep ASan guards under feature macros so production builds stay clean.
  5. Once fixed, revert the extra prints but leave utility hooks available—you’ll reuse them next time.

This pattern helped us isolate the “poisoned prefix” crash without rewriting allocation logic, and it’s a handy trick any time you suspect a specific pointer is getting clobbered in native code.

⚠️ **GitHub.com Fallback** ⚠️