Skip to content

Instantly share code, notes, and snippets.

@ncovercash
Last active October 14, 2021 04:45
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ncovercash/b5225aeb6ade87438cae28bfd39468a7 to your computer and use it in GitHub Desktop.
Save ncovercash/b5225aeb6ade87438cae28bfd39468a7 to your computer and use it in GitHub Desktop.

Getting Started with Valgrind

The cs-intro.ua.edu and cs-parallel.ua.edu testing servers now come with the utility Valgrind preinstalled.

Valgrind is a utility which is able to detect memory leaks and provide information about memory-related errors (segmentation faults).

Valgrind supports a small set of linux-based operating systems and, in most cases, can still be rather difficult. Therefore, the server's version is extremely helpful for testing.

This guide will show how to use Valgrind.

Sample Code

The following sample code will be used to demonstrate some of valgrind's features. It is C++ code, however, the same works for C (malloc/free).

#include <stdio.h>
using namespace std;

void sampleSegfault() {
    int foo[2];
    foo[999999] = 12; // out of bounds
}

void sampleUseWithoutAlloc() {
    int *foo;
    *foo = 2;
}

void sampleDoubleFree() {
    int *foo = new int;
    delete foo;
    delete foo;
}

void sampleUseAfterFree() {
    int *foo = new int[3];
    delete[] foo;
    foo[0] = 12;
}

void sampleMemoryLeakOne() {
    int *foo = new int;
}
void sampleMemoryLeakTwo() {
    int *foo = new int;
    foo = new int;
    delete foo;
}

void sampleFopen() {
    FILE *cStyle = fopen("test.txt", "r");
}

int main(int argc, char **argv) {
    switch (argv[1][0]) {
        case '0':
            sampleSegfault();
            return 0;
        case '1':
            sampleUseWithoutAlloc();
            return 0;
        case '2':
            sampleDoubleFree();
            return 0;
        case '3':
            sampleUseAfterFree();
            return 0;
        case '4':
            sampleMemoryLeakOne();
            sampleMemoryLeakTwo();
            return 0;
        case '5':
            sampleFopen();
            return 0;
    }
}

Compiling for Valgrind

To compile your code so that Valgrind can provide information about segmentation faults, add the -g flag to g++ or gcc. If there are other options on gcc/g++, such as -std=c++11 or -Wall, add it to this list.

For example, g++ -Wall -std=c++11 test.cpp -o ./compiled becomes g++ -Wall -std=c++11 -g test.cpp -o ./compiled.

For the above code, I used g++ -g memory-test.cpp.

Running Valgrind

To run Valgrind, add valgrind --leak-check=full --show-leak-kinds=all before the normal command.

./a.out would become valgrind --leak-check=full --show-leak-kinds=all ./a.out. To provide arguments to the program, put them at the end and, as usual, argv will start with the comamnd name (./a.out). For example, valgrind --leak-check=full --show-leak-kinds=all ./a.out I_am_argv_1 I_am_argv_2 I_am_argv_3.

Sample Output

Segmentation Fault

valgrind --leak-check=full --show-leak-kinds=all ./a.out 0
==13402== Memcheck, a memory error detector
==13402== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==13402== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==13402== Command: ./a.out 0
==13402== 
==13402== Invalid write of size 4
==13402==    at 0x4006D1: sampleSegfault() (memory-test.cpp:6)
==13402==    by 0x4007E1: main (memory-test.cpp:42)
==13402==  Address 0x1fff3d0b9c is not stack'd, malloc'd or (recently) free'd
==13402== 
==13402== 
==13402== Process terminating with default action of signal 11 (SIGSEGV): dumping core
==13402==  Access not within mapped region at address 0x1FFF3D0B9C
==13402==    at 0x4006D1: sampleSegfault() (memory-test.cpp:6)
==13402==    by 0x4007E1: main (memory-test.cpp:42)
==13402==  If you believe this happened as a result of a stack
==13402==  overflow in your program's main thread (unlikely but
==13402==  possible), you can try to increase the size of the
==13402==  main thread stack using the --main-stacksize= flag.
==13402==  The main thread stack size used in this run was 8388608.
==13402== 
==13402== HEAP SUMMARY:
==13402==     in use at exit: 0 bytes in 0 blocks
==13402==   total heap usage: 1 allocs, 1 frees, 72,704 bytes allocated
==13402== 
==13402== All heap blocks were freed -- no leaks are possible
==13402== 
==13402== For lists of detected and suppressed errors, rerun with: -s
==13402== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
Segmentation fault (core dumped)

Here, we can see that the output tells us that the program tried writing to an address which was not allocated and was not freed (recently). Based on the stack trace, we can see line 42 (in function main) called sampleSegfault() and that the violation occured on line 6.

For reference, sampleSegfault:

void sampleSegfault() {
    int foo[2];
    foo[3] = 12; // out of bounds
}

As the array was created on the stack, it is automatically deleted/freed at the function's exit. Therefore, Valgrind is correct in that all allocations were freed.

