是否有一种编程方式来检查堆栈损坏

我正在使用multithreading嵌入式应用程序。 每个线程都根据其function分配堆栈大小。 最近我们发现其中一个线程通过定义一个超过堆栈大小的局部变量数组来破坏堆栈。 操作系统是uItron。

我的解决方案,我注册了10 mS的计时器,这个计时器将检查堆栈损坏。

堆栈损坏检查方法,1。用一些独特的模式初始化堆栈内存(我使用0x5A5A5A5A)2。检查堆栈内存顶部是否仍然是0x5A5A5A5A

我的问题,

有没有更好的方法来检查这种类型的腐败

忘了添加,立即添加:操作系统:Itron,处理器:ARM9。 编译器:不是GCC(特定于ARM9由处理器供应商提供)……并且没有内置支持堆栈检查…

最近在嵌入式平台上工作时,我看起来很高很低(这是在ARM7上)。

建议的解决方案是您已经提出的:使用已知模式初始化堆栈并确保从函数返回后存在模式。 我认为同样的事情“必须有一个更好的方式”和“没有人自动化这个”。 这两个问题的答案都是“不”,我必须像你一样努力去寻找腐败发生的地方。

我还为data_abort等“滚动了我自己的”exception向量。在’如何回溯调用堆栈的网上有一些很好的例子。 这是您可以使用JTAG调试器执行的操作,在发生任何这些中止向量时中断,然后调查堆栈。 如果您只有1个或2个断点(这似乎是ARM JTAG调试的标准),这可能很有用。

ARM9在片上有JTAG / ETM调试支持; 您应该能够设置一个数据访问观察点,覆盖堆栈顶部附近的64字节,然后触发数据中止,您可以在程序中或外部捕获。

(我使用的硬件仅支持2个读/写观察点,不确定这是否是片上内容或周围第三方调试工具的限制。)

本文档是关于如何与JTAGfunction接口的极低级别描述,建议您阅读处理器的技术参考手册 – 我可以保证在第9章中有相当数量的高级信息(“Debug”支持“) ARM946E-S r1p1 TRM 。

在您深入了解所有这些内容之前(除非您只是为了娱乐/教育而做),请仔细检查您正在使用的硬件和软件是否已经无法为您管理断点/观察点。 在我们使用的调试软件中,“观察点”的概念有点难以找到 – 它是添加断点对话框中标有“硬件”的标签。


另一种选择:你的编译器可能支持一个命令行选项,在函数的入口和出口点添加函数调用(某种“void enterFunc(const char * callingFunc)”和“void exitFunc(const char * callingFunc)”) ,用于function成本分析,更准确的堆栈跟踪或类似。 然后,您可以编写这些函数来检查堆栈的金丝雀值。

(顺便说一句,在我们的例子中,我们实际上忽略了传入的函数名称(我希望我可以让链接器去除它们)并且只使用处理器的链接寄存器(LR)值来记录我们来自哪里。我们使用这是为了获得准确的调用跟踪以及分析信息;此时检查堆栈金丝雀也是微不足道的!)

问题是,当然,调用这些函数会稍微改变函数的寄存器和堆栈配置文件……在我们的实验中并不多,但有点。 性能影响更严重,并且只要存在性能影响,程序中就会出现行为更改的可能性,这可能意味着您例如避免触发您之前可能遇到的深度递归情况……


更新很晚:如果你有一个基于clang + LLVM的管道,你可能可以使用Address Sanitizer(ASAN)来捕获其中一些。 请注意编译器中的类似function! (值得了解UBSAN和其他消毒杀菌剂。)

你用的是什么编译器? 我猜一个特定于操作系统的。 如果您正在使用GCC,您可以使用Stack-Smashing Protector 。 这可能是您的生产系统可以解决问题的一种解决方法,也可以让您在开发过程中检测它。

要有效地检查堆栈损坏,您需要检查可用的堆栈空间,在调用之前在堆栈参数的两侧放置保护,进行呼叫,然后检查呼叫返回时的保护。 这种更改通常需要修改编译器生成的代码。

