如何在整个开发过程中持续监控堆栈分析

随着代码行数从几千到上百万不等,嵌入式软件变得日益复杂,但总体目标依然是实现稳健、正确且快速执行的软件。快速执行的软件需要以最优方式管理可用的 CPU 和内存资源,这对内存空间(尤其是 RAM)有限的嵌入式系统来说不失为一项挑战。为此,必须通过执行堆栈和堆分析对 RAM 的使用情况进行分析。开发人员手动估计堆栈和堆负载是一项艰巨的任务,即使对于小程序来说也是如此。如果估计不正确,则可能会导致堆栈溢出和未定义的行为。因此,一些编码标准要求强制执行内存分配使用最佳实践,以避免不必要的开销。但是,堆栈仍是 RAM 的必要组成部分,需要得到优化利用。

为什么嵌入式系统需要堆栈分析?

当可用堆栈小于代码需求时,就会发生堆栈溢出。然而,当为环境配置的堆栈大于需求时,内存又会被浪费。开发人员必须持续一致地估计安全关键型应用中最差情形下的堆栈使用量,以防止软件运行时发生 RAM 不足的情况。

堆栈估计不正确所带来的风险。

堆栈估计不正确所带来的风险。

如何估计堆栈分析?

手动估计堆栈

虽然手动估计堆栈分析偶尔会有帮助,但对于更复杂的系统来说可能充满挑战。这就需要彻底了解函数调用的深度、所有局部变量的细节,以及执行过程中随时发生的中断帧的大小,等等。这是一个漫长而又容易出错的过程。使用静态代码分析工具可以快速计算出上述结果,而不必手动执行这个过程。

使用静态代码分析器

开发人员可以通过静态代码分析器预测堆栈的使用情况。分析工具有助于分析函数调用深度、局部变量和返回参数的堆栈估计、嵌套中断以及执行期间发生的中断帧的大小。使用静态代码分析器的好处在于,它可以处理编码规则违规、运行时缺陷、编码复杂性以及堆栈分析估计。它在几分钟内就能完成这些操作,从而帮助开发人员节省了手动计算堆栈使用量的时间。

对目标进行测试和测量

静态分析器可以在开发过程中估计堆栈的使用量。但是,最好在真实硬件上获得实际的堆栈使用量数据。许多开发环境都具有硬件模拟功能,并提供实时堆栈分析功能。在实际硬件上执行堆栈分析,并创建溢出场景来测试故障安全例程,这一点非常重要。现在,最大的问题是:何时使用静态分析工具执行堆栈分析?何时在实际目标上执行?

何时执行堆栈分析?

执行堆栈分析是软件开发生命周期中的一个连续过程。如果仅在软件开发生命周期结束时由单独的质量评估团队估计堆栈使用量,则可能会使整个开发工作面临风险。此外,在开发周期的后期解决问题可能会出错且耗时;在确定是更改硬件还是软件设计时,这种做法可能还会造成混乱。执行堆栈分析的最佳时点是:

  • 在添加新功能时

    在软件中每添加一项新功能,都会使堆栈使用量增加。开发人员必须密切关注新功能的堆栈使用情况。
    1. 执行堆栈分析、进行调试和修复复杂代码:在每个主要功能实现后,开发人员可以在本地对特定软件组件或软件模块应用静态分析器,以评估基础软件和已实现软件之间堆栈使用量的增加情况。
    2. 在整个开发过程中监控堆栈分析:QA 团队和产品负责人可以使用静态分析器对持续集成 (CI) 管道进行堆栈估计,以在控制板上显示结果。此过程有助于在软件开发生命周期中跟踪堆栈分析。
    3. 执行良好实践以确保堆栈使用量最低:质量门有助于避免违反 MISRA™ 和 AUTOSAR 编码规范。这些规范要求强制有条件地使用动态内存分配。
  • 在软件发布前

    静态分析器执行的堆栈估计提供了有力的证据,表明堆栈使用量处于控制之中。在每次软件发布之前,都应在标准工作负载、最小负载和最大负载下,对真实目标运行堆栈分析,以全面了解堆栈的使用情况。验证堆栈上溢和下溢事件的故障安全例程也至关重要。

Polyspace 对堆栈估计有何作用?

Polyspace Code Prover™ 对每个函数中局部变量的大小上限和下限执行保守和乐观估计,以得出函数级和程序级的最大和最小堆栈使用量。该分析考虑了函数返回值的大小、函数参数的大小、局部变量的大小,以及内存对齐所需的额外填充。

Polyspace 桌面版上的堆栈分析代码度量。

Polyspace 桌面版上的堆栈分析代码度量。

