Pages

Tuesday, 20 October 2020

Deleaker: memory / resource leak detection and identification

As I've made clear many times in conference talks I’m a big fan of FastMM4, the full version Delphi’s built-in memory manager (I haven’t yet tried out FastMM5, which is fully rewritten but now has either a GPL or a commercial license). FastMM4 is great for identifying memory leaks, memory corruption, interface misuse and so on, and locating the source of the problem via a call stack. However it is true that the business of working through the FastMM4 reports is somewhat manual – reports and call stacks are sent out via debug strings or log flies and then you have to pore over the details and work things backward to the source of the problem. It does the job, but takes a back seat once the details have been discovered.

Some while back I got pointed in the direction of another leak detection tool, Deleaker from Softanics, of which I hadn't heard at that point. It’s taken me a while to clear my desk sufficiently to check it out but I’m pleased to have done so, as it’s a very nice and helpful tool.

I should mention up front that this is a commercial tool, like FastMM5 (when not used with the GPL license) and unlike FastMM4, Delphi LeakCheck, MemCheck 1 and TCondom 2. It costs $99 for a home license and $399 for a single developer license.

Some key points about Deleaker:

  • It is not just a tool for EMBT development languages: it supports Delphi, C++Builder, Visual C++ and Qt Creator
  • It’s not just a memory leak detector, but also supports resource leak detection for a pleasing array of resource types
  • It is not just a library, but offers a full UI experience, either integrated into the IDE (RAD Studio, Visual Studio, Qt Creator) or standalone, or even controlled through a command-line utility for use in Continuous Integration setups
  • It supports Win32 and Win64 targets

If you and/or your development team happens to dabble in C++Builder or Visual C++ then this tool would be a common weapon to deploy against leaks of all sorts.

The list of entity types that can be tracked looking for leaks is impressive:

  • Memory blocks allocated by heap functions, virtual memory, OLE memory: BSTR, SAFEARRAY, etc.
  • GDI objects: HBITMAP, HDC, HPEN, etc.
  • User32 objects: HICON, HCURSOR, etc.
  • Handles: file handles, events, mutexes, etc.
  • File views
  • Activation context cookies
  • Fibers
  • Critical sections
  • Environment strings
  • FLS (fiber-local storage) slots
  • TLS (thread-local storage) slots
  • Atoms

The typical way a Delphi or C++Builder developer would use Deleaker is using the IDE integration – there is a Deleaker menu added between Run and Component in the main menu. This menu allows you to enable or disable Deleaker’s interest in your project, set some options via a dialog and display the Deleaker window (the UI is the same in the standalone tool as it is in the IDE integration). When not debugging the window looks like this (note that when using this from within RAD Studio this is a dockable window, so you can tuck it away in your preferred area of the IDE’s UI):

Here you get a flavour of what is on offer. You can see we have support for taking and comparing snapshots, which is the mechanism we use to identify leaks in the application. There is a button to invoke the resource usage graph, which allows you to see a dynamic representation of resource consumption over the last 60 seconds.

Let’s do a simple test on a simple memory leak by some code like this:

procedure TForm1.Button1Click(Sender: TObject);
begin
  var P: PByte;
  GetMem(P, 1024 * 1024);
end;

Running this up and battering Button1 shows this in the resource graph. The initial slope up was the application startup and the addition rise was the memory allocations caused by the button presses.

OK, that gives a view of consumption and in a real application potentially indicates an issue, so now we can use snapshots to look for a potential problem.

The idea is to take an initial snapshot (using the Take Snapshot button) as a baseline, then shortly thereafter take another snapshot and compare the two. You can then see all the allocations that have not been deallocated and look for any you weren’t expecting.

When you press Take Snapshot Deleaker briefly freezes the information and collates a whole bunch of information it requires. This includes building up call stacks for each allocation that has not been freed. Deleaker understands about the symbol information format used by Delphi and C++Builder. But additionally, since it also operates within Visual Studio it understands Microsoft’s symbol format and has support for using the Microsoft Symbol Servers. This means that where necessary call stacks will include detailed information about Microsoft OS DLLs.

This animated GIF shows Deleaker acquiring its data for the first snapshot.

