【问题标题】:C code on Linux under gdb runs differently if run standalone?如果独立运行,gdb 下 Linux 上的 C 代码运行方式不同?
【发布时间】:2010-03-01 03:17:56
【问题描述】:

我使用代码魔法工具链在 Linux (Fedora) 上构建了一个纯 C 代码。这适用于 ARM Cortex-A8 目标。此代码在 Cortex A8 板上运行,运行嵌入式 Linux。

当我为一些测试用例运行此代码时,它为一些大尺寸 (10MB) 进行动态内存分配 (malloc),它在一段时间后崩溃,并给出如下错误消息:

select 1 (init), adj 0, size 61, to kill
select 1030 (syslogd), adj 0, size 64, to kill
select 1032 (klogd), adj 0, size 74, to kill
select 1227 (bash), adj 0, size 378, to kill
select 1254 (ppp), adj 0, size 1069, to kill
select 1255 (TheoraDec_Corte), adj 0, size 1159, to kill
send sigkill to 1255 (TheoraDec_Corte), adj 0, size 1159
Program terminated with signal SIGKILL, Killed.

然后,当我使用为目标构建的 gdb 为同一测试用例调试此代码时,发生此动态内存分配的点,代码无法分配该内存,malloc 返回NULL。但是在正常的独立运行期间,我相信malloc 应该无法分配,但奇怪的是它可能不会返回NULL,但它会崩溃并且操作系统会杀死我的进程。

  1. 为什么在 gdb 和没有调试器的情况下运行时这种行为会有所不同?
  2. 为什么malloc 会失败却不返回NULL。这可能吗,或者我收到错误消息的原因是什么?
  3. 我该如何解决这个问题?

谢谢,

-AD

【问题讨论】:

  • 你为什么说你希望你的 10MB malloc 无论如何都会失败?你的系统没有那么多内存?
  • 你提到的错误信息是什么?
  • 在 OP 中遗漏了错误消息,现在编辑以添加相同的内容。

标签: c linux embedded gdb


【解决方案1】:

所以,对于这部分问题,有一个肯定的答案:

为什么 malloc 会失败但不返回 NULL。这可能吗,或者我收到错误消息的原因是什么?

在 Linux 中,默认情况下,用于分配内存的内核接口几乎不会完全失败。相反,他们设置你的page table 的方式是,在第一次访问你请求的内存时,CPU 会生成一个page fault,此时内核会处理这个并查找将要使用的物理内存对于那个(虚拟)页面。因此,在内存不足的情况下,您可以向内核请求内存,它会“成功”,并且当您第一次尝试触摸它返回的内存时,this 是当分配实际上失败了,杀死了你的进程。 (或者可能是其他一些不幸的受害者。对此有一些启发式方法,我不太熟悉。请参阅“oom-killer”。)

您的其他一些问题,我的答案不太清楚。

为什么在 gdb 下运行和没有调试器时这种行为会有所不同?
可能(只是猜测)GDB 有自己的malloc,并以某种方式跟踪您的分配。在某种程度上相关的一点上,我实际上经常发现我的代码中的堆错误通常在调试器下是不可重现的。这令人沮丧,让我摸不着头脑,但这基本上是我几乎认为一个人必须忍受的事情......
我该如何解决这个问题?

这有点像大锤式的解决方案(也就是说,它改变了所有进程的行为,而不仅仅是你自己的,而且让你的程序改变全局状态通常不是一个好主意那),但您可以将字符串2 写入/proc/sys/vm/overcommit_memory。请参阅我从 Google 搜索中获得的 this link

如果做不到...我只是确保您分配的金额没有超出预期。

【讨论】:

  • 对于像这样的嵌入式系统,运行一组严格控制的进程,关闭 overcommit 可能是正确的运行方式。如果您有合理的标准启动顺序,您可以在 /etc/sysctl.conf 中永久更改。
【解决方案2】:

根据定义,在调试器下运行与独立运行不同。调试器可以并且确实隐藏了许多错误。如果您为调试而编译,您可以添加相当多的代码,类似于完全未优化的编译(例如,允许您单步执行或观察变量)。在为发布进行编译可以删除调试选项并删除您需要的代码的情况下,您可能会陷入许多优化陷阱。从您的帖子中我不知道谁在控制编译选项或它们是什么。

除非您计划交付要在调试器下运行的产品,否则您应该独立进行测试。理想情况下,您也可以在没有调试器的情况下进行开发,这样您就不必将所有事情都重复一遍。

这听起来像是你的代码中的一个错误,用新的眼光慢慢地重新阅读你的代码,就好像你在向某人解释它,或者实际上是逐行向某人解释它。那里可能有一些你看不到的东西,因为你以同样的方式看待它太久了。令人惊讶的是多少次以及效果如何。

我也可能是编译器错误。执行诸如打印返回值之类的操作会导致编译器生成不同的代码。添加另一个变量并将结果保存到该变量可以使编译器执行不同的操作。尝试更改编译器选项,减少或删除任何优化选项,减少或删除调试器编译器选项等。

这是一个经过验证的系统,还是您正在开发新硬件?例如,尝试在不启用任何缓存的情况下运行。在调试器中工作而不是独立工作,如果不是编译器错误,可能是时间问题,单步刷新管道,以不同方式混合缓存,给缓存和内存系统一个永恒的结果,它没有实时。

简而言之,在调试器下运行会隐藏错误,直到您在最终交付的类似环境中进行测试才能发现这些错误,我只谈到了一些。让它在调试器中工作而不是独立工作并不意外,这只是工具的工作方式。根据您目前给出的描述,可能是您的代码、硬件或工具。

消除它是您的代码或工具的最快方法是反汇编该部分并检查传递的值和返回值是如何处理的。如果返回值被优化,那么你的答案就是。

您是为共享 C 库还是静态库进行编译?也许编译为静态...

【讨论】:

  • @dwelch:感谢所有的指点。我已经更新了错误消息。如果它有助于进一步缩小范围,我在 OP 中搞砸了。谢谢。
猜你喜欢
  • 1970-01-01
  • 2016-06-19
  • 2012-08-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多