2013-02-13 32 views
9

Come faccio ad avere il EXCEPTION_POINTERS, vale a dire sia:Come ottenere EXCEPTION_POINTERS durante un'eccezione EExternal?

  • PEXCEPTION_RECORD e
  • PCONTEXT

dati durante un'eccezione EExternal?

Sfondo

Quando Windows genera un'eccezione, si passa una PEXCEPTION_POINTERS; un puntatore alle informazioni di eccezione:

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

Quando Delphi mi genera un'eccezione EExternal, contiene solo la metà che le informazioni, solo il PEXCEPTION_RECORD:

EExternal = class(Exception) 
public 
    ExceptionRecord: PExceptionRecord; 
end; 

come, durante un'eccezione EExternal, fare ho entrambi?

Esempio di utilizzo

sto provando a scrivere un Minidump utilizzando MiniDumpWriteDump funzione da Delphi.

la funzione ha un paio di parametri opzionali:

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; 

a livello di base posso omettere i tre parametri opzionali:

MiniDumpWriteDump(
    GetCurrentProcess(), 
    GetCurrentProcessId(), 
    hFileHandle, 
    nil, //PMinidumpExceptionInformation 
    nil, 
    nil); 

e riesce. Lo svantaggio è che al minidump mancano le informazioni sulle eccezioni. Tale informazione è (opzionalmente) superato con il 4 ° miniExceptionInfo parametro:

TMinidumpExceptionInformation = record 
    ThreadId: DWORD; 
    ExceptionPointers: PExceptionPointers; 
    ClientPointers: BOOL; 
end; 
PMinidumpExceptionInformation = ^TMinidumpExceptionInformation; 

Questo è un bene, tranne che ho bisogno di un modo per arrivare al EXCEPTION_POINTERS che viene fornito da Windows quando un'eccezione accade.

La struttura TExceptionPointers contiene due membri:

EXCEPTION_POINTERS = record 
    ExceptionRecord : PExceptionRecord; 
    ContextRecord : PContext; 
end; 

so che ad eccezione di Delphi EExternal è la base di tutta la "Windows" eccezioni, e contiene la necessaria PExceptionRecord:

EExternal = class(Exception) 
public 
    ExceptionRecord: PExceptionRecord; 
end; 

Ma non contiene gli associati ContextRecord.

Non è PEXCEPTION_RECORD abbastanza buono?

se cerco di passare il EXCEPTION_POINTERS-MiniDumpWriteDump, lasciando 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; 

Poi la funzione non riesce con l'errore 0x8007021B

Solo una parte di una richiesta ReadProcessMemory o WriteProcessMemory è stato completato

Che dire di SetUnhandledExceptionFilter?

Perché non utilizzare solo lo SetUnhandledExceptionFilter e ottenere il puntatore necessario?

SetUnhandledExceptionFilter(@DebugHelpExceptionFilter); 

function DebugHelpExceptionFilter(const ExceptionInfo: TExceptionPointers): Longint; stdcall; 
begin 
    GenerateDump(@ExceptionInfo); 
    Result := 1; //1 = EXCEPTION_EXECUTE_HANDLER 
end; 

problema con questo è che il non filtrato gestore di eccezioni calci solo se l'eccezione è filtrato. Perché questo è Delphi, sia perché perché ho gestire l'eccezione:

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; 

L'applicazione non si, né voglio a, terminare con un errore WER.

Come ottengo il EXCEPTION_POINTERS durante un EExternal?

Nota: È possibile ignorare tutto da Sfondo su. È inutilmente riempitivo progettato per farmi sembrare più intelligente.

Pre-emptive snarky Heffernan commento: Si dovrebbe smettere di usare Delphi 5.

Bonus lettura

+0

Post-emptive Heffernan commento: Dubito che è più facile nelle versioni successive. E non devi preoccuparti di x64. –

+0

FWIW madExcept offre un facile accesso al record di contesto –

+4

madExcept ottiene il contesto con mezzi piuttosto nefandi. Si aggancia ExceptObjProc. Il che risulta essere piuttosto complicato. Perché il processo di hooking ha bisogno di leggere alcuni registri prima di chiamare una funzione Pascal. Probabilmente potrei capire come farlo usando ME come modello. Ma ci vorrebbero secoli. Personalmente, userò semplicemente ME. Una volta che avremo ME, direi che non hai più bisogno di minidump. La diagnostica ME sarà più facile da usare. –

risposta

7

Dal momento che la Delphi RTL non esporre direttamente il puntatore del contesto, ma estrae solo l'eccezione puntatore e lo fa nelle viscere del sistema, la soluzione sarà in qualche modo specifica per la versione di Delphi che si sta utilizzando.

È da un po 'che non ho installato Delphi 5, ma ho Delphi 2007 e credo che i concetti tra Delphi 5 e Delphi 2007 siano rimasti per lo più invariati.

Con questo in mente, ecco un esempio di come si può fare per 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 Ben fatto. Non avevo apprezzato quanto sia facile collegare ExceptObjProc. –

+0

Eccellente! E speravo che ci fosse un Win32 o RTL 'GetExceptionContext' –

+2

@IanBoyd Il contesto deve essere catturato nel punto in cui viene sollevata l'eccezione. Il sistema operativo lo fa per te. E passa a te. E poi la RTL lo ignora. Quindi penso che questo approccio di hooking sia l'unica soluzione praticabile. La ragione per cui ho pensato che fosse più difficile è che ME fa più di questo. Aggancia il meccanismo 'ExceptObjProc' e consente all'utente di utilizzare ancora quel meccanismo. Piuttosto elegante. –