“Unexpected Memory Leak” after “MyTestApplication.exe: Memory Leak Detected”
Posted by jpluimers on 2021/07/28
Sometimes after a regular FastMM memory leak test report you get a dialog like this:
--------------------------- Unexpected Memory Leak --------------------------- An unexpected memory leak has occurred. The unexpected small block leaks are: 61 - 68 bytes: Unknown x 1 --------------------------- OK ---------------------------
The normal FastMM memory leak reporting dialog looks like this:
--------------------------- MyTestApplication.exe: Memory Leak Detected --------------------------- ... Note: Memory leak detail is logged to a text file in the same folder as this application. To disable this memory leak check, undefine "EnableMemoryLeakReporting". --------------------------- OK ---------------------------
Searching for “Unexpected Memory Leak” in RTL or FastMM code did not reveal results, but that might be me not doing a proper search.
One big problem is that the regular memory leak dialog is being suppressed by setting SuppressMessageBoxes
to True
(see for instance my blog post Application shutdown: wait for all threads to terminate or not? or [WayBack] delphi – Generating a FASTMM report WITHOUT the shutdown dialog – Stack Overflow).
However the “Unexpected Memory Leak” message box is always shown.
Research
Location of the error message caption
GETMEM.INC
has an identifierLeakMessageTitle
:
LeakMessageTitle: _PAnsiChr = 'Unexpected Memory Leak';
- Unlike
FastMM4.pas
,GETMEM.INC
does not take into accountSuppressMessageBoxes
. - Like
FastMM4.pas
,GETMEM.INC
does take into accountReportMemoryLeaksOnShutdown
.
More on GETMEM.INC
: [WayBack] delphi – Reporting memory leaks on shutdown with a console application – Stack Overflow
Versioned FastMM4.pas
.
Complicating the hunt
Not all allocations go via the memory manager. A complicating factor is that:
- ALL
TMonitor.Create
allocations go throughSysAllocMem
:
class function TMonitor.Create: PMonitor; begin if CacheLineSize = 0 then AtomicExchange(CacheLineSize, GetCacheLineSize); if (CPUCount > 1) and (FDefaultSpinCount = 0) then AtomicExchange(FDefaultSpinCount, 1000); if CacheLineSize > SizeOf(Result^) then Result := SysAllocMem(CacheLineSize) else Result := SysAllocMem(SizeOf(Result^)); Result.FSpinCount := FDefaultSpinCount; end;
- ALL
TMonitor.Destroy
deallocations go throughSysFreeMem
:
procedure TMonitor.Destroy; begin if (MonitorSupport <> nil) and (FLockEvent <> nil) then MonitorSupport.FreeSyncObject(FLockEvent); SysFreeMem(@Self); end;
Debugging this is easiest to set a breakpoint in the FastMM4.pas finalization section enabling a breakpoint group that has breakpoints on these methods inside GETMEM.INC
:
function SysGetMem(Size: NativeInt): Pointer;
function SysFreeMem(P: Pointer): Integer;
function SysReallocMem(P: Pointer; Size: NativeInt): Pointer;
function SysAllocMem(Size: NativeInt): Pointer;
For inspecting for instance an asm
construct like TSmallPoolBlockPoolHeader[edx]
, use a conversion PSmallBlockPoolHeader(Pointer(EDX))^,r
Poor mans shotgun approach
This will hide all the SysAllocMem
related leaks:
unit FastMM4WrapperUnit; interface {$ifdef UseFastMM4} uses FastMM4, FastMM4Messages; {$endif UseFastMM4} implementation initialization // finalization // Disable GETMEM.INC reports that neglect SuppressMessageBoxes and NoErrMsg; this will effectively hide the leak as GETMEM.INC also does not write the leaks to a log file {$ifdef UseFastMM4} if SuppressMessageBoxes then begin {$WARN SYMBOL_PLATFORM OFF} NoErrMsg := True {$WARN SYMBOL_PLATFORM ON}; // no ShowMessage from the RTL (except for memory leaks) ReportMemoryLeaksOnShutdown := False; // No ShowMessage from the RTL in the GETMEM.INC teardown end; {$endif UseFastMM4} end.
IsConsole: needs linker option
Note that I tried embedding this in the then portion, but enabling IsConsole fails:
IsConsole := True; // can only force run-time error messages to be written to the console if the "Project/Options/Linking/Generate console application" is set.
The reason is that this does not allocate a console handle, so you really need to ensure the linker has allocated a console for you by enabling Project/Options/Linking/Generate console application
(or option DCC_ConsoleTarget
in the .dproj file)
For more details, see my post console – When is System.IsConsole true in Delphi? – Stack Overflow.
–jeroen
Leave a Reply