TraceAlloc

The TraceAlloc library is a developer tool that allows the user to retrieve a full dump of allocated memory blocks, with full call trace for each block.
On OSX workstation there are tools available (leaks, ObjectAlloc, ...) that helps in hunting down memory leaks, while at the moment (at least when using the non-Apple toolchain) the developer is pretty much left alone when it comes to this task.
Since I spent quite some time looking for a memory leak, I decided to write a simple library to help me spend less time in the future to accomplish the same task.
The TraceAlloc library replaces the default allocator (more exactly, stack over the default allocator), and register/links all the blocks that are allocated, together with the full call trace that lead to such memory allocation.
From a developer point of view, TraceAlloc integration is very simple. First thing to do is to fetch the iPhoneTools package.


Using the pre-built Library

The easiest way to use the TraceAlloc library, is to use the pre-built library that comes with the iPhoneTools package. From inside the TraceAlloc directory, simple issue the following comand:

$ sudo make qinstall

The commands above will install the pre-built TraceAlloc library and include file into the /usr/local/arm-apple-darwin directory.
If your toolchain default install path is different than the above, replace the last command with:

$ sudo make PREFIX=$TCHAINPATH qinstall

Where $TCHAINPATH is the path of your toolchain install directory. You will also need to copy the $lib/libTraceAlloc.dylib library into the /usr/lib directory of your iPhone file system.


Building and Installing from Source

The toolchain GCC at the time of this writing, does not support GCC's __builtin_* macros correctly. In order to have a proper call trace available inside the block dumps, you need to use a GCC version where the __builtin_* macros work.
The GCC supplied with the official Apple SDK at the time of this writing, have to __builtin_* macros properly working, and this is what I used to create the pre-built library supplied inside the iPhoneTools package.
Assuming you $CC and $LD environment variables point to the iPhone toolchain (whatever you use) GCC binaries:

$ cd TraceAlloc
$ make
$ sudo make install

The commands above will build and install the TraceAlloc library and include file into the /usr/local/arm-apple-darwin directory.
If your toolchain default install path is different than the above, replace the last command with:

$ sudo make PREFIX=$TCHAINPATH install

Where $TCHAINPATH is the path of your toolchain install directory. You will also need to copy the $TCHAINPATH/lib/libTraceAlloc.dylib library into the /usr/lib directory of your iPhone file system.


Using the TraceAlloc Library

In order to use the TraceAlloc library in your code, you first need to add a -lTraceAlloc to the list of your linked libraries in your makefiles (or add libTraceAlloc.dylib to the list of your libraries inside your Xcode project files).
Then, you need to include the TraceAlloc include file in your sources:

#import <TraceAlloc.h>

Then, at the beginning of your main() function, you need to add the call to the TraceAlloc function that installs the traceing allocator:

int main(int ac, char **av)
{
    TATraceDefaultAllocator();
    ...
}

The minimum step you should take next, is to add an Unmark function call in your UIApplication exit point:

-(void) applicationWillTerminate
{
    ...
    TAAllocUnmarkFile(CFAllocatorGetDefault(), "/tmp/APPNAME.mdmp", TAF_BRIEF_SYMBOL);
}

When your application will exit, a full dump of the currently allocated blocks is saved inside the file you specified in the TAAllocUnmarkFile() call.
The memory dump files generated by TraceAlloc contains numeric addresses in the call trace, that will have little meaning for the user.
The iPhoneTools package contains a script that can be used to demangle memory dump files, and translate numeric addresses to nice function names.
The steps above represent the minimum requirement in order to use the TraceAlloc library. In many cases though a more finer control of the zones that need to be checkmarked is necessary.
The following section briefly explains the functions contained inside the TraceAlloc library.



Library Functions


void *TAMalloc(unsigned int size);

void *TACalloc(unsigned int count, unsigned int size);
void *TARealloc(void *ptr, unsigned int size);
void TAFree(void *ptr);

This set of functions represent the equivalent of the POSIX malloc/calloc/realloc/free but they use the default allocator instead.
When using the TraceAlloc library, these functions will use the TraceAlloc tracing allocator, and all leaks resulting from your code will reveal themselves in the memory dump files.
If you use standard POSIX memory allocation functions in your code, these will escape the tracing allocator.


CFAllocatorRef TAAllocatorCreate(CFAllocatorRef origATor);

This function creates a tracing allocator stacked over the allocator passed as input parameter. The tracing allocator will use the origATor allocator as memory provider, but it'll add the tracing capabilities to it.


int TATraceDefaultAllocator(void);

This function creates a tracing allocator stacked over the current default allocator, and install the newly created tracing allocator as default one.
You may want to call this API as very first thing in your main() function.
Returns 0 in case of success, or -1 in case of error.


unsigned long TAAllocMark(CFAllocatorRef hookATor);

Creates a mark for the tracing allocator passed as input parameter. If you used the TATraceDefaultAllocator() API in you main() function, the default (tracing) allocator will be returned by the CFAllocatorGetDefault() call. The TAAllocMark() function return the value of the mark that can be used with the mark dump/checkpoint functions.


