Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Sonstige Fragen zu Delphi (https://www.delphipraxis.net/19-sonstige-fragen-zu-delphi/)
-   -   Delphi Abfangen von Betriebssystemexceptions (https://www.delphipraxis.net/56311-abfangen-von-betriebssystemexceptions.html)

Lemmy1 3. Nov 2005 18:21


Abfangen von Betriebssystemexceptions
 
Hi zusammen,

ein Delphifrage wollte ich schon seit Jahren gerne einmal geklärt haben. In einem C++ (Microsoft C++ 6 und 7) Programm habe ich einmal folgendes getan:

Erstmal ein einfacher Exceptionhandler:
Code:
try {
  // code
  } __except(ProcessException(GetExceptionInformation())){
  }
}
Innerhalb von ProcessException war nun folgendes möglich:
Code:
int ProcessException(LPEXCEPTION_POINTERS lpEP)
  switch (lpEP->ExceptionRecord->ExceptionCode){
  case EXCEPTION_ACCESS_VIOLATION:
    // snip...
    // Unter lpEP->ExceptionRecord->ExceptionInformation sind diverse Infos verfügbar: Alle Register, den benutzen Speicher etc
    // snip...
    // Und damit gehts weiter
    return EXCEPTION_CONTINUE_EXECUTION;
  }
}
Sieht auf den ersten Blick ziemlich dumm aus, hat aber eine sehr konkrete Anwendung. Mit VirtualAlloc kann man sich vorher erstmal einen Bereich und dann einzelne Memoryareas sperren. Damit ist es möglich, writes auf bestimmte Memoryadressen zu erkennen, etwas zu tun und anschließend den Code weiter auszuführen als ich nichts gewesen wäre.

Meinen Nintendo64 Emulator musste ich seiner Zeit komplett von Delphi nach C++ umschreiben, weil ich aus Delphi diese Infos nicht rausbekommen habe. Durch diese Technik konnte ich die Performance teils um bis zu 50% (je nach game) steigern. Oder kennt da jemand einen Weg mit Asm/WinApi?

Grüße und Danke im Vorraus

Lemmy1 3. Nov 2005 18:59

Re: Exceptionhandling wie in C++ möglich?
 
Hab gerade selber etwas in der Delphipraxis zu einem ganz anderen Thema etwas gefunden. Bin mal so frei und kopier das einfach so raus:

Delphi-Quellcode:
function IsObject(AObject: Pointer): Boolean;
asm
      OR   EAX,EAX                            // AObject == nil ??
      JNZ  @@1
      RET

@@1: XOR  EDX,EDX                            // install Exception Frame, SEH
      PUSH OFFSET @@3
      PUSH DWord Ptr FS:[EDX]
      MOV  FS:[EDX],ESP
     
      MOV  EAX,[EAX]                          // EAX := AObject^.ClassType
      OR   EAX,EAX                            // ClassType == nil ??
      JZ   @@2

      CMP  EAX,[EAX].vmtSelfPtr               // EAX = ClassType.vmtSelfPtr
      SETZ AL

@@2: POP  DWord Ptr FS:[EDX]
      POP  EDX
      RET

// Exception Handler, wird aufgerufen wenn zwischen @@1 und @@2 eine AV auftritt,
// zum Debugger muß auf @@3 ein Breakpoint gesetzt werden,
// Dieser SEH ist NICHT sichtbar für Delphi's Debugger !!

@@3: MOV  EAX,[ESP + 00Ch]                   // context
      MOV  DWord Ptr [EAX + 0B0h],0            // context.eax = 0
      MOV  DWord Ptr [EAX + 0B8h],OFFSET @@2   // context.eip = @@2
      SUB  EAX,EAX                            // 0 = ExceptionContinueExecution
end;
Also mit ASM scheints ja möglich zu sein. Werde mal versuchen, ob man sowas komfortabel und sauber kapseln kann. Wenn ja, lad ich den entsprechenden Code hoch.

Grüße
Daniel

Lemmy1 3. Nov 2005 22:54

Re: Exceptionhandling wie in C++ möglich?
 
Liste der Anhänge anzeigen (Anzahl: 1)
Na also es hat geklappt. Bin jetzt soweit, dass ich im Falle einer Exception den ContextRecord via Standard-Pascal routinen modifizieren kann. Ein Beispielprojekt habe ich angehängt. Da ich hoffe, dass das noch weitere Leute brauchen können, hier die Erklärung (Assemblerkenntnisse sind hier leider notwendig):

Über
Delphi-Quellcode:
procedure CallProcedure(TargetProcedure: TParameterlessProcedure;
  ExceptionHandler : TExceptionHandlerProc);
kann eine procedure aufgerufen werden.
Es gibt zwei Parameter:
- TargetProcedure ist der aufzurufende Code
- ExceptionHandler ist der Exceptionhandler

Beispiel für einen Aufruf:
Delphi-Quellcode:
var
  Dummy : Cardinal;

procedure Test; register;
begin
  asm
    // do something that crashs
    mov eax, 0
    mov eax, [eax]
  end;
end;

