Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   Program crasht beim Schließen, aber... (https://www.delphipraxis.net/203495-program-crasht-beim-schliessen-aber.html)

freejay 24. Feb 2020 09:15

Program crasht beim Schließen, aber...
 
... nur manchmal: Mal crasht* es, wenn es von der IDE gestartete wird - selbst wenn man "Ohne Debugger ausführen" auswählt, aber vom Explorer aus gestartet nicht... Mal umgekehrt...

Ich programmiere ja nun schon seit 20 Jahren in Delphi und konnte irgendwann jedes Problem lösen, aber hier stecke ich nun völlig fest und hab keine Ahnung mehr, wo oder wie ich suchen soll. Ich habe mittels Debugging schon ein paar mal die Stelle gefunden, an der es dann final gekracht hat: Beide Male war es ein .Destroy (auf unterster Ebene), dass ein Objekt, das bereits nil war, löschen wollte. Nur war das ein paar Tage später (in denen ich vermutlich auch Änderungen am Code vorgenommen hatte) eine andere Stelle und ein anderes Objekt!

Zum Teil kommt die Fehlermeldung auch bei einem Kollegen, bei mir und anderen aber nicht. Die Probleme haben vor ca. einem halben Jahr angefangen - den Auslöser also bei irgendeinem Update (der IDE oder von Komponenten) dürfte so gut wie unmöglich werden. Hier scheint doch irgendwas mit der Speicherverwaltung durcheinander zu gehen - oder so. Das ist mir dann echt zu hoch...

Habt Ihr noch irgendwelche Ideen, wie ich mich dem Problem nähern kann? Mir fällt nix mehr dazu ein (und es ist eines meiner wichtigsten Projekte...).



*Mit "crash" meine ich eine "Access violation".

Sinspin 24. Feb 2020 09:22

AW: Program crasht beim Schließen, aber...
 
Hallo, Ursachen, außer Programmierfehler, gibt es viele. Windows Patches, Win Defender, Virenscanner. Es kann alles sein und habe auch schon alles gehabt.
Zum Thema Programmierfehler, lass mal einen Scanner für Speicherlöcher mitlaufen. Das hat mir schon manchmal die Augen göffnet.

jziersch 24. Feb 2020 09:27

AW: Program crasht beim Schließen, aber...
 
Tip: Besorg Dir FastMem, aktiviere den FullDebugMode and lasse auf doppelt freigegebene Objekte prüfen.

freejay 24. Feb 2020 09:44

AW: Program crasht beim Schließen, aber...
 
Zitat:

Zitat von jziersch (Beitrag 1458174)
Tip: Besorg Dir FastMem, aktiviere den FullDebugMode and lasse auf doppelt freigegebene Objekte prüfen.

Meinst Du damit FastMM oder ist FastMem etwas anderes?

MyRealName 24. Feb 2020 09:48

AW: Program crasht beim Schließen, aber...
 
Er meint ganz sicher FastMM.
Auch MadExcept ist eine gute Variante, da es ja auch den StackTrace mit anzeigt. Einfach mit Reinkompilieren und fertig

p80286 24. Feb 2020 10:06

AW: Program crasht beim Schließen, aber...
 
Zitat:

Zitat von freejay (Beitrag 1458168)
Nur war das ein paar Tage später (in denen ich vermutlich auch Änderungen am Code vorgenommen hatte) eine andere Stelle und ein anderes Objekt!

Zum Teil kommt die Fehlermeldung auch bei einem Kollegen, bei mir und anderen aber nicht. Die Probleme haben vor ca. einem halben Jahr angefangen - den Auslöser also bei irgendeinem Update (der IDE oder von Komponenten) dürfte so gut wie unmöglich werden.

Das klingt doch sehr nach einer nicht initialisierten Variablen, oder einem "wildgewordenen" Pointer. die "Schuld" würde ich da nicht bei einem Update, sondern eher bei einer cleveren Lösung suchen.

Gruß
K-H

scrat1979 24. Feb 2020 10:17

AW: Program crasht beim Schließen, aber...
 
Ich hatte mal ein ähnliches Problem und bekam auf der Suche nach dem Fehler graue Haare. Ursache war letztendlich ein fehlender „override“ in der Destructor-Deklaration des selbst erstellten Objektes. Prüfe auch mal, ob überall der übergeordnete Desktruktor mittels inherited aufgerufen wird. Auch als Profi kann einem das ja mal durch die Lappen gehen... viel Erfolg!

himitsu 24. Feb 2020 11:30

AW: Program crasht beim Schließen, aber...
 
Im Delphi ist schon länger FastMM drin, aber nur in einer kleinen Version.

Außer dem einfachen MemoryLeak-Test, den man per Variable aktivieren kann, fehlt der Rest.

Beim großen FastMM kann man eben auch beim Freigeben von OLbjekten/Pointern ein Überschreiben aktivieren, was spätere Zugriffe erkennen lässt.
Normal wird Speicher ja nicht sofort freigeben und man kann nach dem Free/Destroy noch auf Einiges zugreifen, mit dem ungültigen Zeiger.

freimatz 24. Feb 2020 12:31

AW: Program crasht beim Schließen, aber...
 
Ginge das auch mit madExcept? Habe da keine Option genau dazu gefunden.

freejay 24. Feb 2020 12:58

AW: Program crasht beim Schließen, aber...
 
Zitat:

Zitat von jziersch (Beitrag 1458174)
Tip: Besorg Dir FastMem, aktiviere den FullDebugMode and lasse auf doppelt freigegebene Objekte prüfen.

Danke für den Tipp. Das werde ich mir die Tage mal ansehen.

freejay 24. Feb 2020 12:59

AW: Program crasht beim Schließen, aber...
 
Zitat:

Zitat von scrat1979 (Beitrag 1458182)
Ich hatte mal ein ähnliches Problem und bekam auf der Suche nach dem Fehler graue Haare. Ursache war letztendlich ein fehlender „override“ in der Destructor-Deklaration des selbst erstellten Objektes. Prüfe auch mal, ob überall der übergeordnete Desktruktor mittels inherited aufgerufen wird. Auch als Profi kann einem das ja mal durch die Lappen gehen... viel Erfolg!

Danke! Schau ich mir mal an. An sowas hätte ich nie gedacht...

hhcm 24. Feb 2020 13:09

AW: Program crasht beim Schließen, aber...
 
Zitat:

Zitat von freimatz (Beitrag 1458202)
Ginge das auch mit madExcept? Habe da keine Option genau dazu gefunden.

Meinst du den Memory Leak test? Das geht in den MadExcept Settings unter "active error search" auf der ersten Seite "report resource leaks"

MyRealName 24. Feb 2020 13:49

AW: Program crasht beim Schließen, aber...
 
Zitat:

Zitat von freimatz (Beitrag 1458202)
Ginge das auch mit madExcept? Habe da keine Option genau dazu gefunden.

Wenn es um die Memory leaks geht, dann einfach in den basic settings schauen, ganz unten unter "report resource leaks"

hhcm 24. Feb 2020 17:58

AW: Program crasht beim Schließen, aber...
 
Zitat:

Zitat von MyRealName (Beitrag 1458208)
Wenn es um die Memory leaks geht, dann einfach in den basic settings schauen, ganz unten unter "report resource leaks"

Knappe Stunde später, gleiche Antwort.

freimatz 24. Feb 2020 18:55

AW: Program crasht beim Schließen, aber...
 
Jein. Es ging mir nicht um Memory Leaks sondern um "auf doppelt freigegebene Objekte". Das ist ja quasi das Gegenteil.

Inzwischen habe ich es wohl gefunden. Es ist die wohl Option "instantly crash on buffer overrun/underrun".

Ging bei mir leider nicht, weil es dann ein out of memory Fehler kam. Ich meinem Falle kann ich den Fehler anderweitig eingrenzen. Aber gut zu wissen.

himitsu 25. Feb 2020 00:52

AW: Program crasht beim Schließen, aber...
 
Sowas kann man per se sich auch selbst schnell bauen.
Eigentlich würde es reichen, wenn man das FreeMemory überschreibt und dort den Inhalt "zerstört", dann werden Zugriffe nach dem Free fehlschlagen.

Der wichtige Teil ist natürlich das FillMemory/FillChar. :zwinker:
Delphi-Quellcode:
function MyFreeMem(P: Pointer): Integer;
begin
  FillMemory(P, UnknownSize, $A5); // 1010 0101 = HighBit + Odd
  Result := SysFreeMem(P);
end;

var
  MemMgr: TMemoryManagerEx;

initialization
  GetMemoryManager(MemMgr);
  MemMgr.FreeMem := MyFreeMem;
  SetMemoryManager(MemMgr);

finalization
  GetMemoryManager(MemMgr);
  MemMgr.FreeMem := SysFreeMem; // oder MemMgr.FreeMem im Init speichern
  SetMemoryManager(MemMgr);

end.
Aber leider ist es nicht so einfach im FreeMem die Größe rauszubekommen.
(wäre auch zu einfach gewesen, wenn diese MemoryAPI uns den Wert direkt als Parameter geben würde :roll:)

Im Windows (wenn FastMM verwendet) könnte erstmal rausfinden in was für einem Block (SmallXXX, Medium oder Large) der Speicher liegt
und dementsprechend dann auslesen wie groß der Speicher ist (inkl. dem zusätzlichen Speicher dahinter).
Bei den Small ist die Größe bekannt und bei den Anderen steht sie (ähnlich wie hier) vor den Daten gespeichert.

In Liunx/MacOS/iOS/Android kann es nochmal anders aussehn.

Nja, also der umständlicheren Einfachheit halber einfach die Größe noch mit einfügen, um schnell und sicher den Wert vom GetMem zu bekommen.
Delphi-Quellcode:
function MyGetMem(Size: NativeInt): Pointer;
begin
  Inc(Size, SizeOf(Integer));
  Result := SysGetMem(Size);
  PInteger(Result)^ := Size;
  Inc(NativeInt(Result), SizeOf(Integer));
end;

function MyFreeMem(P: Pointer): Integer;
begin
  Dec(NativeInt(P), SizeOf(Integer));
  FillMemory(P, PInteger(P)^, $A5); // 1010 0101 = HighBit + Odd
  Result := SysFreeMem(P);
end;

function MyReallocMem(P: Pointer; Size: NativeInt): Pointer;
begin
  Dec(NativeInt(P), SizeOf(Integer));
  Inc(Size, SizeOf(Integer));
  Result := SysReallocMem(P, Size);
  PInteger(Result)^ := Size;
  Inc(NativeInt(Result), SizeOf(Integer));
end;

function MyAllocMem(Size: NativeInt): Pointer;
begin
  Inc(Size, SizeOf(Integer));
  Result := SysAllocMem(Size);
  PInteger(Result)^ := Size;
  Inc(NativeInt(Result), SizeOf(Integer));
end;

var
  MemMgr: TMemoryManagerEx;

initialization
  GetMemoryManager(MemMgr);
  MemMgr.GetMem    := MyGetMem;
  MemMgr.FreeMem   := MyFreeMem;
  MemMgr.ReallocMem := MyReallocMem;
  MemMgr.AllocMem  := MyAllocMem;
  SetMemoryManager(MemMgr);

finalization
  GetMemoryManager(MemMgr);
  MemMgr.GetMem    := SysGetMem;
  MemMgr.FreeMem   := SysFreeMem;
  MemMgr.ReallocMem := SysReallocMem;
  MemMgr.AllocMem  := SysAllocMem;
  SetMemoryManager(MemMgr);

end.
und nicht vergessen, dass dieses in der DPR als erste Unit im Uses sein muß

Incocnito 25. Feb 2020 07:03

AW: Program crasht beim Schließen, aber...
 
Leider etwas ab vom Thema, aber mit dem Memory-Manager habe ich 0 Erfahrung, daher interessiert mich das gerade ...
1) Du mischt in deinem Beispielcode Integer und NativeInt (zumindest für mich augenscheinlich), ist das ein "Fehler"
der nicht so schlimm ist, da die meisten 32-Bit-Anwendungen schreiben, oder muss das tatsächlich so aus einem
bestimmten Grund, oder ist das Beispiel unvollständig (sieht vollständig aus) und läuft so eh nicht, oder ...?
2) Was ist "Alert" im initialization-Block? Ohne Semikolon? In der Delphi-Hilfe habe ich (so auf die Schnelle)
nichts finden können.

