Entwickler-Ecke

Sonstiges (Delphi) - Seltsames Speicherloch


Gausi - Mo 03.09.07 10:57
Titel: Seltsames Speicherloch
Ich habe seit kurzem ein kleines Memory-Leak. Laut FastMM4 ist es ein String, und wenn ich mir die Log-Datei erstellen lasse, sehe ich, dass der verantwortliche String genau der Projektname ist (ich habe das mal getestet, indem ich den geändert habe). Wenn ich die 8 Bytes vor dem String richtig interpretiere (das sind ja Längenangabe und Referenzzähler, oder?) dann gibt es genau eine Referenz auf diesen String. Ich bin mir aber recht sicher, dass ich den Exenamen/Paramstr(0) ohne den Pfad nirgendwo explizit benutze.

Jemand ne Idee, wo und wie ich die Ursache dafür suchen und beheben kann? Hier mal die Log-Datei, falls jemand damit was anfangen kann:

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
A memory block has been leaked. The size is: 44

Stack trace of when this block was allocated (return addresses):
402A57 
404C61 
405225 
40D661 
42E01C 
4047EC 
404853 
4073B3 
5AC7AE 

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

The allocation number is: 146

Current memory dump of 256 bytes starting at pointer address 16A9158:
01 00 00 00 1F 00 00 00 62 6F 61 68 64 61 73 69 73 74 65 69 6E 74 6F 6C 6C 65 73 70 72 6F 67 72
61 6D 6D 2E 65 78 65 00 10 6F 95 FE 80 80 80 80 00 00 00 00 31 92 6A 01 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 21 0F 00 00 57 2A 40 00 61 4C 40 00 25 52 40 00 61 D6 40 00 CB 91 51 00
E0 B1 55 00 EC 47 40 00 53 48 40 00 B3 73 40 00 77 2A 40 00 D6 4B 40 00 3F 5A 40 00 76 B1 55 00
8C 47 40 00 BA C8 5A 00 33 38 E3 75 BD A9 23 77 00 00 00 00 28 00 00 00 00 00 00 00 90 91 6A 01
80 8C 5E 00 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80
80 80 80 80 80 80 80 80 6F 6E 95 FE 80 80 80 80 00 00 00 00 D1 92 6A 01 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 1F 0F 00 00 57 2A 40 00 61 4C 40 00 25 52 40 00 61 D6 40 00 CB 91 51 00
.  .  .  .  .  .  .  .  b  o  a  h  d  a  s  i  s  t  e  i  n  t  o  l  l  e  s  p  r  o  g  r
a  m  m  .  e  x  e  .  .  o  •  þ  €  €  €  €  .  .  .  .  1  ’  j  .  .  .  .  .  .  .  .  .
.  .  .  .  .  .  .  .  !  .  .  .  W  *  @  .  a  L  @  .  %  R  @  .  a  Ö  @  .  Ë  ‘  Q  .
à  ±  U  .  ì  G  @  .  S  H  @  .  ³  s  @  .  w  *  @  .  Ö  K  @  .  ?  Z  @  .  v  ±  U  .
Œ  G  @  .  º  È  Z  .  3  8  ã  u  ½  ©  #  w  .  .  .  .  (  .  .  .  .  .  .  .  �  ‘  j  .
€  Œ  ^  .  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €
€  €  €  €  €  €  €  €  o  n  •  þ  €  €  €  €  .  .  .  .  Ñ  ’  j  .  .  .  .  .  .  .  .  .
.  .  .  .  .  .  .  .  .  .  .  .  W  *  @  .  a  L  @  .  %  R  @  .  a  Ö  @  .  Ë  ‘  Q  .

--------------------------------2007/9/3 11:38:42--------------------------------
This application has leaked memory. The small block leaks are (excluding expected leaks registered by pointer):

37 - 44 bytes: String x 1


Chryzler - Mo 03.09.07 11:50

Kann dir zwar bei dem Problem ned helfen, aber ich frage mich gerade ob ein 44 Byte großes Speicherloch so schlimm ist? Afaik wird der Speicher ja beim Beenden dann sowieso wieder freigegeben, er steht halt nur nicht während der Laufzeit zur Verfügung. Oder hab ich was komplett falsch verstanden? Ich zieh mir mal den Wikipedia-Artikel rein...


BenBE - Mo 03.09.07 14:25

Kannst Du für deine EXE von Delphi mal bitte eine Detaillierte Mapfile passend zum Log erzeugen lassen?

