Valgrind

This is a short introduction on how to use Valgrind (memory checker)

What is it?

Valgrind is a program to check whether other programs are using their memory correctly or if there is memory corruption etc.

Valgrind is currently only available on Linux/x86, that means logging in on salt or the machines in Ma136/Ma146, either locally at the computer or using ssh/PuTTY. Another option is to use a web terminal.

How to use it?

Example source code in C:

 1.    #include <stdlib.h>
 2.    #include <string.h>
 3.    #include <stdio.h>
 4.    int main(void) {
 5.        char *a, *b;
 6.        a = malloc(10);
 7.        b = malloc(30);
 8.        strcpy(b, "I am not a mushroom.");
 9.        strcpy(a, "This is not a blue cow.");
10.        puts(a);
11.        puts(b);
12.        return 0;
13.    }

This allocates 10 bytes of memory for a, then stores 20ish chars at that location. This will obviously not fit, but that memory is stored somewhere within the programs own memory and so does not cause a segmentation fault due to invalid memory.

Compiling

Compiling the program with most warnings enabled (-Wall -W -pedantic), debug information (-g) and sending the result to 'testprog' (-o testprog):


lacolhost:/tmp> gcc -Wall -W -pedantic -g -o testprog testprog.c

testprog.c: In function 'main':
testprog.c:10:5: warning: '__builtin_memcpy' writing 24 bytes into a region of size 10 overflows the destination [-Wstringop-overflow=]
   10 |     strcpy(a, "This is not a blue cow.");
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lacolhost:/tmp>  

