驾驭功能安全应用中的编译器挑战
跨行业,功能安全对开发者施加严格要求:代码必须防御性工程化,防范内存损坏、宇宙射线干扰等意外事件。C、C++等高级语言含语言规范未完全定义行为的特性。这些未定义行为可能在安全关键系统产生意外、潜在灾难性结果。因此标准强制防御性编码、严格可测试性、全面代码覆盖、从需求到应用代码的可追溯性,确保安全功能完整唯一实现。
安全关键领域,尤其汽车,常需复杂外部诊断、标定、开发工具。防御性编码和外部数据访问非编译器固有概念。C、C++均不适配内存损坏,若防御措施非代码显式可达,编译器可能优化掉。防御因此必须在语法和语义上可达以幸存优化过程。
未定义行为极难发现。即使识别,也无保证编译二进制按预期行为。调试工具用的"后门"数据访问是编译器不考虑语言构造的另例,导致不可预见副作用。
编译器优化可能消除看似合理的防御代码,若其位于不可行路径——无任何输入能执行的路径。即使通过单元测试覆盖的代码,最终构建时也可能被移除。因此高源码级覆盖率不保证可执行文件含预期安全检查。
目标代码验证(OCV)因此是后果严重系统的最佳实践。OCV确认编译目标匹配开发者意图,这在编译器可能以违反安全假设方式转换代码时至关重要。
编译前后
功能安全、安全、编码标准(IEC 61508、ISO 26262、IEC 62304、MISRA C/C++)强调需演示需求基测试中多少源码被执行。高覆盖率降低现场失效概率,但依赖编译器忠实复现源码级行为的假设——对最关键应用无法成立。
生成目标代码的控制和数据流很少精确镜像源码。目标代码与汇编1:1映射揭示差异。例如图1展示用TI编译器优化关闭时从源片段生成的汇编。启用优化时流程图剧烈变化,因编译器应用假设无未定义行为的数学变换。

图1:源码vs汇编对比,展示优化如何改变控制流。
编译器可能优化掉防御调用,若判定相关分支不可达。图2演示CLANG编译中,因编译器推断状态变量仅能持有两值,移除错误处理器调用。此优化虽高效,违背防御性编码安全意图。

图2:CLANG因不可达分支移除防御错误调用。
改变周围上下文可改变编译器优化决策。图3新增访问状态变量的函数;编译器现保留防御调用,因额外上下文使分支潜在可行。进一步变更如返回状态指针,强制编译器在汇编中保留显式值(图4)。

图3:上下文变更保留防御分支。

图4:指针别名阻止防御代码优化。
单元测试桩中通过指针操纵状态可防防御代码被优化掉,但此行为不持久至生产构建。因此单元测试覆盖率单独可能掩盖缺失安全检查。
目标代码验证
OCV通过对比源码和汇编提供安全网。图6展示简单函数,单次测试调用达100%源码级覆盖。然而汇编流程图(图8)揭示单元测试遗漏的未执行路径。强制测试覆盖所有汇编路径,OCV揭示源码级测试忽略的间隙。

图6:单元测试中达100%覆盖的源码。

图7:源码基本块流程图。

图8:汇编流程图揭示未测试路径。
OCV虽不能阻止编译器应用优化规则,但将开发者意图与可执行行为间任何不匹配暴露出来,使产品部署前能采取纠正行动。
跨领域最佳实践
对许多系统,源码级单元测试覆盖率足满足认证机构。然而在最关键安全关键应用——跨航空航天、汽车、医疗、工业领域——补充源码覆盖以OCV至关重要。OCV将编译产品与标准要求的严格安全要求对齐,提供最高信心可执行文件按预期行为。
嵌入式