Debugger
Updated 2025-01-01T00:00:00.000ZValgrind Memory Debugging
Valgrind Memcheck, Callgrind, Helgrind, and DRD — detecting memory errors, use-after-free, leaks, data races, and reading Valgrind reports.
TL;DR
Valgrind is a dynamic analysis framework. memcheck (default) finds heap errors and leaks. callgrind profiles function calls. helgrind/drd find data races.
bash
valgrind --tool=memcheck --leak-check=full ./app
valgrind --tool=callgrind ./app && callgrind_annotate callgrind.out.*
valgrind --tool=helgrind ./appInstallation
bash
sudo apt install valgrind # Debian/Ubuntu
sudo dnf install valgrind # Fedora/RHEL
brew install valgrind # macOS (limited support)Build with debug info for readable reports:
bash
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-g -O0" ..
# or
g++ -g -O0 -o app main.cppMemcheck — Memory Error Detection
bash
valgrind --tool=memcheck \
--leak-check=full \
--show-leak-kinds=all \
--track-origins=yes \
--verbose \
./app [args]| Option | Purpose |
|---|---|
--leak-check=full | Detailed leak reports (not just totals) |
--show-leak-kinds=all | Show definite, indirect, possible, and reachable leaks |
--track-origins=yes | Track where uninitialized values come from (slower) |
--error-exitcode=1 | Return non-zero exit code on errors (for CI) |
--suppressions=file | Suppress known false positives |
--gen-suppressions=all | Generate suppression entries for all errors |
Reading Memcheck Reports
Invalid read/write
cpp
==12345== Invalid read of size 8
==12345== at 0x401B2F: use_ptr (main.cpp:42)
==12345== at 0x401A10: main (main.cpp:15)
==12345== Address 0x5204080 is 0 bytes after a block of size 24 alloc'd
==12345== at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck.so)
==12345== at 0x401A20: allocate (main.cpp:10)Invalid read of size 8— reading 8 bytes out of bounds- The stack trace shows where the bad access happened
- The second block shows where the buffer was allocated
Use after free
cpp
==12345== Invalid read of size 4
==12345== Address 0x5204e80 is 4 bytes inside a block of size 40 free'd
==12345== at 0x4C30D3B: free (in .../vgpreload_memcheck.so)
==12345== at 0x401B10: cleanup (main.cpp:55)
==12345== Block was alloc'd at
==12345== at 0x4C2FB0F: malloc (in .../vgpreload_memcheck.so)Memory leak summary
cpp
==12345== LEAK SUMMARY:
==12345== definitely lost: 1,024 bytes in 1 blocks
==12345== indirectly lost: 512 bytes in 4 blocks
==12345== possibly lost: 0 bytes in 0 blocks
==12345== still reachable: 8,192 bytes in 2 blocks- Definitely lost — no pointer to this memory exists (real leak)
- Indirectly lost — reachable only through definitely-lost memory
- Possibly lost — pointer exists but points to middle of block
- Still reachable — pointer exists at exit (often fine: global/static)
Callgrind — Call Graph Profiler
bash
# Profile the application
valgrind --tool=callgrind \
--callgrind-out-file=cg.out \
./app
# Text report
callgrind_annotate cg.out
# GUI visualization (install kcachegrind)
kcachegrind cg.outbash
# Profile only part of execution
valgrind --tool=callgrind --instr-atstart=no ./app
# In code:
#include <valgrind/callgrind.h>
CALLGRIND_START_INSTRUMENTATION;
hot_function();
CALLGRIND_STOP_INSTRUMENTATION;
CALLGRIND_DUMP_STATS;Helgrind — Data Race Detector
bash
valgrind --tool=helgrind \
--history-level=approx \
./threaded_appSample output:
cpp
==12345== Possible data race during read of size 4 at 0x601060
==12345== at 0x4008E4: worker (race.cpp:15)
==12345== This conflicts with a previous write of size 4
==12345== at 0x400911: main (race.cpp:30)Helgrind also detects:
- Lock order violations (potential deadlocks)
- pthread API misuse
- Unlocked variable accesses
DRD — Data Race Detector (Alternative)
bash
valgrind --tool=drd ./threaded_appDRD uses less memory than Helgrind and is faster on programs with many threads. Helgrind gives more detailed "happens-before" analysis.
Suppression Files
Suppress known false positives or third-party library noise:
cpp
# myapp.supp
{
openssl_init_leak
Memcheck:Leak
match-leak-kinds: reachable
fun:malloc
...
fun:OPENSSL_init_ssl
}bash
valgrind --suppressions=myapp.supp ./appGenerate suppressions automatically:
bash
valgrind --gen-suppressions=all ./app 2>&1 | grep -A 20 "^{" > new.suppCMake Integration
cmake
find_program(VALGRIND valgrind)
if(VALGRIND)
add_custom_target(memcheck
COMMAND ${VALGRIND}
--leak-check=full
--error-exitcode=1
$<TARGET_FILE:myapp>
DEPENDS myapp
)
endif()Valgrind vs Address Sanitizer
| Valgrind Memcheck | AddressSanitizer | |
|---|---|---|
| Slowdown | 10–50x | 2x |
| Requires recompile | No | Yes (-fsanitize=address) |
| Stack buffer overflows | No | Yes |
| Use-after-return | No | Yes (with flag) |
| Leak detection | Yes | Yes |
| Initialization errors | Yes | Partial (MSan) |
| Thread errors | Helgrind/DRD | ThreadSanitizer |
| CI overhead | High (slow) | Low |
| Best for | Legacy binaries, thorough leak hunt | Fast CI integration |
Rule of thumb: use ASan in CI (fast), Valgrind for deep investigations or when you can't recompile.
Edit on GitHubUpdated 2025-01-01T00:00:00.000Z