要了解和调试堆栈利用过量的问题,开发人员可以在本地运行 Polyspace®,通过检查函数调用深度来确定堆栈利用过量的确切原因,并通过优化利用可用资源来降低堆栈使用量。

函数 table_loop() 的调用树和更高的堆栈估计。

函数 table_loop() 的调用树和更高的堆栈估计。

在整个开发过程中监控堆栈分析

Polyspace Access™ 是结果数据库服务器,可以在 Web 浏览器上呈现图形用户界面。CI 流程可以触发 Polyspace Server™ 上的堆栈分析,以生成堆栈使用量估计结果。此结果可以上传到结果数据库。QA 团队和产品负责人可以在图形化前端持续查看堆栈使用量,并在可用堆栈资源过度使用的情况下采取必要的措施。

Polyspace Access 中的项目级堆栈估计。

Polyspace Access 中的项目级堆栈估计。

下一步是,检查堆栈使用量较高的函数,并将特定函数分配给开发人员,以供进一步调查和调试。Polyspace 允许您为分析结果指定状态和严重性并添加注释,然后在 Jira 等 Bug 跟踪工具中将这些结果分配给开发人员。

Polyspace Access 中的函数级堆栈估计和结果审查控制板。

Polyspace Access 中的函数级堆栈估计和结果审查控制板。

执行良好实践以确保堆栈使用量最低

对于产品级代码,必须绝对避免违反编码标准,如 MISRA C™、MISRA C++、AUTOSAR C++ 等。这些编码标准要求强制禁止动态内存分配,并推荐特定用例来优化静态内存分配。Polyspace Bug Finder™ 有助于识别任何违反最佳实践的行为,开发人员可以在本地监控这些行为,而产品负责人可通过 Polyspace Access 监控这些行为。以下编码规则详细说明了静态内存分配的最佳实践。静态内存分配可以使用 Polyspace Bug Finder 进行分析。

编码规范

规则

描述

MISRA C:2004

20.4

不能使用动态堆内存分配。

MISRA C:2012

21.3

不能使用 <stdlib.h> 的内存分配和取消分配函数。

MISRA C++:2008

18-4-1

不能使用动态堆内存分配。

AUTOSAR C++14

A18-5-1

不能使用函数 malloc、calloc、realloc 和 free。

AUTOSAR C++14

A18-5-2

不能使用非定位 new 或 delete 表达式。

AUTOSAR C++14

A18-5-3

delete 表达式的形式应与用于分配内存的 new 表达式的形式保持一致。

AUTOSAR C++14

A18-5-4

如果为某个项目全局定义了运算符“delete”的有大小或无大小版本,则应同时定义有大小和无大小版本。

AUTOSAR C++14

A18-5-5

内存管理函数应确保以下各项:(a) 行为是确定的,能够预测出在最差情形下的执行时间,(b) 避免内存碎片化,(c) 避免运行时出现内存不足,(d) 避免不匹配的分配或取消分配,以及 (e) 不依赖对内核的非确定性调用。

AUTOSAR C++14

A18-5-7

如果项目中使用动态内存管理函数的非实时实现,则只应在非实时程序阶段分配和取消分配内存。

AUTOSAR C++14

A18-5-8

存活期不超过函数的对象应具有自动存储期。

AUTOSAR C++14

A18-5-9

动态内存分配和取消分配函数的自定义实现应满足 C++ 标准中相应“必要行为”条款中指定的语义要求。

AUTOSAR C++14

A18-5-10

定位 new 运算符只能与对齐正确且指向足够存储容量的指针结合使用。

AUTOSAR C++14

A18-5-11

运算符“new”和运算符“delete”应一起定义。

随着代码的循环复杂度、嵌套函数调用的数量、函数中变量数目等的增加,堆栈的使用量也在增加。Polyspace 能够控制影响堆栈使用量的诸多变量,并支持设置代码复杂度阈值。

设置代码复杂度阈值。

设置代码复杂度阈值。

Polyspace Bug Finder 提供了许多针对静态和动态内存分配的运行时检查。解决所有高、中和低优先级缺陷有助于降低内存分配带来的风险。

运行时静态和动态内存检查。

运行时静态和动态内存检查。

无论使用何种方法来计算堆栈使用量,稍微增大堆栈大小都不失为一个好办法。这种方法有助于避免测试期间可能未检测到的堆栈溢出导致的系统漏洞。

堆栈溢出漏洞是许多嵌入式应用程序在实际运行中表现出不可定义行为的一个重要原因。在正确的时间使用正确的工具并遵循最佳实践,可以增强对软件防止堆栈溢出的信心。