function ExceptionHandlerProc(
  var ExceptionInfo : Windows.TExceptionRecord;
  EstablisherFrame : Pointer;
  var ContextRecord : Windows.TContext;
  DispatchedContext : Pointer) : TExceptionContinue; cdecl;
begin
  ContextRecord.Eax := Cardinal(@Dummy);
  Result := ecContinueExecution;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  try
    CallProcedure(Test, ExceptionHandlerProc);
    Caption := 'yes';
  except
    Caption := 'no';
  end;
end;
Was passiert hierbei? Beim Click auf Button1 wird der Funktion CallProcedure mitgeteilt, dass Sie "Test" ausführen soll. Im Falle eines Fehlers soll sie zu ExceptionHandlerProc springen. "Test" ist nun absichtlich so geschrieben, dass sie crasht. Aus diesem Grunde springt ExceptionHandlerProc ein. Sie behebt den Fehler (korrigiert das Register EAX) und sagt dem System, dass es an der gleichen Stelle weitermachen soll.

Man könnte in so einem Handler auch diverse andere Sachen tun:
- Loggen ohne den Programmfluss zu zerstören (allerdings sind die Daten im Handler dazu recht dürftig)
- EIP erhöhen, und damit die Assembler-Funktion überspringen
- Statt ecContinueExecution könnte man auch ecContinueSearch zurückgeben. In diesem Fall kann der Delphi-Exceptionhandler verwendet werden, um den Fehler zu verarbeiten.

Es gibt an dem Code noch zwei verbesserungswürdige Stellen:
  • Statt einer parameterlosen Procedure wäre es schöner, komplexere procedures aufzurufen. Mir fehlt da aber etwas die Idee, wie so etwas elegant geht, ohne 100 Überladungen zu bauen.
  • Wäre schön, wenn man TExceptionHandlerProc vereinfachen könnte. Die aktuelle Version wird direkt so vom Betriebssystem aufgerufen, womit ich keinen Einfluss auf die Signatur habe. Schöner wäre es also, innerhalb der Unit erst eine private Funktion aufzurufen, die das ganze etwas kapselt. Das würde durch Verwendung von Unit-Variablen funktionieren (man muss sich ja irgendwie den echten Handler merken), womit die Threadfähigkeit etwas im Eimer wäre. Alternativ könnte man es mit Codegenerierung zur Laufzeit versuchen, was allerdings immer mit Mühe verbunden ist.

Falls jemanden interessiert, was derzeit intern abläuft:
Delphi-Quellcode:
procedure CallProcedure(TargetProcedure: TEmptyProcedure;
  ExceptionHandler : TExceptionHandlerProc);
asm
  // Install Exception Frame
  PUSH   ExceptionHandler
  PUSH   FS:[0]
  MOV    FS:[0], ESP

  // Call procedure
  CALL   TargetProcedure

  // Restore exception handler
  MOV    EAX, [ESP]
  MOV    FS:[0], EAX
  ADD    ESP, 8
end;
Da ich das Interface des Exceptionhandlers übersetzt habe, wurde der ASM Code selbst ziemlich klein.

Für alle, die dieses Thema interessiert: Hier gibt es einen sehr ausführlichen Crashcourse, der mir sehr geholfen hat.

himitsu 4. Nov 2005 00:35

Re: Abfangen von Betriebssystemexceptions
 
Gibt es einen bestimmten Grund, warum du den Wert für FS:[0] über EAX umleitest?
Wenn nicht, dann könnte es doch so aussehn ^^
Delphi-Quellcode:
procedure CallProcedure...
asm
  ...
  // Restore exception handler
  POP    FS:[0]
  ADD    ESP, 4  // POP &ExceptionHandler
end;
Und zusätlich müßte man so dann sogar 'n parameterlose Funktion übergeben können.

z.B.
Delphi-Quellcode:
Type TParameterlessFunction = Function; register;

Function CallFunction(TargetFunction: TParameterlessFunction;
  ExceptionHandler : TExceptionHandlerProc): Integer;
{der Rest ist gleich}


Und mal sehn was ich hiermit anfangen kann ^^

Lemmy1 5. Nov 2005 00:59

Re: Abfangen von Betriebssystemexceptions
 
Liste der Anhänge anzeigen (Anzahl: 1)
Yup das stimmt wohl.

Ist wohl generell sinnvoller, ein Cardinal hin- und eines zurückzuschicken. In der Win32 Welt bekommt man ja da mit Pointertricks alles reingequetscht :)

Hab das Beispielprojekt dementsprechend angepasst.

Noch eine Anmerkung, die ich vergessen habe: Innerhalb vom ExceptionHandlerProc sollte man nur dann etwas tun, wenn das zweite Bit in ExceptionFlags NICHT gesetzt ist:
Delphi-Quellcode:
  if (ExceptionInfo.ExceptionFlags and 2 <> 0) then
  begin
    // This is a second time the handler is called
    Exit;
  end;
Grund ist, dass beim nichtbehandeln einer Exception das OS den Handler sonst ein zweites Mal aufruft. Mit obigem Test kann man das erkennen.


Alle Zeitangaben in WEZ +1. Es ist jetzt 01:49 Uhr.

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