Sorry, dass ich hier Off-Topic schreibe, aber ich verweise nun ja auf einen Post, daher denke ich ist das hier
sinnvoller als einen eigenen Thread auf zu machen.

LG
Incocnito

himitsu 25. Feb 2020 09:59

AW: Program crasht beim Schließen, aber...
 
Ja, das Mischen war Absicht, um besser erkennen zu können was wo damit gekeint ist.
NativeInt für den Pointer-Cast und Integer für die Size.

Nochmal überlegt wäre in Bezug auf 64 Bit IntPtr für Pointer-Cast und NativaInt für die Size wohl besser, falls doch jemand einen Speicherblock größer als 2 bzw. 4 GB reservieren will.


Allert?
Upsss, keine Ahnung ... da hab ich wohl geschlafen, beim Copy&Paste.


Wie gesagt, der "eigentliche" Code besteht hier nur aus dem FillMemory bzw. FillChar, womit der Speicher zerstört und mit Bytes gefüllt wird, die einen Fehler provizieren sollen, sobald jemand darauf zugreift.



Es trifft natürlich nicht alle Fälle, denn wenn etwas Anderes diesen Speicher inzwischen wiederverwendet ... wenn das ein Objekt ist, dann wird Free natürlich weiterhin falsch ausgerührt.
Hier könnte man die eigentliche Freigabe im MM noch verzögern (für kleinere Speicherblöge, da Objekte ja selten groß sind)