int TADumpBlocks(CFAllocatorRef hookATor, unsigned long mark, int fd, int flags);

Dump all the allocated blocks within the mark passed as input parameter. The hookATor parameter is the reference to the tracing allocator (likely CFAllocatorGetDefault()). The fd parameter is the file descriptor that will receive the dump output. The flags parameter can contain a combination (zero or more) of the following values:

TAF_BRIEF_SYMBOL
The TraceAlloc library can try to use the dlfcn.h capabilities in order to demangle the call trace addresses.
Unfortunately symbols coming out of the dladdr() function are not as accurate as the one that are retrievable from nm dumps (using the memdmp-demangle.pl script supplied inside the iPhoneTools package).
Since memory dumps without symbol demangling are more compact, you can pass this flag to tell the library to not try the dladdr() function name resolution.

TAF_FULL_NAME
The dladdr() function returns the full path of the library mapping a given numeric address. The TraceAlloc library only outputs the file name as default, but you can pass this flag and get the full path. Memory dump files can get pretty large when using this flag.

Returns 0 in case of success, or -1 in case of error.


int TADumpBlocksFile(CFAllocatorRef hookATor, unsigned long mark, const char *path, int flags);

Same as TADumpBlocks() but you can pass a file path instead of a file descriptor.
Returns 0 in case of success, or -1 in case of error.


int TAAllocUnmark(CFAllocatorRef hookATor, int fd, int flags);

Pops back the current mark allocated with the TAAllocMark() API. Dumps all the currently allocated blocks inside the current mark, into the fd passed as input parameter. The flags parameter is the same as the one described in the TADumpBlocks() API.
Returns 0 in case of success, or -1 in case of error.


int TAAllocUnmarkFile(CFAllocatorRef hookATor, const char *path, int flags);

Same as the TAAllocUnmark() API, but you can pass a path name instead of a file descriptor.
Returns 0 in case of success, or -1 in case of error.


int TAGetStatus(CFAllocatorRef hookATor, struct TraceAllocatorStatus *status);

Retrieves the current status of the allocator, defined inside the struct TraceAllocatorStatus structure:

struct TraceAllocatorStatus {                                                                                        
unsigned long mark;                                                                                          
unsigned long allocSize;                                                                                     
unsigned long numBlocks;                                                                                     
};

The mark field represent the current mark for the allocator. The allocSize field represent the total size (in bytes) of the allocated memory. The numBlocks field represent the total number of blocks of allocated memory.
Returns 0 in case of success, or -1 in case of error.



Example Memory Dump File


This is an example of a single block dump generated by the TraceAlloc library:

Block @ 0x4060e0 (64 bytes)                                                                                          
--> Trace:                                                                                                           
        `CoreFoundation@0x305009ab      0x304ff000
        `CoreFoundation@0x30500845      0x304ff000
        `CoreFoundation@0x30509a7f      0x304ff000
        `CoreFoundation@0x30509827      0x304ff000
        `CoreFoundation@0x3051c0e9      0x304ff000
        `CoreFoundation@0x30530dd1      0x304ff000
        `CoreFoundation@0x30530db9      0x304ff000
        `GraphicsServices@0x30ab412c    0x30ab2000
        `GraphicsServices@0x30ab83e4    0x30ab2000
        `UIKit@0x328c89ac       0x328bc000
                                                                           

The output tell you the address of the block, its size, and the call trace where the block has been allocated. The call trace dept ( macro) is set to a miximum of 12 entries inside the TraceAlloc.c source file, and can be increased if needed. note that per-block memory allocation will increase too, if you increase the value of TA_MAX_TRACE.
The trace, in the form above, tells you in which module the PC was at the time of the call, and the address inside this module.
The last value is the load address, inside your application, where the module was mapped.
When passed through the memdmp-demangle.pl script, the block dump will look like:

Block @ 0x4060e0 (64 bytes)                                                                                          
--> Trace:                                                                                                           
        '_CFAllocatorAllocate (+ 127)'  0x304ff000
        '__CFRuntimeCreateInstance (+ 145)'     0x304ff000
        '___CFDictionaryInit (+ 595)'   0x304ff000
        '_CFDictionaryCreateMutable (+ 31)'     0x304ff000
        '__CFStandardApplicationPreferences (+ 41)'     0x304ff000
        '_CFPreferencesAppBooleanValue (+ 21)'  0x304ff000
        '_CFPreferencesGetAppBooleanValue (+ 9)'        0x304ff000
        '___GSEventClassInitialize (+ 88)'      0x30ab2000
        '_GSInitialize (+ 64)'  0x30ab2000
        '-[UIApplication _initWithArgc:argv:] (+ 624)'  0x328bc000


The `MODULE@ADDRESS entries have been replaced with API names (and offsets), for an easier understanding of the call trace.




Download

The TraceAlloc library is included inside the iPhoneTools package.



Back Home