(An Introduction to Dr. Memory and Valgrind)
Background
There are several tools available to help you find memory bugs in your programs. This document will discuss two of them: Dr. Memory and Valgrind.Dr. Memory operates on unmodified application binaries running on Windows, Linux, Mac, or Android on commodity IA-32, AMD64, and ARM hardware.
Pros:
Cons:
Valgrind runs on several popular platforms, such as x86, AMD64 and PPC32. Read more about the benefits of using Valgrind here.
Pros:
Cons:
Dr. Memory
On Windows, you can use either MinGW or Microsoft's compiler to build programs that Dr. Memory can check. In order for Dr. Memory to work correctly, you may need to compile your program with special command line options. Refer to Preparing Your Application on their website for detailed information.We're going to use this file: mem.bugs.cpp (HTML), that has several memory-related bugs:Although Dr. Memory will work with Windows, Mac, and Linux, I'm only going to show how to use it under Windows because Valgrind is a better choice if you're running Mac or Linux. Also, the version of Dr. Memory used for these examples is 1.11.0 -- build 2. If you're running Windows 10 (or later), you should use the latest version as Windows is a moving target.
First, we need to compile mem.bugs.cpp with the appropriate options:
The -ggdb option adds additional debugging information (e.g. filenames and line numbers) to the executable so the debugger can tell you where the problems are located.g++ -ggdb mem.bugs.cpp -o mingw64.exe
Running the program under Dr. Memory (test #1, memory leak):
and the output (I've highlighted the important lines)drmemory -batch -no_summary -- mingw64.exe 1
The -no_summary option tells Dr. Memory to only output information when there are problems. Without the option, Dr. Memory will display several lines of output, even if there were no problems. Like most good command line tools, silence (no output) indicates success.~~Dr.M~~ ~~Dr.M~~ Error #1: LEAK 123 direct bytes 0x0000000003220a50-0x0000000003220acb + 0 indirect bytes ~~Dr.M~~ # 0 replace_operator_new_array [d:\drmemory_package\common\alloc_replace.c:2928] ~~Dr.M~~ # 1 libwinpthread-1.dll!? +0x0 (0x0000000064944d85) ~~Dr.M~~ # 2 msvcrt.dll!_threadid +0x259 (0x00007ff90456a5da <msvcrt.dll+0x3a5da>) ~~Dr.M~~ # 3 msvcrt.dll!errno +0x8 (0x00007ff904537cd9 <msvcrt.dll+0x7cd9>) ~~Dr.M~~ # 4 msvcrt.dll!strtoui64_l +0x83 (0x00007ff904534424 <msvcrt.dll+0x4424>) ~~Dr.M~~ # 5 msvcrt.dll!onexit +0xf5 (0x00007ff90455a686 <msvcrt.dll+0x2a686>) ~~Dr.M~~ # 6 msvcrt.dll!strtol +0x63 (0x00007ff904534714 <msvcrt.dll+0x4714>) ~~Dr.M~~ # 7 test1 [c:\Users\mmead\drmem/mem.bugs.cpp:7] ~~Dr.M~~ # 8 main [c:\Users\mmead\drmem/mem.bugs.cpp:72]
You can see by the output that Dr. Memory has detected the memory leak of 123 bytes at line #7 in the source file:
The output shows that on line #72 (in the function main), there is a call to a function (test1). The memory was allocated on line #7 in function test1. All of the lines above test1 can be ignored as they are functions in the runtime libraries.1 #include <iostream> 2 #include <cstdlib> 3 #include <cstring> 4 5 void test1() 6 { 7 new char[123]; 8 } 9
If you were to compile the program using the 32-bit MinGW compiler, you would see this output similar to this:
Again, I've highlighted the important lines. You'll also see that there is a POSSIBLE LEAK of 28 bytes that is shown. Since none of the lines regarding that leak contain references to the code in mem.bugs.cpp, you can ignore them. (You may or may not see the possible leak.)~~Dr.M~~ ~~Dr.M~~ Error #1: POSSIBLE LEAK 28 direct bytes 0x02db00f8-0x02db0114 + 0 indirect bytes ~~Dr.M~~ # 0 replace_malloc [d:\drmemory_package\common\alloc_replace.c:2576] ~~Dr.M~~ # 1 msvcrt.dll!realloc +0x40a (0x762c770b <msvcrt.dll+0x4770b>) ~~Dr.M~~ # 2 msvcrt.dll!unlock +0x3c7 (0x762e70c8 <msvcrt.dll+0x670c8>) ~~Dr.M~~ # 3 msvcrt.dll!_getmainargs +0x18 (0x762b57c9 <msvcrt.dll+0x357c9>) ~~Dr.M~~ # 4 pre_cpp_init ~~Dr.M~~ # 5 __tmainCRTStartup ~~Dr.M~~ # 6 KERNEL32.dll!BaseThreadInitThunk +0x23 (0x75df8484 <KERNEL32.dll+0x18484>) ~~Dr.M~~ ~~Dr.M~~ Error #2: LEAK 123 direct bytes 0x02db0310-0x02db038b + 0 indirect bytes ~~Dr.M~~ # 0 replace_operator_new_array [d:\drmemory_package\common\alloc_replace.c:2928] ~~Dr.M~~ # 1 test1 [c:\Users\mmead\drmem/mem.bugs.cpp:7] ~~Dr.M~~ # 2 main [c:\Users\mmead\drmem/mem.bugs.cpp:72]
Note: If Dr. Memory fails to detect errors with your 64-bit MinGW programs, you should rebuild
them as 32-bit MinGW programs, instead. There are known problems with some Windows 10 computers and
Dr. Memory running 64-bit programs. If that still doesn't work on your computer, then
you should use Microsoft's compiler, which is detailed next.
If you are getting lots of false-positives from libraries (not your code), you can suppress them
by telling Dr. Memory to ignore them. Here's a
short video that shows you how to do that.
First, we need to compile mem.bugs.cpp with the appropriate options:
The command line options are explained here.cl /Zi /MT /EHsc /Oy- /Ob0 /Fems64.exe mem.bugs.cpp
Running the program under Dr. Memory (test #1, memory leak):
and the output (I've highlighted the important lines)drmemory -batch -no_summary -- ms64.exe 1
You can see by the output that Dr. Memory has detected the memory leak of 123 bytes at line #7 in the source file, just as it did with the MinGW executable.~~Dr.M~~ ~~Dr.M~~ Error #1: LEAK 123 direct bytes 0x0000026f8a203e70-0x0000026f8a203eeb + 0 indirect bytes ~~Dr.M~~ # 0 replace_operator_new_array [d:\drmemory_package\common\alloc_replace.c:2928] ~~Dr.M~~ # 1 std::use_facet<> [c:\program files (x86)\microsoft visual studio\2017\enterprise\vc\tools\msvc\14.15.26726\include\xlocale:546] ~~Dr.M~~ # 2 std::locale::~locale [c:\program files (x86)\microsoft visual studio\2017\enterprise\vc\tools\msvc\14.15.26726\include\xlocale:410] ~~Dr.M~~ # 3 std::ctype<>::widen [c:\program files (x86)\microsoft visual studio\2017\enterprise\vc\tools\msvc\14.15.26726\include\xlocale:2578] ~~Dr.M~~ # 4 std::basic_ios<>::widen [c:\program files (x86)\microsoft visual studio\2017\enterprise\vc\tools\msvc\14.15.26726\include\ios:125] ~~Dr.M~~ # 5 std::ios_base::_Init [f:\dd\vctools\crt\crtw32\stdhpp\xiosbase:558] ~~Dr.M~~ # 6 __crt_strtox::parse_integer<> [minkernel\crts\ucrt\inc\corecrt_internal_strtox.h:202] ~~Dr.M~~ # 7 __crt_seh_guarded_call<>::operator()<> [minkernel\crts\ucrt\devdiv\vcruntime\inc\internal_shared.h:208] ~~Dr.M~~ # 8 test1 [c:\users\mmead\drmem\mem.bugs.cpp:7] ~~Dr.M~~ # 9 std::basic_ostream<>::basic_ostream<> [f:\dd\vctools\crt\crtw32\stdhpp\ostream:54] ~~Dr.M~~ #10 atexit [f:\dd\vctools\crt\vcstartup\src\utility\utility.cpp:275] ~~Dr.M~~ #11 __scrt_common_main_seh [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl:288]
If you were to compile the program using Microsoft's 32-bit compiler, you would see this output similar to this:
~~Dr.M~~ ~~Dr.M~~ Error #1: LEAK 123 direct bytes 0x00cbcce8-0x00cbcd63 + 0 indirect bytes ~~Dr.M~~ # 0 replace_operator_new_array [d:\drmemory_package\common\alloc_replace.c:2928] ~~Dr.M~~ # 1 test1 [c:\users\mmead\drmem\mem.bugs.cpp:7] ~~Dr.M~~ # 2 main [c:\users\mmead\drmem\mem.bugs.cpp:72]
Quick Links:
Valgrind
Valgrind is only available for Linux and Mac, and I will be showing it on Linux. Valgrind is also a suite of tools that can look for problems other than memory. Read the documentation to see all of the things that it can do. The version of Valgrind used for these examples is 3.10.1, but any newer version should work the same.
The first thing is to compile. The recommended options are -g (include debugging information) and -O0 (disable optimizations, that's an uppercase letter 'O' followed by a zero):If you are running a recent version of Windows, you can enable the Windows Subsystem for Linux (WSL) which will give you a working Linux system. Once set up, you can use almost any of the plethora of developer tools for Linux. I wrote a brief tutorial on how to set up WSL here.
Valgrind works the same way in both 32-bit and 64-bit programs, so I'm just going to show the 64-bit output.g++ -g -O0 -o gnu64 mem.bugs.cpp
Running the program (test #1, memory leak) under Valgrind:
produces this output:valgrind ./gnu64 1
Note: The numbers in the left-hand column are the process ID (PID) of the running program and can be ignored.==25831== Memcheck, a memory error detector ==25831== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al. ==25831== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info ==25831== Command: ./gnu64 1 ==25831== ==25831== ==25831== HEAP SUMMARY: ==25831== in use at exit: 72,827 bytes in 2 blocks ==25831== total heap usage: 2 allocs, 0 frees, 72,827 bytes allocated ==25831== ==25831== LEAK SUMMARY: ==25831== definitely lost: 123 bytes in 1 blocks ==25831== indirectly lost: 0 bytes in 0 blocks ==25831== possibly lost: 0 bytes in 0 blocks ==25831== still reachable: 72,704 bytes in 1 blocks ==25831== suppressed: 0 bytes in 0 blocks ==25831== Rerun with --leak-check=full to see details of leaked memory ==25831== ==25831== For counts of detected and suppressed errors, rerun with: -v ==25831== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
I highlighted the important part which shows the 123 bytes of memory that was not deallocated. There's also a false-positive leak of 72,827 bytes. I'll show how to suppress that later. There is a lot of "noise" being output, so we're going to suppress a lot of that. Do that with the --quiet option:
Unfortunately, this has suppressed all of the output so we need to enable some of it:valgrind --quiet ./gnu64 1
Now the output looks like this:valgrind --quiet --leak-check=full ./gnu64 1
It shows exactly what we wanted, including the filename and line numbers where to look.==26112== 123 bytes in 1 blocks are definitely lost in loss record 1 of 2 ==26112== at 0x4C2B800: operator new[](unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==26112== by 0x400AA4: test1() (mem.bugs.cpp:7) ==26112== by 0x400C88: main (mem.bugs.cpp:72) ==26112== ==26112== 72,704 bytes in 1 blocks are still reachable in loss record 2 of 2 ==26112== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==26112== by 0x4E9FC25: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25) ==26112== by 0x40101D9: call_init.part.0 (dl-init.c:78) ==26112== by 0x40102C2: call_init (dl-init.c:36) ==26112== by 0x40102C2: _dl_init (dl-init.c:126) ==26112== by 0x4001299: ??? (in /lib/x86_64-linux-gnu/ld-2.19.so) ==26112== by 0x1: ??? ==26112== by 0xFFEFFFD46: ??? ==26112== by 0xFFEFFFD4E: ??? ==26112==
Ok, let's suppress the false-positive since it's going to show up in every test. I'm just going to give a quick overview of how to do that. For more details, read Suppressing errors and Writing suppression files.
First, you have to generate a suppression file:
Now, when you run it you'll see this:valgrind --quiet --leak-check=full--gen-suppressions=yes ./gnu64 1
Valgrind has paused and the last line is prompting you to answer the question Print suppression ? Since the memory leak being shown is an actual memory leak, you DO NOT want to suppress that. If you do, Valgrind will not report it as a leak anymore, which is not what we want. Press n to skip the suppression.==26594== 123 bytes in 1 blocks are definitely lost in loss record 1 of 2 ==26594== at 0x4C2B800: operator new[](unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==26594== by 0x400AA4: test1() (mem.bugs.cpp:7) ==26594== by 0x400C88: main (mem.bugs.cpp:72) ==26594== ==26594== ==26594== ---- Print suppression ? --- [Return/N/n/Y/y/C/c] ----
Now, Valgrind displays the false-postive and asks again if you want to suppress this:
You'll notice that none of the lines reference our source code (mem.bugs.cpp), which is a good sign that the leak is not in our code.==26894== 72,704 bytes in 1 blocks are still reachable in loss record 2 of 2 ==26894== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==26894== by 0x4E9FC25: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25) ==26894== by 0x40101D9: call_init.part.0 (dl-init.c:78) ==26894== by 0x40102C2: call_init (dl-init.c:36) ==26894== by 0x40102C2: _dl_init (dl-init.c:126) ==26894== by 0x4001299: ??? (in /lib/x86_64-linux-gnu/ld-2.19.so) ==26894== by 0x1: ??? ==26894== by 0xFFEFFFD46: ??? ==26894== by 0xFFEFFFD4E: ??? ==26894== ==26894== ==26894== ---- Print suppression ? --- [Return/N/n/Y/y/C/c] ----
This time, type y to generate the suppression output. This is what Valgrind displays on my computer:
You need to copy the text (including the curly braces) and paste it into a text file that will be used as the suppression file. You can name the text file anything you want. I usually name my suppression files false.supp, but you can name it anything.{ <insert_a_suppression_name_here> Memcheck:Leak match-leak-kinds: reachable fun:malloc obj:/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25 fun:call_init.part.0 fun:call_init fun:_dl_init obj:/lib/x86_64-linux-gnu/ld-2.19.so obj:* obj:* obj:* }
Note: Do NOT copy the text from this web page. Every system produces a different suppression file. If you just copy the one from this web page, it won't work. You must generate the file on your particular computer.
Now, when you run Valgrind, you will specify this suppression file on the command line so that Valgrind will ignore (suppress) the false-positive:
This assumes that you put the suppression file in the same directory that you are running in. Now, the output looks like this:valgrind --quiet --leak-check=full --suppressions=false.supp ./gnu64 1
which contains just the memory leak that we put in the code.==27200== 123 bytes in 1 blocks are definitely lost in loss record 1 of 2 ==27200== at 0x4C2B800: operator new[](unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==27200== by 0x400AA4: test1() (mem.bugs.cpp:7) ==27200== by 0x400C88: main (mem.bugs.cpp:72) ==27200==
Now, run Valgrind like this:Pro Tip: Since this false-positive will show up in every program you run, you'll want to put this file in some other location and refer to that location. You don't want to have dozens (or hundreds) of these files around because, as you'll shortly see, when the suppression file needs to be updated, you don't want to have to update 100 of them. You could put the file in your bin directory in your home directory (for example: /home/user/bin/false.supp). Yeah, it's not a binary file (executable), but the bin directory in my home directory is a convenient location and exists on all systems I use.
Now, you can use this suppression file for all of your programs, regardless of where they are on your computer.valgrind --quiet --leak-check=full --suppressions=/home/user/bin/false.supp ./gnu64 1
This is the only false-positive that Valgrind is showing and it's coming from the standard C++ library libstdc++.so. Depending on what kinds of other libraries/code you include in your program, you may get more, sometimes MANY more, false-positives. You would simply run Valgrind with theNote: The suppression files are specific to the versions of the operating system, compiler, libraries, and Valgrind. You may find that, if you update any of these things that you will have to re-generate the suppression file. You can see this in the suppression file as it references version 6.0.25 of libstdc++.so. If this library gets updated, the suppression file will no longer work.
Finally, you'll notice that the first line of the suppression text was this:
This is so you can give a name to the suppression block. It's unused by Valgrind, but it allows you to document what the block is suppressing. I usually change it to this:{ <insert_a_suppression_name_here> Memcheck:Leak . . . }
It doesn't matter what you call it but it must be present. Valgrind skips the first non-blank line of each block, so if it's not there, the suppression won't work.{ [false positive from libstdc++] Memcheck:Leak . . . }
I've just scratched the surface of what Valgrind can do. For more information, consult the Valgrind User Manual.
You can see all of the output from Valgrind using GNU's 64-bit compiler with the test file mem.bugs.cpp here.
Tracking File Descriptors with Valgrind
Valgrind can do a lot more than just detect memory bugs. I'm going to show one more thing that Valgrind can detect. Many beginning programmers fail to close files when they are done with them. This can lead to problems.
Tracking file descriptors is a fancy way of saying that you can detect files that were not closed (e.g. via fclose). File handles (descriptors) are a finite resource just like memory, and if you fail to close them, you can run out of them (just like memory). Valgrind has an option that enables this check. Here's a starting place (fd.c):
This is a complete program that doesn't do anything. Or does it? Build it:int main() { return 0; }
and run Valgrind on it with the appropriate option:gcc fd.c -g -o fd
and this is the output:valgrind -q --track-fds=yes ./fd
Valgrind is saying that we have 3 leaks. They are not memory leaks, they are resource leaks, file descriptors, to be exact. But the code didn't open any files! Think back to your beginning C/C++ programming course and you'll remember that when your program runs, three "files" are opened for you:==31318== FILE DESCRIPTORS: 3 open at exit. ==31318== Open file descriptor 2: /dev/pts/2 ==31318== <inherited from parent> ==31318== ==31318== Open file descriptor 1: /dev/pts/2 ==31318== <inherited from parent> ==31318== ==31318== Open file descriptor 0: /dev/pts/2 ==31318== <inherited from parent>
You don't have to open these files, as they are already opened for you. You also don't have to close them, as the system will close them when the program ends. They show up as being still open because they aren't closed until long after main finishes. That means these are false-positives that we can ignore. (Also, notice that they were inherited from the parent.)These three files are also referenced as file descriptors 0, 1, and 2. (Sometimes refered to as file handles.) The operating systems keeps track of open files using integers. File descriptors 0, 1, and 2 are generally reserved for those three files. When a file is opened, the lowest unused integer (i.e. 3) is used. That's why when you open the first file in your program, it will always be file descriptor 3. If you open a second file, it will be file descriptor 4, and so on.
OK, let's open a file and "forget" to close it:
Now, running it under Valgrind produces this output:#include <stdio.h> /* FILE, fopen */ void test1() { /* Open a file, but don't close it. */ FILE *fp = fopen("foo.txt", "wt"); } int main() { test1(); return 0; }
You can see that there is a fourth file descriptor shown (file descriptor 3) which is the file that our program opened for writing. It also shows you the line number that opened the file. This makes it pretty easy to locate bugs (file leaks) in your program.==4960== FILE DESCRIPTORS: 4 open at exit. ==4960== Open file descriptor 3: foo.txt ==4960== at 0x4F26170: __open_nocancel (syscall-template.S:81) ==4960== by 0x4EB0ED7: _IO_file_open (fileops.c:228) ==4960== by 0x4EB0ED7: _IO_file_fopen@@GLIBC_2.2.5 (fileops.c:333) ==4960== by 0x4EA53D3: __fopen_internal (iofopen.c:90) ==4960== by 0x40052D: test1 (fd.c:5) ==4960== by 0x400542: main (fd.c:10) ==4960== ==4960== Open file descriptor 2: /dev/pts/2 ==4960== <inherited from parent> ==4960== ==4960== Open file descriptor 1: /dev/pts/2 ==4960== <inherited from parent> ==4960== ==4960== Open file descriptor 0: /dev/pts/2 ==4960== <inherited from parent>
One last thing to mention. You'll see that the first 3 file descriptors don't reference any filenames (they use pseudo-terminals). If you were to redirect stdin, stdout, and stderr (assume in.txt exists):
you would see something like this output from Valgrind:valgrind -q --track-fds=yes ./fd < in.txt > out.txt 2> err.txt
Valgrind shows the actual names of the files that are used with the redirection. Pretty cool!==10412== FILE DESCRIPTORS: 3 open at exit. ==10412== Open file descriptor 2: /home/user/data/Courses/notes/code/drmem/err.txt ==10412== <inherited from parent> ==10412== ==10412== Open file descriptor 1: /home/user/data/Courses/notes/code/drmem/out.txt ==10412== <inherited from parent> ==10412== ==10412== Open file descriptor 0: /home/user/data/Courses/notes/code/drmem/in.txt ==10412== <inherited from parent>
Summary:
As was mentioned at the top of this webpage, Valgrind can do much more than just check for memory misuse. It includes an entire suite of tools: