Как получить EXCEPTION_POINTERS во время исключения EExternal?
Как я могу получить EXCEPTION_POINTERS
то есть оба:
PEXCEPTION_RECORD
а такжеPCONTEXT
данные во время EExternal
исключение?
Фон
Когда Windows выдает исключение, она передает PEXCEPTION_POINTERS
; указатель на информацию об исключении:
typedef struct _EXCEPTION_POINTERS {
PEXCEPTION_RECORD ExceptionRecord;
PCONTEXT ContextRecord;
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;
Когда Дельфи бросает мне EExternal
исключение, он содержит только половину этой информации, PEXCEPTION_RECORD
только:
EExternal = class(Exception)
public
ExceptionRecord: PExceptionRecord;
end;
Как во время EExternal
исключение, я получу оба?
Пример использования
я пытаюсь написать Minidump, используя MiniDumpWriteDump
функция от Delphi.
Функция имеет несколько необязательных параметров:
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;
На базовом уровне я могу опустить три необязательных параметра:
MiniDumpWriteDump(
GetCurrentProcess(),
GetCurrentProcessId(),
hFileHandle,
nil, //PMinidumpExceptionInformation
nil,
nil);
и это удается. Недостатком является то, что в минидампе отсутствует информация об исключении. Эта информация (необязательно) передается с использованием 4- го параметра miniExceptionInfo:
TMinidumpExceptionInformation = record
ThreadId: DWORD;
ExceptionPointers: PExceptionPointers;
ClientPointers: BOOL;
end;
PMinidumpExceptionInformation = ^TMinidumpExceptionInformation;
Это хорошо, за исключением того, что мне нужен способ добраться до EXCEPTION_POINTERS
это предоставляется Windows, когда происходит исключение.
TExceptionPointers
Структура содержит два члена:
EXCEPTION_POINTERS = record
ExceptionRecord : PExceptionRecord;
ContextRecord : PContext;
end;
я знаю что Дельфи EExternal
исключение является основой всех исключений "Windows" и содержит необходимые PExceptionRecord
:
EExternal = class(Exception)
public
ExceptionRecord: PExceptionRecord;
end;
Но он не содержит связанных ContextRecord
,
не PEXCEPTION_RECORD
достаточно хорошо?
Если я попытаюсь передать EXCEPTION_POINTERS
в MiniDumpWriteDump
, оставляя ContextRecord
ноль:
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;
Тогда функция завершается с ошибкой 0x8007021B
Только часть запроса ReadProcessMemory или WriteProcessMemory была выполнена
Как насчет SetUnhandledExceptionFilter
?
Почему бы тебе просто не использовать
SetUnhandledExceptionFilter
и получить указатель, который вам нужен?
SetUnhandledExceptionFilter(@DebugHelpExceptionFilter);
function DebugHelpExceptionFilter(const ExceptionInfo: TExceptionPointers): Longint; stdcall;
begin
GenerateDump(@ExceptionInfo);
Result := 1; //1 = EXCEPTION_EXECUTE_HANDLER
end;
Проблема в том, что нефильтрованный обработчик исключений включается только в том случае, если исключение не отфильтровано. Потому что это Delphi, а потому что я обработал исключение:
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;
Приложение не завершается, и я не хочу, чтобы оно завершалось с ошибкой WER.
Как я могу получить EXCEPTION_POINTERS
во время EExternal
?
Примечание. Вы можете игнорировать все, начиная с фона. Это излишне наполнитель, разработанный, чтобы заставить меня выглядеть умнее.
Упреждающий идиотский комментарий Хеффернана: Вы должны прекратить использование Delphi 5.
Бонус Чтение
- MSDN: анализ аварийных дампов (Windows) (подробный пример вызова
MiniDumpWriteDump
) - CodeProject: после-отладочная отладка вашего приложения с помощью Minidumps и Visual Studio.NET (общие сведения о концепциях, достоинствах и способах создания и использования мини-дампов)
- Stackru: Как создать мини-дамп для моего процесса при его сбое? (первое знакомство с миром мини свалок)
- Stackru: Можно ли предотвратить создание отчетов об ошибках Microsoft для одного приложения? (настройка нефильтрованного обработчика в Delphi)
1 ответ
Поскольку Delphi RTL не предоставляет указатель контекста напрямую, а только извлекает указатель исключения и делает это в недрах системы, решение будет несколько специфичным для используемой версии Delphi.
У меня уже давно установлен Delphi 5, но у меня есть Delphi 2007, и я считаю, что концепции между Delphi 5 и Delphi 2007 практически не изменились.
Имея это в виду, вот пример того, как это можно сделать для 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.