Debugger HeapTrace

Für Fragen von Einsteigern und Programmieranfängern...
MacWomble
Lazarusforum e. V.
Beiträge: 999
Registriert: Do 17. Apr 2008, 01:59
OS, Lazarus, FPC: Mint 21.1 Cinnamon / FPC 3.2.2/Lazarus 2.2.4
CPU-Target: Intel i7-10750 64Bit
Wohnort: Freiburg

Debugger HeapTrace

Beitrag von MacWomble »

Seit ich intensiv mit Klassen arbeite passiert es mir hin und wieder, dass ich einen Fehler der Art bekomme:

Code: Alles auswählen

Heap dump by heaptrc unit of /home/klaus/Entwicklung/DBKlassen/DBTest
116677 memory blocks allocated : 29867684/30182800
116675 memory blocks freed     : 29867620/30182736
2 unfreed memory blocks : 64
True heap size : 1835008
True free heap : 1834496
Should be : 1834560
Call trace for block $00007FFFEDA3CC00 size 24
  $0000000000435A0C
Call trace for block $00007FFFEDA3CB00 size 40
  $0000000000435A0C


Irgendwo habe ich wohl etwas reserviert und nicht wieder frei gegeben. Bisher fand ich solche Sachen recht schnell, aber der ist nun hartnäckig. :shock:
Sicher ist es nur eine Kleinigkeit, die den Fehler verursacht, aber eine Mammutaufgabe den Fehler zu finden. :(

Meine Frage ist nun, wie man einen solchen Fehler am besten lokalisiert.
Beim durchsuchen des Quellcodes kann ich nichts finden.
Gibt es für das Debugging eine brauchbare Anleitung oder Tipps, wie man hier am besten vorgeht?
Alle sagten, dass es unmöglich sei - bis einer kam und es einfach gemacht hat.

wp_xyz
Beiträge: 4885
Registriert: Fr 8. Apr 2011, 09:01

Re: Debugger HeapTrace

Beitrag von wp_xyz »

Du musst das Programm mit Debug-Informationen übersetzen: Projekt-Optionen > Compiler-Einstellungen > Debuggen > "Debugger informationen für GDB erzeugen" und "Zeilennummer für Laufzeitfehler-Backtraces anzeigen" markieren. Damit werden im Output von heaptrc die Zeilennummern und das Funktionssymbol angezeigt.

Beliebte Kandidaten für Speicher-Lecks:
- TList und TFPList: Wurde der von den Pointern belegte Speicher wieder freigegeben? (aber nur, wenn sich niemand anders darum kümmert!)
- TStringList: dto für per AddObject eingehängte Objekte
- TListView, TTreeView: dto für Data
- auch .Delete oder .Clear bei diesen Klassen aus demselben Anlass
- Strings in Records, die mit GetMem auf dem Heap angelegt wurden
- VirtualTreeView: OnFreeNode-Event vergessen, wenn die NodeData-Records Strings enthalten

Warf
Beiträge: 1909
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Debugger HeapTrace

Beitrag von Warf »

Das Memory leak muss aber nicht unbedingt von dir kommen. Wenn du ein Package verwendest das ohne debuggersymbole kompiliert wurde (wie die meisten packages) die ein Memory leak erzeugt kannst du ein ähnliches Phänomen bekommen.

Z.B. Der Telnet Client von Indy ist solch ein Fall. Der erzeugt im initialization Memory, die im finalization nicht frei gegeben wird (ist ja eigentlich auch kein Fehler, nach dem finalization kommt nix mehr und daher kann man auch einfach warten bis das OS direkt den ganzen Prozess aus dem Speicher wirft)

MacWomble
Lazarusforum e. V.
Beiträge: 999
Registriert: Do 17. Apr 2008, 01:59
OS, Lazarus, FPC: Mint 21.1 Cinnamon / FPC 3.2.2/Lazarus 2.2.4
CPU-Target: Intel i7-10750 64Bit
Wohnort: Freiburg

Re: Debugger HeapTrace

Beitrag von MacWomble »

@Warf
Der Fehler kommt von mir, da bin ich leider sicher

@wp-xyz
Die Optionen habe ich so eingestellt. Müsste ich dann nicht auch den Aufrufstack mit Daten erhalten? Dieser ist leer.
Alle sagten, dass es unmöglich sei - bis einer kam und es einfach gemacht hat.

wp_xyz
Beiträge: 4885
Registriert: Fr 8. Apr 2011, 09:01

Re: Debugger HeapTrace

Beitrag von wp_xyz »

Beispiel: Das beigefügte Mini-Projekt erzeugt ein Speicherleck bei Zeile 33, weil ein Control ohne Owner erzeugt und nicht mehr explizit freigegeben wird. Alle Einstellungen sind auf default, nur das HeapTrc-Häkchen in den Projekt-Optionen ist gesetzt.

Code: Alles auswählen

procedure TForm1.FormCreate(Sender: TObject);
begin
  FButton := TButton.Create(nil);   // <--- Die Verwendung von nil hier erzeugt ein Speicherleck
  FButton.Parent := self;
  FButton.Left := 8;
  FButton.Top := 8;
  FButton.Caption := 'Test';
end;

Der Output von HeapTrc (der mit SetHeapTraceOutput in der Projekt-Datei in eine Datei umgeleitet wird) demonstriert, dass diese einfache Speichleck mehrere Einträge hinterlassen kann. Wenn man die ganze Log-datei durchscrollt, dann findet man aber Zeilen, die auf die Ursache des Lecks hindeuten:

Code: Alles auswählen

D:\Prog_Lazarus\lazarus_test_projects\project1.exe 
Heap dump by heaptrc unit
770 memory blocks allocated : 1537487/1539064
757 memory blocks freed     : 1535851/1537384
13 unfreed memory blocks : 1636
True heap size : 2097152 (112 used in System startup)
True free heap : 2094336
Should be : 2094528
...
Call trace for block $0261A778 size 108
  $004F57EC  TCONTROL__CREATE,  line 5186 of ./include/control.inc
  $004E842A  TWINCONTROL__CREATE,  line 6577 of ./include/wincontrol.inc
  $00507846  TBUTTONCONTROL__CREATE,  line 64 of ./include/buttoncontrol.inc
  $00507A4C  TCUSTOMBUTTON__CREATE,  line 35 of ./include/buttons.inc
  $00427128  TFORM1__FORMCREATE,  line 33 of unit1.pas                          // <-- Zeile 33 in unit1, methode FormCreate
  $0041B34D  TCUSTOMFORM__DOCREATE,  line 939 of ./include/customform.inc
  $00419917  TCUSTOMFORM__AFTERCONSTRUCTION,  line 149 of ./include/customform.inc
  $0041FB5F  TFORM__CREATE,  line 3169 of ./include/customform.inc
..
 
Dateianhänge
memoryleak_demo.zip
(1.82 KiB) 83-mal heruntergeladen

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 6208
Registriert: So 7. Jan 2007, 10:20
OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
CPU-Target: 32Bit (64Bit)
Wohnort: Burgenland
Kontaktdaten:

Re: Debugger HeapTrace

Beitrag von af0815 »

wp_xyz hat geschrieben: Alle Einstellungen sind auf default, nur das HeapTrc-Häkchen in den Projekt-Optionen ist gesetzt.

Heaptrace darf NUR über das Häckchen in den Projektoptionen aktiviert werden. Heaptrace greift zu einem frühen Zeitpunkt ein, so das es nur über diese Einstellungen geht (Quelle:Grumpy-Thaddy aus den engl. Forum :mrgreen: ). Keinenfalls darf es selbst über uses eingebunden werden.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

martin_frb
Beiträge: 572
Registriert: Mi 25. Mär 2009, 21:12
OS, Lazarus, FPC: Laz trunk / fpc latest release / Win and other
CPU-Target: mostly 32 bit

Re: Debugger HeapTrace

Beitrag von martin_frb »

Die Verwendung von nil hier erzeugt ein Speicherleck

Hast Du irgendwo ein "FButton.Free" (oder Destroy oder FreeAndNil...)?
Und wird das ausgefuehrt bevor die App beendetet wird?

MacWomble
Lazarusforum e. V.
Beiträge: 999
Registriert: Do 17. Apr 2008, 01:59
OS, Lazarus, FPC: Mint 21.1 Cinnamon / FPC 3.2.2/Lazarus 2.2.4
CPU-Target: Intel i7-10750 64Bit
Wohnort: Freiburg

Re: Debugger HeapTrace

Beitrag von MacWomble »

@wp_xyz
Mit einer solchen Trace-Ausgabe käme ich auch klar, aber ich habe nur das aus dem ersten Post.

@martin_frb
Mit FreeAndNil gebe ich die Klassen im Destroy der Form frei:

Code: Alles auswählen

{ FormCreate }
procedure TAdressEditor.FormCreate(Sender: TObject);
begin
  AdressartenListe := TAdressartenListe.Create();
  AdressanredenListe := TAdressanredenListe.Create();
  KontaktdatenListe := TKontaktdatenListe.Create();
  SteuerartenListe := TSteuerartenListe.Create();
  ZahlungsbedingungsListe := TZahlungsbedingungsListe.Create();
 
  FillAdressartenListe;
  FillAdressanredenListe;
  FillSteuerartenListe;
  FillZahlungsbedingungsListe;
end;
 
{ FormDestroy }
procedure TAdressEditor.FormDestroy(Sender: TObject);
begin
  FreeAndNil(AdressartenListe);
  FreeAndNil(AdressanredenListe);
  FreeAndNil(KontaktdatenListe);
  FreeAndNil(SteuerartenListe);
  FreeAndNil(ZahlungsbedingungsListe);
end;                                   


@af0815
Ich habe das über die Projektoptionen gesetzt.
Alle sagten, dass es unmöglich sei - bis einer kam und es einfach gemacht hat.

martin_frb
Beiträge: 572
Registriert: Mi 25. Mär 2009, 21:12
OS, Lazarus, FPC: Laz trunk / fpc latest release / Win and other
CPU-Target: mostly 32 bit

Re: Debugger HeapTrace

Beitrag von martin_frb »

Ok, mit anderen Klassen (anders als TButten / eigen Klassen), kann auch in der Klasse ein leak sein.

Ansonsten, wenn FButton irgendwann neu zugewiesen wird (andern Button), dann wird der original button zum leak.

Der button trace started mit:
$004F57EC TCONTROL__CREATE, line 5186 of ./include/control.inc
was ist in der Zeile (in deiner Version von Lazarus)?

Aber das kann, ein unter-leak sein. Wenn der ganze Button leaked, erzeugt das jede menge kleinerer Leaks, von Objekten/Strings/... die der Button reserviert hat.

martin_frb
Beiträge: 572
Registriert: Mi 25. Mär 2009, 21:12
OS, Lazarus, FPC: Laz trunk / fpc latest release / Win and other
CPU-Target: mostly 32 bit

Re: Debugger HeapTrace

Beitrag von martin_frb »

Mit einer solchen Trace-Ausgabe käme ich auch klar, aber ich habe nur das aus dem ersten Post.


1) Alles (alle Packages, und Project) mit debug info compilieren.
2) Darauf achten, das alle Packages dieselbe Art debug Info, wie das Project haben. Am besten alles mit "Dwarf" oder "Dwarf mit sets"

