John Barrat
John Barrat

Reputation: 830

Are these Memory Leaks Real

I have been running FastMM4 on my code to see if I have any memory leaks...

This has reported a load of class UniCodeString leaks when I close the program. Are these real and how do I interpret the Event Log: This is a typical block report:

--------------------------------2020/10/21 17:32:01--------------------------------
A memory block has been leaked. The size is: 120

This block was allocated by thread 0x5648, and the stack trace (return addresses) at the time was:
430547 [FastMM4.pas][FastMM4][_ZN7Fastmm411DebugGetMemEx][8737]
409534 [System.pas][System][_ZN6System7_GetMemEx][4803]
412F8C [System.pas][System][_ZN6System17_NewUnicodeStringEi][25403]
414C3C [System.pas][System][_ZN6System16InternalUStrCatNERNS_13UnicodeStringEiPS0_][29902]
4156CB [System.pas][System][_ZN6System9_UStrCatNERNS_13UnicodeStringEi][30998]
5DD71A [System.IniFiles.pas][System.IniFiles][_ZN6System8Inifiles11TMemIniFile8TSection9SetValuesEiNS_13UnicodeStringE][852]
5DEFA6 [System.IniFiles.pas][System.IniFiles][_ZN6System8Inifiles11TMemIniFile11WriteStringENS_13UnicodeStringES2_S2_][1212]
14F335F [IDEIni.pas][IDEIni][_ZN6Ideini15TIDEIniSettings13UpdateSectionEN6System13UnicodeStringE][1153]
14F26E7 [IDEIni.pas][IDEIni][_ZN6Ideini15TIDEIniSettings13UpdateSectionEi][1087]
14F24FD [IDEIni.pas][IDEIni][_ZN6Ideini15TIDEIniSettings4SaveEiiiiibjPN6System7Classes8TStringsENS1_13UnicodeStringE][1078]
14DE604 [IDEMain.pas][IDEMain][_ZN7Idemain10TfmIDEMain9FormCloseEPN6System7TObjectERNS1_7Uitypes12TCloseActionE][573]

The block is currently used for an object of class: UnicodeString

The allocation number is: 675683

Current memory dump of 256 bytes starting at pointer address 7FF4FBF7E170:
24 D9 69 01 B0 04 02 00 01 00 00 00 29 00 00 00 50 00 61 00 74 00 68 00 3D 00 43 00 3A 00 5C 00
50 00 72 00 6F 00 67 00 72 00 61 00 6D 00 20 00 46 00 69 00 6C 00 65 00 73 00 20 00 28 00 78 00
38 00 36 00 29 00 5C 00 50 00 72 00 6F 00 74 00 6F 00 6E 00 49 00 44 00 45 00 5C 00 50 00 44 00
53 00 00 00 CA 9A 8A F5 AF D5 F2 FF 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80
00 00 00 00 00 00 00 00 81 E3 F7 FB F4 7F 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 37 48 0A 00 47 05 43 00 00 00 00 00 B6 08 43 00 00 00 00 00
9D 95 40 00 00 00 00 00 1C 54 41 00 00 00 00 00 11 55 41 00 00 00 00 00 01 56 45 01 00 00 00 00
63 02 46 01 00 00 00 00 E2 1E 46 01 00 00 00 00 92 9B 68 00 00 00 00 00 A5 04 41 00 00 00 00 00
$  Ù  i  .  °  .  .  .  .  .  .  .  )  .  .  .  P  .  a  .  t  .  h  .  =  .  C  .  :  .  \  .
P  .  r  .  o  .  g  .  r  .  a  .  m  .     .  F  .  i  .  l  .  e  .  s  .     .  (  .  x  .
8  .  6  .  )  .  \  .  P  .  r  .  o  .  t  .  o  .  n  .  I  .  D  .  E  .  \  .  P  .  D  .
S  .  .  .  Ê  š  Š  õ  ¯  Õ  ò  ÿ  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €
.  .  .  .  .  .  .  .    ã  ÷  û  ô    .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .
.  .  .  .  .  .  .  .  .  .  .  .  7  H  .  .  G  .  C  .  .  .  .  .  ¶  .  C  .  .  .  .  .
  •  @  .  .  .  .  .  .  T  A  .  .  .  .  .  .  U  A  .  .  .  .  .  .  V  E  .  .  .  .  .
c  .  F  .  .  .  .  .  â  .  F  .  .  .  .  .  ’  ›  h  .  .  .  .  .  ¥  .  A  .  .  .  .  .

Most of the leaks are small, less than 200 bytes but should I be worrying about them?

Upvotes: 0

Views: 721

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 595377

Are these real

Very likely. Particularly the one you have shown, yes.

how do I interpret the Event Log