Benötigt wird: Detaillierte Mapfile, Mit Debug-DCUs. TD32 oder externe Symbole brauchste nicht.
Da eine Mapfile recht viel über den Source verrät, kannst Du mir ggf. auch die besagte Mapfile (und ggf. den Log, falls sich da was von den Adressen geändert hat) per PN zuschicken.


Gausi - Mo 03.09.07 15:51

Ok, das Thema ist erledigt. BenBE hat mal etwas mit der Mapfile rumgezaubert und den Ursprung des Lecks nach und nach eingegrenzt. Das Leck lag nicht bei mir, sondern in der GnuGetText, die ich zur Internationalisierung einsetze(n möchte). Da wurde im initialization-Teil ein String param0 erstellt (und zwar als param0 := extractfile(paramstr(0))). Habe also ein param0 := ''; in den finalization-Teil eingefügt, und das Problem ist erledigt.

@Chryzler: Klar ist das nicht schlimm (und tatsächlich war das Leck noch ein paar Bytes kleiner), aber sowas kann unter Umständen auch Indiz für andere Probleme sein. Und deswegen möchte is sowas beheben.


BenBE - Mo 03.09.07 15:54

*G* Irgendwie fast das gleiche wie Gausi getippt. Hier aber noch mal mit exakten Debug-Infos, falls jemand auch die Unit benutzen sollte:

Ursache war eine globale Variable (Param0) in der Unit GnuGettext (Sprachlokalisierung), die in Zeile 2791 (Initialization-Abschnitt der Unit) wie folgt initialisiert wurde:

Delphi-Quelltext
1:
Param0 := lowercase(extractfilename(paramstr(0)));                    


Eine explizite Zuweisung eines Leerstrings (Param0 := '';) im Finalization-Abschnitt der Unit behebt das Problem.

Ach ja: Ich hatte zu keiner Zeit den Source des Programmes ... Nur um Neidern vorzubeugen. :mrgreen:


Martin1966 - Mo 03.09.07 15:56

Gut zu wissen, danke! :zustimm:

Wäre eventuell sinnvoll diesen Bug den Entwicklern von GnuGettext zu melden.

Lg, Martin


Chryzler - Mo 03.09.07 16:02

Wäre eventuell sinnvoll, uns auch zu zeigen, wie man das aus einer Mapfile rauskriegt. *neidisch* :motz:


BenBE - Mo 03.09.07 16:21

Wenn Du das gleich in nem eigenen Programm nutzten willst, solltest Du dir die Datei ODbgMapfile und die referenzierten Units anschauen.

Ich schreib das demnächst aber auch mal als Stand-Alaone, damit man die Mapfile direkt auswerten lassen kann (für gegebene Adressen).

Grobe Anleitung:
1. Fehleradresse suchen --> VPtr_ErrAddr
2. Modul-Basisadresse der EXE auslesen (meist 00400000) --> VPtr_ExeModule
3. Offset des Codesegmentes innerhalb der EXE ermitteln --> VOff_CodeSeg
4. Die relative Adresse der Fehleradresse im Code-Segment bestimmen --> RPtr_ErrAddr := VPtr_ErrAddr - VPtr_ExeModule - VOff_CodeSeg;
5. Suchen nach:
5.1 Dem Unit-Block, dessen Start-Adresse KLEINER GLEICH RPtr_ErrAddr UND dessen Endadresse GRÖSSER als RPtr_ErrAddr ist --> Str_Unitname
5.2 Den globalen Symbol-Eintrag, dessen Startadresse KLEINER GLEICH RPtr_ErrAddr ist. (der Eintrag, mit der größten Adresse, die dieses Kriterium erfüllen) --> Str_GlobalSym
5.3 Das Gleiche für die Zeilennummern wiederholen --> Int_LineNumber
6. Sich über das Ergebnis freuen --> Result := Format('Die besagte Adresse liegt in Unit %s in der Routine %s bei Zeile $d', [Str_Unitname, Str_GlobalSym, Int_LineNumber]);

Kleiner Hinweis bei Stacktraces:
Da bei Stacktraces die Adresse des nächsten Befehls angegeben wird, muss man u.U. eine Zeile weiter oben gucken. Das gilt genau dann, wenn der CALL innerhalb dessen der Fehler auftrat, der letzte ASM-Befehl der Source-Umsetzung einer Zeile war.


Gausi - Mo 03.09.07 16:38

@Fehlermelden: Wenn man den Links zum SVN folgt, dann gibts da eine Version, wo dieser Fehler bereits gefixed ist. Wahrscheinlich wurde das nur noch nicht in die install.exe übernommen.

