【问题标题】:How to track down a SIGFPE/Arithmetic exception如何追踪 SIGFPE/算术异常
【发布时间】:2011-10-08 08:05:18
【问题描述】:

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

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

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

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

当调试器无能为力时,是否有任何技术可以追踪此类异常的原因?

更新: 在处理十六进制数字、转储内存和进行堆栈取证(感谢 Crashworks)后,我在 ARM 编译器文档中发现了这个宝石(即使我没有使用 ARM Ltd.编译器):

整数除零错误可以通过以下方式捕获和识别 重新实现适当的 C 库帮助函数。这 发生被零除时的默认行为是当信号 使用函数,或 __rt_raise() 或 __aeabi_idiv0() 被重新实现,__aeabi_idiv0() 是 叫。否则,除法函数返回零。 __aeabi_idiv0() 使用附加参数 DIVBYZERO 引发 SIGFPE。

所以我在 __aeabi_idiv0(_aeabi_ldiv0) et Voila! 处放置了一个断点,在被完全丢弃之前我有完整的堆栈跟踪。感谢大家提供非常丰富的答案!

免责声明:“获胜”的答案是完全主观地选择的,考虑到它的建议对我的调试工作的重要性,因为不止一个是信息丰富且真正有用的

【问题讨论】:

  • 我最近做了一个关于追踪各种疯狂的发布模式崩溃的演讲,包括粉碎的堆栈。我的幻灯片主要涵盖 x86 和 PowerPC,但 PPC 与 ARM 非常相似,因此其中一种技术可能会有所帮助(关于堆栈框架布局的模特定细节)。幻灯片在这里:crashworks.org/gdc11/…

标签: c++ linux gcc embedded arm


【解决方案1】:

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

在 cmets 中,我链接了一些演示幻灯片,这些幻灯片说明了 PowerPC 上的技术——请查看 #73-86 附近的类似拙劣堆栈崩溃的案例研究。显然,您的 ARM 堆栈帧的布局会有所不同,但一般原则是成立的。

【讨论】:

  • 我正在尝试这个建议。可能是我需要的。幻灯片也不错,内容丰富,谢谢。
  • 我希望我能更具体一些,但我已经多年没有与 ARM 合作了。祝你好运!
【解决方案2】:

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

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

【讨论】:

  • 您能详细说明一下这种技术吗?谢谢;-)
【解决方案3】:

您可以在可能导致异常的函数中实现特殊保护。 Guard 是一个简单的类,在这个类的构造器中你把文件名和行(_FILE_, _LINE_) 到文件/数组/任何东西。主要条件是这个存储对于这个类的所有实例应该是相同的(一种堆栈)。在析构函数中删除此行。为了让它工作,你需要把这个守卫的创建放在每个函数的第一行,并且只在堆栈上创建它。当您将退出当前块时,将调用解构器。因此,在您出现异常的那一刻,您将从这个临时调用堆栈中知道哪个函数导致了问题。 当然,您可以将此类的创建置于调试条件下

【讨论】:

  • 这可能是一个可行的解决方案,但在我的情况下它并不完全有效,因为我有一个庞大的代码库并且我不知道我的代码的哪一部分导致了异常。修改每个类和函数以包含您建议的内容,这不是一个选项。或者,我可以直接在我的代码中使用回溯gnu.org/software/libc/manual/html_node/Backtraces.html,但它同样具有与您建议的方法相同的缺点。
【解决方案4】:

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

【讨论】:

  • 已经这样做了。同样的结果。它无法确定触发崩溃的确切位置(或者至少让我知道在哪里查看)。
  • @celavek 在这种情况下,为您的代码编写单元测试。在进行除法之前用 assert 检查值。我看不出还有什么可以做的。
  • 一个明智的建议 :) 但至少需要几天/几周的时间才能覆盖单元测试的潜在位置。
【解决方案5】:

由于它使用 raise() 来引发异常,我希望 signal() 应该能够捕获它。不是这样吗?

或者,您可以在 __aeabi_uldivmod 处设置条件断点,以在除数 (r1) 为 0 时中断。

【讨论】:

  • 信号确实被调试器捕获了。但是堆栈跟踪是我在我的问题中发布的不完整的,因为 __divsi3() 是来自 libgcc 的实际除法“/”运算符实现,所以它一定是从代码中的某处“调用”的,因此我的结论堆栈实际上已被异常破坏。我使用 $r1==0 在 __aeabi_uldivmod() 设置了一个条件断点,但它从未被命中,我仍然会崩溃。另一件事是我没有该库的调试符号,因为我正在使用的工具链版本(CodeSourcery-Lite)没有提供。
猜你喜欢
  • 2020-05-24
  • 1970-01-01
  • 1970-01-01
  • 2022-10-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-29
  • 2012-02-13
相关资源
最近更新 更多