freejay 25. Feb 2020 12:04

AW: Program crasht beim Schließen, aber...
 
Danke schon mal für die Tipps.

Wie ich jetzt FastMM ausprobieren wollte, ist das nächste komische Problem aufgetaucht (ist nicht durch FastMM verursacht):

Im Moment kann ich das Projekt IM DEBUG MODUS nicht mal starten. Da kommt ein Fehler in der Funktion GetDynaMethod in Unit System in der markierten Zeile:

Delphi-Quellcode:
function GetDynaMethod(vmt: TClass; selector: SmallInt): Pointer;
{$IFDEF PUREPASCAL}
type
  TDynaMethodTable = record
    Count: Word;
    Selectors: array[0..9999999] of SmallInt;
    {Addrs: array[0..0] of Pointer;}
  end;
  PDynaMethodTable = ^TDynaMethodTable;
var
  dynaTab: PDynaMethodTable;
  Parent: Pointer;
  Addrs: PPointer;
  I: Cardinal;
begin
  while True do
  begin
    dynaTab := PPointer(PByte(vmt) + vmtDynamicTable)^; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<
    if dynaTab <> nil then
    begin
      for I := 0 to dynaTab.Count - 1 do
        if dynaTab.Selectors[I] = selector then
        begin
          Addrs := PPointer(PByte(@dynaTab.Selectors) + dynaTab.Count * SizeOf(dynaTab.Selectors[0]));
          Result := PPointer(PByte(Addrs) + I * SizeOf(Pointer))^;
          Exit;
        end;
    end;
    Parent := PPointer(PByte(vmt) + vmtParent)^;
    if Parent = nil then Break;
    vmt := PPointer(Parent)^;
  end;
  Result := nil;
