How do i get the EXCEPTION_POINTERS
, i.e. both:
PEXCEPTION_RECORD
and PCONTEXT
data during an EExternal
exception?
When Windows throws an exception, it passes a PEXCEPTION_POINTERS
; a pointer to the exception information:
typedef struct _EXCEPTION_POINTERS {
PEXCEPTION_RECORD ExceptionRecord;
PCONTEXT ContextRecord;
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;
When Delphi throws me an EExternal
exception, it only contains half that information, the PEXCEPTION_RECORD
only:
EExternal = class(Exception)
public
ExceptionRecord: PExceptionRecord;
end;
How, during an EExternal
exception, do i get both?
i am trying to write a Minidump using MiniDumpWriteDump
function from Delphi.
The function has a few optional parameters:
function MiniDumpWriteDump(
hProcess: THandle; //A handle to the process for which the information is to be generated.
ProcessID: DWORD; //The identifier of the process for which the information is to be generated.
hFile: THandle; //A handle to the file in which the information is to be written.
DumpType: MINIDUMP_TYPE; //The type of information to be generated.
{in, optional}ExceptionParam: PMinidumpExceptionInformation; //A pointer to a MINIDUMP_EXCEPTION_INFORMATION structure describing the client exception that caused the minidump to be generated.
{in, optional}UserStreamParam: PMinidumpUserStreamInformation;
{in, optional}CallbackParam: PMinidumpCallbackInformation): Boolean;
At a basic level i can omit the three optional parameters:
MiniDumpWriteDump(
GetCurrentProcess(),
GetCurrentProcessId(),
hFileHandle,
nil, //PMinidumpExceptionInformation
nil,
nil);
and it succeeds. The downside is that the minidump is missing the exception information. That information is (optionally) passed using the 4th miniExceptionInfo parameter:
TMinidumpExceptionInformation = record
ThreadId: DWORD;
ExceptionPointers: PExceptionPointers;
ClientPointers: BOOL;
end;
PMinidumpExceptionInformation = ^TMinidumpExceptionInformation;
This is good, except i need a way to get at the EXCEPTION_POINTERS
that is supplied by Windows when an exception happens.
The TExceptionPointers
structure contains two members:
EXCEPTION_POINTERS = record
ExceptionRecord : PExceptionRecord;
ContextRecord : PContext;
end;
i know that Delphi's EExternal
exception is the base of all "Windows" exceptions, and it contains the needed PExceptionRecord
:
EExternal = class(Exception)
public
ExceptionRecord: PExceptionRecord;
end;
But it doesn't contain the associated ContextRecord
.
PEXCEPTION_RECORD
good enough?If i try to pass the EXCEPTION_POINTERS
to MiniDumpWriteDump
, leaving ContextRecord
nil:
procedure TDataModule1.ApplicationEvents1Exception(Sender: TObject; E: Exception);
var
ei: TExceptionPointers;
begin
if (E is EExternal) then
begin
ei.ExceptionRecord := EExternal(E).ExceptionRecord;
ei.ContextRecord := nil;
GenerateDump(@ei);
end;
...
end;
function GenerateDump(exceptionInfo: PExceptionPointers): Boolean;
var
miniEI: TMinidumpExceptionInformation;
begin
...
miniEI.ThreadID := GetCurrentThreadID();
miniEI.ExceptionPointers := exceptionInfo;
miniEI.ClientPointers := True;
MiniDumpWriteDump(
GetCurrentProcess(),
GetCurrentProcessId(),
hFileHandle,
@miniEI, //PMinidumpExceptionInformation
nil,
nil);
end;
Then the function fails with error 0x8007021B
Only part of a ReadProcessMemory or WriteProcessMemory request was completed
SetUnhandledExceptionFilter
?Why don't you just use
SetUnhandledExceptionFilter
and get the pointer you need?
SetUnhandledExceptionFilter(@DebugHelpExceptionFilter);
function DebugHelpExceptionFilter(const ExceptionInfo: TExceptionPointers): Longint; stdcall;
begin
GenerateDump(@ExceptionInfo);
Result := 1; //1 = EXCEPTION_EXECUTE_HANDLER
end;
Problem with that is that the unfiltered exception handler only kicks in if the exception is unfiltered. Because this is Delphi, and because because i handle the exception:
procedure DataModule1.ApplicationEvents1Exception(Sender: TObject; E: Exception);
var
ei: TExceptionPointers;
begin
if (E is EExternal) then
begin
//If it's EXCEPTION_IN_PAGE_ERROR then we have to terminate *now*
if EExternal(E).ExceptionRecord.ExceptionCode = EXCEPTION_IN_PAGE_ERROR then
begin
ExitProcess(1);
Exit;
end;
//Write minidump
...
end;
{$IFDEF SaveExceptionsToDatabase}
SaveExceptionToDatabase(Sender, E);
{$ENDIF}
{$IFDEF ShowExceptionForm}
ShowExceptionForm(Sender, E);
{$ENDIF}
end;
The application doesn't, nor do i want it to, terminate with a WER fault.
How do i get the EXCEPTION_POINTERS
during an EExternal
?
Note: You can ignore everything from Background on. It's unnecessarily filler designed to make me look smarter.
Pre-emptive snarky Heffernan comment: You should stop using Delphi 5.
MiniDumpWriteDump
)Since the Delphi RTL doesn't expose the context pointer directly but only extracts the exception pointer and does so in the bowels of System, the solution is going to be somewhat specific to the version of Delphi you are using.
It's been a while since I've had Delphi 5 installed, but I do have Delphi 2007 and I believe that the concepts between Delphi 5 and Delphi 2007 have remained largely unchanged as far as this goes.
With that in mind, here's an example of how it can be done for Delphi 2007:
program Sample;
{$APPTYPE CONSOLE}
uses
Windows,
SysUtils;
var
SaveGetExceptionObject : function(P: PExceptionRecord):Exception;
// we show just the content of the general purpose registers in this example
procedure DumpContext(Context: PContext);
begin
writeln('eip:', IntToHex(Context.Eip, 8));
writeln('eax:', IntToHex(Context.Eax, 8));
writeln('ebx:', IntToHex(Context.Ebx, 8));
writeln('ecx:', IntToHex(Context.Ecx, 8));
writeln('edx:', IntToHex(Context.Edx, 8));
writeln('esi:', IntToHex(Context.Esi, 8));
writeln('edi:', IntToHex(Context.Edi, 8));
writeln('ebp:', IntToHex(Context.Ebp, 8));
writeln('esp:', IntToHex(Context.Esp, 8));
end;
// Below, we redirect the ExceptObjProc ptr to point to here
// When control reaches here we locate the context ptr on
// stack, call the dump procedure, and then call the original ptr
function HookGetExceptionObject(P: PExceptionRecord):Exception;
var
Context: PContext;
begin
asm
// This +44 value is likely to differ on a Delphi 5 setup, but probably
// not by a lot. To figure out what value you should use, set a
// break-point here, then look in the stack in the CPU window for the
// P argument value on stack, and the Context pointer should be 8 bytes
// (2 entries) above that on stack.
// Note also that the 44 is sensitive to compiler switches, calling
// conventions, and so on.
mov eax, [esp+44]
mov Context, eax
end;
DumpContext(Context);
Result := SaveGetExceptionObject(P);
end;
var
dvd, dvs, res: double; // used to force a div-by-zero error
begin
dvd := 1; dvs := 0;
SaveGetExceptionObject := ExceptObjProc;
ExceptObjProc := @HookGetExceptionObject;
try
asm
// this is just for register context verification
// - don't do this in production
mov esi, $BADF00D5;
end;
// cause a crash
res := dvd / dvs;
writeln(res);
except
on E:Exception do begin
Writeln(E.Classname, ': ', E.Message);
Readln;
end;
end;
end.