【问题标题】:Guard page exceptions in Delphi?Delphi中的保护页面异常?
【发布时间】:2009-04-19 10:01:16
【问题描述】:

Raymond Chen 有一个帖子,he tells how bad IsBadXxxPtr function is by eating guard page exception

我不太明白它是如何应用于 Delphi 的。通常应该由谁以及如何(即不调用 IsBadXxxPtr)处理此异常? 我知道 Delphi 插入了一个代码,它(例如)访问大型静态数组的内存 - 正是因为这个原因:扩展堆栈。

但是如果出现保护页面异常:谁将在 Delphi 应用程序中处理它?我不能以不适当的方式使用 try/except 来不小心弄乱它吗? Delphi 的调试器会通知我这些异常吗?

【问题讨论】:

  • 备份一下。你需要调用 IsBadXXXPtr 函数之一做什么?
  • 我不需要任何 IsBadXXXPtr(我同意 Raymond - 在这些情况下最好崩溃)。我刚刚发现我不知道,这种情况在 Delphi 中是如何处理的。这就是我问的原因——只是出于好奇。
  • 在访问大型静态数组时,您“知道”Delphi 插入哪些代码来扩展堆栈?我从来没有见过这样的东西。
  • 只需在 Button1Click 的局部变量中声明一个大数组。运行您的应用程序并在 Button1Click 的“开始”行设置断点。单击按钮并带上 CPU 调试器,您会看到如下内容: L1: add esp, XXX push eax dec eax jnz L1 就是这样。这个循环遍历你的数组并触及它。

标签: delphi exception


【解决方案1】:

Windows 结构化异常处理 (SEH) 具有两阶段结构。当发生异常时,Windows首先通过注册的异常处理程序链查找异常处理程序(其头部存储在x86上的fs:[0]中,即FS段指向的段中的第一个dword寄存器——所有丑陋的 16 位段偏移逻辑并没有在 32 位中消失,只是变得不那么相关了)。

通过调用具有特定标志的函数来完成搜索,该标志存储在堆栈上的每个异常帧中。 fs:[0] 指向最顶层的帧。每一帧都指向前一帧。最终,列表中的最后一帧是由操作系统提供的(如果出现未处理的异常,此处理程序将弹出一个应用程序崩溃对话框)。

这些函数通常检查异常的类型,并返回一个代码来指示要做什么。可以返回的代码之一基本上是“忽略此异常并继续”。如果 Windows 看到这一点,它会将指令指针重置到异常点并恢复执行。另一个代码表明这个异常帧应该处理给定的异常。第三个代码是“我不会捕获这个异常,继续搜索”。 Windows 不断调用这些异常过滤器函数,直到找到一个以一种或另一种方式处理异常的函数。

如果 Windows 发现一个通过捕获异常来处理异常,那么它将继续将堆栈展开回该处理程序,其中包括再次调用所有函数,只传入一个不同的标志。此时函数执行finally 逻辑,直到执行except 逻辑的处理程序。

但是,对于堆栈页面保护异常,处理过程是不同的。该语言的任何异常处理程序都不会选择处理此异常,因为否则堆栈增长机制将中断。相反,过滤器搜索一直过滤到操作系统提供的基本异常处理程序,该处理程序通过提交适当的内存来增加堆栈分配,然后返回适当的返回码以指示操作系统应该从中断的地方继续,而不是展开堆栈。

该工具和调试基础架构旨在让这些特定异常正确发挥作用,因此您无需担心处理它们。

您可以在Matt Pietrek's excellent article in MSJ from over a decade ago 中阅读有关 SEH 的更多信息。

【讨论】:

  • 在Delphi中,不可能指定“忽略这个并继续”结果,不是吗?因此,即使您愿意,也无法在 Delphi 中设置保护页面,与内核提供的任何内容分开。该语言并没有真正公开 SEH 提供的所有内容,如果这样做了,Linux 和 .Net 可能会更难移植,不是吗?
  • Rob - 如果你自己设置异常框架,你可以指定任何你喜欢的东西,这需要一点汇编程序。 .NET 异常支持称为异常过滤器的东西,它映射到 SEH 的搜索阶段,但据我所知,它们仅在 ILASM 语法和 Visual Basic for .NET 中公开。 WRT Linux,我没有深入研究过 dcc 使用的机制,但它是一种 PC 映射机制,为简单起见,我怀疑它不是两阶段的。
  • (Kylix 中的 PC 映射异常机制完全由编译器和 RTL 实现,因为 Linux 在非常低级的 reenter-on-the-same-thread 信号机制之外没有异常机制.)
  • Alexander,它必须处理该异常。这就是它如何工作的基础。 IsBadReadPtr 尝试从给定地址读取。如果该尝试引发异常,它会捕获该异常并返回 true。但是如果地址是用于保护页面的,那么已经有另一个异常处理程序应该捕获异常并以不同的方式处理它。 IsBadReadPtr 通过提前触发异常来破坏该功能。预期的处理程序永远不会看到异常。
  • Alexander,栈扩展方法是专门为触发保护页的线程设计的,即具有线程亲和性。 IsBadReadPtr 可以为 不同的 线程触发保护页面,因此让异常泄漏到当前线程的异常处理程序链末尾的过滤器是行不通的;该处理程序会尝试扩展错误线程的堆栈。
【解决方案2】:

通过查看 cmets,在我看来,“保护页面异常”混乱完全发生在内核中,而不是您需要从用户空间担心的事情。

您必须记住,本文是为 C++ 编写的,它在内存管理方面远没有 Delphi 先进。 Delphi 中的未初始化指针问题比 C/C++ 中的问题要少得多,原因有两个:

  1. Delphi 在编译时检查未初始化的变量,这(无论出于何种原因)很多 C 编译器都会遇到问题。
  2. Delphi 将其所有动态内存初始化为 0,因此您无需处理随机堆垃圾,这可能看起来像一个很好的指针,但实际上并非如此。这意味着大多数错误指针都会给您带来访问冲突,这很容易调试,而不是默默地失败和破坏内存。

【讨论】:

    猜你喜欢
    • 2016-05-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-06
    • 2015-06-01
    • 2010-09-17
    • 2021-06-10
    相关资源
    最近更新 更多