14

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

我有一个高度多线程(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 也有陷阱,但只有在堆栈消失之后。

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

非常感谢,--比尔

4

2 回答 2

4

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

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

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

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

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

于 2012-10-03T22:59:10.573 回答
2

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

于 2012-10-03T16:33:46.840 回答