What it is
Valgrind is a suite of debugging and profiling tools for dynamic analysis, most commonly used to detect memory management errors in programs written in C and C++.
Installation
Linux
sudo apt update && sudo apt install valgrind
# or
sudo yum install valgrind
macOS
Valgrind is not officially supported on macOS. However, you can install a port using Homebrew:
brew install valgrind
Note that the macOS port might have limitations or not be as robust as the Linux version.
Windows
Valgrind does not have direct Windows support. For Windows, consider using alternatives like Visual Studio’s built-in memory diagnostic tools or Dr. Memory.
Core Concepts
Memcheck
The primary tool in Valgrind for detecting memory errors. It works by running your program in a simulated environment and monitoring memory access.
Tool Options
Valgrind can run various tools, with memcheck being the default and most common. Other tools include callgrind (for call-graph profiling), cachegrind (for cache profiling), and helgrind/drd (for thread debugging).
Instrumentation
Valgrind instruments your executable at runtime, adding checks around memory operations. This instrumentation is what allows it to detect errors.
Commands / Usage
The general syntax is:
valgrind [valgrind-options] <your_program> [program_arguments]
Running Memcheck (Default Tool)
Basic Memory Error Detection
valgrind ./my_program arg1 arg2
Runs my_program with arguments arg1 and arg2, reporting memory errors detected by Memcheck.
Suppressing Errors
valgrind --suppressions=my_suppressions.supp ./my_program
Runs my_program and applies suppression rules defined in my_suppressions.supp to hide known or unfixable errors.
Tracking Leaks (Leak Check)
valgrind --leak-check=full ./my_program
Performs a thorough check for memory leaks, providing detailed information about where leaked memory was allocated. Other options: summary (default, less detail), no.
Tracking Uninitialized Memory Usage
valgrind --track-origins=yes ./my_program
Tracks the origin of uninitialized values, helping to identify where they were first introduced. This adds overhead but is crucial for debugging certain bugs.
Showing Call Stacks for Errors
valgrind --show-leak-kinds=all --verbose ./my_program
Provides more verbose output and shows all kinds of leaks (definite, indirect, possibly, reachable).
Profiling with Callgrind
valgrind --tool=callgrind ./my_program
Runs the program using the Callgrind tool, which generates call graph profiling information. This is useful for understanding program performance and function call overhead.
Profiling with Cachegrind
valgrind --tool=cachegrind ./my_program
Runs the program using the Cachegrind tool, which simulates CPU caches to profile cache misses and memory access patterns.
Thread Debugging with Helgrind
valgrind --tool=helgrind ./my_program
Runs the program using Helgrind to detect data races and other threading errors.
Thread Debugging with DRD
valgrind --tool=drd ./my_program
Runs the program using DRD, another tool for detecting data races and other threading errors, often with better performance than Helgrind.
Valgrind Options
--tool=<toolname>
Specifies which Valgrind tool to use. Defaults to memcheck.
valgrind --tool=callgrind ./my_program
--leak-check=<summary|yes|full|no>
Controls the level of detail for leak checking. full is most detailed.
valgrind --leak-check=full ./my_program
--show-leak-kinds=<all|definite|indirect|possible|reachable>
Determines which types of leaks to report. all is comprehensive.
valgrind --show-leak-kinds=all ./my_program
--track-origins=<yes|no>
Tracks the origin of uninitialized values. yes is more thorough but slower.
valgrind --track-origins=yes ./my_program
--gen-suppressions=<all|default|nofunc>
Generates suppression rules for detected errors. all attempts to suppress all errors found.
valgrind --gen-suppressions=all --leak-check=full ./my_program > leaks.supp
--suppressions=<filename>
Specifies a file containing suppression rules.
valgrind --suppressions=my_custom.supp ./my_program
--num-callers=<0-200>
The number of ancestor calls to show in stack traces. Higher values provide more context but increase overhead.
valgrind --num-callers=10 ./my_program
--log-file=<filename>
Writes Valgrind’s output to a file instead of stderr.
valgrind --log-file=valgrind_output.txt ./my_program
--quiet
Suppresses most Valgrind output, useful when redirecting to a file or piping.
--verbose
Increases the verbosity of Valgrind’s output.
--time-stamp=<yes|no>
Adds timestamps to the output.
--trace-children=<yes|no>
Tells Valgrind to also instrument child processes. Defaults to yes.
valgrind --trace-children=no ./my_program
--vgdb=<yes|no>
Enables the Valgrind GDB server. Defaults to yes.
valgrind --vgdb=yes ./my_program
--vgdb-error=<0-999>
Crash the client program when Valgrind encounters an error, and wait for the GDB server to be attached. 0 means always wait.
valgrind --vgdb-error=1 ./my_program
--vgdb-pipe=<filename>
Use a named pipe for communication with the GDB server.
Common Patterns
Detecting All Memory Errors and Leaks
valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./my_program
This command is the workhorse for finding most memory-related bugs.
Generating Leak Suppressions
valgrind --leak-check=full --show-leak-kinds=all --gen-suppressions=all ./my_program > auto-suppressions.supp
Run this to generate a starting point for a suppression file. You’ll need to review and edit auto-suppressions.supp to keep only the necessary suppressions.
Debugging a Specific Allocation
If Valgrind reports a leak, and you want to see where it was allocated:
valgrind --leak-check=full --show-leak-kinds=all ./my_program
Look for the in 0x... lines in the output, which indicate the call stack where the memory was allocated.
Profiling with Callgrind and Visualizing Results
valgrind --tool=callgrind ./my_program
# Then visualize with kcachegrind or qcachegrind
kcachegrind callgrind.out.<pid>
This helps identify performance bottlenecks by showing function call counts and costs.
Debugging Child Processes
valgrind --trace-children=yes ./my_program_with_fork
Ensures that Valgrind instruments processes created by fork() or exec().
Attaching GDB to a Valgrind Session
valgrind --vgdb=yes --vgdb-error=1 ./my_program
# In another terminal:
gdb ./my_program
(gdb) target remote | vgdb --pid=<pid_of_my_program>
This allows you to debug your program under Valgrind’s supervision using GDB.
Gotchas
Performance Overhead
Valgrind significantly slows down program execution (often 10x-50x slower) and increases memory usage due to instrumentation. Be patient, especially with large programs or complex test cases.
False Positives/Negatives
While powerful, Valgrind is not infallible.
- False Positives: Sometimes Valgrind might report an error that isn’t actually a bug (e.g., memory managed by a third-party library in an unusual way). Suppressions are used to handle these.
- False Negatives: Valgrind might miss some subtle bugs, especially those related to complex race conditions or specific hardware behaviors.
Dynamic Libraries
Valgrind has varying levels of support for dynamically loaded libraries. It generally works well, but issues can arise with complex library loading scenarios.
Stripped Binaries
Valgrind relies on debug symbols (-g flag during compilation) to provide meaningful stack traces. If your binary is stripped, the error reports will be much harder to interpret. Always compile with debug symbols when using Valgrind.
macOS Port Limitations
The macOS port of Valgrind is less mature than the Linux version. Some features might not work as expected, and compatibility can be an issue.
__attribute__((noinline))
Functions marked with __attribute__((noinline)) might not appear correctly in Valgrind’s call stacks, especially with tools like Callgrind.
longjmp and setjmp
Valgrind has specific handling for setjmp/longjmp, but complex control flow involving these can sometimes confuse its analysis.
Exit Codes
Valgrind itself returns an exit code. 0 means no errors were detected by Memcheck. Non-zero values indicate errors or issues. Your program’s exit code is reported separately.