Using Without Allocating

valgrind --leak-check=full --show-leak-kinds=all ./a.out 1
==14550== Memcheck, a memory error detector
==14550== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==14550== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==14550== Command: ./a.out 1
==14550== 
==14550== Use of uninitialised value of size 8
==14550==    at 0x4006E5: sampleUseWithoutAlloc() (memory-test.cpp:11)
==14550==    by 0x4007FD: main (memory-test.cpp:45)
==14550== 
==14550== Invalid write of size 4
==14550==    at 0x4006E5: sampleUseWithoutAlloc() (memory-test.cpp:11)
==14550==    by 0x4007FD: main (memory-test.cpp:45)
==14550==  Address 0x0 is not stack'd, malloc'd or (recently) free'd
==14550== 
==14550== 
==14550== Process terminating with default action of signal 11 (SIGSEGV): dumping core
==14550==  Access not within mapped region at address 0x0
==14550==    at 0x4006E5: sampleUseWithoutAlloc() (memory-test.cpp:11)
==14550==    by 0x4007FD: main (memory-test.cpp:45)
==14550==  If you believe this happened as a result of a stack
==14550==  overflow in your program's main thread (unlikely but
==14550==  possible), you can try to increase the size of the
==14550==  main thread stack using the --main-stacksize= flag.
==14550==  The main thread stack size used in this run was 8388608.
==14550== 
==14550== HEAP SUMMARY:
==14550==     in use at exit: 0 bytes in 0 blocks
==14550==   total heap usage: 1 allocs, 1 frees, 72,704 bytes allocated
==14550== 
==14550== All heap blocks were freed -- no leaks are possible
==14550== 
==14550== Use --track-origins=yes to see where uninitialised values come from
==14550== For lists of detected and suppressed errors, rerun with: -s
==14550== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
Segmentation fault (core dumped)

Here, Valgrind says that there was a "use of an uninitialized value" on line 11 in sampleUseWithoutAlloc.

We can see that sampleUseWithoutAlloc attempts to use foo before creating it (so it is undefined):

void sampleUseWithoutAlloc() {
    int *foo;
    *foo = 2;
}

Freeing Twice

