【问题标题】:What can modify the frame pointer?什么可以修改帧指针?
【发布时间】:2010-09-20 02:29:11
【问题描述】:

我现在在一个相当庞大的 C++ 应用程序中出现了一个非常奇怪的错误(在 CPU 和 RAM 使用以及代码长度方面非常庞大 - 超过 100,000 行)。这是在双核 Sun Solaris 10 机器上运行的。该程序订阅股票价格馈送并将它们显示在用户配置的“页面”上(页面是用户自定义的窗口结构 - 该程序允许用户配置此类页面)。在其中一个底层库变为多线程之前,该程序过去一直可以正常工作。受此影响的程序部分已相应更改。关于我的问题。

大约在每三次执行中,程序将在启动时出现段错误。这不一定是硬性规则——有时它会连续崩溃 3 次,然后连续工作 5 次。有趣的是段错误(阅读:痛苦)。它可能以多种方式表现出来,但最常见的情况是函数 A 调用函数 B,在进入函数 B 时,帧指针会突然设置为 0x000002。功能A:

   result_type emit(typename type_trait<T_arg1>::take _A_a1) const
     { return emitter_type::emit(impl_, _A_a1); }

这是一个简单的信号实现。 impl_ 和 _A_a1 在崩溃时的框架内定义良好。在实际执行该指令时,我们最终到达程序计数器 0x000002。

这并不总是发生在那个函数上。事实上,它发生在很多地方,但这是一种简单的情况,不会留下太多出错的余地。有时会发生的情况是堆栈分配的变量会突然无缘无故地坐在垃圾内存上(总是在 0x000002 上)。其他时候,相同的代码会运行得很好。所以,我的问题是,什么可以如此严重地破坏堆栈?什么实际上可以改变帧指针的值?我当然从来没有听说过这样的事情。我能想到的唯一一件事就是在数组上越界写入,但我已经使用堆栈保护器构建了它,它应该会出现这种情况的任何实例。我在这里的堆栈范围内也很好。我也看不到另一个线程如何覆盖第一个线程的堆栈上的变量,因为每个线程都有自己的堆栈(这都是 pthreads)。我已经尝试在 linux 机器上构建它,虽然我没有在那里遇到段错误,但大约三分之一的时间它会冻结在我身上。

