Introduction

In this article I will discuss a way to build a memory leak detection program for C and C++ applications. This is not an attempt at writing a fully fledged leak detector but rather an introduction to one way (of many) ways of finding leaks.

The approach I've gone for is library injection and even if the C++ source code provided is for Linux (tested on Ubuntu 11.10) the method should work for any platform that allows library injection.

What is a leak

There are many ways in which an application can leak resources, and there are many different resources that can be leaked.
In this article I'll focus on and only look for memory leaks (not file handles or sockets or any other resource that might be allocated), but even when constraining the scope to just memory there are still many different kinds of leaks.
When talking about memory leaks in C++, most people think of scenarios like this (very simplified one);

Memory is allocated but is not deleted/unallocated before the reference to the allocated memory goes out of scope. This means of course that
the memory will be unavailable for use as well as for further allocations until the program terminates.

But memory leaks can appear in other more subtle ways as well, sometimes the memory is still referenced but just not used, such as when items are regularly added to a std::vector without ever being released. If such a std::vector is never cleared and still never again looked at by the application it can be considered leak, even though the memory is still referenced.

In this article, for simplicity, I'll only look at the first scenario; allocated memory that is not deallocated. In short, I'll show a way of tracking each call to malloc and free and record information so that they can be married up.

You might argue that you're using C++ and are therefore allocating and deallocating your memory using new and delete but in most cases the implementation of those will still call the C versions malloc and free.

Library Injection

Library Injection is when a user tells the OS to first look in LibraryA for system wide methods before looking in the "standard" libraries, where LibraryA is a library containing an overload of a system function. On some Linux and Unix distributions this can be achieved using the LD_PRELOAD environment variable to set the library that is to be injected.

To track memory allocations and deallocations a shared library containing overloads for malloc and free must be created and then injected so that whenever an allocation of memory is requested the custom version is hit first.

First step is to create a C++ file, leakfinder.cpp, that will contain the two methods;

Important!
It is important to test the injection in a terminal other than the one used to compile the source code or the injection might (will) interfere with the compiler and any other commands that use malloc or free.
Therefore, when working with this example make sure you have one terminal open for compiling and building (both the leak finder and the example applictions), and one where the LD_PRELOAD environment variable is set where you can run the test applications.

Another way of making sure the pre-load only is applicable for the c_example application is by running it like this; LD_PRELOAD=./leakfinder.so c_example.

Set the LD_PRELOAD in your terminal to point to the leakfinder.so and run the c_example test application using;

~/SomeFolder$export LD_PRELOAD=./leakfinder.so

~/SomeFolder$./c_example

When first run it turns out that no allocations or deallocations are detected, this is because of the linkage. As g++ (a C++ and not a C compiled) was used to compile the leakfinder.cpp file it has applied name mangling and that means he function names do not match the intended system functions malloc and free. To resolve this issue the functions has to be declared with C linkage:

When the leak finder with the correct linkage is preloaded and the example is run it will generate an output that looks something like this;

Loads and loads of allocations and deallocations intercepted!
This is because even though the test application only explicitly allocates a few things other parts also allocates and deallocates stuff, such as the printf and even the cout calls in the injected code.

The print out ends with a Segmentation fault which is of course because you cannot hope to replace an actual memory allocation with a print statement and expect stuff to still work " /> , but the method is proved to work.

Actual implementation

As it is clearly very important for the overloaded methods malloc and free to still do the work they are intended to do (as in allocating and deallocating memory on and from the heap) the injected code must look up pointers to the actual implementation and delegate the calls to these before tracking them.

Finding it

To find the actual implementation is done by calling dlsym (defined in dlfcn.h), this function returns function pointers to functions made available using dlopen. Using the constant RTLD_NEXT the previous entry can be retrieved, and that pointer can be stored in a static variable to cache it so the lookup doesn't have to take place every time;

In the code above the system version (or standard version) of malloc and free are looked up using dlsym(RTLD_NEXT, [name of function]) and then stored in sys_malloc and sys_free respectively.
After the system version of the functions are cached (if not already cached) they're called to perform the allocation or deallocation and after that the leak finder will intercept the calls to track the information required to compile a list of leaks.

Allocation Info

To allow the user to fix leaks, each leak needs to be associated with additional information to make it easy to track down the leak and correct it, in leakfinder I track four different things;

References

Stacktrace

Size

Thread

References

In order to marry up a deallocation to a previous allocation there need to be something that is unique and consistent across malloc and free calls, and one thing that can be used is the address of the allocated memory. It has to be unique as no two allocations are allowed to get the same piece of memory and it's consistent across calls as it's the return value of the allocation and the parameter to the deallocation.

Stacktrace

In order for a memory leak detector to be useful it needs to be able to tell the user where the leak was allocated.
One way to get a stacktrace in Linux is to use the backtrace and backtrace_symbols functions in execinfo.h.

Function backtrace gets all the return addresses for all the functions that are currently active on the stack in a particular thread, which is essentially the stacktrace.
In order to get a more readable version of the stacktrace the return addresses from backtrace can be fed into backtrace_symbols to get the names of the functions on the stack.

Size and Thread

Less important but still sometimes useful is to record the size of the allocation and which thread it was allocated on.

The size is passed in as a size_t argument to malloc so that is easy enough to grab and record.

Recording the current thread id might be slightly harder depending on the thread library used, in this example I've used the pthread library so I get the thread id as a pthread_t by calling pthread_self().

allocation_info

The information listed above, reference, stacktrace, size and thread are in leakfinder stored in a class called allocation_info.

Tracking leaks

Armed with a way of intercepting allocations (library injection) and a way to store allocation information (allocation_info) we're now ready to implement our basic memory leak detector.

