4

我在嵌入式环境中检测到内存损坏(我的程序在具有专有操作系统的机顶盒上运行)。但我无法找到它的根本原因。内存损坏本身是在多次启动和退出应用程序的压力测试后检测到的。考虑到我无法设置内存断点,因为每次启动应用程序时损坏的变量都会改变它的地址,有没有办法抓住这种损坏的根本原因?

(内存断点是当环境改变给定内存地址的值时启动的断点)

另请注意,我所有的软件都是使用 C 语言开发的。

谢谢你的帮助。

4

8 回答 8

6

这些都是嵌入式系统上的难题,没有简单的答案。一些技巧:

  • 查看内存损坏的值。这可以给出明确的提示。
  • 查看内存损坏旁边的数据结构。
  • 查看内存损坏中是否存在模式。它总是在一个相似的地址吗?
  • 看看是否可以在运行时设置内存断点。
  • 嵌入式系统是否允许对内存区域进行沙盒处理?设置沙箱以保护您的数据内存。

祝你好运!

于 2009-11-05T21:27:02.837 回答
5

数据存储在哪里以及所涉及的两个进程如何访问它?

如果结构是在堆外分配的,请尝试分配一个更大的块并在结构之前和之后放置大的保护区。这应该让您了解它是否是周围的堆分配之一,它已经溢出到与您的结构相同的分配中。如果你发现你的结构周围的内存没有受到影响,并且只有结构本身被破坏,那么这表明损坏是由一些知道你的结构位置的东西引起的,而不是随机的记忆踩踏。

如果结构位于数据部分中,请检查链接器映射输出以确定结构附近存在哪些其他数据。检查那些是否也已损坏,引入警戒区域,并检查问题是否与结构有关,如果你强迫它移动到不同的位置。这再次表明损坏是否是由了解您的结构位置的某些东西引起的。

您还可以通过将数据从堆切换到数据部分来测试这一点,反之亦然。

如果在将结构移到别处或引入保护区后发现结构不再损坏,则应检查链接器映射或跟踪堆以确定附近还有哪些其他数据,并检查对这些区域的访问是否存在缓冲区溢出。

但是,您可能会发现,无论它位于何处,问题都会跟随结构。如果是这种情况,则审核围绕结构引用的所有代码。每次访问前后检查内容。

要检查损坏是否是由另一个进程或中断处理程序引起的,请在每个任务切换以及调用每个 ISR 之前和之后添加挂钩。挂钩应检查内容是否已损坏。如果有,您将能够确定哪个流程或 ISR 负责。

如果该结构曾被读入本地进程堆栈,请尝试增加进程堆栈并检查是否没有发生数组溢出等。即使没有读入堆栈,您也可能在某个时候在堆栈上有指向它的指针。检查附近调用的所有子函数是否存在堆栈问题或类似问题,这些问题可能导致指针被不相关的代码块错误地使用。

还要考虑编译器或 RTOS 是否有问题。尝试关闭编译器优化,并检查生成的代码失败。同样考虑这是否可能是由于您的专有 RTOS 中的上下文切换错误所致。

最后,如果您与另一个硬件设备或 CPU 共享内存并且启用了数据缓存,请确保通过使用未缓存访问或类似策略来处理此问题。

于 2009-11-06T00:57:37.590 回答
4

是的,使用调试器很难追踪这些问题。

一些想法:

  • 定期进行代码审查(在追踪特定错误方面并不快,但对于发现此类问题通常很有价值)
  • 注释掉或#if 0删除代码段,然后运行删减的应用程序。尝试注释掉不同的部分以尝试缩小错误发生在代码的哪个部分。
  • 如果您的架构允许您轻松禁用某些进程/任务的运行,那么通过消除过程也许您可以缩小导致错误的进程。
  • 如果您的操作系统是协作式多任务处理,例如循环法(我认为这对于抢占式多任务处理来说太难了):将代码添加到“拥有”结构的任务的末尾,以保存对结构的“检查”。该检查可能是 memcpy(如果您有时间和空间)或 CRC。然后在所有其他任务运行后,添加一些代码来验证与保存的检查相比的结构。这将检测到任何变化。
于 2009-11-05T23:32:11.357 回答
2

我假设您的问题是您怀疑专有代码的某些部分导致了问题。

我过去曾使用一位同事如此高雅地称之为“自杀笔记”的方式处理过类似的问题。我将分配一个缓冲区,该缓冲区能够存储被破坏的结构的多个副本。我会像循环列表一样使用这个缓冲区,定期存储结构当前状态的副本。如果检测到损坏,“遗书”将被转储到文件或串行输出中。这将使我对更改的内容和方式有一个很好的了解,并且通过增加日志记录频率,我能够缩小损坏操作的范围。

