我为在ARM CortexA9处理器上运行的Linux交叉编译了一个C ++应用程序,该处理器因SIGFPE / Arithmetic异常而崩溃。最初我认为这是因为gcc的 -O3 标志引入了一些优化,但后来我在调试模式下构建它仍然崩溃。
我用gdb调试了应用程序,它捕获了异常,但不幸的是,触发异常的操作似乎也会丢弃堆栈,因此我无法获得有关该代码中导致这种情况发生的位置的任何详细信息。我最终得到的唯一细节是触发异常的操作(来自下面的堆栈跟踪):
3 raise() 0x402720ac
2 __aeabi_uldivmod() 0x400bb0b8
1 __divsi3() 0x400b9880
__ aeabi_uldivmod()正在执行一个无符号的长分区和提醒,所以我尝试了强力方法并搜索我的代码以寻找可能使用该操作的地方但没有取得太大成功,因为它被证明是一项艰巨的任务。此外,我试图通过零来检查潜在的划分,但再次检查代码库是否非常大并且检查每个除法操作这是一个麻烦且有些愚蠢的方法。因此,必须有一种更聪明的方法来弄清楚发生了什么。
当调试器无法提供多少帮助时,是否有任何技术可以追踪此类异常的原因?
更新:在对十六进制数字进行处理后,转储内存并进行堆栈取证(感谢Crashworks)我在ARM编译器文档中遇到了这个gem(尽管我没有使用ARM有限公司)编译器):
可以捕获并识别整数除零错误 重新实现适当的C库辅助函数。该 当除以零时的默认行为是当信号时 功能被使用,或 重新实现__rt_raise()或__aeabi_idiv0(),__ aeabi_idiv0()是 调用。否则,除法函数返回零。 __aeabi_idiv0()使用附加参数DIVBYZERO引发SIGFPE。
所以我在__aeabi_idiv0(_aeabi_ldiv0)et Voila上放了一个断点!在完全删除之前我有完整的堆栈跟踪。感谢大家提供的非常丰富的答案!
免责声明:“获胜”答案是单独和主观地选择考虑其建议对我的调试工作的重量,因为不止一个是提供信息并且非常有用。
答案 0 :(得分:9)
我的第一个建议是打开一个内存窗口,查看堆栈指针周围的区域,然后深入挖掘它,看看你是否可以在附近找到未损坏的堆栈帧,这可能会让你知道崩溃的位置。通常堆栈交换只会烧掉几个堆栈帧,所以如果你向上看几百个字节,你就可以通过受损区域并大致了解代码的位置。你甚至可以看看 down 堆栈,假设死函数可能在它死之前调用了其他函数,因此可能有一个旧帧仍在内存中指向当前IP。
在评论中,我链接了一些演示幻灯片,这些幻灯片演示了PowerPC上的技术 - 在类似的拙劣堆栈崩溃案例研究中查看#73-86左右。显然,你的ARM堆栈帧将以不同的方式布局,但一般原则仍然存在。
答案 1 :(得分:3)
(使用Fedor Skrynnikov的基本思想,但改为使用编译器帮助)
使用-pg
编译代码。这将在每个函数中插入对mcount
和mcountleave()
的调用。 不链接GCC配置文件库,但提供您自己的。您要在mcount
和mcountleave()
中唯一要做的就是保留当前堆栈的副本,因此只需将堆栈的前128个字节左右复制到固定缓冲区即可。堆栈和缓冲区都将一直处于缓存中,所以它相当便宜。
答案 2 :(得分:2)
您可以在可能导致异常的函数中实现特殊保护。 Guard是一个简单的类,在这个类的约束中你放了文件和行的名称( _ FILE _ , _ LINE _ )进入文件/数组/等等。主要条件是此类存储应该对此类的所有实例(堆栈类型)相同。在析构函数中删除此行。要使其工作,您需要在每个函数的第一行创建此保护,并仅在堆栈上创建它。当你将离开当前块时,将调用解构器。因此,在您的异常时刻,您将从这个即兴的callstack中知道哪个函数导致了问题。 Ofcaurse你可以在调试条件下创建这个类
答案 3 :(得分:2)
启用核心文件的生成,并使用调试器
打开核心文件答案 4 :(得分:2)
由于它使用raise()来引发异常,我希望signal()应该能够捕获它。情况不是这样吗?
或者,您可以在__aeabi_uldivmod处设置条件断点,以便在除数(r1)为0时断开。