Valgrind is a powerful programming tool for memory profiling, memory leak detection. Also it can be used for thread error analysis and profile programs in detail. Valgrind instrumentation framework permits to build new tools. Valgrind is available for many Unix based operating systems. Here is an example how to detect memory leak in simple c++ program in Ubuntu OS.
Installing Valgrind:
# sudo apt-get install valgrind |
Creating simple program with memory leak:
#include <stdio.h> int main() { printf("Allocate %ld bytes\n", 100*sizeof(int)); int * pint = new int[100]; delete pint; return 0; } |
Compiling in release mode:
# g++ -o leaktest leaktest.cpp |
Running:
# ./leaktest Allocate 400 bytes |
Profiling with Valgrind:
# valgrind --log-file=./leaktesttrace.log --tool=memcheck --leak-check=full ./leaktest |
The the results are saved in leaktesttrace.log, which looks like this:
# cat leaktesttrace.log ==10248== Memcheck, a memory error detector ==10248== Copyright (C) 2002-2017, and GNU GPL’d, by Julian Seward et al. ==10248== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info ==10248== Command: ./leaktest ==10248== Parent PID: 6917 ==10248== ==10248== Mismatched free() / delete / delete [] ==10248== at 0x4C3123B: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==10248== by 0x108766: main (in /Alex/valgindtest/leaktest) ==10248== Address 0x5b7e0c0 is 0 bytes inside a block of size 400 alloc’d ==10248== at 0x4C3089F: operator new[](unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==10248== by 0x108751: main (in /Alex/valgindtest/leaktest) ==10248== ==10248== ==10248== HEAP SUMMARY: ==10248== in use at exit: 0 bytes in 0 blocks ==10248== total heap usage: 3 allocs, 3 frees, 74,128 bytes allocated ==10248== ==10248== All heap blocks were freed — no leaks are possible ==10248== ==10248== For counts of detected and suppressed errors, rerun with: -v ==10248== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0) |
The log file shows that something wrong with delete operation – line: “Mismatched free() / delete / delete []“.
and the total abstract is “ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)”
Let us debug the code, because the source was compiled in release mode we can do it in disassemble presentation:
# gdb leaktest GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git Copyright (C) 2018 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type “show copying” and “show warranty” for details. This GDB was configured as “x86_64-linux-gnu”. Type “show configuration” for configuration details. For bug reporting instructions, please see: Find the GDB manual and other documentation resources online at: For help, type “help”. Type “apropos word” to search for commands related to “word”… Reading symbols from leaktest…(no debugging symbols found)…done. (gdb) run Starting program: /Alex/valgindtest/leaktest Allocate 400 bytes [Inferior 1 (process 11119) exited normally] (gdb) disassemble main Dump of assembler code for function main: 0x000055555555472a <+0>: push %rbp 0x000055555555472b <+1>: mov %rsp,%rbp 0x000055555555472e <+4>: sub $0x10,%rsp 0x0000555555554732 <+8>: mov $0x190,%esi 0x0000555555554737 <+13>: lea 0xb6(%rip),%rdi # 0x5555555547f4 0x000055555555473e <+20>: mov $0x0,%eax 0x0000555555554743 <+25>: callq 0x5555555545f0 <printf@plt> 0x0000555555554748 <+30>: mov $0x190,%edi 0x000055555555474d <+35>: callq 0x5555555545e0 <_Znam@plt> 0x0000555555554752 <+40>: mov %rax,-0x8(%rbp) 0x0000555555554756 <+44>: mov -0x8(%rbp),%rax 0x000055555555475a <+48>: mov $0x4,%esi 0x000055555555475f <+53>: mov %rax,%rdi 0x0000555555554762 <+56>: callq 0x555555554600 <_ZdlPvm@plt> 0x0000555555554767 <+61>: mov $0x0,%eax 0x000055555555476c <+66>: leaveq 0x000055555555476d <+67>: retq End of assembler dump. |
I suspect that
0x0000000000000748 <+30>: mov $0x190,%edi 0x000000000000074d <+35>: callq 0x5e0 <_Znam@plt> |
is assembler presentation of this line of c++ code “int * pint = new int[100];“, because 0x190 is 100*sizeof(int) and it is what we see that start leaktest program: Allocate 400 bytes.
To confirm let us set break point at address of _Znam@plt and run
(gdb) break *0x5555555545e0 Breakpoint 1 at 0x5555555545e0 (gdb) run Starting program: /Alex/valgindtest/leaktest Allocate 400 bytes Breakpoint 1, 0x00005555555545e0 in operator new[](unsigned long)@plt () |
the gdb shows that we stopped at new operator so our assumption was correct.
New set break point at ZdlPvm@plt because i suspect that it is delete operator and continue to execute:
(gdb) break *0x555555554600 Breakpoint 2 at 0x555555554600 (gdb) c Continuing. Breakpoint 2, 0x0000555555554600 in operator delete(void*, unsigned long)@plt () |
Again correct expectation that it is delete operation.
However
0x000055555555475a <+48>: mov $0x4,%esi 0x000055555555475f <+53>: mov %rax,%rdi 0x0000555555554762 <+56>: callq 0x555555554600 <_ZdlPvm@plt> |
It tries to allocate only 4 byte (1 integer) – mov $0x4,%esi, instead of the hole allocated integer array with 100 (400 bytes).
Correcting the c++ code changing line:
delete pint; |
to
delete[] pint; |
and recompiling and profiling with Valgrind. The new Valgrind log file does not show any memory allocation errors:
# cat leaktesttrace.log ==11815== Memcheck, a memory error detector ==11815== Copyright (C) 2002-2017, and GNU GPL’d, by Julian Seward et al. ==11815== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info ==11815== Command: ./leaktest ==11815== Parent PID: 6917 ==11815== ==11815== ==11815== HEAP SUMMARY: ==11815== in use at exit: 0 bytes in 0 blocks ==11815== total heap usage: 3 allocs, 3 frees, 74,128 bytes allocated ==11815== ==11815== All heap blocks were freed — no leaks are possible ==11815== ==11815== For counts of detected and suppressed errors, rerun with: -v ==11815== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) |
New examine disassembler of the corrected code:
Dump of assembler code for function main: 0x000055555555470a <+0>: push %rbp 0x000055555555470b <+1>: mov %rsp,%rbp 0x000055555555470e <+4>: sub $0x10,%rsp 0x0000555555554712 <+8>: mov $0x190,%esi 0x0000555555554717 <+13>: lea 0xb6(%rip),%rdi # 0x5555555547d4 0x000055555555471e <+20>: mov $0x0,%eax 0x0000555555554723 <+25>: callq 0x5555555545d0 <printf@plt> 0x0000555555554728 <+30>: mov $0x190,%edi 0x000055555555472d <+35>: callq 0x5555555545c0 <_Znam@plt> 0x0000555555554732 <+40>: mov %rax,-0x8(%rbp) 0x0000555555554736 <+44>: cmpq $0x0,-0x8(%rbp) 0x000055555555473b <+49>: je 0x555555554749 <main+63> 0x000055555555473d <+51>: mov -0x8(%rbp),%rax 0x0000555555554741 <+55>: mov %rax,%rdi 0x0000555555554744 <+58>: callq 0x5555555545e0 <_ZdaPv@plt> 0x0000555555554749 <+63>: mov $0x0,%eax 0x000055555555474e <+68>: leaveq 0x000055555555474f <+69>: retq |
The _ZdlPvm@plt function was replaces by _ZdaPv@plt, I suspect that previous dl meant delete, but current da means delete array.
Let us set break point at _ZdaPv@plt address and run:
(gdb) break *0x5555555545e0 Breakpoint 1 at 0x5555555545e0 (gdb) run Starting program: /Alex/valgindtest/leaktest Allocate 400 bytes Breakpoint 1, 0x00005555555545e0 in operator delete[](void*)@plt () |
Currently we stopped at delete[] instead of delete as we had previously.