我完全按照您在dsPIC上使用CMX-Tiny +的建议完成了,但是在堆栈检查中我还为每个堆栈保留了一个“隐藏潮汐标记”。 我不是检查堆栈顶部的值,而是从顶部迭代以找到第一个非签名值,如果这比以前高,我将它存储在静态变量中。 这是在优先级最低的任务中完成的,因此无论何时安排其他任何事情都会执行它(基本上替换空闲循环;在您的RTOS中,您可以挂钩空闲循环并在那里执行)。 这意味着它通常比10ms定期检查更频繁地检查; 在那个时候整个调度程序可以搞砸了。

我的方法是超大堆栈,运行代码,然后检查高潮标记以确定每个任务的余量(和ISR堆栈 – 不要忘记!),并相应调整堆栈,如果我需要从超大堆栈中恢复’浪费’空间(如果不需要空间,我不打扰)。

这种方法的优点是你不要等到堆栈被破坏才能检测到潜在的问题; 你在开发和检查更改时监视它。这很有用,因为如果损坏到达TCB或返回地址,你的调度程序可能会破坏,检查在溢出后永远不会启动。

一些RTOS内置了这个function(embOS,我知道的vxWorks)。 使用MMU硬件的操作系统可能会通过将堆栈放在受保护的内存空间中来更好,因此溢出会导致数据中止。 这也许是你寻求的“更好的方式”; ARM9有一个MMU,但支持它的操作系统往往更昂贵。 QNX Neutrino也许?

附加说明

如果您不想手动进行高潮检查,只需将堆栈超大1K,然后在堆栈检查任务陷阱中,当边距降至1K以下时。 这样,当调度程序仍然可行时,您更有可能捕获错误条件。 不是万无一失的,但是如果你开始分配足够大的对象,一次性打击堆栈,无论如何都会在你的脑海中响起警报 – 这是由更深层次的函数嵌套等引起的更常见的慢堆栈蠕变帮助。

克利福德。

正如李提到的那样,最好的办法是将Electric Fence移植到ARM9专有的编译器上。 如果不这样做,ARM ABI和堆栈格式已有详细记录,因此您可以编写CHECK_STACK函数来validation返回地址是否指向函数等。

但是,除非你是编译器,否则很难真正编写其中的一些检查,所以如果你没有特别依赖这个编译器,GCC 确实支持ARM,它也支持堆栈保护。

你有内核源码吗? 我最后一次编写内核时,我在内核中添加了(作为选项)堆栈检查。

每当上下文切换发生时,内核都会检查2个堆栈:

(1) 任务被换出 – >如果任务在运行时炸掉堆栈,现在就让我们知道。

(2) 目标(目标)任务 – >在我们跳入新任务之前,让我们确保一些狂野的代码没有破坏它的堆栈。 如果它的堆栈已损坏,甚至不切换到任务,我们就搞砸了。

从理论上讲,可以检查所有任务的堆栈,但上面的注释提供了我检查这两个堆栈(可配置)的原因。

除此之外,应用程序代码可以在空闲循环,tick ISR等中监视任务(包括中断堆栈,如果你有)…

查看这些类似的问题: 处理嵌入式系统中的堆栈溢出,以及如何可视化avr程序的内存sram使用情况 。

就个人而言,我会使用你的处理器的内存管理单元。 它可以以最小的软件开销为您进行内存检查。

在MMU中设置将用于堆栈的存储区。 它应该与MMU不允许访问的两个存储区域接壤。 当您的应用程序运行时,一旦溢出堆栈,您将收到exception/中断。

因为在发生错误时您会收到exception,所以您确切知道应用程序在哪里堆栈变坏了。 您可以查看调用堆栈,以确切了解自己的位置。 这使得查找问题变得容易,而不是通过在问题发生很久之后发现问题来找出问题所在。

如果您不允许内存访问ram的底部,MMU也可以检测零指针访问。

如果您拥有RTOS的源代码,则可以构建堆栈的MMU保护并将其堆积到其中。

理想情况下, valgrind会支持您的平台/操作系统。 令我感到震惊的是,你没有为每个线程的堆栈获得单独的vm内存区域。 如果有任何方法来构建你的应用程序,以便它也可以在linux上运行,你可以重现那里的bug并用valgrind捕获它。