根据您的操作系统,您可以通过查看所有正在运行的进程并查看哪些当前持有信号量来对检测到的损坏做出反应(您正在使用某种共享内存的访问控制机制,对吗?)。通过拍摄这些数据的快照,您也许可以在破坏您的数据之前记录抓住锁的罪魁祸首。同样,尝试将共享内存区域的锁保持一段荒谬的时间,看看有问题的程序是否抱怨。有时他们会给出一条错误消息,其中包含可以帮助您调查的重要信息(例如,有问题的程序的行号、函数名称或代码偏移量)。

如果您愿意做一点链接器功夫,您很可能可以指定任何静态分配数据相对于程序起始地址的地址。这可能会为您提供足够一致的内存地址来设置内存断点。

不幸的是,这类问题不容易调试,特别是如果您没有所涉及的一个或多个程序的源代码。如果您可以获得足够的信息来了解您的数据是如何被损坏的,那么您可能能够调整您的结构以预测和预期损坏(有时在使用不完全符合规范或标准的代码时需要) .

于 2009-11-09T23:13:58.840 回答
1

专有操作系统可能会稍微限制您的选择。您可能能够做的一件事是在台式机上运行问题代码(假设您可以删除特定于硬件的代码),并使用那里可用的更复杂的工具(即guardmalloc、电子围栏)。

您正在使用的 C 库可能包含一些用于检测堆损坏的例程(例如 glibc 就是这样)。打开它们以及您拥有的任何跟踪设施,这样您就可以看到堆损坏时发生的情况。

于 2009-11-06T01:21:24.423 回答
1

您检测到内存损坏。你能更具体一点吗?例如,它是核心转储的崩溃吗?

通常,操作系统将完全释放所有资源并在程序正常退出时处理您的程序。即使是专有操作系统也设法做到这一点,尽管它不是给定的。

因此,间歇性问题似乎是在压力之后触发的,但只是偶然,或者可能是在初始化驱动程序或程序与之通信的其他进程中,或者可能是错误处理错误,比如当操作系统本身处于运行状态时内存分配失败强调例如懒惰整理已关闭的程序。

自定义 malloc/realloc/free 代理函数中的 Printfs,甚至是 Electric Fence 样式的自定义分配器,如果它像缓冲区溢出一样简单,可能会有所帮助。

于 2009-11-05T21:20:09.997 回答
1

使用内存分配调试工具,如 ElectricFence、dmalloc 等 - 至少它们可以捕获简单的错误和大多数中等复杂的错误(溢出、欠载,甚至在某些情况下在空闲后写入(或读取))等。我个人最喜欢的是dmalloc。

于 2009-11-05T21:27:14.000 回答
0

首先,我假设您使用的是未运行 Linux 或其他支持 POSIX 的操作系统的裸机芯片(如果您使用的是更好的技术,例如 Valgrind 和 ASan)。

以下是一些跟踪嵌入式内存损坏的提示:

  • 使用 JTAG 或类似方法在已损坏的内存区域上设置内存观察点,您可能能够捕捉到内存被意外写入与正确写入的时刻,许多 JTAG 调试器包含允许您的 IDE 插件获取堆栈跟踪
  • 在您的硬故障处理程序中尝试生成一个可以打印的调用堆栈,以便您大致了解代码崩溃的位置,请注意,由于内存损坏可能在崩溃实际发生之前的某个时间发生,因此您获得的堆栈跟踪不太可能现在有帮助,但使用下面提到的更好的技术,堆栈跟踪会有所帮助,但在裸机上生成回溯可能是一项非常困难的任务,如果您碰巧使用的是 Cortex-M 线处理器,请查看https://github .com/armink/CmBacktrace或尝试在网上搜索有关为您的特定芯片生成返回/堆栈跟踪的建议
  • 如果您的编译器支持它,则使用堆栈金丝雀来检测并在堆栈上写入内容时立即崩溃,有关详细信息,请在网络上搜索 GCC 或 Clang 的“堆栈保护器”
  • 如果您在具有 MPU(例如 ARM Cortex-M3)的芯片上运行,那么您可以使用 MPU 对正在损坏的内存区域或在该区域被损坏之前的一小部分内存区域进行写保护,这将导致芯片在损坏的那一刻而不是很久以后崩溃
于 2021-01-08T19:49:37.830 回答