|嵌入式开发:创建堆栈监视器的7个步骤

|嵌入式开发:创建堆栈监视器的7个步骤

文章图片


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

步骤 1 – 执行最坏情况堆栈分析
许多编译器和工具链会自动将堆栈大小设置为 0x400 字节 , 相当于一千字节的 RAM 。 对于许多应用程序来说 , 一千字节的堆栈大小通常就足够了 , 但这是计算机科学而不是猜谜游戏 , 那么工程师如何确保堆栈大小合适呢?答案是执行最坏情况堆栈分析 。
最坏情况堆栈分析可以通过许多不同的方式执行 , 超出了本文的范围 。 一般来说 , 开发人员有许多项目需要完全理解 。 首先 , 有必要了解其应用程序的调用深度;有多少函数正在调用在返回链之前调用函数的函数 。 每个返回地址都存储在堆栈中 。 其次 , 开发人员需要了解这些函数中每个变量的数量和大小 , 以估计每个函数将使用多少堆栈空间 。 最后 , 嵌入式开发人员需要确定可以同时触发多少个中断以及每个中断帧的大小 。
步骤 2 – 设置堆栈大小
最坏情况堆栈分析的输出将导致堆栈的大小 。 尽管对系统进行了仔细分析 , 但计算堆栈大小可能很困难且很难做到 , 将最终数字乘以 1.5 并没有什么坏处 , 只是为了确保包含一个合理的缓冲区以应对不可预见的情况 。 然后可以通过项目属性或链接器文件更改堆栈大小 , 具体取决于首选项和工具功能 。
步骤 3 – 选择一种保护方法
正确调整堆栈大小是防止堆栈溢出和破坏附近内存区域的良好进展 , 但它仍然不允许检测此类溢出事件 。 在嵌入式系统中 , 有多种方法可以检测到此类事件 。 首先 , 是使用内存保护单元并设置堆栈的边界 , 以便如果堆栈越过边界 , 则会触发中断 , 然后系统可以记录问题并按照程序恢复系统 。 其次 , 如果正在使用 RTOS , 开发人员可以启用堆栈溢出检测 。 默认情况下 , 许多 RTOS 都启用了此检测 , 但我看到有文章建议关闭此功能以提高性能!不建议开发人员禁用堆栈溢出检测 , 否则你可能会感到堆栈溢出错误的冷落 。 最后 , 在没有 MPU 或使用 RTOS 的资源受限系统中 , 开发人员可以非常轻松地创建自己的堆栈监视器 。
步骤 4 – 将保护部分添加到链接器
嵌入式开发人员可以通过多种不同的方式创建堆栈保护部分 , 但指定保护大小和位置的一种有用方法是使用链接器文件 。 可以更新链接器文件以包含保护大小和位置 。 大小是完全任意的 。 一个经验法则是让它足够大 , 这样如果发生溢出 , 它就不会溢出防护区域 。 在图 1 中可以看到保护部分的示例 。

图1
步骤 5 – 使用模式填充保护空间
【|嵌入式开发:创建堆栈监视器的7个步骤】创建保护部分很棒 , 但除非其中填充了已知模式 , 否则它并不是非常有用 。 然后可以稍后由应用程序代码检查保护模式 。 任何模式都可以放置在防护区域中 , 但我发现使用人类可读的模式很有用 。 使用 0xC0DE 模式是我最喜欢使用的模式之一 。 图 1 显示了一个人口密集的警戒区域的示例 。 确切的实现将根据所使用的工具链而有所不同 。
步骤 6 – 定期检查模式
应用程序代码应设置为定期检查整个保护部分是否仍然包含正确的模式 。 模式的变化将由堆栈溢出引起 。 此检查的应用程序代码相对简单 。 嵌入式开发人员只需要遍历每个模式并验证它是否仍然正确 。 图 2 显示了一个使用指针检查堆栈保护填充模式的示例循环 。 如果检测到更改 , 则应用程序可以分支并尝试记录系统堆栈并开始恢复过程 。

图2
步骤 7 – 测试警卫
创建堆栈监视器的最后一步当然是测试它!测试它的最佳方法之一是编写一小段代码来修改堆栈保护模式 。 堆栈保护的定期检查应该检测到模式已经改变 , 这表明堆栈已经溢出 。
经过测试的堆栈监视器对提高系统的可靠性和稳健性大有帮助 。 一旦监控的堆栈能够检测到溢出 , 就需要额外的应用程序代码来决定如何处理该信息 。 记录调用深度、寄存器值和应用程序状态将帮助开发人员重复溢出并发现根本原因 。