创建堆栈监视器的7个步骤

在嵌入式系统中寻找最棘手的错误之一是,当堆栈溢出其边界并开始覆盖附近的内存区域时。当发生中断和函数调用的完美风暴时,通常会随机出现堆栈溢出的症状,这使得它们很难被检测到。为了防止通过使用堆栈监视器而导致堆栈溢出,开发人员可以采取七个步骤来确保堆栈保留在其分配的内存区域中。

步骤#1 –执行最坏情况的堆栈分析

许多编译器和工具链会自动将堆栈大小设置为0x400字节,这相当于一千字节的RAM。通常,对于许多应用程序来说,一千字节的堆栈大小就足够了,但这是计算机科学,而不是猜测游戏,因此工程师如何确保堆栈的大小正确?答案是执行最坏情况的堆栈分析。

最坏情况的堆栈分析可以通过许多不同的方式执行,这超出了本文的范围。通常,开发人员具有许多需要完全理解的项目。首先,必须了解其应用程序的调用深度。有多少个函数正在调用函数,这些函数在返回备份链之前先调用函数。每个返回地址都存储在堆栈中。其次,开发人员需要了解这些函数中每个变量的数量和大小,以估计每个函数将使用多少堆栈空间。最后,开发人员将需要确定可以同时触发多少个中断以及每个中断帧的大小。

步骤#2 –设置堆栈大小

最坏情况堆栈分析的输出将导致堆栈应具有的大小。尽管对系统进行了仔细的分析,但计算堆栈大小可能很困难,也很困难,将最终数字乘以1.5不会无济于事,只是为了确保在意外情况下包含合理的缓冲区。然后,可以根据项目和工具功能,通过项目属性或链接器文件来更改堆栈大小。

步骤#3 –选择一种保护方法

适当调整堆栈大小是防止堆栈溢出和破坏附近的内存区域的良好进展,但仍然无法检测到此类溢出事件。在嵌入式系统中,有多种检测此类事件的方法。第一种是使用内存保护单元并设置堆栈的边界,以便在堆栈越过边界时会触发中断,然后系统可以记录问题并按照以下步骤恢复系统。其次,如果正在使用RTOS,则开发人员可以启用堆栈溢出检测。默认情况下,许多RTOS都启用了此检测功能,但是我看过一些文章建议关闭此功能以提高性能!不建议开发人员禁用堆栈溢出检测,否则您可能会感到堆栈溢出错误。最后,在没有MPU或未使用RTOS的资源受限的系统中,开发人员可以非常轻松地创建自己的堆栈监视器。

步骤#4 –将防护部分添加到链接器

开发人员可以用多种方法创建堆栈保护区,但指定保护区大小和位置的一种有用方法是使用链接器文件。链接器文件可以更新为包括防护大小和位置。大小完全是任意的。经验法则是使其足够大,以便在发生溢出时不会溢出保护区。图1中显示了防护部分的外观示例。

GUARD_SIZE = DEFINED(__guard_size__) __guard_size__:0x00000100;

.guard:
{
  .=ALIGN(8);
  FILL(0xC0DE);
  .+=GUARD_SIZE-1;
  BYTE(0xE)
}>m_data

图1 –链接器外观示例

步骤#5 –用图案填充防护空间

创建防护部分很棒,但是除非其中填充了已知的模式,否则它并不是非常有用。然后可以通过应用程序代码检查保护模式。任何图案都可以放置在保护区域中,但是我发现使用易于阅读的图案很有用。模式0xC0DE的使用是我的最爱之一。图1显示了一个填充的保护区域的外观示例。确切的实现将根据所使用的工具链而有所不同。

步骤#6 –定期检查模式

应设置应用程序代码以定期检查整个保护部分是否仍包含正确的模式。模式更改将由堆栈溢出引起。此检查的应用程序代码相对简单。开发人员只需要遍历每种模式并验证其是否正确即可。图2显示了使用指针检查堆栈保护填充模式的示例循环。如果检测到更改,则应用程序可以分支并尝试记录系统堆栈并开始恢复过程。

void main(void)
{
    uint32_t * GuardPtr = (uint32_t*) GUARD_START;
    
    for(int Index=0; Index < GUARD_SIZE; Index++)
    {
        if(*GuardPtr == 0xC0DE)
        {
           // Do Nothing or signal OK
        }
        else
        {
           // Flag error! Attempt recovery ...
        }
    }
}

图2 – Guard应用程序可能看起来像的示例

步骤#7 –测试后卫

创建堆栈监视器的最后一步当然是对其进行测试!对其进行测试的最佳方法之一是编写一小段代码,以修改堆栈保护模式。定期检查堆栈保护装置应检测到模式已更改,这表示堆栈已溢出。

经过测试的堆栈监视器在提高系统的可靠性和健壮性方面还有很长的路要走。一旦受监视的堆栈能够检测到溢出,就需要额外的应用程序代码来决定如何处理该信息。记录呼叫深度,寄存器值和应用程序状态将帮助开发人员重复溢出并发现根本原因。

最后的想法

开发人员在开始软件开发时常常会忽略该堆栈。除非开发人员不努力对其进行监视,否则堆栈溢出是难以发现的错误之一。检测堆栈溢出并不难,并且监视器性能稍有下降是值得的!

发表评论

您的电子邮件地址不会被公开。 必需的地方已做标记 *

该网站使用Akismet减少垃圾邮件。 了解如何处理您的评论数据.