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.
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.
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.
The TraceAlloc
library is
included inside the iPhoneTools
package.