Note that the symbols shown towards the bottom of the screen at the end of the animation are not representative of what you will actually see. Current versions of Deleaker will "demangle" these "mangled" symbol names to provide a more readable stack trace.

After the first snapshot I press Button1 and then take a second snapshot. Now I have Snapshot #1 and Snapshot #2. The Compare with… button can then instigate the comparison between the current snapshot and another one:

This now shows the differences between the two.


Here we see a single unfreed allocation of Heap memory of size 1048576 (which is 1024 x 1024) and Deleaker shows the source file and line number of the allocation as well as the call stack leading up the allocation.

Double-clicking on any entry in the call stack (either your code or RTL/VCL code) will take you to the relevant location in the relevant source file (or you can use the Show Source Code button).

In leaks coming just out of your own code the fact that MS symbols are available is neither here nor there. By default Deleaker doesn’t even trouble you with them in the call stack: the Show Full Stack checkbox is unchecked above. However if you need the extra detail then you can enable that option. Here is an unfreed memory allocation coming from a callback routine being invoked by the Windows API EnumWindows with full stack shown:


This time I’ll leak a Delphi object: a TList. If used as before the Allocations list shows that there is an unfreed allocation of a Delphi object.


However if we go to the Delphi Objects view we can see that the unfreed item is confirmed as a TList:


Of course if a more complex object is leaked, such as a component like a TButton, which itself creates a number of objects for its own purposes, then the list of unfreed objects becomes larger, but all these have the same call stack:


So far we’ve just looked at heap allocations, be they of basic memory or of object instances. One of the compelling features of Deleaker is that it also tracks various Windows resources not tracked by many other utilities (see list from earlier). Here, for example is a GDI handle leak:
const
  pattern: array[0..3] of cardinal = (10, 1, 1, 1);
var
  lb: TLogBrush;
  pen, oldpen: HPEN;
begin
  lb.lbStyle := BS_SOLID;
  lb.lbColor := RGB(255, 0, 0);
  pen := ExtCreatePen(
    PS_COSMETIC or PS_USERSTYLE, 1, lb, length(pattern), @pattern);
  if pen <> 0 then
    try
      oldpen := SelectObject(Canvas.Handle, pen);
      try
        Canvas.MoveTo(0, 0); Canvas.LineTo(ClientWidth, ClientHeight);
      finally
        SelectObject(Canvas.Handle, oldpen);
      End;
    finally
      // DeleteObject(pen); // Oops! We forgot to deallocate the handle!
    end;
end;

In this case the snapshot comparison shows up 3 unfreed allocations for a block of heap memory and two HPENs (pen handles) with the Leak type set to <All Leaks>. Note that the Leak type dropdown could be set to GDI objects to focus in on the HPENs.

Looking at the call stacks for each I can readily see the one I forced:


Incidentally the resource usage graph will track whatever resource type you wish. Here it is showing my test app consuming more and more HPENs as I keep pressing my button.

During my experimenting with Deleaker I have bumped into several bugs and shortcomings and have passed them all along to Artem from Softanics, who is usually quick to respond. One issue had already been fixed by the time I reported it and another was fixed quite promptly. A couple of shortcomings (Delphi mangled symbol names not demangled, and RTL/VCL source files not being automatically located) were also addressed in version 2020.27.0.0. This responsive experience was reassuring.

I like Deleaker – the IDE integration of the UI (especially the call stack entry support for double-clicking to see the source code) makes it much more straightforward to work with that FastMM4. But of course for all that convenience there is a price tag.

Whether you can justify spending out for the sake of convenience will be a personal decision, but if you can I think you will be happy you did. Additionally, if you fear you are leaking resource handles and are having trouble tracking down how this is happening then making use of this sort of tool will expedite the resolution of such issues.


1. OK, that hasn’t been updated in some years...

2. Yeah, this one is also somewhat stale, and required a cached Google page as the EDN page it was on is no longer available

3 comments:

  1. Your link to TCondom is broken. I only get a Google cache error. Maybe a link to archive.org will work?

    ReplyDelete
  2. Yeah, that one works:
    https://web.archive.org/web/20200128234431/edn.embarcadero.com/article/28344

    ReplyDelete
    Replies
    1. Thanks very much for the corrected link - I've fixed the one in the text

      Delete