The leak report you have shown indicates a UnicodeString is leaked. UnicodeString is a managed type. The only times I have ever seen a UnicodeString being leaked are when:

  • it is a member of a class or record, and a dynamically allocated instance of that type is itself leaked. A leak report should show this instance being leaked as well.

  • a pointer to an allocated UnicodeString has been corrupted due to being overwritten unexpectedly, usually by a logic bug somewhere in the code.

  • it is declared as a threadvar and is not cleared manually before a thread exits. This is documented behavior:

    Dynamic variables that are ordinarily managed by the compiler (long strings, wide strings, dynamic arrays, variants, and interfaces) can be declared with threadvar, but the compiler does not automatically free the heap-allocated memory created by each thread of execution. If you use these data types in thread variables, it is your responsibility to dispose of their memory from within the thread, before the thread terminates.

Let's look at your shown report more closely.

    A memory block has been leaked. The size is: 120

Self-explanatory. A memory block allocated with a size of 120 bytes was leaked.

    This block was allocated by thread 0x5648, and the stack trace (return addresses) at the time was:
    430547 [FastMM4.pas][FastMM4][_ZN7Fastmm411DebugGetMemEx][8737]
    409534 [System.pas][System][_ZN6System7_GetMemEx][4803]
    412F8C [System.pas][System][_ZN6System17_NewUnicodeStringEi][25403]
    414C3C [System.pas][System][_ZN6System16InternalUStrCatNERNS_13UnicodeStringEiPS0_][29902]
    4156CB [System.pas][System][_ZN6System9_UStrCatNERNS_13UnicodeStringEi][30998]
    5DD71A [System.IniFiles.pas][System.IniFiles][_ZN6System8Inifiles11TMemIniFile8TSection9SetValuesEiNS_13UnicodeStringE][852]
    5DEFA6 [System.IniFiles.pas][System.IniFiles][_ZN6System8Inifiles11TMemIniFile11WriteStringENS_13UnicodeStringES2_S2_][1212]
    14F335F [IDEIni.pas][IDEIni][_ZN6Ideini15TIDEIniSettings13UpdateSectionEN6System13UnicodeStringE][1153]
    14F26E7 [IDEIni.pas][IDEIni][_ZN6Ideini15TIDEIniSettings13UpdateSectionEi][1087]
    14F24FD [IDEIni.pas][IDEIni][_ZN6Ideini15TIDEIniSettings4SaveEiiiiibjPN6System7Classes8TStringsENS1_13UnicodeStringE][1078]
    14DE604 [IDEMain.pas][IDEMain][_ZN7Idemain10TfmIDEMain9FormCloseEPN6System7TObjectERNS1_7Uitypes12TCloseActionE][573]

The leaked memory was allocated by a thread whose ID was 0x5648 at runtime. The stack trace shows the chain of function calls that led up to the allocation of the memory block that was leaked. In this case, the stack trace begins at your TForm.OnClose event handler, so that thread was clearly the main UI thread. The actual chain of function calls was then:

  • IDEMain.TfmIDEMain.FormClose() in IDEMain.pas, which called:
  • IDEIni.TIDEIniSettings.Save() in IDEIni.pas, which called:
  • IDEIni.TIDEIniSettings.UpdateSection(Integer) in IDEIni.pas, which called:
  • IDEIni.TIDEIniSettings.UpdateSection(UnicodeString) in IDEIni.pas, which called:
  • System.IniFiles.TMemIniFile.WriteString() in System.IniFiles.pas, which called:
  • System.IniFiles.TMemIniFile.TSection.SetValues() in System.IniFiles.pas, which called:
  • System._UStrCatN() in System.pas, which called:
  • System.InternalUStrCatN() in System.pas, which called:
  • System._NewUnicodeString() in System.pas, which called:
  • System._GetMem() in System.pas, which called:
  • FastMM4.DebugGetMem() in FastMM4.pas, which allocated the memory that was leaked

So, your OnClose handler was saving a string value to an .INI file, and internally that write performed a string concatenation inside of TSection.SetValues(), the result of which was leaked. I'm guessing because the concatenated string was saved in a TSection object that was itself leaked.

    The block is currently used for an object of class: UnicodeString

Self-explanatory.

    The allocation number is: 675683