Wenn das nicht hilft, ggf fpc (RTL) mit debug info.

wp_xyz
Beiträge: 4885
Registriert: Fr 8. Apr 2011, 09:01

Re: Debugger HeapTrace

Beitrag von wp_xyz »

MacWomble hat geschrieben:

Code: Alles auswählen

{ FormCreate }
procedure TAdressEditor.FormCreate(Sender: TObject);
begin
  AdressartenListe := TAdressartenListe.Create();
  AdressanredenListe := TAdressanredenListe.Create();
  KontaktdatenListe := TKontaktdatenListe.Create();
  SteuerartenListe := TSteuerartenListe.Create();
  ZahlungsbedingungsListe := TZahlungsbedingungsListe.Create();
 
  FillAdressartenListe;
  FillAdressanredenListe;
  FillSteuerartenListe;
  FillZahlungsbedingungsListe;
end;
 
{ FormDestroy }
procedure TAdressEditor.FormDestroy(Sender: TObject);
begin
  FreeAndNil(AdressartenListe);
  FreeAndNil(AdressanredenListe);
  FreeAndNil(KontaktdatenListe);
  FreeAndNil(SteuerartenListe);
  FreeAndNil(ZahlungsbedingungsListe);
end;                                   

Das ist genau das was ich meinte: Bei Listen kann es nicht ausreichend sein, die Liste zu zerstören, sondern man muss evtl. auch die Listenelemente zerstören. Nur wenn die Liste von TObjectList/TFPObjectListe abgeleitet ist, werden die Listenelemente automatisch gelöscht (in der Regel, aber auch da nicht immer). Bei TList/TFPList dagegen sind nur Pointer gespeichert und jede Speicheranforderung für ein Listenelement muss exlizit ein entsprechendes Destroy/FreeMem-oder-wie-auch-immer-Gegenstück haben.