end;
{$ELSE !PUREPASCAL}
...
Eigentlich wird im normalen Quelltext nur eine Action auf enabled bzw. disabled gesetzt (nicht erschrecken: uralter Quelltext mit deutschen Bezeichnungen... :oops:):

Delphi-Quellcode:
  Form1.acDateiSpeichern.Enabled := Form1.bVeraendert;
Ich bekomme keinen Fehler, wenn ich die RELEASE VERSION starte...

Irgendwelche Ideen dazu???

freejay 25. Feb 2020 12:11

AW: Program crasht beim Schließen, aber...
 
PS: Ich verwende keine Pointer und auch praktisch keine Variablen, die auf Objekte zeigen...

himitsu 25. Feb 2020 14:19

AW: Program crasht beim Schließen, aber...
 
Form1 oder acDateiSpeichern ist dann wohl nil/ungültig.

freejay 25. Feb 2020 14:53

AW: Program crasht beim Schließen, aber...
 
Zitat:

Zitat von himitsu (Beitrag 1458329)
Form1 oder acDateiSpeichern ist dann wohl nil/ungültig.

Die Zuweisung an die Action acDateiSpeichern löst eine Übertragung des Enabled-Status auf Controls, die diese Action als Action eingebunden haben aus. Und da geht's schief (Unit System.Action):


Delphi-Quellcode:
procedure TContainedAction.SetEnabled(Value: Boolean);
var
  I: Integer;