【问题讨论】:

    标签: c++ callstack corruption


    【解决方案1】:

    堆栈损坏,肯定是 99.9%。

    你应该仔细寻找的气味是:-

    • 使用“C”数组
    • 使用“C”strcpy 样式函数
    • memcpy
    • malloc 和免费
    • 任何使用指针的线程安全
    • 未初始化的 POD 变量。
    • 指针算法
    • 函数试图通过引用返回局部变量

    【讨论】:

      【解决方案2】:

      我今天遇到了那个确切的问题,并且在gdb 的泥泞中深陷其中,并且在调试了一个小时之前我突然想到我只是在 C 的数组边界(我没想到它最不希望的地方)上写了数组。

      因此,如果可能,请改用vectors,因为如果您在调试模式下尝试,任何 decend STL 实现都会提供良好的编译器消息(而 C 数组会用段错误惩罚您)。

      【讨论】:

        【解决方案3】:

        我不确定你所说的“帧指针”是什么:

        在实际执行时 指令,我们最终在程序 计数器 0x000002

        这听起来像是返回地址被破坏了。帧指针是指向当前函数调用上下文的堆栈位置的指针。它可能指向返回地址(这是一个实现细节),但帧指针本身并不是返回地址。

        我认为这里没有足够的信息来真正给你一个好的答案,但一些可能是罪魁祸首的事情是:

        • 调用约定不正确。如果您使用与函数编译方式不同的调用约定来调用函数,则堆栈可能会损坏。

        • RAM 命中。通过错误指针写入的任何内容都可能导致垃圾最终进入堆栈。我不熟悉 Solaris,但大多数线程实现都将线程放在同一个进程地址空间中,因此任何线程都可以访问任何其他线程的堆栈。一个线程可以获得指向另一个线程堆栈的指针的一种方法是,如果将局部变量的地址传递给最终处理不同线程上的指针的 API。除非您正确同步事物,否则最终会导致指针访问无效数据。鉴于您正在处理“简单的信号实现”,似乎一个线程可能正在向另一个线程发送信号。也许那个信号中的一个参数有一个指向本地的指针?

        【讨论】:

          【解决方案4】:

          堆栈溢出堆栈损坏之间存在一些混淆。

          堆栈溢出 是一个非常具体的问题,原因是尝试使用比操作系统分配给您的线程更多的堆栈。三个正常的原因是这样的。

          void foo()
          {
            foo();  // endless recursion - whoops!
          }
          
          void foo2()
          {
            char myBuffer[A_VERY_BIG_NUMBER];  // The stack can't hold that much.
          }
          
          class bigObj
          {
            char myBuffer[A_VERY_BIG_NUMBER];  
          }
          
          void foo2( bigObj big1)  // pass by value of a big object - whoops!
          {
          }
          

          在嵌入式系统中,线程堆栈的大小可能以字节为单位,即使是简单的调用序列也可能导致问题。默认情况下,在 Windows 上,每个线程获得 1 Meg 的堆栈,因此导致堆栈溢出的常见问题要少得多。除非您有无限递归,否则总是可以通过增加堆栈大小来缓解堆栈溢出,即使这通常不是最佳答案。

          堆栈损坏只是意味着写入当前堆栈帧的边界之外,因此可能会损坏其他数据 - 或返回堆栈上的地址。

          最简单的:-

          void foo()
          { 
            char message[10];
          
            message[10] = '!';  // whoops! beyond end of array
          }
          

          【讨论】:

            【解决方案5】:

            这听起来像是一个堆栈溢出问题 - 一些东西超出了数组的边界并践踏了堆栈上的堆栈帧(也可能是返回地址)。有大量关于这个主题的文献。 “The Shell Programmer's Guide”(第 2 版)包含可以帮助您的 SPARC 示例。

            【讨论】:

              【解决方案6】:

              对于 C++ 的单元化变量和竞争条件,可能会导致间歇性崩溃。

              【讨论】:

                【解决方案7】:

                是否可以通过 Valgrind 运行这个东西?也许 Sun 提供了类似的工具。 Intel VTune(其实我想的是Thread Checker)也有一些非常好的线程调试工具。

                如果您的雇主愿意为更昂贵的工具付出代价,他们确实可以让这类问题更容易解决。

                【讨论】:

                  【解决方案8】:

                  破坏帧指针并不难——如果你查看例程的反汇编,你会发现它在例程开始时被压入并在结束时被拉出——所以如果有任何东西覆盖堆栈,它可能会丢失.堆栈指针是堆栈当前所在的位置 - 帧指针是它开始的位置(对于当前例程)。

                  首先,我将验证所有库和相关对象都已重新构建干净并且所有编译器选项都是一致的 - 我之前遇到过类似的问题(Solaris 2.5),这是由没有的目标文件引起的t 被重建。

                  这听起来完全像一个覆盖 - 如果它只是一个错误的偏移量,那么在内存周围放置保护块将无济于事。

                  每次核心转储后,检查核心文件以尽可能多地了解故障之间的相似性。然后尝试确定被覆盖的内容。我记得帧指针是最后一个堆栈指针 - 所以在当前堆栈帧中不应该修改帧指针之前的逻辑上的任何内容 - 所以可以记录这个并将其复制到其他地方并在返回时进行比较。

                  【讨论】:

                    【解决方案9】:

                    将值 2 分配给变量是否有意义,而是将其地址分配给 2?

                    其他详细信息我都忘记了,但“2”是您的问题描述中反复出现的主题。 ;)

                    【讨论】:

                      【解决方案10】:

                      我认为这听起来肯定是由于超出范围的数组或缓冲区写入而导致的堆栈损坏。只要写入是顺序的,而不是随机的,堆栈保护器就会很好。

                      【讨论】:

                        【解决方案11】:

                        我赞同这可能是堆栈损坏的概念。我要补充一点,切换到多线程库让我怀疑发生的事情是一个潜伏的错误已经暴露。缓冲区溢出的排序可能发生在未使用的内存上。现在它正在访问另一个线程的堆栈。还有许多其他可能的情况。

                        很抱歉,如果这没有给出如何找到它的太多提示。

                        【讨论】:

                          【解决方案12】:

                          我尝试了 Valgrind,但不幸的是它没有检测到堆栈错误:

                          “除了性能损失之外,Valgrind 的一个重要限制是它无法检测使用静态或堆栈分配数据时的边界错误。”

                          我倾向于同意这是一个堆栈溢出问题。棘手的事情是追踪它。就像我说的,这个东西有超过 100,000 行代码(包括内部开发的自定义库——其中一些可以追溯到 1992 年)所以如果有人有任何捕捉这类东西的好技巧,我会感激。到处都在处理数组,该应用程序使用 OI 作为其 GUI(如果您还没有听说过 OI,请不胜感激)所以仅仅寻找逻辑谬误是一项艰巨的任务,我的时间很短。

                          还同意 0x000002 是可疑的。它是崩溃之间唯一的常数。更奇怪的是,这只出现在多线程开关上。我认为由于多线程而导致的较小堆栈是现在出现这种情况的原因,但这纯粹是我的假设。

                          没有人问过这个问题,但我是用 gcc-4.2 构建的。另外,我可以在这里保证 ABI 的安全,所以这也不是问题。至于 RAM 命中的“堆栈末尾的垃圾”,它普遍为 2(尽管在代码中的不同位置)的事实让我怀疑垃圾往往是随机的。

                          【讨论】:

                            【解决方案13】:

                            不可能知道,但这里有一些我能想到的提示。

                            • 在 pthreads 中,您必须分配堆栈并将其传递给线程。你分配够了吗?没有像单线程进程那样的自动堆栈增长。
                            • 如果您确定不会通过写入过去分配的堆栈数据来破坏堆栈,请检查 rouge 指针(主要是未初始化的指针)。
                            • 其中一个线程可能会覆盖其他线程所依赖的某些数据(检查您的数据同步)。
                            • 在这里调试通常不是很有帮助。我会尝试创建大量日志输出(跟踪每个函数/方法调用的进入和退出),然后分析日志。
                            • 错误在 Linux 上的表现方式不同这一事实可能会有所帮助。您在 Solaris 上使用什么线程映射?确保将每个线程映射到它自己的 LWP 以简化调试。

                            【讨论】:

                              【解决方案14】:

                              还同意 0x000002 是可疑的。它是崩溃之间唯一的常数。更奇怪的是,这只出现在多线程开关上。我认为由于多线程而导致的较小堆栈是现在出现这种情况的原因,但这纯粹是我的假设。

                              如果您通过引用或地址传递堆栈上的任何内容,如果另一个线程在第一个线程从函数返回后尝试使用它,这肯定会发生。

                              您可以通过将应用强制到单个处理器上来重现这一点。我不知道你是如何用 Sparc 做到这一点的。

                              【讨论】:

                                猜你喜欢
                                • 1970-01-01
                                • 1970-01-01
                                • 2020-07-16
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                相关资源
                                最近更新 更多