If you’re using AddressSanitizer (ASan) for bug detection and you’ve noticed that it fails to log a DEADLYSIGNAL
(e.g., segmentation fault, heap buffer overflow) only when LeakSanitizer (LSan) is disabled, you’re not alone. This confusing behavior can lead to missed bugs and hours of debugging confusion.
In this blog post, we’ll explain why this happens, how ASan and LSan interact, and how you can ensure you always get reliable bug reports — with or without LeakSanitizer
What Is AddressSanitizer?
AddressSanitizer (ASan) is a fast memory error detector that finds:
- Stack/heap/global buffer overflows
- Use-after-free bugs
- Use-after-scope
- Heap double free
- Memory access after realloc
It works by instrumenting code at compile time to include memory checks at runtime.
What Is LeakSanitizer?
LeakSanitizer (LSan) is a tool that detects memory leaks, and it is often bundled with ASan. LSan:
- Tracks memory allocations
- Verifies deallocation at program exit
- Reports memory leaks if any exist
By default, when you compile with -fsanitize=address
, LeakSanitizer is enabled, unless you explicitly disable it.
Problem: ASan Doesn’t Report My DEADLYSIGNAL
Bug Without LSan
When you run your program and hit a DEADLYSIGNAL
(e.g., SIGSEGV
, SIGABRT
), you expect ASan to log the crash and stack trace.
But in some cases, disabling LeakSanitizer leads to the bug not being reported or partially logged, which is counterintuitive since LSan is only supposed to detect leaks at exit.
Reproduction Example
Let’s say you compile a buggy program with:
clang -fsanitize=address -fno-omit-frame-pointer -g main.cpp -o test
Everything works — ASan reports both buffer overflows and memory leaks.
But now try disabling LSan:
ASAN_OPTIONS=detect_leaks=0 ./test
Or:
clang -fsanitize=address -fno-omit-frame-pointer -g -fno-sanitize=leak main.cpp -o test
Suddenly, you no longer see detailed crash reports from ASan, especially for certain DEADLYSIGNAL
s.
Why Does This Happen?
This behavior is due to runtime coordination between ASan and LSan, specifically in how runtime initialization and teardown are handled.
ASan + LSan = Coordinated Runtime
When LSan is enabled, the runtime includes logic to:
- Set up signal handlers
- Flush diagnostic output during crashes or exits
- Hook into thread teardown
When LSan is disabled, parts of this coordination don’t get initialized, especially:
- Some of the crash signal handlers
- Stack trace printing at
SIGABRT
,SIGSEGV
- Buffer flushes at the end of program execution
Disabling LSan Sometimes Cuts Off Critical Diagnostics
When detect_leaks=0
or -fno-sanitize=leak
is used:
- LSan’s hooks into ASan are removed.
- Certain cleanup code and signal forwarding logic is skipped.
- You may lose diagnostics like full stack traces or summaries for fatal signals.
In short: LSan helps drive part of ASan’s runtime, and disabling it disables more than just leak detection — it disables parts of the bug-reporting lifecycle.
Confirming the Behavior
To confirm this, you can test the following:
// crash.cpp
#include <cstdlib>
int main() {
int* a = (int*)malloc(4);
a[1] = 42; // Buffer overflow
return 0;
}
Compile normally (with LSan):
clang -fsanitize=address -g crash.cpp -o crash
./crash
# You'll get an ASan error report: heap-buffer-overflow with stack trace
Disable leak detection:
ASAN_OPTIONS=detect_leaks=0 ./crash
# Now the crash might not be reported at all, or only a minimal signal message.
How to Fix or Work Around This
1. Don’t Disable LeakSanitizer Unless You Must
If possible, keep LeakSanitizer enabled. Even if you don’t care about memory leaks, it plays a supporting role in diagnostics.
If you’re worried about false positives or performance, you can disable leak reports but keep the runtime active:
ASAN_OPTIONS=detect_leaks=1,leak_check_at_exit=0
This keeps the hooks without reporting leaks at shutdown.
2. Use Separate Compilation Options (Selective Sanitizing)
If you need to exclude LSan from specific translation units, you can still compile most of your code with full ASan/LSan enabled.
clang -fsanitize=address main.cpp -o test
And only exclude LSan for specific files:
clang -fsanitize=address -fno-sanitize=leak third_party.cpp
3. Use abort_on_error=1
to Force Immediate Reporting
Sometimes ASan delays output until shutdown. For better behavior in crash scenarios:
ASAN_OPTIONS=abort_on_error=1 ./crash
This can force a clearer signal of when and where the crash happens.
4. Use Latest Clang/LLVM Versions
Many of the inconsistencies between ASan and LSan were improved in more recent versions of Clang. If you’re on an older toolchain, consider upgrading.
Final Thoughts
Disabling LeakSanitizer is more impactful than many developers realize. While it’s intended just to suppress leak detection, it has broader effects due to how ASan/LSan coordinate signal handling and diagnostics.
To reliably detect all bugs:
- Avoid disabling LSan unless absolutely necessary.
- Use runtime flags like
leak_check_at_exit=0
if you only want to silence leak reports. - Know that
-fno-sanitize=leak
removes runtime hooks that could affect other bug reports.
Summary
Symptom | Root Cause | Fix |
---|---|---|
No ASan logs for crashes | LeakSanitizer disabled | Keep detect_leaks=1 |
Missing stack traces | ASan runtime shutdown incomplete | Use abort_on_error=1 |
Inconsistent bug logging | Signal handlers not fully installed | Upgrade Clang, avoid disabling LSan |
Bonus Tip: Always Use -fno-omit-frame-pointer
It ensures accurate stack traces:
clang -fsanitize=address -fno-omit-frame-pointer -g myfile.cpp -o myfile
For more interesting topics stay in touch with wwebhub