valgrind --leak-check=full --show-leak-kinds=all ./a.out 2
==14567== Memcheck, a memory error detector
==14567== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==14567== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==14567== Command: ./a.out 2
==14567== 
==14567== Invalid free() / delete / delete[] / realloc()
==14567==    at 0x4C2B9DB: operator delete(void*) (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==14567==    by 0x40071A: sampleDoubleFree() (memory-test.cpp:17)
==14567==    by 0x400809: main (memory-test.cpp:48)
==14567==  Address 0x5ae3c80 is 0 bytes inside a block of size 4 free'd
==14567==    at 0x4C2B9DB: operator delete(void*) (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==14567==    by 0x40070E: sampleDoubleFree() (memory-test.cpp:16)
==14567==    by 0x400809: main (memory-test.cpp:48)
==14567==  Block was alloc'd at
==14567==    at 0x4C2A91F: operator new(unsigned long) (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==14567==    by 0x4006FE: sampleDoubleFree() (memory-test.cpp:15)
==14567==    by 0x400809: main (memory-test.cpp:48)
==14567== 
==14567== 
==14567== HEAP SUMMARY:
==14567==     in use at exit: 0 bytes in 0 blocks
==14567==   total heap usage: 2 allocs, 3 frees, 72,708 bytes allocated
==14567== 
==14567== All heap blocks were freed -- no leaks are possible
==14567== 
==14567== For lists of detected and suppressed errors, rerun with: -s
==14567== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

This is an example of an issue which would cause an abort. Valgrind tells us that there was an "invalid free() / delete / delete[] / realloc()". In addition to this, it tells us many useful pieces of information:

  • The invalid free occurred at line 17
  • The block was originally freed on line 16
  • The block was allocated (created) with new on line 15

In addition to this, we can see in the summary that there was one too many frees issued.

Using After Freeing

This one is similar to the previous example.

valgrind --leak-check=full --show-leak-kinds=all ./a.out 3
==14602== Memcheck, a memory error detector
==14602== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==14602== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==14602== Command: ./a.out 3
==14602== 
==14602== Invalid write of size 4
==14602==    at 0x40074A: sampleUseAfterFree() (memory-test.cpp:23)
==14602==    by 0x400815: main (memory-test.cpp:51)
==14602==  Address 0x5ae3c80 is 0 bytes inside a block of size 12 free'd
==14602==    at 0x4C2C0BB: operator delete[](void*) (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==14602==    by 0x400745: sampleUseAfterFree() (memory-test.cpp:22)
==14602==    by 0x400815: main (memory-test.cpp:51)
==14602==  Block was alloc'd at
==14602==    at 0x4C2B03F: operator new[](unsigned long) (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==14602==    by 0x40072E: sampleUseAfterFree() (memory-test.cpp:21)
==14602==    by 0x400815: main (memory-test.cpp:51)
==14602== 
==14602== 
==14602== HEAP SUMMARY:
==14602==     in use at exit: 0 bytes in 0 blocks
==14602==   total heap usage: 2 allocs, 2 frees, 72,716 bytes allocated
==14602== 
==14602== All heap blocks were freed -- no leaks are possible
==14602== 
==14602== For lists of detected and suppressed errors, rerun with: -s
==14602== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

In this, we see that the program attempted to write inside a block which was freed.

Memory Leaks

valgrind --leak-check=full --show-leak-kinds=all ./a.out 4
==14608== Memcheck, a memory error detector
==14608== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==14608== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==14608== Command: ./a.out 4
==14608== 
==14608== 
==14608== HEAP SUMMARY:
==14608==     in use at exit: 8 bytes in 2 blocks
==14608==   total heap usage: 4 allocs, 2 frees, 72,716 bytes allocated
==14608== 
==14608== 4 bytes in 1 blocks are definitely lost in loss record 1 of 2
==14608==    at 0x4C2A91F: operator new(unsigned long) (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==14608==    by 0x400763: sampleMemoryLeakOne() (memory-test.cpp:27)
==14608==    by 0x400821: main (memory-test.cpp:54)
==14608== 
==14608== 4 bytes in 1 blocks are definitely lost in loss record 2 of 2
==14608==    at 0x4C2A91F: operator new(unsigned long) (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==14608==    by 0x40077B: sampleMemoryLeakTwo() (memory-test.cpp:30)
==14608==    by 0x400826: main (memory-test.cpp:55)
==14608== 
==14608== LEAK SUMMARY:
==14608==    definitely lost: 8 bytes in 2 blocks
==14608==    indirectly lost: 0 bytes in 0 blocks
==14608==      possibly lost: 0 bytes in 0 blocks
==14608==    still reachable: 0 bytes in 0 blocks
==14608==         suppressed: 0 bytes in 0 blocks
==14608== 
==14608== For lists of detected and suppressed errors, rerun with: -s
==14608== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

In this example, sampleMemoryLeakOne and sampleMemoryLeakTwo both leak memory. Valgrind tells us about each of these leaks and where the relevant memory was allocated.

Valgrind has a few different types of memory leak classifications. Here are what the most common mean:

  • Definitely lost refers to memory that has directly been lost, such as in the above example where the memory is allocated and, once the function is returned, the pointers referring to this memory are lost. For memory to be directly lost, every pointer pointing to the memory has to be lost.
  • Indirectly lost refers to memory that still has pointers pointing to it, however, cannot be reached from the main program. For example, if a linked list's head is lost, the head itself would be directly lost. However, all of its children would be indirectly lost as a result of the head being lost.
  • Still reachable referes to memory which is still being pointed to by some code (99% of the time internal parts of standard library, such as file pointers managed by C/C++) at the program exit

File Left Opened

valgrind --leak-check=full --show-leak-kinds=all ./a.out 6
==14671== Memcheck, a memory error detector
==14671== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==14671== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==14671== Command: ./a.out 6
==14671== 
==14671== 
==14671== HEAP SUMMARY:
==14671==     in use at exit: 0 bytes in 0 blocks
==14671==   total heap usage: 1 allocs, 1 frees, 72,704 bytes allocated
==14671== 
==14671== All heap blocks were freed -- no leaks are possible
==14671== 
==14671== For lists of detected and suppressed errors, rerun with: -s
==14671== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
ncovercash@cs-intro:~> valgrind --leak-check=full --show-leak-kinds=all ./a.out 5
==14673== Memcheck, a memory error detector
==14673== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==14673== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==14673== Command: ./a.out 5
==14673== 
==14673== 
==14673== HEAP SUMMARY:
==14673==     in use at exit: 552 bytes in 1 blocks
==14673==   total heap usage: 2 allocs, 1 frees, 73,256 bytes allocated
==14673== 
==14673== 552 bytes in 1 blocks are still reachable in loss record 1 of 1
==14673==    at 0x4C2A2AF: malloc (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==14673==    by 0x579B3DC: __fopen_internal (in /lib64/libc-2.22.so)
==14673==    by 0x4007B2: sampleFopen() (memory-test.cpp:36)
==14673==    by 0x400832: main (memory-test.cpp:58)
==14673== 
==14673== LEAK SUMMARY:
==14673==    definitely lost: 0 bytes in 0 blocks
==14673==    indirectly lost: 0 bytes in 0 blocks
==14673==      possibly lost: 0 bytes in 0 blocks
==14673==    still reachable: 552 bytes in 1 blocks
==14673==         suppressed: 0 bytes in 0 blocks
==14673== 
==14673== For lists of detected and suppressed errors, rerun with: -s
==14673== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Here, we see that sampleFopen resulted in calling fopen. As a result of fopen, the internal parts of C allocated some related memory to keep track of the file/file pointer. Since there was never a corresponding fclose, it is still here at the end of the program.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment