Why Won’t AddressSanitizer Log My DEADLYSIGNAL/Bug Only When LeakSanitizer Is Off?

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 DEADLYSIGNALs.


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