FastMM keeps track of how many memory allocations it performs during the lifetime of the program.

    Current memory dump of 256 bytes starting at pointer address 7FF4FBF7E170:
    24 D9 69 01 B0 04 02 00 01 00 00 00 29 00 00 00 50 00 61 00 74 00 68 00 3D 00 43 00 3A 00 5C 00
    50 00 72 00 6F 00 67 00 72 00 61 00 6D 00 20 00 46 00 69 00 6C 00 65 00 73 00 20 00 28 00 78 00
    38 00 36 00 29 00 5C 00 50 00 72 00 6F 00 74 00 6F 00 6E 00 49 00 44 00 45 00 5C 00 50 00 44 00
    53 00 00 00 CA 9A 8A F5 AF D5 F2 FF 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80
    00 00 00 00 00 00 00 00 81 E3 F7 FB F4 7F 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 37 48 0A 00 47 05 43 00 00 00 00 00 B6 08 43 00 00 00 00 00
    9D 95 40 00 00 00 00 00 1C 54 41 00 00 00 00 00 11 55 41 00 00 00 00 00 01 56 45 01 00 00 00 00
    63 02 46 01 00 00 00 00 E2 1E 46 01 00 00 00 00 92 9B 68 00 00 00 00 00 A5 04 41 00 00 00 00 00
    $  Ù  i  .  °  .  .  .  .  .  .  .  )  .  .  .  P  .  a  .  t  .  h  .  =  .  C  .  :  .  \  .
    P  .  r  .  o  .  g  .  r  .  a  .  m  .     .  F  .  i  .  l  .  e  .  s  .     .  (  .  x  .
    8  .  6  .  )  .  \  .  P  .  r  .  o  .  t  .  o  .  n  .  I  .  D  .  E  .  \  .  P  .  D  .
    S  .  .  .  Ê  š  Š  õ  ¯  Õ  ò  ÿ  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €
    .  .  .  .  .  .  .  .    ã  ÷  û  ô    .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .
    .  .  .  .  .  .  .  .  .  .  .  .  7  H  .  .  G  .  C  .  .  .  .  .  ¶  .  C  .  .  .  .  .
      •  @  .  .  .  .  .  .  T  A  .  .  .  .  .  .  U  A  .  .  .  .  .  .  V  E  .  .  .  .  .
    c  .  F  .  .  .  .  .  â  .  F  .  .  .  .  .  ’  ›  h  .  .  .  .  .  ¥  .  A  .  .  .  .  .

This is a dump of the raw data in the memory block that was leaked. That block was located at memory address $7FF4FBF7E170. The first half is the hex-formatted representation of the raw bytes, and the second half is the human-readable ASCII interpretation of those bytes.

Since we know the block belongs to a UnicodeString, we can dig into the data a little further.

A UnicodeString begins with a StrRec header:

StrRec = packed record
{$IF defined(CPUX64)}
  _Padding: LongInt; // Make 16 byte align for payload..
{$ENDIF}
  codePage: Word;
  elemSize: Word;
  refCnt: Longint;
  length: Longint;
end;

The format of the name mangling used in the log tells me you are using one of the Clang-based compilers, so if we assume a 64bit compiler, that would make the StrRec be 16 bytes in size, and if we break up the first 16 bytes of the dump, we get the following values:

24 D9 69 01  _Padding, ignored
B0 04        codePage = 1200, UTF-16
02 00        elemSize = 2, sizeof(WideChar)
01 00 00 00  refCnt   = 1
29 00 00 00  length   = 41 WideChar elements

That is consistent with a valid UnicodeString. So, if we then look at the next ((41+1)*2)=84 bytes in the dump, we see the following:

50 00 61 00 74 00 68 00 3D 00 43 00 3A 00 5C 00
50 00 72 00 6F 00 67 00 72 00 61 00 6D 00 20 00
46 00 69 00 6C 00 65 00 73 00 20 00 28 00 78 00
38 00 36 00 29 00 5C 00 50 00 72 00 6F 00 74 00
6F 00 6E 00 49 00 44 00 45 00 5C 00 50 00 44 00
53 00 00 00

And from the corresponding ASCII dump:

P  .  a  .  t  .  h  .  =  .  C  .  :  .  \  .
P  .  r  .  o  .  g  .  r  .  a  .  m  .     .
F  .  i  .  l  .  e  .  s  .     .  (  .  x  .
8  .  6  .  )  .  \  .  P  .  r  .  o  .  t  .
o  .  n  .  I  .  D  .  E  .  \  .  P  .  D  .
S  .  .  .

Which, taking UTF-16 into account, forms the Unicode string value:

'Path=C:\Program Files (x86)\ProtonIDE\PDS'

That is the actual string that was leaked. The concatenation operation in TSection.SetValues() was likely to join the substrings 'Path', '=', and 'C:\Program Files (x86)\ProtonIDE\PDS' together, assuming TMemIniFile.WriteString() had been called like this:

var Ini: TMemIniFile;
...
Ini.WriteString('Path', 'C:\Program Files (x86)\ProtonIDE\PDS');

The rest of the dumped data is just random garbage that happened to be in the same memory block, because FastMM allocates memory in buckets of fixed-sized blocks. In this case, the leaked UnicodeString was allocated in a bucket that was using 120-byte blocks.

If I had to guess, either you leaked the TMemIniFile object (which should appear elsewhere in the leak report), or TMemIniFile in your version of Delphi has a logic bug that leaks a TSection object (which should appear elsewhere in the leak report). This is where you can now start debugging into your code to trace the root cause of the leak.

Upvotes: 15

Related Questions