Delphi 在异常后获取堆栈跟踪

12

我正在尝试弄清楚在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 的人已经做过了,但我想知道它是如何实现的。


你对阅读汇编语言有多熟练?;-) - Warren P
10
我从未理解为什么 RTL 没有将这个功能集成进去…… - Wouter van Nifterick
@WarrenP:我认为我的能力还算可以,至少足够修复这个问题,但看来我运气不佳。:( - Orwell
1
“我想知道它是如何完成的。” - 阅读源代码。JCL源代码是开放的,mORMot和一些免费的日志记录器和分析工具也是如此。有许多FLOSS产品包含了堆栈展开功能。你只需要阅读它们的代码并从中学习。 - Arioch 'The
如果你需要做某些事情,但没有时间自己琢磨,你可以联系madshi(MadExcept的作者),并请他为你设计一种解决方案。这就是我会做的。这些东西很难。 - Warren P
2个回答

22
无法在 OnException 事件内手动获得可行的堆栈跟踪。正如您已经注意到的,错误发生时的堆栈在触发该事件时已经消失。你正在寻找需要在异常引发时获取堆栈跟踪的信息。第三方异常记录器(如MadExcept,EurekaLog等)通过钩入RTL内部的关键函数和核心异常处理程序来处理这些细节。
在最近的Delphi版本中,SysUtils.Exception 类现在具有公共的 StackTraceStackInfo 属性,在 OnException 事件中会很有用,但 Embarcadero 选择出于未知原因不对这些属性进行本地实现。它需要将处理程序分配给由 Exception 类公开的各种回调来为这些属性生成堆栈跟踪数据。但是,如果您安装了 JclDebug,则可以在自己的代码中提供自己的回调处理程序,使用 JCL 的堆栈跟踪函数来生成属性的堆栈数据。

22
由于Embarcadero是编译器的制造商,因此它最适合实施堆栈跟踪。在暴露回调之前,第三方勾取是唯一的选择。有了这些回调,如果不提供本地解决方案,则是一种托辞,在我看来。Embarcadero本可以而且应该提供这个功能,但他们似乎选择不这样做。 - Remy Lebeau
5
至少,我希望看到 Embarcadero 添加一个新的单元到 RTL 中,用户可以根据需要将其添加到他们的“uses”子句中,它会分配自己的处理程序到回调函数并生成基本的调用堆栈信息。对于那些不想购买/安装第三方记录器的用户来说,这将是一个选择。 - Remy Lebeau
4
@WarrenP,我不希望看到那种情况发生。因为那样Emba会负责开发它。而我认为让Mathias掌舵更好。 - David Heffernan
3
@David Heffernan :“那是第三方库的工作。”这是否与Delphi帮助采取的相同方式?由用户来解决吗?“Embarcadero没有关于此主题的进一步信息……”如果Embarcadero没有,谁应该拥有这些信息呢?不可原谅。正如Remy所说,“他们选择不这样做”-也就是说,他们选择了走捷径,让我们没有信息-像往常一样。 - Vector
6
只有在 Embarcadero 的世界中,提供异常堆栈跟踪被视为第三方库的工作。 - Stephen Drew
显示剩余7条评论

10

我想在异常抛出之前获取堆栈,或者换句话说,在 OnException 调用之前 (之后也可以)获取内容。

实际上,您不需要在 OnException 调用之前获取堆栈。因为这已经包含在内了。您需要的是在异常抛出时获取堆栈。并且这需要尽快进行堆栈跟踪,以便在异常抛出后立即进行。在 OnException 调用中太晚了,因为异常已传播到顶层处理程序。

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

此外,堆栈遍历比您的基本代码要高级得多。我的意思不是贬低,而是指在 x86 上进行堆栈遍历是一项棘手的任务,而 madExcept 代码非常精细。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接