Memory Analyzer v1.0 demo
Four months ago we’ve written about a new tool we are currently developing, for tracking memory allocations in real-time. Since then, the code has changed a lot. It has been cleaned up and improved, both memory and performance wise, and today we are proud to release a demo version of it. You can grab it right now or you can continue reading, in order to become familiar with its features.
How it works
Memory Analyzer keeps track of each and every allocation made by the target application, including allocations made by all of its loaded modules. In order to accomplish that, it injects a DLL into the target’s address space and attaches hooks to every memory related function. Whenever a (re)allocation/deallocation occurs, the injected DLL (client) sends a message to Memory Analyzer (server). The server is responsible for keeping all the information needed in order to find the newly allocated memory block later on, or, in the case of a deallocation, it removes the memory block from the list of active blocks. At any time, the user is able to inspect all active memory blocks, with information about the location in the code where the allocation happened, the heap used for the allocation, as well as the size of the block.
The injection method used in the current version of Memory Analyzer, is able to capture allocations/deallocations made even before the main function of the application starts (e.g. allocations made inside constructors of static/global objects). This is the biggest difference from the previous version of Memory Analyzer, where there was no injection mechanism and the user was responsible for re-linking his application with the tracking DLL. Injection, in this case, happened at the time a static instance of the tracking object was initialized. Since the user isn’t able to control the order of initialization of static/global variables, the old method didn’t guarantee that allocations made by code executed before main() will be caught. The new method doesn’t require recompilation of the target application and it can be used right away. The downside is that there are a lot more captured allocations, and as a result there might be noise in the output.
Currently, the injected DLL includes hooks for the following functions:
- HeapXXX, LocalXXX and GlobalXXX from kernel32.dll, where XXX is Alloc, ReAlloc and Free
- HeapDestroy from kernel32.dll
- scalar and vector versions of operators new and delete from mfc80.dll, mfc80d.dll, mfc90d.dll and mfc90.dll
- scalar and vector versions of operators new and delete from msvcr90d.dll, msvcr90.dll, msvcr80d.dll and msvcr80.dll
- malloc/calloc/realloc/free and all of their _aligned and _aligned_offset versions from msvcr90d.dll, msvcr90.dll, msvcr80d.dll and msvcr80.dll
As you can see the list isn’t complete yet, but we hope that we have covered the majority of cases. Things still missing are VirtualXXX(Ex) functions from kernel32.dll and unicode versions of MFC DLLs.
The demo has been tested successfully on the following systems:
- Windows XP SP2 x86 with VS2005 and VS2008
- Windows Vista Home Premium x86 and Ultimate SP1 x86 with VS2008
- Windows 7 RC1 (build 7100) x64 with VS2008
Configuration is really simple. The following screenshot shows Memory Analyzer’s configuration tab.
- Target: The path to the target application. Use the Browse button to find the location of the executable on hdd.
- Command Line: Optional command line arguments for the target application.
- PDB Search Path: Optional path for .pdb files. PDB files are used to get function name and line number information for the callstack. If you need accurate callstack info, it is advised to specify the correct path. Note that you can use the Browse button multiple times in order to build a more complex path.
- Module List (and enclosed controls): Used to specify which modules we are actually interested in monitoring, instead of monitoring every one of them. Currently, you can’t exclude any modules from tracking. This is something we are working on.
- Max Callstack Frames: The maximum number of callstack frames reported for each allocation. Currently it is hard-coded to 5 in order to keep the CPU usage and memory requirements as low as possible.
- Hide internal allocations: Enables/disables the reporting of known allocations, such as those made internally by MFC or the CRT. If you would like to see all the allocations made by your application, uncheck this option. Currently, the list of known internal allocations is really small, so you will still get some of them in the output.
- Hook kernel32.dll: Check it in order to intercept all kernel32.dll functions (see the list above). This option has been introduced in order to overcome a bug with dbghelp.dll. If you encounter an ACCESS_VIOLATION message in the debugger’s log (last tab), while having this option checked, uncheck it and try again. Unfortunately this is a random bug (the hardest kind) and usually it seems to be fixed with a system restart. If you encounter this bug, and it’s persistent, please report back.
When you are done with the configuration, hit Go and switch to another tab in order to observe the output.
The first tab (“General”) reports statistics about the allocations made by the target application. The names should be self-explanatory. The graph displays the total memory used by the application during its lifetime, and it refreshes every 200 msec (approximately). Note that both LocalXXX and GlobalXXX set of functions are counted as HeapXXX calls.
The second tab (“Allocations”) displays all active memory blocks. In order to refresh the list, hit the Refresh button. Please note that currently the list box is refilled every time you press the Refresh button, so it might take a while if there are many (e.g. thousands) active allocations. When the application finishes, this tab will display all memory blocks which haven’t been freed.
Due to the way Memory Analyzer injects the DLL into the target process, you might get memory leaks from places you can’t control. Those can be either true memory leaks, or blocks of memory which haven’t been freed early enough to be caught by the injected DLL. Examples of such leaks are memory blocks allocated by the CRT during initialization, as well as memory blocks allocated by standard library functions (like printf, etc.). Currently, Memory Analyzer has a simple suppression mechanism implemented which is used to hide some of those allocations. Unfortunately, it’s not exposed to the user right now. We are working on it, and it will probably be available in the next version. For now, we’ve suppressed some of those allocations for you.
The next tab is the fragmentation tab. At the top of the page there is a Refresh button. By pressing it the list of heaps will be populated with all the heaps currently used by the application. By changing the heap handle, the view changes to reflect the layout of the new heap. Something to remember when looking at this view is that Memory Analyzer intercepts (and reports) the first function which is responsible for an allocation (i.e. malloc instead of RtlAllocateHeap). This means that the reported memory block doesn’t contain any necessary header structure used (e.g.) by the CRT memory manager. As a result, memory blocks which should be next to each other, might appear with a small gap between them. Also note that realloc(NULL, 0) returns a valid pointer (a memory block with zero size) and this might appear as a discontinuity between free blocks. By clicking on any one of the red/orange blocks, you will get its callstack and its size.
Next is the histogram tab. By pressing the Refresh button you will get a histogram of all the allocations made by the application, grouped in buckets of the next power of two.
After the histogram there is the memory log tab. By pressing the Start/Stop button you can enable logging of all the operations tracked by Memory Analyzer. Pressing Clear will clear the list. This view is useful if you would like to see which places in your code allocate memory at run-time (e.g. inside your renderer’s inner loop). As with all the previous tabs (except the histogram tab) you get a callstack and other information about the operation being logged.
The final tab is the debugger’s output tab. Here you will find information about the state of the injection process (watch out for ACCESS_VIOLATION exceptions during injection) as well as other debugger related stuff. Since the target application runs inside a debugger, you can’t attach another debugger to it. For making you life a bit easier, Memory Analyzer captures and reports all OutputDebugString() messages. This way you can output meaningful messages from your application in order to make debugging easier. Note that this is needed in case a problem appears only when running the target inside Memory Analyzer’s debugger, and you would like to check what’s causing it.
The only thing required in order to use Memory Analyzer (except from the actual application) is Microsoft Visual C++ 2008 Feature Pack Redistributable Package (x86). If you have it already installed, you won’t need anything else. If you are not sure, try running the application and if you get an error, download it and install it. Since this is a demo version we didn’t build an installer to automate the process. Future versions might get an installer.
Except from the things mentioned in the previous post (such as the implementation of an API for sending custom messages from the target app to the server, a way to compare two dumps, etc.), we are currently implementing a system for suppressing allocations the user doesn’t care about. Due to the nature of the application and the environment it is supposed to run, it makes a perfect candidate for a Visual Studio plug-in. We haven’t looked into that direction yet, but it’s something we are considering. If you have something in mind which might fit into Memory Analyzer, please say it. It might appear in a future version!
Get the demo here.
Thanks for reading. If you try the demo, please report back any problems/bugs you encounter. We will also be very happy to hear that you used it successfully.
Happy memory tracking!