(note that running gcc -wall -w ... instead of gcc -Wall -W ... will turn off all warnings instead, use capital W's)

Lately, GCC has started to use static analysis to find problems in the code even before running. Let's in this case assume that we either have an older compiler, we didn't see this, or something else - we proceed.

Running the program


lacolhost:/tmp> ./testprog
This is not a blue cow.
ue cow.

.. not quite the result I wanted.

Later GCC versions will cause these runs to seemingly work, but may crash in other ways.

Running the program through valgrind


lacolhost:/tmp> valgrind  ./testprog

==895866== Memcheck, a memory error detector
==895866== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==895866== Using Valgrind-3.16.1 and LibVEX; rerun with -h for copyright info
==895866== Command: ./testprog
==895866==
==895866== Invalid write of size 8
==895866==    at 0x1091AE: main (testprog.c:10)
==895866==  Address 0x4a43048 is 8 bytes inside a block of size 10 alloc'd
==895866==    at 0x483877F: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==895866==    by 0x109156: main (testprog.c:7)
==895866==
==895866== Invalid write of size 8
==895866==    at 0x1091BC: main (testprog.c:10)
==895866==  Address 0x4a43050 is 6 bytes after a block of size 10 alloc'd
==895866==    at 0x483877F: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==895866==    by 0x109156: main (testprog.c:7)
==895866==
==895866== Invalid read of size 1
==895866==    at 0x483BC94: strlen (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==895866==    by 0x48F15FF: puts (ioputs.c:35)
==895866==    by 0x1091CB: main (testprog.c:11)
==895866==  Address 0x4a4304a is 0 bytes after a block of size 10 alloc'd
==895866==    at 0x483877F: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==895866==    by 0x109156: main (testprog.c:7)
==895866==
==895866== Invalid read of size 1
==895866==    at 0x48FDB93: _IO_default_xsputn (genops.c:399)
==895866==    by 0x48FDB93: _IO_default_xsputn (genops.c:370)
==895866==    by 0x48FBD22: _IO_new_file_xsputn (fileops.c:1265)
==895866==    by 0x48FBD22: _IO_file_xsputn@@GLIBC_2.2.5 (fileops.c:1197)
==895866==    by 0x48F16C7: puts (ioputs.c:40)
==895866==    by 0x1091CB: main (testprog.c:11)
==895866==  Address 0x4a4304a is 0 bytes after a block of size 10 alloc'd
==895866==    at 0x483877F: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==895866==    by 0x109156: main (testprog.c:7)
==895866==
This is not a blue cow.
I am not a mushroom.
==895866==
==895866== HEAP SUMMARY:
==895866==     in use at exit: 40 bytes in 2 blocks
==895866==   total heap usage: 3 allocs, 1 frees, 1,064 bytes allocated
==895866==
==895866== LEAK SUMMARY:
==895866==    definitely lost: 40 bytes in 2 blocks
==895866==    indirectly lost: 0 bytes in 0 blocks
==895866==      possibly lost: 0 bytes in 0 blocks
==895866==    still reachable: 0 bytes in 0 blocks
==895866==         suppressed: 0 bytes in 0 blocks
==895866== Rerun with --leak-check=full to see details of leaked memory
==895866==
==895866== For lists of detected and suppressed errors, rerun with: -s
==895866== ERROR SUMMARY: 29 errors from 4 contexts (suppressed: 0 from 0)

Here, we got the wanted result (blue cow and not a mushroom) because valgrind put out enough safety nets so our program did not corrupt itself.

This will print out when the program is doing something wrong, for instance at testprog.c line 11 it is writing over memory allocated at testprog.c line 7.

Showing leaked memory

Adding the flags --leak-check=full --show-reachable=yes will give additional information, like:


lacolhost:/tmp> valgrind --leak-check=full --show-reachable=yes ./testprog
...
==896857== HEAP SUMMARY:
==896857==     in use at exit: 40 bytes in 2 blocks
==896857==   total heap usage: 3 allocs, 1 frees, 1,064 bytes allocated
==896857==
==896857== 10 bytes in 1 blocks are definitely lost in loss record 1 of 2
==896857==    at 0x483877F: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==896857==    by 0x109156: main (testprog.c:7)
==896857==
==896857== 30 bytes in 1 blocks are definitely lost in loss record 2 of 2
==896857==    at 0x483877F: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==896857==    by 0x109164: main (testprog.c:8)

Listing allocated memory that is never free()'d.

Fixing the program

Valgrind has shown that a did not get enough memory allocated and that a and b were never freed. New attempt at the code, increasing the size for a and freeing memory:


 1.    #include <stdlib.h>
 2.    #include <string.h>
 3.    #include <stdio.h>
 4.    int main(void) {
 5.        char *a, *b;
 6.        a = malloc(30);
 7.        b = malloc(30);
 8.        strcpy(b, "I am not a mushroom.");
 9.        strcpy(a, "This is not a blue cow.");
10.        puts(a);
11.        puts(b);
12.        free(a);
13.        free(b);
14.        return 0;
15.    }

Compiling and running



lacolhost:/tmp> gcc -Wall -W -pedantic -g -o testprog testprog.c
lacolhost:/tmp> ./testprog
This is not a blue cow.
I am not a mushroom.
lacolhost:/tmp> valgrind ./testprog
==899044== Memcheck, a memory error detector
==899044== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==899044== Using Valgrind-3.16.1 and LibVEX; rerun with -h for copyright info
==899044== Command: ./testprog
==899044==
This is not a blue cow.
I am not a mushroom.
==899044==
==899044== HEAP SUMMARY:
==899044==     in use at exit: 0 bytes in 0 blocks
==899044==   total heap usage: 3 allocs, 3 frees, 1,084 bytes allocated
==899044==
==899044== All heap blocks were freed -- no leaks are possible
==899044==
==899044== For lists of detected and suppressed errors, rerun with: -s
==899044== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

No errors and no leaks. Great!

Types of errors

What will Valgrind find?

  • Allocated memory that is not freed ("definitely lost", "possibly lost", "still reachable")
  • Trying to write into memory that was recently freed ("Invalid read", "Invalid write" and a stack trace where it was freed)
  • Reading/writing outside an allocated array ("Invalid read", "Invalid write")
  • Reading uninitialised memory ("Use of uninitialised value", "Conditional jump or move depends on uninitialised value")
  • Files still open (memory not freed)
  • etc..

What will Valgrind not find?

  • Writing outside a static array (char a[10] and writing >10 bytes there)