【问题标题】:How exactly does the SEH (structured exception handling) mechanism work on ARM?SEH(结构化异常处理)机制究竟是如何在 ARM 上工作的?
【发布时间】:2014-07-04 16:15:23
【问题描述】:

我知道一般概述:http://msdn.microsoft.com/en-us/library/dn743843.aspx;关于win32的异常机制和一些win64的异常机制有很多可用的信息,但是ARM不是很清楚。

我想知道:当产生机器异常(内存保护违规)时,究竟控制权转移到哪里(这样我可以设置断点)?它会转移到内核模式吗?大概它从向量表中查找处理程序的地址并在那里传输。 kernel 代码是执行 SEH 展开,还是在用户模式下完成展开?如果在用户模式下,具体在哪里 - .exe、coredll 或其他地方的一部分?

背景

我们有一个大型应用程序,它将 __try / __except 处理程序放置在每个线程的顶层周围,以便以一种信息丰富的方式记录崩溃。这在 MIPS 上运行良好多年。我们现在将它移植到 ARM 上的 WinCE 7,在 VS2008 下构建。我们发现有时在发布版本中无法正确处理异常。这似乎取决于代码中生成异常的确切位置(我有一个测试函数,它故意访问 NULL 指针以引发 SEH 异常,我从代码中的不同位置调用它)。

我们正在使用 /EHsc 选项来启用 SEH。发布版本使用 /O2 /Os ;删除 /O2 使其工作但明显变慢。这让我相信优化器正在删除 SEH 工作所需的东西——但究竟是什么?并非所有函数都有“mov r12, sp ...”序言,但有些函数仍然不能正常工作。 PDATA中是否有错误? PDATA 条目的数量是否有上限? “dumpbin /pdata”的输出不是每一行都有函数名是正常的吗?

通过使用调试器(尽管发布版本),我可以在我的异常过滤函数(从 __except 调用)的开头放置一个断点,并观察它永远不会在失败情况下输入。程序刚刚退出。

【问题讨论】:

    标签: c++ exception arm windows-ce


    【解决方案1】:

    我不熟悉底层内核细节,但指向 CRT 异常处理程序的指针存储在函数之前的双字中:

    0001106C     DCD _C_specific_handler  ; handler function
    00011070     DCD _scope_table         ; scope table (__try/__except map)
    00011074 start
    00011074     MOV             R12, SP
    

    也许将你的断点放在_C_specific_handler 上,看看它是否被调用。实际函数在 COREDLL 中。如果它确实被调用但没有将异常传递给您的代码,则范围表信息可能由于某种原因是错误的。

    编辑:以上适用于 WinCE 6 及更早版本。既然你提到了 VS2008,我很确定 WinCE 7 仍然使用相同的异常处理模型。您提到的链接(基于展开代码)适用于新的、仅限 ARMv7 的 WinRT 内核(我认为 Windows Embedded Compact 2013 也使用它)。

    【讨论】:

    • 啊哈!你能指出我对范围表的解释吗?我也一直无法确定 MSDN 文档的哪些部分实际上适用于 WinCE 7。
    • 参见struct _SCOPE_TABLE in nkarm.h
    • 这里是MSDN link,但它没有描述范围表
    【解决方案2】:

    我们正在使用 /EHsc 选项来启用 SEH

    您应该将/EHa 与 SEH 一起使用。 /EHsc 允许编译器优化掉所有异常处理逻辑,如果它可以证明 C++ throw 语句不会在块内到达。

    结构化异常处理 __try__except 即使与 /EHsc 一起工作,但 C++ 堆栈展开不会发生结构化异常。因此,异常后全局状态可能不一致,这可能导致您的处理程序失败。

    【讨论】:

    • 我对@9​​87654327@ 不感兴趣,我对内存异常(这是硬件陷阱)感兴趣。我也相信 /EHa 为 cpu 陷阱异常调用析构函数,而 /EHsc 没有?
    • @pjc50:没错。如果您正在处理结构化异常,即硬件陷阱的 Windows 映射,则需要使用 /EHa 以便堆栈展开正常工作。 (如果您跳过堆栈展开,您的程序将具有未定义的行为。)我对throw 的观点是您不希望有 throw 语句,并且因为您没有,C++ 编译器完全有权丢弃所有异常-处理逻辑。
    • 不展开堆栈的行为在 C 意义上是“未定义的”,但很简单:您泄漏了堆栈及其上的所有内容。但是如果程序正在崩溃,那么运行析构函数很可能再次崩溃,所以我们要避免它。正如我所说,这在 MIPS 和 win32 上运行良好多年,并且在 ARM 上也运行良好 - 除非启用优化。
    • (具体来说,__except() 括号内的代码在堆栈展开之前运行,我们用它来记录堆栈跟踪)
    • @pjc50:你试过/EHsc吗? __except 过滤器在堆栈展开之前仍会运行;只有在堆栈中更深的过滤器才会首先运行,这可能很重要。例如,当您的日志文件被锁定时,您无法登录。
    【解决方案3】:

    这可能不是您问题的确切答案,但如果您的目标是捕获关键硬件异常并记录其调用堆栈,那么自 Windowce CE 6.0 起,您可以使用AddVectoredExceptionHandler。在我工作的应用程序(vs2005,仅限 ARM)上,我可以借助此函数获取调用堆栈,您也不需要添加 SEH 捕获/抛出,异常处理程序将始终被调用。

    【讨论】:

      【解决方案4】:

      这很容易。在您最喜欢的搜索引擎中输入“ARM ARM”。

      更窄的搜索“ARM 预取异常”。

      本质上,当处理器尝试从未定义的内存区域获取数据时,会生成数据获取异常。处理器将执行转移到称为异常向量的预定义地址。其余的行为取决于程序员或操作系统。例如,在嵌入式系统上,可能会调用系统故障函数。在桌面平台上,操作系统会生成信号或异常并终止程序。

      其他平台可能具有高级内存管理单元 (MMU) 处理器,这些处理器可以具有栅栏。当程序访问围栏区域之外时,会产生异常或中断。该区域将被编程到 MMU 中。这使操作系统能够保护用户程序不应访问的内存区域,即使内存确实存在。

      访问未定义的内存区域或您的程序无权访问的内存被定义为“未定义行为”。此行为可能因编译器或平台而异,并且没有标准行为。有些平台可能会产生“信号”,有些平台可能会产生“异常”,有些会传递消息,有些则会崩溃。如果您想要可移植的内存异常处理,您将需要编写特定于平台的代码。

      【讨论】:

      • 我想描述的是“取决于操作系统”的行为;我熟悉一般背景。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-29
      • 2012-01-14
      • 2021-12-26
      • 2016-02-01
      • 2012-01-26
      • 2019-10-12
      相关资源
      最近更新 更多