home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / vp21beta.zip / ARTLSRC.RAR / HEAPCHK.PAS < prev    next >
Pascal/Delphi Source File  |  2000-08-15  |  25KB  |  795 lines

  1. {█▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀█}
  2. {█                                                       █}
  3. {█      Virtual Pascal version 2.1                       █}
  4. {█      Heap checking routines                           █}
  5. {█      ─────────────────────────────────────────────────█}
  6. {█      Copyright (C) 2000 Allan Mertner                 █}
  7. {█                                                       █}
  8. {▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀}
  9.  
  10. // To use, include this unit in the program as the first unit in the
  11. // Uses clause.
  12.  
  13. // To not use automatically, compile it with the HEAPCHK_MANUAL conditional
  14. // define, which also disables over-and under-run detection.
  15.  
  16. // If HEAPCHK_MANUAL is specified, call InitHeapCheck to install the
  17. // heap checker.
  18.  
  19. {$Delphi+,Use32+}
  20.  
  21. unit HeapChk;
  22.  
  23. interface
  24.  
  25. type
  26.   tOnProblem    = procedure( _AllocatedAt: Pointer );
  27.  
  28. // -------[ Main heap checker entry point ]------------------------------ \\
  29.  
  30. procedure InitHeapCheck( const _LogFile: String; _OnProblem: tOnProblem );
  31.  
  32. implementation
  33.  
  34. uses
  35.   VpSysLow, SysUtils, Objects, Dos;
  36.  
  37. type
  38.   // -------[ A heapblock, copied from system.pas ]---------------------- \\
  39.   PBlockRec = ^TBlockRec;
  40.   TBlockRec = record            // Heap free sub-block record
  41.     Next:      PBlockRec;       // Pointer to the next free sub-block
  42.     Size:      Longint;         // Size of the sub-block
  43.   end;
  44.  
  45.   TProblems = (pNotFreed, pUnderrun, pOverrun);
  46.   TProbSet  = set of TProblems;
  47.  
  48.   // -------[ Information on a single memory allocation ]---------------- \\
  49.   PAllocationInfo = ^TAllocationInfo;
  50.   TAllocationInfo = object(TObject)
  51.     fAddress : Pointer;  // Address of allocated memory
  52.     fSize    : Longint;  // Size of allocated block
  53.     fCaller  : Pointer;  // Address of caller
  54.     fIndex   : Longint;  // Sequentiel allocation number
  55.     fVmt     : Pointer;  // If class variable, this is a class reference
  56.     fType    : Byte;     // Type of allocation, vt* constants
  57.     fProblem : TProbSet; // Overrun, underrrun, freemem problem?
  58.  
  59.     constructor Init( _Address, _Caller, _Class: Pointer; _Size, _Index: Longint );
  60.     function VmtPtr: Pointer;
  61.     function ClassName: String;
  62.     function StringValue: PChar;
  63.     function ProblemCode: Char;
  64.   end;
  65.  
  66.   // -------[ Container for all memory allocations, sorted by number ]--- \\
  67.   TAllocationList = object(TSortedCollection)
  68.     function Compare(Key1, Key2: Pointer): Integer; virtual;
  69.     function KeyOf(Item: Pointer): Pointer; virtual;
  70.  
  71.     procedure NewAllocation( var _Address: Pointer; _Caller, _Object: Pointer; _Size: Longint );
  72.     procedure FreeAllocation( var _Address: Pointer; var _Problems: TProbSet );
  73.   end;
  74.  
  75.   PProblemInfo = ^TProblemInfo;
  76.   TProblemInfo = object(TObject)
  77.     fIndex   : Longint;  // Number of allocation causing problem
  78.     fProblem : TProbSet; // Problems with allocation
  79.  
  80.     constructor Init( _Index: Longint; _ProblemCode: char );
  81.   end;
  82.  
  83.   // -------[ Container for previous memory problems, sorted by index ]-- \\
  84.   TProblemIndexList = object(TSortedCollection)
  85.     function Compare(Key1, Key2: Pointer): Integer; virtual;
  86.   end;
  87.  
  88.   // -------[ Simple log file container class ]-------------------------- \\
  89.   TLogFile = object
  90.   private
  91.     fLog: Text;
  92.     fUpdate: Integer;
  93.     fLines: Integer;
  94.   protected
  95.     procedure LoadProblems( var _ProblemList: TProblemIndexList );
  96.     procedure ListProblems( const _AllocList: TAllocationList );
  97.   public
  98.     constructor Init( const _LogFile: String );
  99.     destructor Done;
  100.     procedure AddLine( const _s: String );
  101.     procedure AddLineFmt( const _s: String; const _Args: array of const );
  102.     procedure BeginUpdate;
  103.     procedure EndUpdate;
  104.   end;
  105.  
  106. // ---------------------------------------------------------------------- \\
  107. // -------[ Internally used globals and constants ]---------------------- \\
  108. // ---------------------------------------------------------------------- \\
  109.  
  110. var
  111.   AllocationList: TAllocationList;     // Global list of unfreed allocs
  112.   ProblemList: TProblemIndexList;      // List of problems from previous run
  113.   LogFile: TLogFile;                   // Log file used to load/store info
  114.  
  115. const
  116.   OnProblem   : tOnProblem = nil;      // User routine called on problem alloc
  117.   MemMgrIndex : Longint = 0;           // Running index of memory allocation
  118.   MaxBlocks   : Longint = 0;           // Max. allocated blocks of memory
  119.   MaxMemory   : Longint = 0;           // Max. allocated heap memory
  120.   CurMemory   : Longint = 0;           // Currently allocated heap memory
  121.   Overruns    : Longint = 0;           // Number of overruns
  122.   Underruns   : Longint = 0;           // Number of underruns
  123.  
  124.   SysVerified : LongBool = False;      // True, if System unit verified
  125.   HeapChkOn   : Boolean = False;       // True, if HeapChk is enabled
  126.  
  127. {$IFNDEF HEAPCHK_MANUAL}
  128.   StartOffset = 4;                     // Offset where real data begins
  129.   ExtraAlloc  = 8;                     // Extra memory to allocate
  130.   heapchk_BufferBegin   = $AC4BF321;   // Begin buffer for Underrun chk
  131.   heapchk_BufferEnd     = $9723CA3B;   // End buffer for Overrun chk
  132. {$ELSE}
  133.   StartOffset = 0;                     // No offset when not overrun checking
  134. {$ENDIF}
  135.  
  136. // ---------------------------------------------------------------------- \\
  137. // -------[ TLogFile: handling of log file ]----------------------------- \\
  138. // ---------------------------------------------------------------------- \\
  139.  
  140. constructor TLogFile.Init( const _LogFile: String );
  141. var
  142.   Temp: String;
  143.   FileName: String;
  144. begin
  145.   FileName := _LogFile;
  146.   if FileName = '' then
  147.     begin
  148.       Temp := GetEnv( 'TEMP' );
  149.       if Temp = '' then
  150.         Temp := GetEnv( 'TMP' );
  151.       if ( Temp <> '' ) and ( Temp[Length(Temp)] <> SysPathSep ) then
  152.         Temp := Temp + SysPathSep;
  153.       FileName := ExtractFileName( ParamStr(0) );
  154.       FileName := Temp + ChangeFileExt( FileName, '.mem' );
  155.     end;
  156.   Writeln( '!! HeapChk FileName is ',FileName);
  157.   Assign( fLog, FileName );
  158.   fUpdate := 0;
  159.   fLines := 0;
  160. end;
  161.  
  162. destructor TLogFile.Done;
  163. begin
  164.   if fUpdate > 0 then
  165.     close( fLog );
  166. end;
  167.  
  168. procedure TLogFile.AddLine( const _s: String );
  169. begin
  170.   BeginUpdate;
  171.   inc( fLines );
  172.   Writeln( fLog, _s );
  173.   EndUpdate;
  174. end;
  175.  
  176. procedure TLogFile.AddLineFmt( const _s: String; const _Args: array of const );
  177. begin
  178.   AddLine( Format( _s, _Args ) );
  179. end;
  180.  
  181. procedure TLogFile.BeginUpdate;
  182. begin
  183.   inc( fUpdate );
  184.   if fUpdate = 1 then
  185.     begin
  186.       if fLines = 0 then
  187.         Rewrite( fLog )
  188.       else
  189.         begin
  190.           {$I-}
  191.           Append( fLog );
  192.           {$I+}
  193.           if IOResult <> 0 then
  194.             Rewrite( fLog );
  195.         end;
  196.     end;
  197. end;
  198.  
  199. procedure TLogFile.EndUpdate;
  200. begin
  201.   dec( fUpdate );
  202.   if fUpdate = 0 then
  203.     Close( fLog );
  204. end;
  205.  
  206. procedure TLogFile.LoadProblems( var _ProblemList: TProblemIndexList );
  207. var
  208.   Line: String;
  209.   Index: Longint;
  210.   Problem: PProblemInfo;
  211.  
  212. begin
  213.   _ProblemList.Init( 100, 100 );
  214.   if fUpdate <> 0 then
  215.     exit;
  216.   {$I-}
  217.   Reset( fLog );
  218.   {$I+}
  219.   if IOResult <> 0 then
  220.     exit;
  221.  
  222.   while not eof( fLog ) do
  223.     begin
  224.       Readln( fLog, Line );
  225.       if Line[1] = '!' then
  226.         begin
  227.           Index := StrToIntDef( Copy(Line, 4, 7), -1 );
  228.           if Index <> -1 then
  229.             begin
  230.               Problem := New( PProblemInfo, Init( Index, Line[2] ) );
  231.               _ProblemList.Insert( Problem );
  232.             end;
  233.         end;
  234.  
  235.     end;
  236.   close( fLog );
  237. end;
  238.  
  239. const
  240.   DumpLength = 24;
  241.  
  242. procedure TLogFile.ListProblems( const _AllocList: TAllocationList );
  243.   function ProblemLine( const _pItem: pAllocationInfo ): String;
  244.   var
  245.     sContent: String;
  246.     sLoc: String;
  247.     pSource: Pointer;
  248.     FileName: String;
  249.     LineNo: Integer;
  250.     pC: pChar;
  251.     i: Integer;
  252.  
  253.   begin
  254.     // Construct contents of unfreed pointer
  255.     case _pItem^.fType of
  256.       vtObject     : sContent := Format( '(vmt: %p)', [_pItem^.VmtPtr] );
  257.       vtClass      : sContent := Format( '(%s)', [_pItem.ClassName] );
  258.       vtAnsiString : sContent := Format( '(str: ''%s'')', [_pItem.StringValue] );
  259.     else
  260.       begin
  261.         pC := pChar(_pItem^.fAddress) + StartOffset;
  262.         sContent := '';
  263.         i := 0;
  264.         while (i < DumpLength) and (i < _pItem^.fSize - 2*StartOffset) do
  265.           begin
  266.             if pC^ >= ' ' then
  267.               sContent := sContent + pC^
  268.             else
  269.               sContent := sContent + '.';
  270.             inc(i);
  271.             inc(pC);
  272.           end;
  273.       end;
  274.     end;
  275.     Result := Format( '!%s %7d %p %8d %-*s',
  276.                       [_pItem^.ProblemCode, _pItem^.fIndex,
  277.                        _pItem^.fAddress, _pItem^.fSize, DumpLength,
  278.                        sContent] );
  279.  
  280.     // Determine where Problem originated
  281.     if _pItem^.fCaller = nil then
  282.       sLoc := ' [Unknown]'
  283.     else
  284.       begin
  285.         pSource := GetLocationInfo( _pItem^.fCaller, FileName, LineNo );
  286.         if pSource = nil then          // Source not found
  287.           sLoc := Format( ' [Addr = %p]', [_pItem^.fCaller] )
  288.         else                           // Source found
  289.           sLoc := Format( ' [%s #%d]', [FileName, LineNo] );
  290.       end;
  291.  
  292.     Result := Result + sLoc;
  293.   end;
  294.  
  295.   function GetNumberLine( const _s: String; _Value: Integer ): String;
  296.   var
  297.     Fmt: String;
  298.   begin
  299.     if _Value = 0 then
  300.       Fmt := 'no '
  301.     else
  302.       Fmt := '%d ';
  303.     Result := Format( Fmt + _s, [_Value] );
  304.     if _Value <> 1 then
  305.       Result := Result + 's';
  306.   end;
  307.  
  308. var
  309.   AllocInx: Longint;
  310.   s: String;
  311.   Fmt: String;
  312.  
  313. begin
  314.   BeginUpdate;
  315.  
  316.   if fLines > 0 then
  317.     AddLine( '' );
  318.  
  319.   // Write list of memory Problems.  This list is also read and used for
  320.   // automatic positioning on the next run
  321.   if _AllocList.Count > 0 then
  322.     begin
  323.       AddLine( '  ] List of problematic memory blocks [ ');
  324.       AddLine( '    (p is bitmapped; bit 0 - leak; bit 1 - overrun; bit 2 - underrun)');
  325.       AddLine( '    ----' );
  326.       AddLine( ' p   Index  Address    Bytes Data                     [Allocated from]');
  327.       AddLine( ' - ------- -------- -------- ------------------------ ----------------');
  328.       for AllocInx := 0 to _AllocList.Count-1 do
  329.         AddLine( ProblemLine( _AllocList.At(AllocInx) ) );
  330.       AddLine( '' );
  331.     end;
  332.  
  333.   // Write a summary of problems
  334.   AddLine( '  ] Summary [' );
  335.   AddLine( '    -------' );
  336.   s := Format( '  Leaked blocks %d; Leaked mem %d; max blocks %d; max mem %d',
  337.                [_AllocList.Count, CurMemory, MaxBlocks, MaxMemory]);
  338.   AddLine( s );
  339.  
  340. {$IFNDEF HEAPCHK_MANUAL}
  341.   // Summarize over-and underruns
  342.   s := '  ' + GetNumberLine( 'overrun', Overruns );
  343.   s := s + '; ' + GetNumberLine( 'underrun', Underruns );
  344.   AddLine( s );
  345. {$ENDIF}
  346.   EndUpdate;
  347. end;
  348.  
  349. // ---------------------------------------------------------------------- \\
  350. // -------[ Handler when error detected ]-------------------------------- \\
  351. // ---------------------------------------------------------------------- \\
  352.  
  353. const
  354.   // Error codes for heap checking
  355.   heap_freeinvalidmemory = 1;
  356.   heap_SizeCorrupt       = 2;
  357.   heapchk_underrun       = 3;
  358.   heapchk_overrun        = 4;
  359.   heapchk_system         = 5;
  360.  
  361. procedure ErrorHandler( _Code: Integer; P: Pointer );
  362. var
  363.   s: String;
  364.  
  365. begin
  366.   case _Code of
  367.     heap_freeinvalidmemory :
  368.       s := 'Freeing unallocated memory at %p';
  369.     heap_SizeCorrupt :
  370.       s := 'Size of allocation corrupted for %p';
  371.     heapchk_system :
  372.       s := 'System Unit inconsistent; caller addresses unavailable.';
  373.   else
  374.     s := 'Unknown error (%p)';
  375.   end;
  376.   s := Format( s, [P] );
  377.   if LogFile.fLines = 0 then
  378.     begin
  379.       LogFile.AddLine( '  ] Errors occurred during execution [');
  380.       LogFile.AddLine( '    ------');
  381.     end;
  382.   LogFile.AddLine( '  ! ' + s );
  383. end;
  384.  
  385. // ---------------------------------------------------------------------- \\
  386. // -------[ TProblemInfo: A single problem ]----------------------------- \\
  387. // ---------------------------------------------------------------------- \\
  388.  
  389. constructor TproblemInfo.Init( _Index: Longint; _ProblemCode: Char );
  390. var
  391.   i: Byte;
  392. begin
  393.   fIndex   := _Index;
  394.   i := ord(_ProblemCode) - ord('0');
  395.   fProblem := TProbSet( i );
  396. end;
  397.  
  398.  
  399. // ---------------------------------------------------------------------- \\
  400. // -------[ TProblemIndexList: List of Problems from previous run ]------------ \\
  401. // ---------------------------------------------------------------------- \\
  402.  
  403. function TProblemIndexList.Compare(Key1, Key2: Pointer): Integer;
  404. begin
  405.   Result := PProblemInfo(Key1)^.fIndex - PProblemInfo(Key2)^.fIndex;
  406. end;
  407.  
  408. // ---------------------------------------------------------------------- \\
  409. // -------[ TAllocationInfo - information about a single allocation ]---- \\
  410. // ---------------------------------------------------------------------- \\
  411.  
  412. constructor TAllocationInfo.Init( _Address, _Caller, _Class: Pointer; _Size, _Index: Longint );
  413. begin
  414.   fAddress := _Address;
  415.   fSize    := _Size;
  416.   fIndex   := _Index;
  417.   fCaller  := _Caller;
  418.  
  419.   if _Class = nil then
  420.     fType  := 0
  421.   else if Longint(_Class) = 2 then
  422.     begin
  423.       fType  := vtAnsiString;
  424.       _Class := nil;
  425.     end
  426.   else if Longint(_Class) and 1 = 1 then
  427.     begin
  428.       fType := vtObject;
  429.       _Class := Pointer(Longint(_Class) and $fffffffe)
  430.     end
  431.   else
  432.     fType := vtClass;
  433.   fVmt     := _Class;
  434.   // Default problem: It's allocated, but not freed
  435.   fProblem := [ pNotFreed ];
  436. end;
  437.  
  438. function TAllocationInfo.ProblemCode: Char;
  439. begin
  440.   Result := chr( byte(fProblem) + ord('0') );
  441. end;
  442.  
  443. function TAllocationInfo.ClassName: String;
  444. begin
  445.   if fType = vtClass then
  446.     Result := TClass(fVmt).ClassName
  447.   else
  448.     Result := '';
  449. end;
  450.  
  451. function TAllocationInfo.VmtPtr: Pointer;
  452. begin
  453.   if fType = vtObject then
  454.     Result := fVmt
  455.   else
  456.     Result := nil;
  457. end;
  458.  
  459. function TAllocationInfo.StringValue: PChar;
  460. begin
  461.   if fType = vtAnsiString then
  462.     Result := PChar(fAddress) + 8 + StartOffset
  463.   else
  464.     Result := '';
  465. end;
  466.  
  467.  
  468. // ---------------------------------------------------------------------- \\
  469. // -------[ TAllocationList: Managing allocations/deallocations ]-------- \\
  470. // ---------------------------------------------------------------------- \\
  471.  
  472. function TAllocationList.Compare(Key1, Key2: Pointer): Integer;
  473. begin
  474.   Result := Longint(Key1) - Longint(Key2);
  475. end;
  476.  
  477. function TAllocationList.KeyOf(Item: Pointer): Pointer;
  478. begin
  479.   Result := PAllocationInfo( Item )^.fAddress;
  480. end;
  481.  
  482. procedure TAllocationList.NewAllocation( var _Address: Pointer; _Caller, _Object: Pointer; _Size: Longint );
  483. var
  484.   pItem: pAllocationInfo;
  485.   Problem: TProblemInfo;
  486.   FileName: String;
  487.   LineNo: Longint;
  488.  
  489. begin
  490.   inc( CurMemory, _Size );
  491.  
  492.   pItem := New( pAllocationInfo, Init( _Address, _Caller, _Object, _Size, MemMgrIndex ) );
  493.   Insert( pItem );
  494.   Problem.Init( MemMgrIndex, ' ' );
  495.  
  496.   if ProblemList.IndexOf( @Problem ) <> -1 then
  497.     begin
  498.       // On a previous run, this pointer was not freed
  499.       if assigned( OnProblem ) then
  500.         OnProblem( _Caller )
  501.       else
  502.         asm
  503.           // Execution stops here, if the memory block being allocated
  504.           // was not freed on a previous run.
  505.           // To find the culprit, open a CPU window (View-VPU), and use
  506.           // Goto (Ctrl-G) to go to the address held in _Caller. Then
  507.           // hit Ctrl-Shift-V to see the associated source code.
  508.           // It is often a good idea to use F4 (Run to cursor) at this
  509.           // stage, after which the call stack can be inspected to
  510.           // determine the cause of the memory Problem.
  511.           int 3
  512.         end;
  513.     end;
  514.  
  515.   FillChar(_Address^, _Size, $CC);
  516.  
  517. {$IFNDEF HEAPCHK_MANUAL}
  518.   PLongint(_Address)^ := heapchk_BufferBegin;
  519.   PLongint(Longint(_Address) + _Size - StartOffset)^ := heapchk_BufferEnd;
  520.   _Address := Pointer(Longint(_Address) + StartOffset);
  521. {$ENDIF}
  522.  
  523.   // Remember max. number of blocks allocated
  524.   if Count > MaxBlocks then
  525.     MaxBlocks := Count;
  526.   if CurMemory > MaxMemory then
  527.     MaxMemory := CurMemory;
  528.  
  529.   inc( MemMgrIndex );
  530. end;
  531.  
  532. procedure TAllocationList.FreeAllocation( var _Address: Pointer; var _Problems: TProbSet );
  533. var
  534.   Inx: Longint;
  535.   Item: TAllocationInfo;
  536.   pItem: PAllocationInfo;
  537.   Size: Longint;
  538. begin
  539. {$IFNDEF HEAPCHK_MANUAL}
  540.   _Address := Pointer(Longint(_Address) - StartOffset);
  541. {$ENDIF}
  542.   _Problems := [];
  543.   Item.Init( _Address, nil, nil,  0, 0 );
  544.   Inx := IndexOf( @Item );
  545.   if Inx <> -1 then
  546.     begin
  547.       pItem := At( Inx );
  548.       Size := PBlockRec( Longint(_Address) - SizeOf(TBlockRec) )^.Size;
  549.       if abs(Size - pItem^.fSize) > 2*SizeOf(TBlockRec) then
  550.         ErrorHandler( heap_SizeCorrupt, _Address );
  551.  
  552. {$IFNDEF HEAPCHK_MANUAL}
  553.       if PLongint(_Address)^ <> heapchk_BufferBegin then
  554.         begin
  555.           ErrorHandler( heapchk_underrun, _Address );
  556.           Include( pItem^.fProblem, pUnderrun );
  557.         end;
  558.       if PLongint(Longint(_Address) + pItem^.fSize - StartOffset)^ <> heapchk_BufferEnd then
  559.         begin
  560.           ErrorHandler( heapchk_overrun, _Address );
  561.           Include( pItem^.fProblem, pOverrun );
  562.         end;
  563. {$ENDIF}
  564.  
  565.       Exclude( pItem^.fProblem, pNotFreed );
  566.       dec( CurMemory, pItem^.fSize );
  567.       FillChar( _Address^, pItem^.fSize, $AB );
  568.       if pItem^.fProblem = [] then
  569.         // Free the item if it was unproblematic, otherwise keep it
  570.         // for reference
  571.         AtFree( Inx )
  572.       else
  573.         _Problems := pItem^.fProblem;
  574.     end
  575.   else
  576.     begin
  577. {$IFNDEF HEAPCHK_MANUAL}
  578.       // Maybe memory was allocated before HeapChk was installed;
  579.       // adjust pointer accordingly
  580.       _Address := Pointer(Longint(_Address) + StartOffset);
  581. {$ENDIF}
  582.       ErrorHandler( heap_freeinvalidmemory, _Address );
  583.     end;
  584. end;
  585.  
  586. // ---------------------------------------------------------------------- \\
  587. // -------[ Implementation of actual memory manager interface ]---------- \\
  588. // ---------------------------------------------------------------------- \\
  589.  
  590. const
  591.   ReEntry : Longint = 0;          // Re-entry flag
  592.  
  593. var
  594.   VPMemMan: TMemoryManager;       // Original VP memory manager
  595.  
  596. function IntCheckGetMem( _Size: Longint; _Caller, _Object: Pointer ): Pointer;
  597. begin
  598. {$IFNDEF HEAPCHK_MANUAL}
  599.   if ReEntry = 0 then
  600.     _Size := _Size + ExtraAlloc;  // Allocate 8 extra bytes for checking
  601. {$ENDIF}
  602.   Result := VPMemMan.GetMem(_Size); // Get memory
  603.   if ReEntry = 0 then
  604.     begin
  605.       inc( ReEntry );
  606.       AllocationList.NewAllocation( Result, _Caller, _Object, _Size );
  607.       dec( ReEntry );
  608.     end;
  609. end;
  610.  
  611. // CheckGetMem is at the core of the heap checker.  When it receives control
  612. // from the System unit's _MemNew function, it saves the return address
  613. // and attempts to determine the real origin of the memory allocation
  614. // request, which is passed on to IntCheckMem that does the actual
  615. // allocation.
  616.  
  617. function CheckGetMem(Size: Longint): Pointer; assembler; {&uses none} {&frame-}
  618. asm
  619.         pop     eax              // Return address
  620.         mov     edx,[SysVerified]
  621.         test    edx,edx
  622.         jz    @@NotObject        // System unit incorrect version
  623.  
  624.         mov     edx,[esp+$0c]    // Get real caller's address
  625.         cmp     edx,OFFSET System.TObject.NewInstance
  626.         jl      @@NotClass
  627.         cmp     edx,OFFSET System.TObject.FreeInstance
  628.         jge     @@NotClass
  629.         mov     edx,[esp+$44]    // Constructing a class instance
  630.         jmp     @@Go             // So class reference pointer in edx
  631.  
  632.       @@NotClass:
  633.         cmp     edx,OFFSET System._ObjCtr
  634.         jl      @@NotObject
  635.         cmp     edx,OFFSET System._ObjDtr
  636.         jge     @@NotObject
  637.         mov     edx,[esp+$2c]    // Constructing an object instance
  638.         or      ecx,1            // Mark as Object
  639.         jmp     @@Go
  640.  
  641.       @@NotObject:
  642.         cmp     edx,OFFSET System._LStrNew
  643.         jl      @@Untyped
  644.         cmp     edx,OFFSET System._LStrPacked
  645.         jge     @@Untyped
  646.         mov     ecx,2            // Mark as AnsiString
  647.         jmp     @@Go
  648.  
  649.       @@Untyped:
  650.         xor     ecx,ecx          // Not constructing a class or an object
  651.  
  652.       @@Go:
  653.                                  // Param::0 - size - already on stack
  654.         push    edx              // Param::1 - caller
  655.         push    ecx              // Param::2 - TObject, if object
  656.         push    eax              // Return address
  657.         jmp     IntCheckGetMem
  658. end;
  659.  
  660. function CheckFreeMem(P: Pointer): Longint;
  661. var
  662.   Problems: TProbSet;
  663. begin
  664.   if ReEntry = 0 then
  665.     begin
  666.       inc( ReEntry );
  667.       AllocationList.FreeAllocation( P, Problems );
  668.       dec( ReEntry );
  669.     end
  670.   else
  671.     Problems := [];
  672.   if Problems = [] then
  673.     Result := VPMemMan.FreeMem(P);                // Free memory
  674. end;
  675.  
  676. function CheckReallocMem(_P: Pointer; _Size: Longint): Pointer;
  677. var
  678.   Bytes : Longint;
  679. begin
  680.   // In order to avoid complications, implement ReAllocMem using
  681.   // GetMem and FreeMem
  682.   GetMem( Result, _Size );
  683.   fillchar( Result^, _Size, 0 );
  684.  
  685.   Bytes := PBlockRec( Longint(_P) - SizeOf(TBlockRec) - StartOffset )^.Size;
  686.   if Bytes > _Size then
  687.     Bytes := _Size;
  688.   move( _P^, Result^, Bytes );
  689.  
  690.   FreeMem( _P );
  691. end;
  692.  
  693. // Verify that System unit is the one that low-level hacks rely on
  694. // If not, disable getting of caller address
  695.  
  696. function VerifySystem: Boolean;
  697.  
  698.   function VerifyCallerAddress( _Caller: Pointer ): Boolean;
  699.   begin
  700.     // Verify that caller was from this vicinity
  701.     Result := ( Longint(_Caller) > Longint(@VerifySystem) )
  702.           and ( Longint(_Caller) < Longint(@InitHeapCheck) );
  703.   end;
  704.  
  705. var
  706.   pAlloc: pAllocationInfo;
  707.   pObj: Objects.pObject;
  708.   pCls: System.tObject;
  709.   p: Pointer;
  710.  
  711. begin
  712.   SysVerified := False;
  713.   if PLongint(@_MemNew)^ = $448B5251 then
  714.     begin
  715.       // _MemNew is compiled correctly; assume True
  716.       SysVerified := True;
  717.  
  718.       // Attempt to allocate memory and check
  719.       GetMem(p, 10);
  720.       pAlloc := PAllocationInfo( AllocationList.At(0) );
  721.       SysVerified := ( PChar(pAlloc^.fAddress) + StartOffset = p )
  722.                  and ( VerifyCallerAddress( pAlloc^.fCaller ) );
  723.       FreeMem(p);
  724.  
  725.       // Attempt to allocate a class and check
  726.       if SysVerified then
  727.         begin
  728.           pCls := Exception.Create( '' );
  729.           pAlloc := PAllocationInfo( AllocationList.At(0) );
  730.           SysVerified := ( pAlloc^.ClassName = 'Exception' )
  731.                      and ( VerifyCallerAddress( pAlloc^.fCaller ) );
  732.           pCls.Free;
  733.         end;
  734.  
  735.       // Attempt to allocate an object and check
  736.       if SysVerified then
  737.         begin
  738.           pObj := New(pStream, Init);
  739.           pAlloc := PAllocationInfo( AllocationList.At(0) );
  740.           SysVerified := ( pAlloc^.VmtPtr = typeof(tStream) )
  741.                      and ( VerifyCallerAddress( pAlloc^.fCaller ) );
  742.           Dispose(pObj, Done );
  743.         end;
  744.     end;
  745. end;
  746.  
  747. // -------[ Exit procedure: Uninstall heap checker ]--------------------- \\
  748.  
  749. procedure HeapCheckExit;
  750. begin
  751.   SetMemoryManager(VPMemMan);          // Return to original mem mgr
  752.   LogFile.ListProblems( AllocationList ); // Write list of problems to log file
  753.   LogFile.Done;
  754.   AllocationList.Done;                 // Free list of allocations
  755.   ProblemList.Done;                    // Free list of problem records
  756. end;
  757.  
  758. const
  759.   HeapChecker: TMemoryManager = (
  760.     GetMem: CheckGetMem;
  761.     FreeMem: CheckFreeMem;
  762.     ReallocMem: CheckReallocMem);
  763.  
  764. // ---------------------------------------------------------------------- \\
  765. // -------[ Main entry point: activate heap checking ]------------------- \\
  766. // ---------------------------------------------------------------------- \\
  767.  
  768. procedure InitHeapCheck( const _LogFile: String; _OnProblem: tOnProblem );
  769. begin
  770.   if HeapChkOn then                    // Prevent dual init!
  771.     exit;
  772.   HeapChkOn := True;
  773.  
  774.   LogFile.Init( _LogFile );            // Initialise the log file object
  775.   LogFile.LoadProblems( ProblemList ); // Load list of problems, if available
  776.   OnProblem := _OnProblem;             // Set user hook
  777.  
  778.   AllocationList.Init( 1000, 1000 );   // Initialise the allocation list
  779.   GetMemoryManager(VPMemMan);          // Get the previous mem mgr
  780.   SetMemoryManager(HeapChecker);       // - and install our own
  781.   VerifySystem;                        // Verify the state of the System unit
  782.   if not SysVerified then
  783.     ErrorHandler( heapchk_system, nil );
  784.   AddExitProc( HeapCheckExit );        // Make sure we terminate
  785. end;
  786.  
  787. {$IFNDEF HEAPCHK_MANUAL}
  788. initialization
  789.   InitHeapCheck( '', nil );
  790. {$ENDIF}
  791. end.
  792.  
  793.  
  794.  
  795.