Valgrind I — memcheck

By | 2013-02-21

In this series I’m going to give you a quick tour of the valgrind tool suite. valgrind is useful for finding those hard-to-find, serious-repercussion bugs that can sneak into your code.

Consider this C++ program:

// C++
#include <iostream>
// C
#include <stdlib.h>
// OS
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

using namespace std;

int main(void)
{
    cerr << "Leaking a file descriptor" << endl;
    int fd = open("/dev/null", O_RDONLY);
    // missing close()
    fd = -1;

    cerr << "Leaking some memory" << endl;
    char *b = new char[1024];
    // missing delete[]
    b = NULL;

    cerr << "Performing invalid access (out of bounds)" << endl;
    char *c = new char[10];
    // c is only 10 bytes long, so we shouldn't access the eleventh
    c[10] = '\xff';
    delete[] c;

    cerr << "Performing invalid access (deleted memory)" << endl;
    char *d = new char[10];
    delete[] d;
    d[0] = '\xff';

    // Use of uninitialised value (the compiler will catch
    // automatic variable misuse, valgrind catches heap-allocated misuse
    int *x = new int;
    if( *x == 10 )
        *x = 1;
    cerr << "x = " << x << endl;


    // If we were being really pedantic...
    close(0);
    close(1);
    close(2);

    return 0;
}

// Command lines for compile:
//  $ g++ -ggdb3 -O2 -Wall -rdynamic -Wextra -c -o memleaker.o memleaker.cc
//  $ g++ -pthread -o memleaker memleaker.o

The faults in the above should be pretty obvious (especially as I’ve commented them). However, those faults are invisible at compile time, and fairly well hidden at runtime. They are perfectly capable of crashing your application though, at some arbitrary and unexpected time. You should strive to write good code, but nobody is perfect. valgrind is an excellent tool for rooting out these sorts of faults. Here’s a sample run on the above program.

First the run:

valgrind --tool=memcheck \
                --undef-value-errors=no \
                --num-callers=40 \
                --track-fds=yes \
                --leak-check=full \
                --show-reachable=yes \
                --log-file=vg-memleaker.log \
                ./memleaker
Leaking a file descriptor
Leaking some memory
Performing invalid access (out of bounds)
Performing invalid access (deleted memory)
x = 0x43014d8

The program is now complete; but valgrind has produced a useful log.

less vg-memleaker.log
==22369== Memcheck, a memory error detector
==22369== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==22369== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==22369== Command: ./memleaker
==22369== Parent PID: 22368
==22369== 
==22369== Invalid write of size 1
==22369==    at 0x804881A: main (memleaker.cc:28)
==22369==  Address 0x4301462 is 0 bytes after a block of size 10 alloc'd
==22369==    at 0x4027C34: operator new[](unsigned int) (vg_replace_malloc.c:357)
==22369==    by 0x8048819: main (memleaker.cc:26)
==22369== 

This is c[10] = '0xff';, where the array is only 10 bytes long and we’ve forgotten that index 10 of a zero-indexed array is actually the eleventh byte and so is accessing unallocated memory.

==22369== Invalid write of size 1
==22369==    at 0x804885C: main (memleaker.cc:34)
==22369==  Address 0x4301498 is 0 bytes inside a block of size 10 free'd
==22369==    at 0x4026C3C: operator delete[](void*) (vg_replace_malloc.c:515)
==22369==    by 0x804885B: main (memleaker.cc:33)
==22369== 
==22369== 

d[0] = '\xff'; isn’t allowed when we’ve deleted the memory that d points at.

==22369== FILE DESCRIPTORS: 2 open at exit.
==22369== Open file descriptor 4: /dev/null
==22369==    at 0x418FB73: __open_nocancel (syscall-template.S:82)
==22369==    by 0x41B1E45: (below main) (libc-start.c:228)
==22369== 

Oops; forgot to close a descriptor. File descriptor leaks can eventually bring your application to a halt, since your OS will limit the number of descriptors any one process may open (ulimit -n shows 1024 on my system). When you hit that limit (even if you’re no longer using the descriptors) every subsequent open() or connect() will fail.

==22369== Open file descriptor 3: /home/andyp/dev/projects/valgrind/vg-memleaker.log
==22369==    <inherited from parent>
==22369== 
==22369== 

We can ignore this one. This is valgrind’s log file, so won’t be open in a real run of our system.

==22369== HEAP SUMMARY:
==22369==     in use at exit: 1,028 bytes in 2 blocks
==22369==   total heap usage: 4 allocs, 2 frees, 1,048 bytes allocated
==22369== 
==22369== 4 bytes in 1 blocks are definitely lost in loss record 1 of 2
==22369==    at 0x4028354: operator new(unsigned int) (vg_replace_malloc.c:292)
==22369==    by 0x804886A: main (memleaker.cc:38)
==22369== 
==22369== 1,024 bytes in 1 blocks are definitely lost in loss record 2 of 2
==22369==    at 0x4027C34: operator new[](unsigned int) (vg_replace_malloc.c:357)
==22369==    by 0x80487F1: main (memleaker.cc:21)
==22369== 

new without a matching delete means a memory leak. These too will eventually exhaust either available virtual memory space (2GB on a 32-bit system) or all swap space, in the meantime bringing your server to its knees as it constantly swaps for every single memory use.

==22369== LEAK SUMMARY:
==22369==    definitely lost: 1,028 bytes in 2 blocks
==22369==    indirectly lost: 0 bytes in 0 blocks
==22369==      possibly lost: 0 bytes in 0 blocks
==22369==    still reachable: 0 bytes in 0 blocks
==22369==         suppressed: 0 bytes in 0 blocks
==22369== 
==22369== For counts of detected and suppressed errors, rerun with: -v
==22369== ERROR SUMMARY: 4 errors from 4 contexts (suppressed: 0 from 0)

valgrind further analyses the types of loss. For example, “definitely lost” is memory to which there is no longer a pointer; “indirectly lost” is memory to which there is a pointer, but the pointer is part of another block which is itself definitely lost. See the docs for more.

Leave a Reply