Finding Memory Leaks Using mtrace

When testing code on small samples, small problems such as memory leaks are often undetectable. However, once the program is run on a larger scale, existing memory leaks become larger problems; they often will cause a program to crash or to swap extensively (slowing performance drastically), and also affect any other programs running on the same system by reducing available memory.

To help avoid, or more often fix, such memory leaks, there are many tools available. One within the GNU platform is called mtrace.

1. Call mtrace() When Your Program Starts

mtrace works by adding hooks into calls which allocate and deallocate memory, logging these calls, and reporting on any mis-matches between the recorded allocations and deallocations. In order to begin recording information, these hooks have to be turned on. Calling the mtrace() function does this. In order to work properly, you must do this at THE VERY FIRST ACTION of the program. As well, since mtrace() is declared in mcheck.h, you'll have to include that as well.

For example, let's look at a simple program:

    #include <stdio.h>
    #include <stdlib.h>
    #include <mcheck.h>
    int main() {
        char *string;
        mtrace();
        string = malloc(100 * sizeof(char));
        return 0;
        }

Notice declarations are before the call to mtrace(), but any executed code comes after. The best place to call mtrace() is the very first executing line of code in main().

2. Compile Program with Debugging Options

Although not strictly necessary, in order to get best use of mtrace, you should compile with debugging options set. This consists of adding '-g' to your compiling and linking commands.

So, if you were to compile a program composed of a single file, let's say hello.c, you would compile it with a command line resembling:

    gcc -g -o mtrace_test mtrace_test.c

However, if you were to compile a program consisting of multiple files, or where you have to do separate compilation and linking, you would have to supply -g on all compilation and linking commands. For example, given the files mtrace_test.c and file2.c, the commands would resemble the following:

    gcc -g -c mtrace_test.c
    gcc -g -c file2.c
    gcc -g -o mtrace_test mtrace_test.o file2.o

Often in larger software, you would modify the flags variables (CFLAGS, CXXFLAGS, FFLAGS, LD_OPTS and/or LDFLAGS) in your Makefile to add these, or you would set and export these as environment variables before running configure. An example of this last approach:

    export CFLAGS='-g'
    export LDFLAGS='-g'
    ./configure
    make 
3. Set MALLOC_TRACE

mtrace() saves its data to whatever file is named in the environment variable MALLOC_TRACE. So, in order to have the data saved, you must set this before running your program.

For bash, you could set the variable to 'mtrace.out' by running the following:

    export MALLOC_TRACE="mtrace.out"

For C shell, it would be:

    setenv MALLOC_TRACE mtrace.out
4. Run The Program Once

Now you just need to run your program as usual. While it runs, it will save all the necessary data to the file specified in the MALLOC_TRACE environment variable.

I recommend running the program in such a way as to cover as much of the program's functionality as possible. If there are any problems, you want to try to make sure you trigger them.

5. View The Data

You should now have a file containing the trace data. In the case of the examples above, it would be mtrace.out. To view the data in a readable format, run the mtrace command, giving the program name and trace file name as arguments. Using the above examples, with our program name being mtrace_test and trace file name being mtrace.out, our command would be:

    mtrace mtrace_test mtrace.out

Assuming the C code at the beginning was the code in mtrace_test.c, the following output would be produced:

    Memory not freed:
    -----------------
               Address     Size     Caller
    0x0000000000501460     0x64  at /array/home/dcurrie/test/mtrace/mtrace_test.c:11

The address column is mostly meaningless. Feel free to ignore that - it can vary from run to run. More importantly, the entries in the "Memory not freed" report contains the file name and line number the allocation call has been traced to, and also contains the number of bytes allocated (0x64 is hexadecimal for 100)

So, the information tells us that data allocated on line 11 of mtrace_test.c is never deallocated, and so we should take corrective action to deallocate that particular memory.

Go back