什么可以修改帧指针?

时间:2008-10-30 22:44:12

标签: c++ callstack corruption

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

大约每三次执行一次,该程序将在启动时出现段错误。这不一定是一个严格的规则 - 有时它会连续三次崩溃,然后连续工作五次。这是有趣的段错(阅读:痛苦)。它可以以多种方式表现出来,但最常见的是函数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机器上构建它,虽然我没有在那里得到段错误,但大约有三分之一它会冻结在我身上。

14 个答案:

答案 0 :(得分:9)

堆栈损坏,绝对是99.9%。

你应该仔细观察的气味是: -

  • 使用'C'数组
  • 使用'C'strcpy-style函数
  • 的memcpy
  • malloc and free
  • 任何使用指针的线程安全
  • 未初始化的POD变量。
  • 指针算术
  • 尝试通过引用返回局部变量的函数

答案 1 :(得分:4)

我今天遇到了这个确切的问题而且在gdb泥泞中膝盖深处并且在我发生之前的一个小时内进行了调试,我只是写了数组边界(我没想到的最少)一个C数组。

所以,如果可能的话,使用vector代替,因为如果你在调试模式下尝试,那么任何decend STL实现都会给出好的编译器消息(而C数组会用segfaults惩罚你)。

答案 2 :(得分:3)

我不确定你所谓的“帧指针”,如你所说:

  

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

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

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

  • 错误的调用约定。如果使用与编译函数不同的调用约定调用函数,则堆栈可能会损坏。

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

答案 3 :(得分:3)

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

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

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兆的堆栈,因此导致堆栈溢出不是常见问题。除非你有无限的递归,否则堆栈溢出总是可以通过增加堆栈大小来缓解,即使这通常不是最好的答案。

堆栈损坏只是意味着在当前堆栈帧的边界外写入,从而可能破坏其他数据 - 或者返回堆栈中的地址。

最简单: -

void foo()
{ 
  char message[10];

  message[10] = '!';  // whoops! beyond end of array
}

答案 4 :(得分:1)

这听起来像是一个堆栈溢出问题 - 有些东西正在超出数组的边界并且在堆栈上遍历堆栈帧(也可能是返回地址)。有关该主题的大量文献。 “Shell程序员指南”(第2版)有SPARC示例可以帮助您。

答案 5 :(得分:1)

使用C ++未初始化的变量和竞争条件可能是间歇性崩溃的嫌疑。

答案 6 :(得分:1)

有可能通过Valgrind运行这个东西吗?也许Sun提供了类似的工具。英特尔VTune(实际上我在考虑线程检查器)也有一些非常好的线程调试工具等。

如果您的雇主可以承担更昂贵的工具的成本,他们可以真正使这些问题更容易解决。

答案 7 :(得分:1)

框架指针的破坏并不困难 - 如果你看一下例程的反汇编,你会看到它在例行程序的开始被推动并在结束处被拉动 - 所以如果有什么事情覆盖了堆栈它就会丢失。堆栈指针是堆栈当前所在的位置 - 帧指针是它开始的位置(对于当前例程)。

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

这听起来就像是一次覆盖 - 如果它只是一个糟糕的偏移,那么在内存周围放置防护块就不会有帮助。

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

答案 8 :(得分:0)

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

其他细节在我身上丢失,但“2”是问题描述中反复出现的主题。 ;)

答案 9 :(得分:0)

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

答案 10 :(得分:0)

我认为它很可能是堆栈损坏。我将补充说,切换到一个多线程库使我怀疑发生的事情已经暴露出潜伏的bug。可能是在未使用的内存上发生了缓冲区溢出的排序。现在它正在击中另一个线程的堆栈。还有许多其他可能的情况。

很抱歉,如果没有提供很多关于如何找到它的提示。

答案 11 :(得分:0)

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

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

我倾向于同意这是一个堆栈溢出问题。棘手的是跟踪它。就像我说的那样,这个东西有超过100,000行代码(包括内部开发的自定义库 - 其中一些可以追溯到1992年)所以如果有人有任何好的技巧来捕捉那种东西,我会是感激。整个地方都有阵列工作,应用程序使用OI作为其GUI(如果你还没有听说过OI,请感激不尽)所以只是寻找一个逻辑谬误是一项艰巨的任务,我的时间很短。

也同意0x000002是可疑的。它是崩溃之间唯一的常数。甚至更奇怪的事实是,这只会出现多线程开关。我认为由于多线程而导致的较小堆栈现在正在使这个问题突然出现,但这对我来说是纯粹的假设。

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

答案 12 :(得分:0)

  

也同意0x000002是可疑的。它是崩溃之间唯一的常数。甚至更奇怪的事实是,这只会出现多线程开关。我认为由于多线程而导致的较小堆栈现在正在使这个问题突然出现,但这对我来说是纯粹的假设。

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

您可以通过强制将应用程序放到单个处理器上来重现此问题。我不知道你是怎么用Sparc做的。

答案 13 :(得分:0)

不可能知道,但这里有一些提示,我可以想出来。

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