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.