我正在使用多线程嵌入式应用程序。每个线程根据其功能分配堆栈大小。最近我们发现其中一个线程通过定义一个超过堆栈大小的局部变量数组来破坏堆栈。操作系统是uItron。
我的解决方案, 我注册了10 mS的计时器,这个计时器将检查堆栈损坏。
堆栈损坏检查方法, 1.使用一些独特的模式初始化堆栈内存(我使用0x5A5A5A5A) 2.从时间检查堆栈存储器的顶部是否仍为0x5A5A5A5A
我的问题,
有没有更好的方法来检查此类损坏
忘记添加,立即添加:操作系统:Itron,处理器:ARM9。编译器:不是GCC(特定于ARM9由处理器供应商提供)......并且没有内置支持堆栈检查......
答案 0 :(得分:8)
ARM9在片上有JTAG / ETM调试支持;您应该能够设置一个数据访问观察点,例如,堆栈顶部附近的64字节,然后触发数据中止,您可以在程序中或外部捕获数据中止。
(我使用的硬件仅支持2个读/写观察点,不确定这是否是片上内容或周围第三方调试工具的限制。)
This document,这是一个关于如何与JTAG功能接口的极低级别描述,建议您阅读处理器的Technical Reference Manual - 我可以保证有相当数量的更高 - 第9章(“调试支持”)中的级别信息ARM946E-S r1p1 TRM。
在您深入了解所有这些内容之前(除非您只是为了娱乐/教育而做),请仔细检查您正在使用的硬件和软件是否已经无法为您管理断点/观察点。在我们使用的调试软件中,“观察点”的概念有点难以找到 - 它是添加断点对话框中标有“硬件”的标签。
另一种选择:你的编译器可能支持一个命令行选项来在函数的入口和出口点添加函数调用(某种类型的“void enterFunc(const char * callingFunc)”和“void exitFunc(const char * callingFunc) )“),用于功能成本分析,更准确的堆栈跟踪或类似。然后,您可以编写这些函数来检查堆栈的金丝雀值。
(顺便说一下,在我们的例子中,我们实际上忽略了传入的函数名称(我希望我可以让链接器剥离这些)并只使用处理器的链接寄存器(LR)值来记录我们来自哪里我们使用它来获取准确的调用跟踪以及分析信息;此时检查堆栈金丝雀也是微不足道的!)
问题是,当然,调用这些函数会稍微改变函数的寄存器和堆栈配置文件......在我们的实验中并不多,但有点。性能影响更严重,只要存在性能影响,程序中就会出现行为改变的可能性,这可能意味着您避免触发您之前可能遇到的深度递归情况......
非常晚的更新:现在,如果您有基于clang + LLVM的管道,您可以使用Address Sanitizer (ASAN)来捕获其中一些。请注意编译器中的类似功能! (值得了解UBSAN和其他消毒剂。)
答案 1 :(得分:6)
您使用的是什么编译器?我猜一个特定于操作系统的。如果您正在使用GCC,则可以使用Stack-Smashing Protector。这可能是您的生产系统可以解决问题的一种方法,也可以让您在开发中检测它。
要有效地检查堆栈损坏,您需要检查可用的堆栈空间,在呼叫之前在堆栈参数的两侧放置保护,拨打电话,然后检查呼叫返回时的保护。这种更改通常需要修改编译器生成的代码。
答案 2 :(得分:4)
最近在嵌入式平台上工作时,我看起来很高很低(这是在ARM7上)。
建议的解决方案是您已经提出的:使用已知模式初始化堆栈并确保从函数返回后存在模式。我认为同样的事情“必须有一个更好的方式”和“没有人自动化这个”。这两个问题的答案都是“不”,我必须像你一样努力去寻找腐败发生的地方。
我还为data_abort等“滚动了我自己的”异常向量。在'如何回溯调用堆栈的网上有一些很好的例子。这是您可以使用JTAG调试器执行的操作,在发生任何这些中止向量时中断,然后调查堆栈。如果您只有1个或2个断点(这似乎是ARM JTAG调试的标准),这可能很有用。
答案 3 :(得分:3)
我完全按照您在dsPIC上使用CMX-Tiny +的建议完成了,但是在堆栈检查中我还为每个堆栈维护一个“隐藏潮汐标记”。我不是检查堆栈顶部的值,而是从顶部迭代以找到第一个非签名值,如果这比以前高,我将它存储在静态变量中。这是在优先级最低的任务中完成的,因此无论何时安排其他任何事情都会执行它(基本上替换空闲循环;在您的RTOS中,您可以挂钩空闲循环并在那里执行)。这意味着它通常比10ms定期检查更频繁地检查;在那个时候整个调度程序可能会被搞砸。
然后我的方法是超大堆栈,运用代码,然后检查高潮标记以确定每个任务的余量(以及ISR堆栈 - 不要忘记!),并相应调整堆栈我需要从超大堆栈中恢复'浪费'空间(如果不需要空间,我不打扰)。
这种方法的优点是你不要等到堆栈被破坏才能检测到潜在的问题;你在开发和检查更改时监视它。这很有用,因为如果损坏到达TCB或返回地址,你的调度程序可能会破坏,检查在溢出后永远不会启动。
有些RTOS内置了这个功能(我知道的是embOS,vxWorks)。使用MMU硬件的操作系统可能会通过将堆栈放在受保护的内存空间中来更好,因此溢出会导致数据中止。这也许是你寻求的“更好的方式”; ARM9有一个MMU,但支持它的操作系统往往更昂贵。 QNX Neutrino也许?
如果您不想手动进行高潮检查,只需将堆栈超大1K,然后在堆栈检查任务陷阱中,当保证金降至1K以下时。这样,当调度程序仍然可行时,您更有可能捕获错误条件。不是万无一失的,但是如果你开始分配足够大的对象,一次打击堆栈,无论如何都会在你的脑袋里响起警报 - 这是由更深层次的函数嵌套等引起的更常见的慢堆栈蠕变帮助。
克利福。
答案 4 :(得分:2)
正如李提到的那样,最好的办法是将Electric Fence移植到ARM9专有编译器上。如果不这样做,ARM ABI和堆栈格式都有详细记录,因此您可以编写CHECK_STACK函数来验证返回地址是否指向函数等。
然而,除非你是编译器,否则很难真正编写其中的一些检查,所以如果你没有特别依赖这个编译器,GCC 支持ARM它也支持堆栈防护装置。
答案 5 :(得分:2)
你有内核源码吗?我最后一次编写内核时,我在内核中添加了(作为选项)堆栈检查。
每当上下文切换发生时,内核都会检查2个堆栈:
(1)正在换出的任务 - >如果任务在运行时炸掉了堆栈,现在就让我们知道。
(2)目标(目标)任务 - >在我们进入新任务之前,让我们确保一些狂野的代码没有破坏它的堆栈。如果它的堆栈已损坏,甚至不会切换到任务,我们就搞砸了。
理论上可以检查所有任务的堆栈,但上面的注释提供了我检查这两个堆栈(可配置)的原因。
除此之外,应用程序代码可以在空闲循环,滴答ISR等中监视任务(包括中断堆栈,如果你有)...
答案 6 :(得分:2)
查看以下类似问题:handling stack overflows in embedded systems和how can I visualise the memory sram usage of an avr program。
就个人而言,我会使用你的处理器的内存管理单元。它可以以最小的软件开销为您进行内存检查。
在MMU中设置将用于堆栈的存储区。它应该与MMU不允许访问的两个存储区域接壤。当您的应用程序运行时,一旦溢出堆栈,您将收到异常/中断。
因为您在发生错误时遇到异常,所以您确切知道应用程序中堆栈变坏的确切位置。您可以查看调用堆栈,以确切了解自己的位置。这使得找到问题变得容易,而不是通过在问题发生很久之后发现问题来找出问题所在。
如果您不允许内存访问ram的底部,MMU也可以检测到零指针访问。
如果您拥有RTOS的源代码,您可以构建堆栈的MMU保护并堆积到其中。
答案 7 :(得分:1)
理想情况下valgrind会支持您的平台/操作系统。令我感到震惊的是,你没有为每个线程的堆栈获得单独的vm内存区域。如果有任何方法来构建你的应用程序,以便它也可以在linux上运行,你可以重现那里的bug并用valgrind捕获它。