Debugging C++ with Valgrind on Linux

Debugging C++ with Valgrind on Linux

Finding memory bugs like memory-leaks, memory corruptions and memory access violations can be difficult if you don’t have the right tool to help you narrow down the scope and provide clues. This is what Valgrind does well for code written in C/C++ — it can save you hours of frustration.

How to run

You can install Valgrind using your package manager (for Ubuntu sudo apt install valgrind). Your program can be compiled with any compiler, but try to keep it on no optimizations to get the most out of Valgrind.

For your program named hello, run:

Terminal window
valgrind ./hello

Understanding Valgrind

Valgrind is a debugging tool available on Linux, an open-source project and free to use. Valgrind helps with memory leak detection, threading bugs and can help optimize code using its profiling support. Valgrind is designed to work as non-intrusively as possible with existing executables — this means you don’t need to re-link or re-build a binary in order to use Valgrind features. Simply run Valgrind passing it operational parameters and the executable to be tested along with any parameters the executable might accept.

The executable is run on a synthetic CPU provided by the Valgrind core; every single instruction executed is simulated, which will cause the executable to run much slower. Let’s look at Valgrind by testing the Linux date command from the shell.

Running valgrind ./hello produces output like the following:

==20943== Memcheck, a memory error detector
==20943== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==20943== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==20943== Command: ./hello
==20943==
Hello World!
==20943==
==20943== HEAP SUMMARY:
==20943== in use at exit: 72,768 bytes in 3 blocks
==20943== total heap usage: 6,247 allocs, 6,244 frees, 1,071,757 bytes allocated
==20943==
==20943== 32 bytes in 1 blocks are still reachable in loss record 1 of 3
==20943== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==20943== by 0x6340E77: CRYPTO_malloc (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
...
==20943== LEAK SUMMARY:
==20943== definitely lost: 0 bytes in 0 blocks
==20943== indirectly lost: 0 bytes in 0 blocks
==20943== possibly lost: 0 bytes in 0 blocks
==20943== still reachable: 72,768 bytes in 3 blocks
==20943== suppressed: 0 bytes in 0 blocks
==20943==
==20943== For counts of detected and suppressed errors, rerun with: -v
==20943== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

As you can see, there are no errors reported by Valgrind (shown in the last line). There were 6247 allocations and 6244 frees on the heap, with 3 memory leaks that are still reachable. Also reported is the total bytes allocated over the run-period. This lets us peek inside a process to see how memory is getting utilized.

The general report format is:

==99999== some valgrind message:

Where 99999 is the process ID — above, 20943 is the process ID that was monitored.

If you can, when debugging, make sure to debug code that is not optimized. Otherwise you will encounter odd results. Also build with -Wall to eliminate all possible compile-time warnings, as Valgrind does not report these.

When fixing errors reported by Valgrind, fix them in the order reported, as subsequent errors may be caused by earlier errors. If you want to know how many times an error has occurred, use the -v option. This option will also turn on verbose reporting.

Logging to file

Since Valgrind takes everything as an input parameter, the following invocations will not work as you expect — no piping is possible for output. The file operation and any other program passed to Valgrind will get instrumented, which is not what you want.

Terminal window
valgrind some_exe > valtest.log
valgrind some_exe | tee valtest.log

The correct way to log is to use the option --log-file=filename.

Threading

Valgrind fully supports threaded programs. The executable code will use the native thread library, however Valgrind will serialize the calls and restrict all thread operations to a single CPU core. Valgrind will only run one thread at a time; this is done to avoid implementation problems. Thread scheduling is still handled by the OS. Multi-threaded executables will run much slower — on average Valgrind instrumentation results in a 10 to 50 times slow-down of execution for a single-threaded program.

Detecting memory leaks with Valgrind

Memcheck is the memory error detector tool in Valgrind. To use it, you “may” specify the option --tool=memcheck, but you don’t need to since Memcheck is the default tool of Valgrind. To get a detailed memory check, run:

Terminal window
valgrind --leak-check=full --show-leak-kinds=all ./hello

Let’s debug a test program. The sample program below allocates memory and fails to delete it. The simple leak-test app takes a single parameter which is the size in bytes of the memory to be allocated.

#include <iostream>
#include <cstring>
#include <stdlib.h>
using namespace std;
int main(int argc, char* argv[])
{
int size = 10;
if( argc == 2 ) size = atoi(argv[1]);
int* p = new int[size];
memset( p, 0, 10 );
return 0;
}

main.cpp

Create a test folder and save this file as main.cpp.

Building with GNU GCC

The program can be built with any compiler. Here we are using GNU g++; no special steps are required.

Terminal window
mkdir build
cd build
g++ ../main.cpp -o testapp
Terminal window
valgrind --leak-check=yes ./testapp 1000

Note: Valgrind needs the system debug symbols installed in order for it to work correctly. If you see an error like the one below, you will need to install the debug symbols for libc on Linux. Read the Valgrind message carefully for instructions on what must be done.

==8838== Memcheck, a memory error detector
==8838== Copyright (C) 2002-2011, and GNU GPL'd, by Julian Seward et al.
==8838== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyright info
==8838== Command: ./testapp
==8838==
valgrind: Fatal error at startup: a function redirection
valgrind: which is mandatory for this platform-tool combination
valgrind: cannot be set up. Details of the redirection are:
valgrind: A must-be-redirected function
valgrind: whose name matches the pattern: strlen
valgrind: in an object with soname matching: ld-linux-x86-64.so.2
valgrind: was not found whilst processing
valgrind: symbols from the object with soname: ld-linux-x86-64.so.2
valgrind: Possible fixes: (1, short term): install glibc's debuginfo
valgrind: package on this machine. (2, longer term): ask the packagers
valgrind: for your Linux distribution to please in future ship a non-
valgrind: stripped ld.so (or whatever the dynamic linker .so is called)
valgrind: that exports the above-named function using the standard
valgrind: calling conventions for this platform. The package you need
valgrind: to install for fix (1) is called
valgrind: On Debian, Ubuntu: libc6-dbg
valgrind: On SuSE, openSuSE, Fedora, RHEL: glibc-debuginfo
valgrind: Cannot continue -- exiting now. Sorry.

On Ubuntu, to install libc debug symbols, type:

Terminal window
sudo apt-get install libc6-dbg

If Valgrind runs correctly you will see something similar to the following output:

==29618== Memcheck, a memory error detector
==29618== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==29618== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==29618== Command: ./testapp 1000
==29618==
==29618==
==29618== HEAP SUMMARY:
==29618== in use at exit: 76,704 bytes in 2 blocks
==29618== total heap usage: 2 allocs, 0 frees, 76,704 bytes allocated
==29618==
==29618== 4,000 bytes in 1 blocks are definitely lost in loss record 1 of 2
==29618== at 0x4C2E80F: operator new[](unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==29618== by 0x4007F0: main (in /tmp/test/build/testapp)
==29618==
==29618== LEAK SUMMARY:
==29618== definitely lost: 4,000 bytes in 1 blocks
==29618== indirectly lost: 0 bytes in 0 blocks
==29618== possibly lost: 0 bytes in 0 blocks
==29618== still reachable: 72,704 bytes in 1 blocks
==29618== suppressed: 0 bytes in 0 blocks
==29618== Reachable blocks (those to which a pointer was found) are not shown.
==29618== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==29618==
==29618== For counts of detected and suppressed errors, rerun with: -v
==29618== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

This output clearly states that there is a memory leak. There are many kinds of memory leaks; two important ones are:

  • definitely lost — you have a memory leak.
  • probably lost — you may have a memory leak, unless you are doing something unusual with moving heap pointers around.

Let’s add the missing delete:

#include <iostream>
#include <cstring>
#include <stdlib.h>
using namespace std;
int main(int argc, char* argv[])
{
int size = 10;
if( argc == 2 ) size = atoi(argv[1]);
int* p = new int[size];
memset( p, 0, 10 );
delete[] p;
return 0;
}

Build and run Valgrind one more time. This time there is no error or memory leak:

==30609== Memcheck, a memory error detector
==30609== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==30609== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==30609== Command: ./testapp 1000
==30609==
==30609==
==30609== HEAP SUMMARY:
==30609== in use at exit: 72,704 bytes in 1 blocks
==30609== total heap usage: 2 allocs, 1 frees, 76,704 bytes allocated
==30609==
==30609== LEAK SUMMARY:
==30609== definitely lost: 0 bytes in 0 blocks
==30609== indirectly lost: 0 bytes in 0 blocks
==30609== possibly lost: 0 bytes in 0 blocks
==30609== still reachable: 72,704 bytes in 1 blocks
==30609== suppressed: 0 bytes in 0 blocks
==30609== Reachable blocks (those to which a pointer was found) are not shown.
==30609== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==30609==
==30609== For counts of detected and suppressed errors, rerun with: -v
==30609== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Valgrind tells us 2 allocations took place and 1 free of 76,704 bytes and no memory leaks on exit.

NOTE: if you want to get more detailed memory-leak output, use:

Terminal window
valgrind --leak-check=full

You can also add the -v verbose option.

Few other types of issues which can be detected are:

  • Uninitialized variable detection (these errors can be difficult to track down in larger code, so to get more information use --track-origin=yes — this option will cause a slow-down in execution speed, so use it when needed)
  • Invalid memory access
  • Buffer overrun detection
  • Source and destination memory overlap
  • System calls with inadequate read/write permission
  • Mismatch memory allocation and deletion
  • Double delete

Taken from the Valgrind site:

In C++ it’s important to deallocate memory in a way compatible with how it was allocated. The deal is:

  • If allocated with malloc, calloc, realloc, valloc or memalign, you must deallocate with free.
  • If allocated with new, you must deallocate with delete.
  • If allocated with new[], you must deallocate with delete[].

Last words

Even if you are not having to track down a memory bug, as a professional you should be in the practice of executing all your code through Valgrind. You’ve seen how easy it is to use Valgrind, so turn it into a new habit.

Tools and Setup

colour-valgrind (python wrapper)

Heaptrack — a heap memory profiler for Linux

Heaptrack — A Heap Memory Profiler for Linux by Milian Wolff