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.