【问题标题】:infinite abort() in a backrace of a c++ program core dumpc++ 程序核心转储中的无限中止()
【发布时间】:2012-02-26 19:08:22
【问题描述】:

我有一个无法解决的奇怪问题。请帮忙!

该程序是一个在 ARM Linux 机器上运行的多线程 c++ 应用程序。最近我开始长期测试它,有时它会在 1-2 天后崩溃,如下所示:

*** glibc detected ** /root/client/my_program: free(): invalid pointer: 0x002a9408 ***

当我打开核心转储时,我看到主线程似乎有一个损坏的堆栈:我只能看到无限的 abort() 调用。

GNU gdb (GDB) 7.3 
...
This GDB was configured as "--host=i686 --target=arm-linux".
[New LWP 706]
[New LWP 700]
[New LWP 702]
[New LWP 703]
[New LWP 704]
[New LWP 705]
Core was generated by `/root/client/my_program'.
Program terminated with signal 6, Aborted.
#0  0x001c44d4 in raise ()
(gdb) bt
#0  0x001c44d4 in raise ()
#1  0x001c47e0 in abort ()
#2  0x001c47e0 in abort ()
#3  0x001c47e0 in abort ()
#4  0x001c47e0 in abort ()
#5  0x001c47e0 in abort ()
#6  0x001c47e0 in abort ()
#7  0x001c47e0 in abort ()
#8  0x001c47e0 in abort ()
#9  0x001c47e0 in abort ()
#10 0x001c47e0 in abort ()
#11 0x001c47e0 in abort ()

而且它一直在继续。我试图通过向上移动堆栈来找到它的底部:第 3000 帧 甚至更多,但最终核心转储用完了帧,我仍然不明白为什么会发生这种情况。

当我检查其他线程时,那里的一切似乎都很正常。

(gdb) info threads
  Id   Target Id         Frame 
  6    LWP 705           0x00132f04 in nanosleep ()
  5    LWP 704           0x001e7a70 in select ()
  4    LWP 703           0x00132f04 in nanosleep ()
  3    LWP 702           0x00132318 in sem_wait ()
  2    LWP 700           0x00132f04 in nanosleep ()
* 1    LWP 706           0x001c44d4 in raise ()
(gdb) thread 5
[Switching to thread 5 (LWP 704)]
#0  0x001e7a70 in select ()
(gdb) bt
#0  0x001e7a70 in select ()
#1  0x00057ad4 in CSerialPort::read (this=0xbea7d98c, string_buffer=..., delimiter=..., timeout_ms=1000) at CSerialPort.cpp:202
#2  0x00070de4 in CScanner::readResponse (this=0xbea7d4cc, resp_recv=..., timeout=1000, delim=...) at PidScanner.cpp:657
#3  0x00071198 in CScanner::sendExpect (this=0xbea7d4cc, cmd=..., exp_str=..., rcv_str=..., timeout=1000) at PidScanner.cpp:604
#4  0x00071d48 in CScanner::pollPid (this=0xbea7d4cc, mode=1, pid=12, pid_str=...) at PidScanner.cpp:525
#5  0x00072ce0 in CScanner::poll1 (this=0xbea7d4cc) 
#6  0x00074c78 in CScanner::Poll (this=0xbea7d4cc) 
#7  0x00089edc in CThread5::Thread5Poll (this=0xbea7d360) 
#8  0x0008c140 in CThread5::run (this=0xbea7d360) 
#9  0x00088698 in CThread::threadFunc (p=0xbea7d360) 
#10 0x0012e6a0 in start_thread ()
#11 0x001e90e8 in clone ()
#12 0x001e90e8 in clone ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)

(类和函数名称有点奇怪,因为我更改了它们 -:) 所以,线程 #1 是堆栈损坏的地方,每隔一个 (2-6) 的回溯显示

Backtrace stopped: previous frame identical to this frame (corrupt stack?).

这是因为线程 2-6 是在线程 #1 中创建的。

问题是我无法在 gdb 中运行该程序,因为它运行在嵌入式系统上。我不能使用远程 gdb 服务器。唯一的选择是检查不经常发生的核心转储。

您能否提出一些可以推动我前进的建议? (也许我可以从核心转储中提取其他内容,或者以某种方式在代码中制作一些挂钩以捕获 abort() 调用)。

更新:Basile Starynkevitch 建议使用 Valgrind,但事实证明它仅适用于 ARMv7。我有 ARM 926,它是 ARMv5,所以这对我不起作用。虽然有一些努力为 ARMv5 编译 valgrind:Valgrind cross compilation for ARMv5telvalgrind on the ARM9

更新 2:无法使 Electric Fence 与我的程序一起使用。该程序使用 C++ 和 pthreads。我得到的 Efence 版本,2.1.13 在我启动一个线程并尝试做一些或多或少复杂的事情(例如将一个值放入 STL 向量)后在任意位置崩溃。我看到有人在网上提到 Efence 的一些补丁,但没有时间尝试它们。我在我的 Linux PC 上试过这个,而不是在 ARM 上,其他工具,如 valgrind 或 Dmalloc 没有报告代码有任何问题。所以,使用 2.1.13 版本的 efence 的每个人都准备好遇到 pthread 问题(或者可能是 pthread + C++ + STL,不知道)。

【问题讨论】:

  • 似乎是使用valgrind 的一个很好的理由(最近已移植到 ARM、IIRC)。 valgrind.org
  • 感谢您的建议!不知道是移植到ARM上的
  • 原来 valgrind 仅移植到 ARM v7。我有 ARM 926 处理器,但我的编译器拒绝构建它:(
  • 如果 ARMv5 没有 valgrind,也许你可以使用 Electric Fence?它会在某些类型的非法内存访问上立即导致 SEGFAULT,这可能会使核心转储更有用。
  • Nikita,这并不能真正解决您的问题,但我会告诉您我的工作:(1) 确保您已完成代码设计。在这种情况下,它将是逆向工程,但仍然如此。需要设计 MT 代码,尤其是嵌入式代码。否则你会有很多悲伤。 (2) 添加带有时间和线程信息的 printfs。编写一个小型库,可让您将 MT 定时小型日志添加到代码中。稍后您将最终使用它。 (3) 看看你的板子是否有 JTAG 调试器。它们通常由电路板制造商提供。

标签: c++ linux embedded coredump


【解决方案1】:

我对“无限”中止的猜测是,要么 abort() 导致循环(例如 abort -> 信号处理程序 -> abort -> ...),要么 gdb 无法正确解释堆栈上的帧。

无论哪种情况,我都建议手动检查有问题的线程的堆栈。如果 abort 导致循环,您应该看到一个模式或至少 abort 的返回地址每隔一段时间重复一次。也许您可以通过手动跳过(重复)堆栈的大部分内容来更轻松地找到问题的根源。

否则,您应该会发现没有重复的模式,并且希望堆栈中某处的失败函数的返回地址。在最坏的情况下,由于缓冲区溢出等原因,这些地址会被覆盖,但也许你仍然可以幸运地识别出它被覆盖的内容。

【讨论】:

  • 谢谢@mweerdeen,我会试试的!
【解决方案2】:

这里的一种可能性是,该线程中的某些东西通过大量覆盖堆栈上的数据结构,破坏了堆栈中所有需要的数据,从而非常非常严重地破坏了堆栈。这使得事后调试非常不愉快。

如果您可以随意重现该问题,那么正确的做法是在 gdb 下运行该线程,并在堆栈被破坏的那一刻准确地观察正在发生的事情。反过来,这可能需要进行某种仔细搜索以确定错误发生的确切位置。

如果你不能随意重现问题,我可以建议的最好办法是非常仔细地在线程本地存储中为该线程寻找线索,看看它是否暗示了线程在死亡命中之前执行的位置。

【讨论】:

  • 不幸的是,我只能使用事后核心转储 -(
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-11
  • 2020-12-08
  • 1970-01-01
  • 1970-01-01
  • 2018-06-12
相关资源
最近更新 更多