如何跟踪SIGFPE/算术异常

How to track down a SIGFPE/Arithmetic exception

本文关键字:异常 SIGFPE 何跟踪 跟踪      更新时间:2023-10-16

我有一个为Linux交叉编译的c++应用程序,运行在ARM CortexA9处理器上,由于SIGFPE/算术异常而崩溃。最初我认为这是因为gcc的-O3标志引入了一些优化,但后来我在调试模式下构建它,它仍然崩溃。

我用捕获异常的gdb调试了应用程序,但不幸的是,触发异常的操作似乎也丢弃了堆栈,所以我无法获得有关导致这种情况发生的代码中的位置的任何详细信息。我最终能得到的唯一细节是触发异常的操作(来自下面的堆栈跟踪):

    3 raise()  0x402720ac   
    2 __aeabi_uldivmod()  0x400bb0b8    
    1 __divsi3()  0x400b9880

__aeabi_uldivmod()正在执行无符号长除法和提醒,因此我尝试了蛮力方法并搜索了我的代码中可能使用该操作的地方,但没有太大成功,因为它被证明是一项艰巨的任务。我也试着检查可能的除零,但是代码库非常大,检查每一个除法操作,这是一个繁琐的,有点愚蠢的方法。所以一定有更聪明的方法来弄清楚发生了什么。

当调试器无能为力时,有什么技术可以追踪这些异常的原因吗?

UPDATE: 在处理十六进制数,转储内存和做堆栈取证(感谢Crashworks)之后,我在ARM编译器文档中遇到了这个gem(即使我没有使用ARM有限的编译器):

整数除零错误可以被捕获和识别重新实现适当的C库辅助函数。的当除零发生时的默认行为是当信号函数,或者__rt_raise()或__aeabi_idiv0()被重新实现,__aeabi_idiv0()被重新实现调用。否则,除法函数返回零。__aeabi_idiv0()用一个额外的参数DIVBYZERO引发SIGFPE。

所以我在__aeabi_idiv0(_aeabi_ldiv0)处设置了一个断点,瞧!,在被完全销毁之前,我有完整的堆栈跟踪。感谢大家提供的信息丰富的答案!

免责声明:"获胜"的答案是单独选择的,并且主观地考虑了其建议在我调试工作中的权重,因为不止一个是信息丰富且真正有帮助的

我的第一个建议是打开一个内存窗口,查看堆栈指针周围的区域,并深入研究它,看看是否可以在附近找到未损坏的堆栈帧,这可能会给您提供有关崩溃位置的线索。通常堆栈垃圾只会烧掉几个堆栈帧,所以如果您向上查看几百字节,就可以越过损坏的区域,大致了解代码的位置。您甚至可以在堆栈中查看,假设死亡的函数可能在它死亡之前调用了其他函数,因此可能仍然有一个旧帧在内存中指向当前IP。

在评论中,我链接了一些演示幻灯片,在PowerPC上演示了该技术—查看第73-86条关于类似的拙劣堆栈崩溃的案例研究。显然,你的ARM的堆栈帧会以不同的方式布局,但一般原则是成立的。

(使用Fedor Skrynnikov的基本思想,但使用编译器帮助)

-pg编译代码。这将在每个函数中插入对mcountmcountleave()的调用。不要使用链接到GCC分析库,而是提供自己的分析库。在mcountmcountleave()中,您想要做的唯一一件事就是保留当前堆栈的副本,因此只需将堆栈的顶部128字节左右复制到固定缓冲区。堆栈和缓冲区都将一直在缓存中,所以它相当便宜。

可以在可能导致异常的函数中实现特殊的保护。Guard是一个简单的类,在这个类的构造函数中,你把文件名和行(_ file __ line _)放到file/array/whatever中。主要条件是该类的所有实例(类似于堆栈)的存储空间应该是相同的。在析构函数中删除这一行。要使其工作,您需要将此保护的创建放在每个函数的第一行,并且只在堆栈上创建它。当您离开当前块时,将调用解构器。因此,在发生异常的那一刻,您将从这个临时调用堆栈中知道哪个函数引起了问题。当然,您可以将该类的创建置于调试条件下

启用生成核心文件,并使用调试器

打开核心文件

因为它使用raise()引发异常,我希望signal()应该能够捕获它。事实并非如此吗?

或者,您可以在__aeabi_uldivmod中设置一个条件断点,当divisor (r1)为0时中断。