Von welcher Liste sind denn TAdressartenListe, TAdressAnredenListe, TKontaktDatenListe und TSteuerArtenListe abgeleitet? Und wie erzeugst du die Elemente für diese Liste und wie fügst du sie der Liste hinzu?

Aber das löst natürlich nicht, warum bei dir im Heaptrc-Log keine Zeilennummern auftauchen.
Zuletzt geändert von wp_xyz am Do 21. Mär 2019, 09:11, insgesamt 1-mal geändert.

martin_frb
Beiträge: 572
Registriert: Mi 25. Mär 2009, 21:12
OS, Lazarus, FPC: Laz trunk / fpc latest release / Win and other
CPU-Target: mostly 32 bit

Re: Debugger HeapTrace

Beitrag von martin_frb »

@MacWomble: Du bist nicht zufällig auf Mac?

https://bugs.freepascal.org/view.php?id=32775

Für 32 bit, versuche es mal mit "stabs" (nicht sicher ob das funktioniert...)

MacWomble
Lazarusforum e. V.
Beiträge: 999
Registriert: Do 17. Apr 2008, 01:59
OS, Lazarus, FPC: Mint 21.1 Cinnamon / FPC 3.2.2/Lazarus 2.2.4
CPU-Target: Intel i7-10750 64Bit
Wohnort: Freiburg

