11

我试图弄清楚在 Delphi 中引发异常后如何获取堆栈跟踪。但是,当我尝试使用下面的函数读取 Application.OnException 事件中的堆栈时,堆栈似乎已被刷新并被抛出过程替换。

function GetStackReport: AnsiString;
var
    retaddr, walker: ^pointer;
begin

    // ...

    // History of stack, ignore esp frame
    asm
        mov walker, ebp
    end;

    // assume return address is present above ebp
    while Cardinal(walker^) <> 0 do begin
        retaddr := walker;
        Inc(retaddr);
        result := result + AddressInfo(Cardinal(retaddr^));
        walker := walker^;
    end;
end;

这是我得到的结果:

001A63E3: TApplication.HandleException (Forms)
00129072: StdWndProc (Classes)
001A60B0: TApplication.ProcessMessage (Forms)

这显然不是我想要的,尽管它是正确的。我想检索堆栈,因为它就在抛出异常之前,或者换句话说,在 OnException 调用之前(之后也可以)的内容。

有没有办法做到这一点?

我知道我正在重新发明轮子,因为 madExcept/Eurekalog/jclDebug 的人已经这样做了,但我想知道它是如何完成的。

4

2 回答 2

22

无法从OnException事件内部手动获取可行的堆栈跟踪。正如您已经注意到的,错误发生时的堆栈在触发事件时已经消失。您要查找的内容需要在引发异常时获取堆栈跟踪。第三方异常记录器,如 MadExcept、EurekaLog 等,通过挂钩 RTL 本身内部的关键函数和核心异常处理程序来为您处理这些细节。

在最近的 Delphi 版本中,SysUtils.Exception该类现在确实具有公共StackTraceStackInfo属性,这在事件中将很有用,OnException除了 Embarcadero 出于未知原因选择不本地实现这些属性的事实。它需要第三方异常记录器将处理程序分配给Exception类公开的各种回调,以生成属性的堆栈跟踪数据。但是,例如,如果您安装了 JclDebug,那么您可以在自己的代码中提供自己的回调处理程序,使用 JCL 的堆栈跟踪函数来生成属性的堆栈数据。

于 2013-04-08T23:27:25.770 回答
10

我想检索堆栈,因为它就在抛出异常之前,或者换句话说,在 OnException 调用之前(之后也可以)的内容。

实际上,您不希望 OnException 调用之前的堆栈。这就是你已经得到的。您希望堆栈位于引发异常的位置。这需要在加注后尽快进行堆栈跟踪。OnException 调用为时已晚,因为异常已经传播到顶级处理程序。

madExcept 通过挂钩所有处理异常的 RTL 函数来工作。它挂钩了最低级别的功能。这需要一些认真的努力才能实现。使用这些例程,代码可以捕获堆栈跟踪等。请注意,挂钩是特定于版本的,需要对 RTL 进行逆向工程。

更重要的是,堆栈遍历比您的基本代码高级得多。我并不是说贬义,只是在 x86 上的堆栈行走是一件棘手的事情,而且 madExcept 代码经过了很好的磨练。

这是基本的想法。如果您想了解更多信息,您可以免费获得 JclDebug 的源代码。或者购买 madExcept 并获取其来源。

于 2013-04-08T22:36:52.270 回答