AddressSanitizer in a Cross-Built Application: Verify Runtime and Address-Space Prerequisites

Qualify an AddressSanitizer build in stages: the compiler must instrument the application, the image must provide the matching userspace runtime, and the process must initialize its shadow-memory layout before a bug report is meaningful. A successful cross-link establishes none of the target startup or resource limits by itself.

This guide pins GCC 14.2, its bundled sanitizer source and Buildroot 2025.02. A bounded x86-64 host fixture tested a clean allocation path and one intentional heap-boundary error. No target image, kernel sanitizer or production service was exercised.

Record the instrumented build contract

Capture the compiler target tuple, version, libc, ABI, link mode and sanitizer flags for every component under test. The GCC 14.2 instrumentation reference describes -fsanitize=address as memory-access instrumentation for errors including out-of-bounds access and use after free. It also lists incompatible sanitizer combinations.

Compile and link through the intended compiler driver with the sanitizer option present where required. Preserve debug information and a readable stack-trace configuration, such as the fixture’s -O1 -g -fno-omit-frame-pointer. These choices improve diagnosis; they are not a guarantee that every optimized source operation appears as a separate frame.

Keep third-party or uninstrumented code in the coverage inventory. A program using ASan does not imply that every instruction in every loaded library has compiler-inserted checks. Interceptors and instrumented callers add coverage, but they do not make an unreviewed binary dependency universally checked.

Compiler/runtime deployment leads through clean shadow-memory startup to an owned heap-boundary negative test; the initial leak-check failure and process-local exclusion are explicitly shown.
Original sanitizer qualification flow with honest host startup and error-detection outcomes.

Verify runtime deployment from the final image

Inspect the executable’s ELF machine, interpreter and DT_NEEDED entries with the target-capable inspection tools. Locate the actual sanitizer runtime and its dependency closure in the image. Record hashes and the compiler/runtime relationship instead of copying a workstation library with a familiar filename.

For supported dynamic toolchains, Buildroot 2025.02’s toolchain configuration documents BR2_TOOLCHAIN_EXTRA_LIBS for additional runtime libraries and gives sanitizer libraries as examples. This mechanism only copies libraries that the selected toolchain actually provides; it does not add sanitizer support to an unsupported architecture or compiler build.

Review static and dynamic linkage separately. Adding a static-runtime option is not a universal repair for missing deployment files and can change the supported interception and loading arrangement. Keep the chosen link mode within the compiler/runtime’s documented contract.

Separate virtual layout from physical memory use

The GCC 14.2 bundled ASan mapping source defines architecture-dependent shadow layouts and a shadow scale of three for the described mapping. That relationship does not mean the whole process needs only a fixed one-eighth physical-memory overhead.

Shadow address ranges, committed pages, allocator metadata, redzones, quarantine and application behavior are different resource quantities. A large virtual reservation is not the same as resident memory, and a small clean startup is not the worst allocation workload.

On the target, record address-space width, loader layout, applicable process limits and startup diagnostics. A shadow-mapping failure should remain a startup qualification failure. Do not weaken address randomization or production memory limits as an unexplained default workaround.

Use a clean smoke test first

Start with an owned tiny program that allocates a small buffer, accesses only its valid extent, frees it and exits. Check both exit status and stderr. This distinguishes runtime initialization problems from the intentional error that a later negative test is meant to report.

The first default host run did not pass: LeakSanitizer reported that its leak-checking path could not operate under ptrace in the execution environment. That diagnostic was preserved. It would be misleading to label the default combined startup successful merely because the application reached its allocation code.

The repeat used process-local ASAN_OPTIONS=detect_leaks=0 for both host cases. The version-pinned sanitizer flags distinguish this leak-detection switch. The resulting evidence qualifies address-error detection in that environment; it does not qualify leak detection.

Read the bounded negative control

The fixture allocated eight bytes. Its clean path wrote byte seven and exited zero. A separate process deliberately wrote one byte at index eight, immediately beyond the owned allocation, using a volatile access so the diagnostic operation remained in the generated test.

With the scoped leak setting above, the negative process produced an AddressSanitizer heap-buffer-overflow report identifying a one-byte write and exited nonzero. The program was original test code in a disposable userspace process. No target memory, kernel fault injection or production workload was involved.

The result shows that this compiler/runtime/environment combination detected that instrumented access. It does not establish detection of every overflow, custom allocator misuse, race, DMA write or uninstrumented library access. Keep the negative fixture small enough that its intended defect is unambiguous.

Plan target coverage and overhead measurements

Repeat clean startup and a controlled owned-buffer negative case in an approved disposable target image. Preserve the exact application and runtime artifacts, process options, report and exit status. Confirm that the error is the expected instrumented access rather than a missing library or unrelated startup failure.

Then exercise representative application paths with an explicit time and memory budget. Measure startup, peak resident usage, virtual mapping and allocation-heavy phases separately. Report instrumentation-induced timing changes rather than presenting sanitizer timings as production performance.

Include relevant plugin and thread lifecycles, error cleanup and supported allocator configurations. Keep leak checks as a separate tested capability if the target environment supports them. A clean run means no report under that tested coverage and workload, not proof that the application is memory-safe.

Define a release diagnostic profile

Maintain the sanitizer build as a reproducible diagnostic configuration with known runtime dependencies and collection procedures. Keep reports bound to build IDs or hashes and preserve symbols through the analysis workflow. Do not deploy an unqualified debug configuration merely to obtain a field report.

The deliverable is a compiler/runtime/image checklist plus explicit clean and negative startup evidence. Here those outcomes are host-only, with leak detection excluded after a verified environment limitation. Target startup, resource budgets and broader coverage remain named acceptance tasks rather than implied successes.

Similar Posts