没有符号的调试核心文件

18
我有一个C应用程序,我们已经部署到客户的站点上。它在HP-UX上编译并运行。用户报告了一个崩溃,并且我们已经获得了核心转储文件。到目前为止,我一直无法在内部复制崩溃。 正如你所预料的那样,核心文件/部署的可执行文件完全没有任何符号。当我在gdb中加载它并执行bt时,我得到的最佳结果是这个:
(gdb) bt
#0  0xc0199470 in ?? ()

我可以对该文件执行“strings core”命令,但我的理解是我只能获取到可执行文件中的所有字符串,因此似乎很难在其中跟踪任何内容。

我有一个带有-g选项编译的可调式版本的可执行文件,但是它比发布版本晚了几个月。如果我尝试使用该核心启动gdb,我会看到以下内容:

warning: exec file is newer than core file.
Core was generated by `program_name'.
Program terminated with signal 11, Segmentation fault.
__dld_list is not valid according to __dld_flags.

#0  0xc0199470 in ?? ()
(gdb) bt
#0  0xc0199470 in ?? ()

虽然编译一个调试版本并部署到客户现场等待另一次崩溃是可行的,但出于多种原因,这将相对困难且不可取。

我对代码非常熟悉,并根据客户的错误报告相对了解其在代码中崩溃的位置。

是否有任何方式可以从此核心转储中获取更多信息?通过字符串或其他调试器或任何东西?谢谢。

8个回答

12

从gdb返回的这种类型的响应:

(gdb) bt
#0  0xc0199470 in ?? ()

如果堆栈被缓冲区溢出攻击破坏,其中返回地址在内存中被覆盖,导致程序计数器被设置为一个看似随机的区域,也会发生这种情况。

这是即使使用相应符号数据库构建时也可能导致符号查找错误(或奇怪的回溯)的方法之一。如果在拥有符号表后仍然遇到此问题,则很可能是您的客户数据与您的代码产生了一些问题。


1
这个答案对我来说似乎非常合理。我一定会仔细查看代码,寻找潜在的溢出区域。 - Morinar
如果使用“副本”进行调试没有显示任何内容,那么就该开始查看寄存器和堆栈转储,以尝试推断您如何进入了无人区。这也可能是一个失效(或未初始化)的函数指针、分配超限、或者是错误的缓冲区大小或“坏”输入导致缓冲区炸裂(使用sprintf()/sscanf与不受控制的输入等)。 - jesup
我从来没有在这里弄清楚任何事情,但我接受这个结果,因为它仍然似乎是最有可能发生的事情。 - Morinar

9

针对未来:

  1. 确保您始终使用外部符号数据库构建(这不是调试版本 - 它是发布版本,但您需要将符号表单独存储)
  2. 在部署的版本中保留它

针对此情况:

您知道大致区域,因此要确定是否正确,请转到堆栈跟踪并找到汇编代码 - 仔细观察并看看它是否与您的源代码匹配(如果您知道生成此汇编的某些源代码,则更容易)。 如果看起来正确,则可以对您的假设进行一些验证。 您可能可以通过查看堆栈(因为您知道传递了什么并声明了什么)来确定本地变量的值。


我怎样找到汇编代码或获取堆栈跟踪?到目前为止,我看到的所有堆栈跟踪都已复制粘贴在上面了... - Morinar
该命令是“disassemble”——请参见http://www.unknownroad.com/rtfm/gdbtut/gdbadvanced.html。 - Lou Franco
我执行了以下操作并得到了如下结果: (gdb) disassemble 没有函数包含所选帧的程序计数器。这对我来说似乎更像是Sufian所建议的破碎堆栈。 - Morinar

6
在gdb中,“info registers”应该能够提供足够的执行状态信息,用于与可执行文件和相关共享库的反汇编配合使用。我通常使用objdump进行反汇编,将输出重定向到文件中,然后在我喜欢的编辑器中打开该文件 - 这对于在解决问题时记录笔记非常有用。此外,gdb的“info target”和“info sharedlib”可以帮助确定共享库加载的位置。
凭借寄存器状态、堆栈内容和反汇编结果以及一点运气,应该很容易(虽然可能有些繁琐)重构调用堆栈(除非堆栈已被缓冲区溢出或类似灾难性事件破坏...这种情况下可能需要Ouija板或水晶球)。
您还可以将带有-g标志的新版本的反汇编与剥离版本的反汇编进行对比。

4
  1. 始终使用源代码控制(CVS/GIT/Subversion等),即使是测试版本。
  2. 所有版本打标签。
  3. 考虑(未来)使用调试(-g)进行构建,并在发布前剥离可执行文件。注意:不要制作带有和不带有-g的两个构建版本;它们可能不匹配,因为即使在相同的优化级别下,-g有时也会导致生成不同的代码。在超高性能关键代码中,您可以放弃对关键文件使用-g,对大多数文件而言,这不会有任何区别。
  4. 如果你真的卡住了,就把堆栈和堆的相关部分转储成十六进制,并手动查看;也许可以拿一个带有仪器的副本,在生成的代码和堆栈中寻找类似的“签名”。这是一种真正的“老派”调试方法... :-)

绝对稳健的建议。我们在这里基本上执行步骤1-3,但无论如何,这些步骤都由完全不同的人员(我们这里有一个负责这些事情的团队)处理,而不是我自己。 - Morinar

1

你是否有编译旧版本所使用的确切源代码(例如:通过源代码树中的标签或类似方式)?也许你可以使用它重新构建,可能会得到关于崩溃发生位置的洞察?


我确实有准确的源代码,但是这段特定的代码从那时起到现在几乎没有改变过(如果有的话)。 - Morinar

1

尝试对核心文件运行“pmap”(如果hp/ux有此工具)。这应该报告核心文件中所有模块的起始地址。有了这些信息,您应该能够获取失败位置的地址并找出哪个库崩溃了。在崩溃地址和库中已知函数的地址之间进行进一步的地址比较(可以通过对库运行“nm”命令获得),可能会帮助您确定哪个函数崩溃了。

即使您确实设法识别出堆栈顶部的函数,它也不太可能是问题的源头……希望它实际上崩溃在您的代码中,而不是标准的C字符串库。在那时重新构建堆栈跟踪是次佳方案。


0

这里没有太多信息。二进制文件已被剥离。但是,从分段错误来看...你应该寻找可能会覆盖内存片段的地方。

这只是一个建议。问题可能有很多。

顺便说一下,如果你在本地机器上无法复现问题,那么客户端数据量可能是一个问题。


0

我认为核心文件不应包含符号。您需要能够构建与已发送给客户完全相同但带有-g的程序版本。如果剥离调试可执行文件,则应与已发送版本完全相同。只有这样,gdb 才能提供有用的信息。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接