Delphi 如何在外部异常期间获取异常指针?
如何获取Delphi 如何在外部异常期间获取异常指针?,delphi,exception-handling,delphi-5,minidump,dbghelp,Delphi,Exception Handling,Delphi 5,Minidump,Dbghelp,如何获取异常\u指针,即两者: PEXCEPTION\u记录和 PCONTEXT EExternal异常期间的数据 背景 当Windows抛出异常时,它会传递一个PEXCEPTION_指针;指向异常信息的指针: typedef struct _EXCEPTION_POINTERS { PEXCEPTION_RECORD ExceptionRecord; PCONTEXT ContextRecord; } EXCEPTION_POINTERS, *PEXCEPTI
异常\u指针
,即两者:
和PEXCEPTION\u记录
PCONTEXT
EExternal
异常期间的数据
背景
当Windows抛出异常时,它会传递一个PEXCEPTION_指针
;指向异常信息的指针:
typedef struct _EXCEPTION_POINTERS {
PEXCEPTION_RECORD ExceptionRecord;
PCONTEXT ContextRecord;
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;
当Delphi抛出一个eeexternal
异常时,它只包含一半的信息,peexception\u记录只包含:
EExternal = class(Exception)
public
ExceptionRecord: PExceptionRecord;
end;
在eeexternal
异常期间,我如何同时获得这两种异常
示例用法
我正在尝试使用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);
基本上,我可以省略三个可选参数:
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提供的
TExceptionPointers
结构包含两个成员:
EXCEPTION_POINTERS = record
ExceptionRecord : PExceptionRecord;
ContextRecord : PContext;
end;
我知道Delphi的外部异常是所有“Windows”异常的基础,它包含所需的异常记录:
EExternal = class(Exception)
public
ExceptionRecord: PExceptionRecord;
end;
但它不包含关联的上下文记录
记录是否足够好?
如果我试图将异常\u指针
传递到minidumpWriteDomainp
,将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
获取所需的指针呢
问题是未过滤的异常处理程序只有在异常未过滤的情况下才会启动。因为这是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故障终止
在执行eeexternal
期间,如何获取异常\u指针
注意:您可以忽略背景上的所有内容。这是为了让我看起来更聪明而设计的不必要的填充物
先发制人的尖刻评论:你应该停止使用Delphi5
额外阅读
-
(有关如何调用
minidumpWriteDomainp
的详细示例)
-
(关于概念、优点以及如何生成和使用迷你转储的一般性讨论)
-
(小型转储世界的初步介绍)
-
(在Delphi中设置未筛选的处理程序)
由于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.
事后诸葛亮·赫弗南评论:我怀疑这在以后的版本中会更容易。而且您不必担心x64.FWIW madExcept使您可以轻松访问上下文记录madExcept通过相当邪恶的方式掌握上下文。它钩住了ExceptObjProc。结果是相当棘手的。因为挂钩进程在调用Pascal函数之前需要读取一些寄存器。我也许可以用我作为模板来解决这个问题。但这需要很长时间。就我个人而言,我只是利用我自己。一旦你有了我,我想你不再需要迷你垃圾桶了。ME诊断将更易于使用。+1做得好。我没有意识到钩住ExceptObjProc是多么容易。太棒了!我希望有一个Win32或RTLGetExceptionContext
@IanBoyd,必须在引发异常时捕获上下文。操作系统会帮你做到这一点。然后把它传给你。然后RTL会忽略它。因此,我认为这种挂钩方式是唯一可行的解决方案。我认为这更难的原因是我做的不止这些。它钩住ExceptObjProc
机制,并允许用户仍然使用该机制。相当漂亮。与其使用HookGetExceptionObject()
依赖ESP
寄存器的每个编译器偏移量来查找上下文
记录,我发现它更易于使用。在HookGetExceptionObject()
之前调用向量化处理程序,它可以将CONTEXT
指针保存到线程局部变量中,然后HookGetExceptionObject()
可以在该线程局部变量中访问它。