Re: Debugger HeapTrace

Beitrag von MacWomble »

Nenene, nix Mac nix Microsoft :twisted: Mein System gehört mir 8)

Es ist unter Linux Mint Cinnamon. Ich habe mehrere Eingabemasken, welche immer aus einem Formular mit einem DrawGrid aufgerufen werden. Das funktioniert auch bisher überall. Für Adressen habe ich zwei Drawgrids (Adressen und Ansprechpartner und rufe entsprechend das eine oder andere Formular zum Editieren auf. Seither habe ich das Problem. Es könnte auch an der Liste liegen, aber der tritt nur auf, wenn ich auch das Editierformular geöffnet hatte. Zuvor hatte ich nur ein Drawgrid für Adressen und das Problem war nicht da. Ich vermute deshalb den Fehler an ganz anderer Stelle.
Inzwischen habe ich mich aber auch dazu entschieden (aus Komfort- und Logikgründen) das DrawGrid für Ansprechpartner nicht mehr so zu implementieren, wodurch sich das Problem von selbst lösen dürfte.

Generell war die Frage, wie man einen solchen Fehler finden soll. Keine weiteren Angaben sind verfügbar und mit Haltemarken kommt man auch nicht wirklich weiter.

Ich hatte gehofft, es würde hier einen Tipp zur (generellen) Fehlersuche in einem solchen Fall geben, was aber anscheinend nicht der Fall ist.

Bei einer Heaptrace-Ausgabe wie oben von wp_xyz kann man ja weitere Informationen finden. Bei der von meinem Programm erzeugten habe ich keine verwertbare Information erhalten. Dies veranlasste mich dazu, hier nach zu fragen.

Vielen Dank jedenfalls für eure Hinweise - hier bleibt wohl nur 'händisches' Suchen und Probieren als Lösungsansatz.
Alle sagten, dass es unmöglich sei - bis einer kam und es einfach gemacht hat.

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 6208
Registriert: So 7. Jan 2007, 10:20
OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
CPU-Target: 32Bit (64Bit)
Wohnort: Burgenland
Kontaktdaten:

Re: Debugger HeapTrace

Beitrag von af0815 »

Nur so eine Frage, wenn du das Beispiel von wp laufen lässt, hast dann auch keine Zeilennummern ? Nur das man deine IDE da ausschließen kann, da du meines wissens nicht mit dem originalen Lazarus arbeitest.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

martin_frb
Beiträge: 572
Registriert: Mi 25. Mär 2009, 21:12
OS, Lazarus, FPC: Laz trunk / fpc latest release / Win and other
CPU-Target: mostly 32 bit

Re: Debugger HeapTrace

Beitrag von martin_frb »

MacWomble hat geschrieben:Ich hatte gehofft, es würde hier einen Tipp zur (generellen) Fehlersuche in einem solchen Fall geben, was aber anscheinend nicht der Fall ist.


1) Wenn das Leak innerhalb eines Konstruktors ist, oder genauer ein Assignment an ein Objekt-Feld enthält, dann kann es sein das das ganze Object leakt.
Ebenso kann es immer sein das ein Objekt leakt, weil es von einen anderem auch geleaktem Objekt gehalten wird.

2) String. Leaktrace zeigt wo der Speicher für den String reserviert wurde. Das Leak is fast immer woanders. Irgendwo anders....
Strings leaken sehr selten alleine... Da ist fast immer irgendwo ein Object, oder ähnliches.

Antworten