【问题标题】:How to get the EXCEPTION_POINTERS during an EExternal exception?如何在 EExternal 异常期间获取 EXCEPTION_POINTERS?
【发布时间】:2013-02-13 15:34:36
【问题描述】:

我如何获得EXCEPTION_POINTERS,即两者:

  • 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 异常期间同时获得两者?

示例用法

我正在尝试使用 Delphi 的 MiniDumpWriteDump 函数编写一个 Minidump。

该函数有几个可选参数:

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;

这很好,除非我需要一种方法来获取在发生异常时由 Windows 提供的 EXCEPTION_POINTERS

TExceptionPointers 结构包含两个成员:

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

我知道 Delphi 的 EExternal 异常是所有 "Windows" 异常的基础,它包含所需的 PExceptionRecord

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

但它不包含关联的ContextRecord

PEXCEPTION_RECORD不够好吗?

如果我尝试将EXCEPTION_POINTERS 传递给MiniDumpWriteDump,则留下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;

然后函数失败并出现错误0x8007021B

ReadProcessMemory 或 WriteProcessMemory 请求仅完成了一部分

SetUnhandledExceptionFilter 呢?

您为什么不直接使用SetUnhandledExceptionFilter 并获取您需要的指针?

SetUnhandledExceptionFilter(@DebugHelpExceptionFilter);

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

问题在于 unfiltered 异常处理程序只有在异常未过滤时才会启动。因为这是 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 错误终止。

如何在EExternal 期间获得EXCEPTION_POINTERS

注意:您可以忽略从背景开始的所有内容。这是不必要的填充物,旨在让我看起来更聪明。

先发制人的尖刻 Heffernan 评论:你应该停止使用 Delphi 5。

阅读奖励

【问题讨论】:

  • Post-emptive Heffernan 评论:我怀疑在以后的版本中它会更容易。而且您不必担心 x64。
  • FWIW madExcept 让您可以轻松访问上下文记录
  • madExcept 通过相当邪恶的方式获取上下文。它钩住了ExceptionObjProc。事实证明这很棘手。因为挂钩过程需要在调用 Pascal 函数之前读取一些寄存器。我可能会想出如何使用 ME 作为模板来做到这一点。但这需要很长时间。就个人而言,我只会使用我。一旦你有了我,我猜你就不再需要小型转储了。 ME 诊断程序将更易于使用。

标签: delphi exception-handling delphi-5 minidump dbghelp


【解决方案1】:

由于 Delphi RTL 不直接公开上下文指针,而仅提取异常指针并在 System 内部执行此操作,因此解决方案将在某种程度上特定于您正在使用的 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.

【讨论】:

  • +1 干得好。我没有意识到挂钩 exceptObjProc 是多么容易。
  • 太棒了!我希望有一个 Win32 或 RTL GetExceptionContext
  • @IanBoyd 必须在引发异常的点捕获上下文。操作系统会为您做到这一点。并传递给你。然后 RTL 忽略它。所以我认为这种挂钩方式是唯一可行的解​​决方案。我认为这更难的原因是我做的比这更多。它挂钩ExceptObjProc 机制并允许用户仍然使用该机制。相当漂亮。
  • 与其让HookGetExceptionObject() 依赖ESP 寄存器的每个编译器偏移量来查找CONTEXT 记录,我发现使用AddVectoredExceptionHandler() 更容易。矢量处理程序在HookGetExceptionObject() 之前调用,可以将CONTEXT 指针保存到线程局部变量中,然后HookGetExceptionObject() 可以访问它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-04-17
  • 2017-03-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多