Debugging a Delphi Application
Using Memory Profiler

Project Setup

  1. Project > Options > Compiler
    • Code Generation:
      • Optimization
      • Stack Frames
    • Debugging:
      • Debug Information
  2. Project > Options > Linker
    • Include TD32 debug info
    • Map File: Detailed
  3. Build project (Project > Build All Projects).

AQTime

  1. Run AQTime and include project executable to debug (File > Open > choose the .exe file).
  2. Select profiler to execute: Options > Profile Options (or from drop-down in toolbar). Use VCL > VCL Class Profiler.
  3. Run executable with profiler (Project > Run).
  4. Test and push the executable to try to break it. Once you are done with tests, simply quit the application.
  5. Review memory leaks report in AQTime (it takes a few seconds to update after the tested application has exited).
Using Built-in Debugging Tools

Using FastMM

  1. Open project in Delphi.
  2. Install FastMM in your project and source code (see instructions distributed with FastMM package).
  3. Set FullDebugMode in Project > Options > Directories/Conditionals > Conditional defines.
  4. Build project (Project > Build All Projects).
  5. Execute project as usual. A dialog will popup if there are any critical errors or memory leaks during run time.

Example, in MyProject.dpr:

program MyProject;
 
  {$DEFINE FullDebugMode}   // for software that needs debug reporting using FastMM4
uses
  FastMM4 {memory manager replacement (and mem leak detector)},
  Forms,
  Windows,
  sysUtils,
  messages,
  Dialogs,
  Classes;
 
begin
  OutputDebugString(pchar('DBGVIEWCLEAR'));               // clear DebugView window (get from www.sysinternals.com)
 
  // register with FastMM4 some known memory leaks
  RegisterExpectedMemoryLeak(28 {21-28}, 1);              // TCriticalSection x 1
  RegisterExpectedMemoryLeak(44 {37-44}, 1);              // TShellFolder x 1
  RegisterExpectedMemoryLeak(60 {45-60}, 1);              // TStringList x 1
 
  FastMM4.ReportMemoryLeaksOnShutdown := DebugHook <> 0;  // report leaks only when using debugger
 
  . . . project code here . . .
 
end.

In FastMM4Options.inc, enable the following define statements:

 {$define UseOutputDebugString}
 {$define LogErrorsToFile}
 {$define LogMemoryLeakDetailToFile}
 {$define ClearLogFileOnStartup}
 {$define EnableMemoryLeakReporting}
 {$define HideExpectedLeaksRegisteredByPointer}
 {$define RequireDebuggerPresenceForLeakReporting}
 {$define RequireDebugInfoForLeakReporting}

References

Using EventLog or DebugView

Create some debugging routines in DebugTools.pas (Pascal unit):

//------------------------------------------------------------------------------
// Unit Name: DebugTools
// Author:    Siegwart Mayr
// Date:      17-May-2006
// Purpose:   Library with debugging routines.
//------------------------------------------------------------------------------
unit DebugTools;
 
interface
  Uses
     Windows, Forms;
 
  //-------------------------------------------
  // types
  //-------------------------------------------
  Type
    TOutlineNodeType = (ntEmpty, ntNode, ntLeaf);
 
  //-------------------------------------------
  // function prototypes
  //-------------------------------------------
  procedure DebugMsg(str: string; OutlineLevel: integer=0; NodeType: TOutlineNodeType=ntNode);
  procedure DebugStr(str: string);
 
  //-------------------------------------------
  // resource strings
  //-------------------------------------------
  ResourceString
    rsOutlineBullet = '+';
    rsOutlineLeaf   = '-';
    rsOutlineEmpty  = '|';
 
implementation
 
//------------------------------------------------------------------------------
// description: Send the specified string to the Windows debugger environment,
//              so that it can be viewed with DbgViewer (http://www.sysinternals.com)
// parameters : str: string, OutlineLevel: integer, NoteType: TOutlineNodeType
// return     : None
//------------------------------------------------------------------------------
procedure DebugMsg(str: string; OutlineLevel: integer=0; NodeType: TOutlineNodeType=ntNode);
{$ifdef FullDebugMode}
var
  LevelSpacer: string;
  NodeSymbol: string;
{$endif}
begin
 {$ifdef FullDebugMode}
   case NodeType of
     ntEmpty: NodeSymbol := rsOutlineEmpty;
     ntNode:  NodeSymbol := rsOutlineBullet + rsOutlineBullet;
     ntLeaf:  NodeSymbol := rsOutlineBullet + rsOutlineLeaf;
   else
     NodeSymbol := rsOutlineBullet;
   end;
 
   case OutlineLevel of
     1: begin
          LevelSpacer := StringOfChar(' ', 1) + NodeSymbol;
        end;
     2: begin
          LevelSpacer := StringOfChar(' ', 2) + NodeSymbol;
        end;
 
     3: begin
          LevelSpacer := StringOfChar(' ', 3) + NodeSymbol;
        end;
   else
     LevelSpacer := '';
   end;
   OutputDebugString(pchar(LevelSpacer + ' ' + str));
 {$endif}
 
end;
 
//------------------------------------------------------------------------------
// description: Simple debugging routine.
// parameters : str: string
// return     : None
//------------------------------------------------------------------------------
procedure DebugStr(str: string);
begin
 //{$ifdef FullDebugMode}
   OutputDebugString(pchar(str));
 //{$endif}
end;
 
end.
  1. Include DebugTools in the Uses clause in the Delphi source code.
  2. Insert DebugStr('My debug message here…') statements in the source code whenever something needs debugging.
  3. Use one of these utilities to view debug messages as application is being executed:
    • When application is run from Delphi's IDE: use Delphi's built-in Event Log (View > Debug Windows > Event Log (Ctrl-Alt-V)).
    • When application is run by itself: use DbgView.exe utility (from http://www.sysinternals.com). Leave utility running while application is run as well.