在ARM上,SEH(结构化异常处理)机制究竟是如何工作的?

3
我了解一般概述: http://msdn.microsoft.com/en-us/library/dn743843.aspx; 有大量关于win32异常机制的信息和一些关于win64的信息,但是ARM并不那么清晰。
我想知道: 当机器异常被生成(内存保护违规),控制会被转移到哪里(以便我可以设置断点)? 它会转移到内核模式吗? 大概它会从向量表中查找处理程序的地址,并将其转移。内核代码执行SEH展开,还是在用户模式下执行展开? 如果在用户模式下,具体在哪里——exe的一部分、coredll还是其他地方? 背景 我们有一个大型应用程序,它在每个线程的顶层周围放置__try / __except处理程序,以便以信息丰富的方式记录崩溃情况。这在MIPS上已经运行良好多年。我们现在正在将其移植到ARM上的WinCE 7,并在VS2008下构建。我们发现在发布版本中有时无法正确处理异常。这似乎取决于异常在代码中生成的位置(我有一个测试函数,故意访问空指针以引发SEH异常,并从代码的各个位置调用此函数)。
我们使用/EHsc选项启用SEH。发布版本使用/O2/Os;删除/O2使其工作但速度明显变慢。这导致我相信优化器正在删除一些必须的内容,以使SEH起作用 - 但是究竟是什么?并非所有函数都具有“mov r12,sp ...”序言,但某些不具备此功能的函数仍然可以正常工作。这是PDATA中的某些错误吗?是否存在PDATA条目数量的上限?“dumpbin / pdata”的输出没有每行都有函数名称,这是正常现象吗?
通过使用调试器(尽管是发布版本),我可以在我的异常过滤函数的开头(从__except调用)设置断点,并观察在失败情况下它从未被调用。程序只是退出了。
4个回答

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。 - pjc50
请查看nkarm.h文件中的struct _SCOPE_TABLE - Igor Skochinsky
这是MSDN链接,但它没有描述作用域表。 - Igor Skochinsky

1
我们正在使用 /EHsc 选项启用 SEH。
使用 SEH 应该使用 /EHa。/EHsc 允许编译器优化所有异常处理逻辑,如果它能证明 C++ 的 throw 语句不会在块内被执行。
结构化异常处理 __try 和 __except 即使使用 /EHsc,也可以工作,但是 C++ 堆栈展开不会发生在结构化异常中。因此,在异常之后全局状态可能不一致,这可能导致处理程序失败。

我对“throw”不感兴趣,我对内存异常(硬件陷阱)感兴趣。此外,我相信“/EHa”会为CPU陷阱异常调用析构函数,而“/EHsc”则不会? - pjc50
@pjc50:没错。 如果你正在处理结构化异常,这些异常是Windows映射硬件陷阱的结果,你需要使用 /EHa 以便堆栈展开能够正确地工作。(如果跳过堆栈展开,你的程序将有未定义的行为。) 我关于 throw 的观点是,你希望没有 throw 语句,并且由于你没有,C++编译器完全有权抛弃所有异常处理逻辑。 - Ben Voigt
在 C 语言中,不进行堆栈展开的行为是“未定义”的,但很简单:您会泄漏堆栈和其中的所有内容。但是,如果程序正在崩溃过程中,则运行析构函数很可能会再次崩溃,因此我们要避免这种情况。正如我所说,多年来,在 MIPS 和 win32 上都可以正常工作,并且在 ARM 上也可以正常工作 - 除非启用了优化。 - pjc50
我们已经在使用 /EHsc,问题是有时候在使用 /O2 时,过滤器没有按照预期运行。 - pjc50
@pjc:抱歉,在我上一条评论中,我的意思是问你是否尝试过使用“/EHa”。 - Ben Voigt
显示剩余2条评论

1

这可能不是你问题的确切答案,但如果你的目标是捕获关键硬件异常并记录其调用堆栈,则自Windows CE 6.0以来,可以使用AddVectoredExceptionHandler。在我工作的应用程序中(vs2005,仅限ARM),我可以借助此功能获取调用堆栈,而且您不需要添加SEH catch/throw,异常处理程序将始终被调用。


0

这非常容易。在您最喜欢的搜索引擎中键入“ARM ARM”。

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

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

其他平台可能具有先进的内存管理单元(MMU)处理器,可以具有隔离功能。 当程序访问超出隔离区域时,会生成异常或中断。 区域将编程到MMU中。 这使得操作系统可以保护用户程序不应访问的内存区域,即使存在内存也是如此。

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


我想要描述的是“由操作系统决定”的行为;我对一般背景比较熟悉。 - pjc50

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