2013-02-13 6 views
9

Как я получаю EXCEPTION_POINTERS, то есть как:Как получить EXCEPTION_POINTERS во время исключения EExternal?

  • PEXCEPTION_RECORD и
  • PCONTEXT

данных во время EExternal исключения?

фон

Когда Windows генерирует исключение, он пропускает PEXCEPTION_POINTERS; указатель на информацию, за исключением:

typedef struct _EXCEPTION_POINTERS { 
    PEXCEPTION_RECORD ExceptionRecord; 
    PCONTEXT   ContextRecord; 
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS; 

Когда Delphi бросает мне 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); 

и преуспевает. Недостатком является то, что в minidump отсутствует информация об исключении. Эта информация (необязательно) передается с помощью 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 исключение Delphi является основой всех "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?

Примечание: Вы можете игнорировать все: Фон на. Это излишне наполнитель, разработанный, чтобы заставить меня выглядеть умнее.

Преимущественное Heffernan комментарий элегантного: Вы должны прекратить использовать Delphi 5.

Bonus Чтение

+0

сообщение Преимущественное Heffernan комментарий: Я сомневаюсь, что это легче в более поздних версиях. И вам не нужно беспокоиться о x64. –

+0

FWIW madExcept дает вам легкий доступ к контекстной записи –

+4

madExcept получает контекст довольно гнусным способом. Он перехватывает ExceptObjProc. Который оказывается довольно сложным. Поскольку процесс подключения должен считывать некоторые регистры перед вызовом функции Pascal. Возможно, я мог бы решить, как это сделать, используя ME в качестве шаблона. Но это займет много времени. Лично я просто использую МЕНЯ. Как только у вас появится ME, я думаю, вам больше не нужны мини-кадры. Диагностика ME будет проще в использовании. –

ответ

7

Так как в 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. 
+2

+1 Хорошо. Я не оценил, как легко подключить ExceptObjProc. –

+0

Отлично! И я надеялся, что есть Win32 или RTL 'GetExceptionContext' –

+2

@IanBoyd. Контекст должен быть захвачен в момент, когда возникает исключение. ОС делает это за вас. И передает его вам. И тогда RTL игнорирует это. Поэтому я считаю, что этот подход к подключению является единственным жизнеспособным решением. Причина, по которой я думал, что это сложнее, заключается в том, что ME делает больше, чем это. Он перехватывает механизм ExceptObjProc и позволяет пользователю использовать этот механизм. Довольно изящный. –