Allocations

The first problem with tracking allocations inside the allocation method is that to track it memory needs to be allocated to store the allocation_info, and that obviously means that for every allocation another allocation is required. Since the additional allocation uses malloc as well the approach leads to a stack overflow.

The solution is to only track allocations that are originating from outside of leakfinder. By declaring a static boolean called isExternalSource that is set to false before the allocation is recorded and back to true when done it is possible to exclude allocation that arise from recording the source allocation. The overloaded malloc method then looks something like this;

This takes care of the exclusion of internal allocation but suffers from threading issues as the staticisExternalSource might be read/written by two threads at the same time causing undefined behaviour.

By guarding the inside of the if-statement with a lock (using pthread threads) the malloc method changes to this;

Now the malloc implementation is thread safe (or thread-safeish, it still suffers from some issues but for sake of simplicity I'm going to keep it this way for this article).
The rest of the implementation is the matter of grabbing the stacktrace and storing it along with the reference, size and thread id. The size is already passed in and the thread id has been grabbed using pthread_self, the remaining this to store is simply the reference which is the address of ptr which is returned by the actual implementation of malloc.

Deallocations

To marry up a deallocation to a previous allocation is simple. The free uses the same method of preventing it to be run for an internal free and thread safety and in addition to this it's just a matter of finding the allocation_info in the allocationsvector. The unique key is the reference or address;

Summing it up

After all the allocations and deallocations have been tracked and matched off the ones that were never deallocated must be reported on, so when is a suitable time to do that?
Since it's pretty much technically impossible to tell if a leak has happened before the program terminates leakfinder uses program exit to sum up the leaks. One might argue that the leak happens when the pointer referencing the memory goes out of scope of the pointer has not been deallocated but since it is possible to store the pointer value in just about any other data structure that can be hard to rely on.

C style destructor

There are different ways to hook into the termination of a program, which one to pick depends on platform and personal taste.
One approach is to use a pragma directive;

#pragma fini (some_exit_handler)

but for leakfinder I've gone for a C style destructor;

staticvoid compile_allocation() __attribute__((destructor));

Using this approach, the method compile_allocation is executed when the shared library is unloaded, this is typically at program exit.

Since all the not unallocated allocations are held in the vector allocations at program exit, the work of the compile_allocation method is just to iterate through the leaks and somehow output the leak information.
Where the best place to output the leak information to I am not entirely sure of. In certain scenarios a file would be convinient but for simplicity I've decided to let leakfinder just dump the summary to standard out.

To avoid extra work isExternalSource needs to be set to false at the beginning of compile_allocaion as printing the summary requires allocations to take place.

To print addresses and pointers in hex variables hex and dec from iomanip are used.

While that has indeed first printed the output of c_example followed by the summary of the four leaks, the stactrace isn't very easy to dechiper. That is because the program is lacking symbols but by compiling it again (in the first terminal) with option -rdynamic this can be corrected;

Now the method name is included and that makes it a whole lot easier to read, and while this works for C++ programs as well their stacktraces are still a bit messy because of name mangling, but still very much readable when compiled with -rdynamic, as seen below in the output from cpp_example (also included in the download);

Points of interest

Like I stated at the beginning of the article, this is not an attempt at a finished leak detection product but an explanation of how such a product can be
created. The observant reader will have realised that the implementation included with this article is lacking in many areas, it does not explicily cater for (for example) calloc and that using a std::vector to store the allocation_info objects results in a linear performance penalty on the lookups.

Regardless of this, I hope the article has provided a certain amount of insight into how leaks can be spotted in a non-intrusive way (in the sense that the application code does not need to be instrumented or otherwise augmented).

Share

About the Author

21 Feb 2014: Best VB.NET Article of January 2014 - Second Prize
18 Oct 2013: Best VB.NET article of September 2013
23 Jun 2012: Best C++ article of May 2012
20 Apr 2012: Best VB.NET article of March 2012
22 Feb 2010: Best overall article of January 2010
22 Feb 2010: Best C# article of January 2010

I did successfully compile and execute your code in Unbutu 10. The result is the same as your pictures provide.

That is because of one step that I missed, I forgot to Open new terminal to run "export LD_PRELOAD=./leakfinder.so" and "./c_example".

It's an awesome article, but It's quite hard for me to understand all statements in source code.

Another question is :

How do the Linux call function "void compile_allocation()" and How do the Linux link "leakfinder.so" to the main program "c_example" ?

I am new to C/C++ memory management. Some knowledges are new to me like : mutex, backtrace, backtrace_symbols.
So, if it is possible, could you please introduce some tutorial/ basic documentations related to C/C++ memory management, memory leak before deeply digging into your source code.

The method compile_allocation is registered as the destructor by appending the attribute (__attribute__((destructor)));

staticvoid compile_allocation() __attribute__((destructor));

It's a gccspecific construct[^], other compilers might support the pragma approach I mentioned in the article.

Essentially the compiler puts these methods into a special place in the compiled binary, and the dynamic library loader knows to call them if it finds one. So it runs the method marked as the constructor at library load and calls the destructor on library unload.

At this point it's unlikely that I'll do an article on C++ memory management, partly because I'm busy with other things but also because it's a topic that's already thoroughly covered.

Thanks for reading my article, I'm glad you found it awesome and I appreciate the feedback.

Can't really do that I'm afraid.
It explicitly excludes any allocations made by the leakfinder itself from its tracked list of allocations.
You would have to check it with another memory leak detection tool.

Instead of using backtrace_symbols you could feed the stack addresses to the addr2line program to obtain the unmangled names. Another options is to use the abi::__cxa_demangle function defined in <cxxabi.h>, but you have to extract the names from the stack strings.