Every few months, there is a discussion on cocoa-dev or a question on stackoverflow.com that basically boils down to “I have a leak or over-release and I can’t use Instruments to debug it. Help?”.
Quite often, the questioner can actually use Instruments just fine, but simply lacks the know-how or hasn’t tried in a while and doesn’t realize that Instruments has improved significantly with each release of the developer tools. No, really, Instruments is a fantastic tool and I use it whenever I can; what you see below is for the exceptional case, not the norm.
There are cases where using Instruments is either inconvenient or impractical. Namely, trying to track down an intermittent crasher or trying to gain insight into memory leaks over a long running session will create a prohibitively large dataset for Instruments to process (Instruments allows for much more detailed analysis of the object graph and this analysis loads a lot more data than the tools I’ll demonstrate below).
Thus, it is helpful to be familiar with the rather powerful set of tools available from the command line and within the debugger.
Almost always, you are going to want to enable a bit of additional data via the malloc infrastructure. Have a look at the malloc(3) man page. There is an entire section devoted to ENVIRONMENT variables and there are a handful of extremely useful variables!
First and foremost, you are almost always going to want to use MallocStackLoggingNoCompact. When enabled, malloc stack logging writes the stack trace of every allocation and free event to an efficiently compact binary file in /tmp/ (it used to be in memory and, thus, used to be a great way to exhaust heap. No longer!!). Unfortunately, it doesn’t record the retain and release events, but simply knowing where the object was allocated is generally quite useful (it is generally relatively easy to track down who retained an object once you know which object it is). Under GC, you can set the AUTO_REFERENCE_COUNT_LOGGING and CFRetain/CFRelease events will be logged to the malloc history.
You can then use the malloc_history command line tool to query for all events related to a particular address in memory.
While malloc_history requires that the process still exists, it doesn’t have to be running! If you run your app under gdb, you can still use malloc_history to query the application even when it is stopped in the debugger!
Speaking of gdb, you can use the info malloc command in gdb to query the same information. Under GC, the info gc-roots and info gc-referers commands can be used to interrogate the collector for information about the connectivity of the object graph in your running application.
If you enable zombies via setting the NSZombieEnabled environment variable to YES, the address spewed in the error message when messaging a zombie can be passed directly to malloc_history.
The leaks command line tool scans memory and detects leaks in the form of allocations of memory for which the address to that memory is not stored anywhere else within the application. The leaks tool has been vastly improved in the Snow Leopard release of the Xcode tools; it is much much faster and spits out false positives almost never. It is still possible to have a leak that leaks cannot detect, of course. And, remember, even if you can still reach memory, it is still a total waste if you never use that memory’s contents again!
So, that is a brief summary of the state of command line memory debugging on Mac OS X as of Snow Leopard. Of course, that’s just a bunch of words. How about an example?
Read the rest of this entry »