【问题标题】:Tools for Isolating a Stack smashing bug隔离堆栈破坏错误的工具
【发布时间】:2012-09-24 14:11:18
【问题描述】:

委婉地说,我有一个小的内存问题,并且正在用完工具和想法来找出原因。

我有一个高度多线程 (pthreads) 的 C/C++ 程序,它在 4.4.4 之后和 4.7.1 之前的 GCC 优化编译下开发了堆栈粉碎问题。

症状是在创建其中一个线程期间,我得到了一个完整的堆栈粉碎,不仅仅是 %RIP,而且所有父帧和大多数寄存器都是 0x00 或其他无意义的地址。 哪个线程导致问题似乎是随机的,但是从日志消息来看,它似乎与同一块代码隔离,并且似乎在创建新线程时出现了半可重复的点。

这使得捕获和隔离有问题的代码变得非常困难,而不是一个可能有数千行的编译单元,因为到目前为止,在有问题的文件中的 print() 在试图缩小范围时被证明是不可靠的活动部分。

导致最终破坏堆栈的线程的线程创建是:

 
extern "C"
{
static ThreadReturnVal ThreadAPI WriterThread(void *act)
{
   Recorder       *rec = reinterpret_cast  (act);
   xuint64        writebytes;
   LoggerHandle m_logger = XXGetLogger("WriterThread");

   if (SetThreadAffinity(rec->m_cpu_mask))
   { ... }
   SetThreadPrio((xint32)rec->m_thread_priority);

   while (true)
   {
     ... poll a ring buffer ... Hard Spin 100% use on a single core, this is that sort of crazy code. 
   }
}

我尝试了调试版本,但症状仅出现在优化版本中,-O2 或更好。 我已经尝试过 Valgrind/memcheck 和 DRD,但在堆栈被吹走之前都没有发现任何问题(大约需要 12 小时才能达到故障)

使用 -O2 -Wstack-protector 编译没有任何问题, 但是,带有 -fstack-protector-all 的构建确实可以保护我免受错误的影响,但不会发出任何错误。

Electric-Fence 也有陷阱,但只有在堆栈消失后。

问题:还有哪些其他工具或技术有助于缩小违规部分的范围?

非常感谢, --比尔

【问题讨论】:

  • 如果它是创建线程的堆栈,一些代码可能会很好 - 你将什么作为参数传递给新线程?
  • 为了清楚起见,您是说它在 g++ 4.4.2 和 4.8 上运行良好,还是这些版本尚未经过测试?
  • Mark B:在 GCC 4.4.2 上运行良好,4.7.x 以上版本与一些依赖库冲突,因此未经测试。
  • 在gdb中,崩溃后x/<some big number>xw $rsp的输出?是一切都归零,还是那里有可识别的东西?
  • 未初始化的堆栈变量可能会导致优化代码中的多线程错误,因为编译器会在调试代码中初始化它们,而在优化代码中,堆栈将不同,从而导致内存中的值与未优化的发布代码不同。

标签: c++ c debugging memory-leaks


【解决方案1】:

解决此类问题的几个选项:

您可以尝试在损坏发生之前在堆栈地址上设置硬件断点,并希望调试器在损坏时足够早地中断以提供模糊有用的调试状态。这里的棘手部分是选择正确的堆栈地址;根据违规线程的“选择”的随机性,这可能不切实际。但是从您的一个 cmets 听起来,它通常是新创建的线程被破坏了,所以这可能是可行的。尝试在线程创建过程中中断,获取线程的堆栈位置,通过一些疯狂的猜测来抵消,设置硬件 BP,然后继续。根据您是否过早、过晚或根本不休息,调整您的偏移量,冲洗并重复。这基本上是高级猜测和检查,如果损坏模式过于随机,可能会受到严重阻碍或完全不切实际,但令人惊讶的是,这经常会导致半清晰的堆栈和成功的调试工作。

另一种选择是开始收集故障转储。尝试在故障转储之间寻找可能有助于您更接近损坏源的模式。也许你会很幸运,其中一个故障转储会“更快”/“更接近源”崩溃。

不幸的是,这两种技术更像是艺术而不是科学。它们是非确定性的,依赖于健康的运气等(至少根据我的经验.. 话虽如此,有些人可以通过崩溃转储做出惊人的事情,但这需要很多时间达到那个水平的技能)。

另外一个注意事项:正如其他人所指出的,未初始化的内存是调试与发布差异的一个非常典型的来源,在这里很容易成为您的问题。但是,另一种需要记住的可能性是时间差异。线程被调度的顺序和时间,在调试和发布中通常有很大的不同,并且很容易导致同步错误被掩盖在一个而不是另一个中。这些差异可能只是由于执行速度差异造成的,但我认为某些运行时故意在调试环境中扰乱线程调度。

【讨论】:

  • 谢谢大家的回答和意见,我接受这个作为答案,但如果你有更多的想法或想法,我会欢迎更多的意见,因为我会继续努力隔离这个错误。
  • 我忘了提,但如果代码库允许,我发现另一个技巧对这类问题很有用:更改最大线程数。拥有可靠地重现错误所需的最小并发线程数通常会导致更友好的调试环境。理想情况下,您可以将其减少到两个线程(或者有时,您会发现它只使用一个线程进行复制,甚至不是真正的同步错误;排除这种情况总是好的)。
【解决方案2】:

您可以使用静态分析工具来检查一些细微的错误,也许发现的错误之一将是您的错误的原因。您可以找到有关这些工具的一些信息here。

【讨论】:

  • 好点,我没有考虑过 Lint 等来尝试找到这个。
猜你喜欢
  • 2018-05-23
  • 1970-01-01
  • 2011-04-21
  • 2011-11-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-06
  • 2019-05-30
  • 1970-01-01
相关资源
最近更新 更多