btw.: man muss diese Zeile auch nicht in den finalization-Teil einpacken - am Ende vom init-Teil geht auch. Die Variable wird nur dort benötigt.


Chryzler - Mo 03.09.07 17:21

user profile iconBenBE hat folgendes geschrieben:
Wenn Du das gleich in nem eigenen Programm nutzten willst, solltest Du dir die Datei ODbgMapfile und die referenzierten Units anschauen.

Ich schreib das demnächst aber auch mal als Stand-Alaone, damit man die Mapfile direkt auswerten lassen kann (für gegebene Adressen).

Grobe Anleitung:
1. Fehleradresse suchen --> VPtr_ErrAddr
2. Modul-Basisadresse der EXE auslesen (meist 00400000) --> VPtr_ExeModule
3. Offset des Codesegmentes innerhalb der EXE ermitteln --> VOff_CodeSeg
4. Die relative Adresse der Fehleradresse im Code-Segment bestimmen --> RPtr_ErrAddr := VPtr_ErrAddr - VPtr_ExeModule - VOff_CodeSeg;
5. Suchen nach:
5.1 Dem Unit-Block, dessen Start-Adresse KLEINER GLEICH RPtr_ErrAddr UND dessen Endadresse GRÖSSER als RPtr_ErrAddr ist --> Str_Unitname
5.2 Den globalen Symbol-Eintrag, dessen Startadresse KLEINER GLEICH RPtr_ErrAddr ist. (der Eintrag, mit der größten Adresse, die dieses Kriterium erfüllen) --> Str_GlobalSym
5.3 Das Gleiche für die Zeilennummern wiederholen --> Int_LineNumber
6. Sich über das Ergebnis freuen --> Result := Format('Die besagte Adresse liegt in Unit %s in der Routine %s bei Zeile $d', [Str_Unitname, Str_GlobalSym, Int_LineNumber]);

Kleiner Hinweis bei Stacktraces:
Da bei Stacktraces die Adresse des nächsten Befehls angegeben wird, muss man u.U. eine Zeile weiter oben gucken. Das gilt genau dann, wenn der CALL innerhalb dessen der Fehler auftrat, der letzte ASM-Befehl der Source-Umsetzung einer Zeile war.

Ähm, danke für die Erklärung erstmal, also das ganze geht mit OllyDbg und dem Plugin so wie ich das verstanden hab? Probier ich später mal aus wenn ich mehr Zeit hab. Na ich meld mich auf jeden Fall nochmal weils nicht geht.. Thx :zustimm:


alias5000 - Mo 03.09.07 18:00

user profile iconBenBE hat folgendes geschrieben:
Ach ja: Ich hatte zu keiner Zeit den Source des Programmes ... Nur um Neidern vorzubeugen. :mrgreen:


Angeber :P :mrgreen:


BenBE - Mo 03.09.07 20:53

@Chryzler: Nope. Das ganze geht mit reinem Notepad. Einfach die Mapfile mit Notepad öffnen, die Standard-Annahmen VPtr_ExeModule := $00400000; und VOff_CodeSeg := $00001000; treffen und fröhlich drauf los rechnen. Das einzige, was wahrscheinlich sich ändern kann, ist der Modul-Offset im Speicher. Dieser kann (wenn man wirklich will) unter Projekt-->Optionen-->Compiler-->Basisadresse gändert werden. Bei DLLs reichen die Angaben vom Prozess-Explorer (oder dem DebugViewer).

Der Vorteil an dieser Methode ist, dass man sie auf beliebige EXE-Files anwenden kann, zu denen man die Mapfile, aber nicht den Source parat hat, weil dieser z.B. nicht mehr in dieser Form existiert (Kunde meldet Bug in alter Version) oder der Bug so selten reproduziert werden kann (z.B. nur beim Kunde) und man daher schlecht den Source oder Debug-Symbole herausgeben kann.

@alias5000: Wozu muss man Programme starten, wenn man ihre Fehler auch so findet ;-) Im Delphi-Debugger wäre dieser Fehler übrigens so gut wie unauffindbar gewesen, da dieser Teil der Programm-Ausführung nahezu nicht debuggtbar ist.

@Chryzler: Wenn's nicht geht, ich stell demnächst mal noch ne EXE-Version des Parsers online, wo man die Mapfile direkt laden kann und dann seine Adressen automatisch aufgelöst bekommt. Für alle, die zu Rechen- und Suchfaul sind ;-)