begin
  if Value <> FEnabled then
  begin
    if Assigned(ActionList) then
    begin
      if ActionList.State = asSuspended then
      begin
        FEnabled := Value;
        Exit;
      end
      else if (ActionList.State = asSuspendedEnabled) then
        Value := True;
    end;
    for I := 0 to ClientCount - 1 do
      if Clients[I] is TContainedActionLink then
        TContainedActionLink(Clients[I]).SetEnabled(Value); // <<<<<<<<<<<<<<<<<<<<<<<<<<<
    FEnabled := Value;
    Change;
  end;
end;
Wenn mir jetzt jemand verraten könnte, wie ich an den Namen des Controls, das mittels Client[i] mit der Action verbunden ist, rankomme...

himitsu 25. Feb 2020 17:56

AW: Program crasht beim Schließen, aber...
 
ActionLink.Action.ActionComponent ?

Wenn das nicht hilft, dann könnte man sich noch aus ActionLink.OnChange das Objekt des Methoden-Zeigers extrahieren.

freejay 27. Feb 2020 10:49

AW: Program crasht beim Schließen, aber...
 
Vielen Dank für die Tipps!

Ich konnte das Problem, das offenbar sowohl für den Crash beim Start (nur im Debug-Modus) als auch beim Beenden (nur im Release-Modus) verantwortlich war, finden:

Delphi-Quellcode:
TAction(Sender).Checked := true;


Diese unscheinbare Zeile wurde leider in einem Fall mit dem Formular als Sender aufgerufen... Das schreibt halt das true irgendwohin... Obwohl dieser Fehler da "schon immer" drin gewesen ist, hatte er wohl erst jetzt durch Erweiterungen des Codes (oder Änderungen in Bibliotheken) Auswirkungen.

Die ersten Tests lassen mich jedenfalls hoffen, dass das tatsächlich die Ursache für die Probleme war.

Puh!

Rolf Frei 27. Feb 2020 13:20

AW: Program crasht beim Schließen, aber...
 
Glückwunsch, dass du es gefunden hast. Sowas kann bei einem Hardcast passierern. Würdest du (Sender as TAction).Checked schreiben, hättest du eine Exception "Ungültige Typumwandlung" erhalten.

freejay 27. Feb 2020 13:24

AW: Program crasht beim Schließen, aber...
 
Zitat:

Zitat von Rolf Frei (Beitrag 1458530)
Glückwunsch, dass du es gefunden hast. Sowas kann bei einem Hardcast passierern. Würdest du (Sender as TAction).Checked schreiben, hättest du eine Exception "Ungültige Typumwandlung" erhalten.

Gut zu wissen! Der Unterschied war mir nicht bewusst. Ich vermeide Zeiger und Typumwandlungen fast vollständig, daher bin ich da nicht so firm... :roll:

himitsu 27. Feb 2020 14:02

AW: Program crasht beim Schließen, aber...
 
Bei einer harten Typkonvertierung wird der Typ der Variable direkt geändert und man sagt dem Compiler das ist jetzt so. (der Compiler macht da nur noch eine Prüfung auf die Speichergröße der Typen) und zur Laufzeit ist das dann alles direkt so.

bei der "weichen" Konvertierung prüft er Compiler nur auf Verwandtschaft von Variablen-Typ und Zieltyp und zur Laufzeit wird eine Funktion aufgerufen, die intern den "aktuellen" Typ prüft und im Fehlerfall eine Exception wirft.
als Generic kannst dir dieses etwa so vorstellen
Delphi-Quellcode:
// (X as T) = AsClass<T>(X) bzw. T(_AsClass(X, T))

function AsClass<T>(Source: TObject): T;
begin
  if not (Source is T) then // if not _IsClass(Source, T) then .... ja, auch das IS ist soeine Funktion
    raise EInvalidCast.Create(SInvalidCast);
  Result := T(Source);
end;

// das Original aus System.pas, welches der Compiler dort aufruft, wo jemand das AS benutzt
function _AsClass(Child: TObject; Parent: TClass): TObject;
{$IFDEF PUREPASCAL}
begin
  Result := Child;
  if not (Child is Parent) then
    Error(reInvalidCast);  // loses return address
end;
{$ELSE}
...


Alle Zeitangaben in WEZ +1. Es ist jetzt 12:17 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz