Dynamic Analysis: AddressSanitizer, UndefinedBehaviorSanitizer, and Valgrind
Detect systems vulnerabilities at runtime: AddressSanitizer (-fsanitize=address), UBSan (-fsanitize=undefined), ThreadSanitizer, shadow memory, and Valgrind Memcheck.
By the end, you’ll be able to…
- Instrument binaries with LLVM/GCC AddressSanitizer (ASan) to catch buffer overflows and use-after-free
- Detect signed integer overflows, null dereferencing, and misaligned pointers using UBSan
- Inspect shadow memory architecture and contrast compile-time sanitizers with Valgrind VM emulation
01The Role of Dynamic Analysis in Systems C++
C++ prioritizes performance over safety: the compiler does not emit runtime bounds checks or garbage collection safeguards. A minor programming mistake—such as reading 1 byte past an array boundary, reading uninitialized memory, or accessing a dangling pointer—triggers Undefined Behavior (UB).
Undefined behavior is insidious because it may appear to work in local testing, but crashes catastrophically under production loads or creates critical remote code execution (RCE) security vulnerabilities.
Dynamic Analysis Sanitizers—developed by Google and integrated directly into Clang and GCC—instrument compiled machine code with lightweight validation hooks that trap and diagnose memory errors the instant they occur at runtime.
# Compile with AddressSanitizer and UndefinedBehaviorSanitizerg++ -O1 -g -fsanitize=address,undefined -fno-omit-frame-pointer main.cpp -o app# Compile with ThreadSanitizer to detect data racesg++ -O1 -g -fsanitize=thread -fno-omit-frame-pointer server.cpp -o server02AddressSanitizer (ASan) and Shadow Memory
AddressSanitizer (-fsanitize=address) detects: Out-of-bounds array access (stack, heap, and global), Use-After-Free (accessing deleted pointers), Double Free, and Memory Leaks.
ASan achieves extreme speed (~2x slowdown compared to Valgrind's 20x slowdown) through an ingenious architecture: Shadow Memory.
ASan maps every 8 bytes of virtual memory to 1 byte of shadow memory. When memory is allocated, redzones (poisoned memory guards) are placed around the allocation in shadow memory. Every load and store instruction is instrumented with a bit check against shadow memory. If an instruction touches a poisoned redzone, ASan halts the program immediately and outputs a detailed stack trace.
03UndefinedBehaviorSanitizer (UBSan) and TSan
UndefinedBehaviorSanitizer (-fsanitize=undefined) traps non-memory undefined behaviors: signed integer overflow, null pointer dereferences, bit shifts exceeding type width (e.g. 1 << 35 on 32-bit int), and misaligned pointer reads.
ThreadSanitizer (-fsanitize=thread) monitors inter-thread memory access to detect Data Races: two threads accessing the same memory concurrently where at least one thread is a writer without mutex synchronization.
Note: TSan cannot be combined with ASan in the same compilation run; they must be executed in separate testing passes.
04Sanitizer Comparison Matrix
Comparing dynamic analysis tools in modern C++ CI/CD test pipelines:
Always run your automated unit and integration test suites with ASan and UBSan enabled.
+-----------------------+---------------------+-------------------+-------------------------+| Tool | Compiler Flag | Primary Detection | Typical Slowdown |+-----------------------+---------------------+-------------------+-------------------------+| AddressSanitizer | -fsanitize=address | Out-of-bounds/UAF | ~2x (Fast) || UndefinedBehaviorSan | -fsanitize=undefined| Overflow, null, UB| ~1.2x (Near-native) || ThreadSanitizer | -fsanitize=thread | Data races | ~3x - 5x || Valgrind Memcheck | External tool | Memory leaks, UAF | ~20x - 50x (Very slow) |+-----------------------+---------------------+-------------------+-------------------------+05Production Systems Architecture & Hardware Trade-Offs
From an operating system and hardware architecture perspective, systems software must account for physical execution realities that compiler abstractions intentionally hide. At the hardware level, instruction caches (I-Cache) and data caches (D-Cache) operate independently, and CPU pipeline front-ends rely heavily on branch target buffers (BTB) to minimize instruction fetch stalls. When code is organized into coherent, spatially contiguous blocks within translation units, binary code density increases, improving instruction cache hit rates across high-frequency execution loops.
Furthermore, modern linkers such as LLVM lld and mold perform Link-Time Optimization (LTO) across translation units. When LTO is enabled (-flto), the compiler writes LLVM bitcode into object files rather than final machine code, allowing the linker to perform cross-translation-unit function inlining, devirtualization, and dead-code stripping across the entire binary.
The ideas worth keeping.
- Dynamic sanitizers instrument binaries to catch memory errors and undefined behavior at runtime.
- AddressSanitizer uses shadow memory to detect buffer overflows and use-after-free bugs with minimal overhead.
- ThreadSanitizer detects multi-threaded data races before they cause silent production corruption.
Make it yours.
Type and test your code right here in the interactive workspace. Run your solution, check the output, and compare with the worked guide when ready.
Exercise 1: Write the compilation command to compile a te
Write the compilation command to compile a test suite test_suite.cpp with debug symbols, AddressSanitizer, and UndefinedBehaviorSanitizer using g++.
Explore a worked solution
g++ -std=c++20 -O1 -g -fsanitize=address,undefined -fno-omit-frame-pointer test_suite.cpp -o test_suiteInclude -g, -O1, -fsanitize=address,undefined, and -fno-omit-frame-pointer.
Exercise 2: Explain how ASan uses shadow memory and re
Explain how ASan uses 'shadow memory' and 'redzones' to catch buffer overflows in arrays.
Explore a worked solution
// ASan maps every 8 bytes of application memory to 1 byte of shadow memory.// When an array is allocated, ASan surrounds the buffer with poisoned 'redzone' bytes.// The compiler instruments every memory access with a shadow byte check.// If an index accesses memory beyond the array, it hits the redzone,// triggering an immediate crash report with exact line numbers and memory dumps.Redzones surround buffers, and shadow memory records whether bytes are valid or poisoned.
What stayed with you?
Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.
Exercise 1: Write the compilation command to compile a te
Write the compilation command to compile a test suite test_suite.cpp with debug symbols, AddressSanitizer, and UndefinedBehaviorSanitizer using g++.
Build completed. Binary executed. [Exit code: 0]
Keep exploring.
The official references behind this lesson, when you want a second angle or the full details.
Pause. Put the pieces together.
You’ve reached the end of Systems Error Handling, std::expected, and Sanitizers. Before moving on, use this task to check what you can do independently.
Build exception-free monadic error pipelines and run test suites under AddressSanitizer.
Try it in your own environment and write down the result. This is a self-check; reading completion does not evaluate your code.
Review this moduleOne lesson closer.
Reading is a start. Trying it yourself makes the connection.