尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

嵌入式固件静态分析实战:从PolySpace到CI集成

嵌入式固件静态分析实战:从PolySpace到CI集成 嵌入式固件的难缠之处在于它平时看起来一切正常可一到现场就会间歇性抽风。我有一次调试一块基于 STM32 的控制器问题定位了整整两周现场有时候启动后采集到的数据全是乱的台架上却永远复现不了。后来把代码扔进 PolySpace 做全路径分析几个小时后结果出来问题一目了然——中断服务函数里把一个全局数组的下标算越界了越界只发生在特定时序下动态测试根本没覆盖到。那次之后我对静态分析这件事的态度变了它不是替代测试而是在测试之外单独建立的一道防线尤其是做嵌入式按 ISO 26262、IEC 62304 或功能安全相关流程走的时候这道防线几乎是必修课。这篇文章我打算用实战的方式从零梳理怎么把 PolySpace 用起来。不管你是在 STM32、ARM Cortex-A 还是其他嵌入式平台上做 C/C 开发只要代码里涉及指针、数组、中断、通信协议解析都值得看完。我会把环境准备、最小工程搭建、结果颜色怎么看、defect 注释规则怎么写以及最后怎么接入 CI 持续跑一整套都摊开来说。内容会偏向实操尽量少讲虚的。1. 为什么嵌入式团队应该在测试之外再建立一条 PolySpace 防线1.1 动态测试覆盖不了的那部分路径先抛开工具本身说说我为什么坚持“测试之外还要有一道静态防线”。嵌入式软件里最常见的一类问题不是逻辑完全错误而是状态相关、输入相关、时序相关的非法操作。比如接收缓冲区根据报文字段算索引字段被污染时数组越界除法之前对分母做了判断但判断和除法之间被中断插了一脚多任务共享变量一个任务写了 16 位值另一个任务读的时候读到半新半旧的数据寄存器配置宏在不同编译选项下展开结果不符合预期。动态测试想要抓这些问题需要构造出恰好触发缺陷的输入组合。数学上这是组合爆炸的问题。你今天写 100 个测试用例全部通过不代表第 101 种输入不会踩到那个越界分支。尤其嵌入式还要面对传感器噪声、总线毛刺、非法帧、掉电时序这些外部扰动不可能全部都跑到。PolySpace 这类工具的思路完全不同它分析的是代码本身所有可能的执行路径不依赖测试用例也不是靠人肉 review 一行行盯。它对每条路径做抽象解释把“运行所有可能输入”这件事用一种近似但保守的方式算一遍然后给出结论。1.2 PolySpace 在开发流程里的角色不是 lint是形式化验证很多团队已经用过 PC-lint、Cppcheck、Coverity 这类工具。它们属于“规则检查器”或者“缺陷模式匹配器”擅长找风格问题、明显的不规范写法。PolySpace 不太一样它包含两条产品线这里先区分清楚工具定位适合场景Polyspace Bug Finder基于静态分析做规则检查、缺陷识别速度快日常提交检查、MISRA C/AUTOSAR 规则、快速预估风险Polyspace Code Prover基于抽象解释做运行时错误形式化验证关键模块、安全认证、需要穷尽路径分析的场合Bug Finder 就像科室的初筛医生快速扫一遍、把可疑点都标出来Code Prover 更像病理科会诊对每一段路径给出“安全、危险、无法确认”的结论。在实际项目里两个工具通常是配合用的。我一般这样分工Bug Finder 挂在 CI 里每次提交都跑保证基本规则和显式缺陷不过夜Code Prover 在关键模块合入之前跑一次专门盯运行时错误比如除零、越界、未初始化、整数溢出、不可达代码。两条工具的数据格式和分析报告可以对接同一套工作流不需要重复解释。2. PolySpace 的工作原理抽象解释、检查项与红绿橙灰2.1 抽象解释到底是怎么“看完所有路径”的第一次用 PolySpace 的人往往会问一个问题它真的能把我的程序所有可能输入都跑到吗答案在程序设计上不是“跑”而是“抽象地解释”。举个生活化的例子。你要检查一座城市所有街道的限高杆会不会被超高货车撞到不需要真的派一辆货车把每条路都开一遍只要拿到地图上每条街道的限高数据再知道货车的最大高度就可以从数学上推出哪条路能过、哪条路不能过。抽象解释干的就是这件事它把所有变量的取值范围抽象成一个集合然后沿着语句逐条传播这个集合。比如代码里写着x y / z分析器知道z可能取到的范围包括 0那么在z 0这条路径上它直接标出“除零错误已验证”。如果z的来源被限定为z 1它就能证明这一行不会除零。这套机制带来一个好处不依赖你写测试用例的想象力。它会把中断、函数调用、循环、递归、全局状态全部纳入路径空间。代价是分析时间比普通 lint 长很多范围越大、越精确时间成本越高。所以“从零搭建”的第一课就是控制分析范围和上下文不要一上来就跑全工程。2.2 结果颜色代表什么绿、红、橙、灰的判定逻辑PolySpace 的 Code Prover 分析结果最核心的是一套颜色状态。很多新手看到满屏橙色就慌了看到红色就觉得天塌了其实要先明白颜色语义。状态含义常见处理绿色当前代码路径未发现该运行时错误属性被验证无需处理红色错误已经被证明会出现在至少一条路径上必须修复橙色在当前分析上下文下无法证明安全但也不能证明一定出错人工审查、补充约束、缩小分析范围灰色对应代码路径不可达或者尚未被分析覆盖检查是否为死代码或是否需要设置额外入口我特别想说一下橙色。橙色不是“误报”翻译成人话是“我证明不了这事绝对不会发生”。在很多情况下原因是分析器的输入假设比你实际运行环境更严苛。比如一个函数入口参数是uint16_t分析器会假设它能取 0 到 65535 的所有值如果你的调用方实际上只会传 1 到 100那么在 PolySpace 看来分母为 0 就是潜在的红色告警。处理橙色代码的方式不是直接忽略而是通过加前置条件、调整入口参数范围或者把分析范围放到具体调用链上去做。后面讲 defect 注释规则时我会专门说怎么留痕。2.3 它和常规规则检查的差异为什么 MISRA 过了还有运行时错误MISRA C 规则检查解决的是“代码风格/约束”层面比如禁止某些危险语法、要求花括号、限定函数返回类型。但 MISRA 过了不代表运行时没有漏洞。最典型的例子是数组下标uint8_t buf[4]; uint8_t idx read_sensor(); buf[idx] 1;这行代码完全符合 MISRA C:2012 的很多写法要求但只要idx可能大于 3就照样越界。规则检查器不会关心idx的取值范围它只检查你是不是用了合法语法。而 PolySpace 的 Code Prover 会跟踪idx的所有可能输入路径如果它无法证明idx 4就会标一个显式告警出来。这就是我理解的“安全防线”不是检查你“写得好不好看”而是检查你“跑起来会不会出事故”。所以做功能安全项目规则检查与运行时验证缺一不可。3. 从零搭建最小实战环境准备、工程配置与第一份报告3.1 环境准备与许可证要跑 PolySpace首先得有一套可用的安装环境。PolySpace 是 MathWorks 产品家族里的工具安装方式和安装 MATLAB 类似安装包里选择“Polyspace Bug Finder”和“Polyspace Code Prover”两个组件。这里我不展开具体安装向导了网上能查到我只说几个容易踩的细节许可证PolySpace 有独立的 License 选项不是装了 MATLAB 就附带。团队使用建议申请独立的分析机 License因为分析任务重放在开发机上跑会影响日常编译。版本不同 R 版本对编译器标准支持不完全一样。常见的嵌入式交叉编译器比如 arm-none-eabi-gcc建议先看一眼官方支持列表。老项目用老版本编译器时别盲目升级工具。环境变量如果交叉编译工具链不在系统 PATH 中在图形界面里启动 Polyspace 时经常找不到编译器命令行里也一样。先把工具链路径加进 PATH再启动分析。3.2 搭一个最小分析工程我建议第一次做 PolySpace 时先不要拿整个固件工程压上去。拿一个最小 C 文件跑通流程理解颜色和报告再逐步扩大范围。下面这个例子故意留了三个典型缺陷#include stdint.h #define BUF_LEN 16 static int16_t buf[BUF_LEN]; int16_t normalize(uint16_t index, int16_t value, int16_t scale) { if (scale 0) { return 0; } int16_t scaled (int16_t)(value * scale); if (index BUF_LEN) { return 0; } return scaled / (int16_t)index; /* 缺陷点1index 可能为 0 */ } void clear_buf(void) { for (uint16_t i 0; i BUF_LEN; i) { buf[i] 0; /* 缺陷点2i BUF_LEN 时越界 */ } }在图形界面里新建一个分析工程把源文件加进去入口函数选择normalize或者clear_buf。PolySpace 会自动识别你用的是 GCC 还是其他编译器前提是你在工程设置里选对了工具链和标准库路径。这里有个关键概念入口点。Code Prover 分析是从入口函数开始的入口函数只代表“从这一层开始做全路径分析”。如果选clear_buf它会把循环最后一次访问数组的情况算进去从而找到越界如果选normalize它会假设index可能是 0所以除零会被标出来。3.3 命令行方式与分析参数图形界面适合摸索和交互式看报告但实际项目里我更推荐命令行方式因为可以脚本化、可复现。最小命令长这样polyspace-code-prover \ -sources demo.c \ -main normalize \ -results-dir results_code_prover \ -report如果要做 Bug Finder命令类似polyspace-bug-finder \ -sources demo.c \ -I ./inc \ -results-dir results_bug_finder \ -report参数不多但有几个我建议一开始就加上的-I千万不要省。头文件解析不完整分析结果就是废纸-D把工程里的条件编译宏显式传进去和实际编译时保持一致-main指定入口不指定的话工具可能默认找main嵌入式固件没有标准 main 或者有多个入口时结果会差很多-report生成 HTML 报告方便给不做工具的人看。如果你手里有一套复杂的 Makefile 工程逐个手动整理-I和-D不现实。这时可以用 PolySpace 自带的polyspace-configure工具让它扫描构建日志自动生成选项文件polyspace-configure -output-options-file options.txt make polyspace-code-prover -options-file options.txt -results-dir results -report这个方法在大型嵌入式工程上非常实用能省下大量时间。3.4 第一次看到结果时该怎么读第一次跑完大概率会看到一片五颜六色。别慌先按以下顺序理先看红色红色是已经证明会出问题的路径逐条判断是否真实真实就修再看橙色橙色是“没法证明安全”挑数量最多的模块逐个去看变量范围最后看灰色灰色可能是死代码也可能是因为入口范围设置不当导致某条路径压根没进入分析。如果红色和橙色都集中在某个模块不是那里代码真的特别烂而是那里输入范围特别复杂。常见的处理办法是把入口函数的参数范围约束成实际调用场景可以用-main concat...或 GUI 里的参数范围设置。命令行里也能指定比如通过设置-entry-point的输入范围参数具体取决于版本但思路是一样的让分析器知道你真实输入域。4. 结果解读与 defect 注释规则哪些缺陷必须修哪些可以豁免4.1 按优先级处理结果的干活顺序跑出结果之后团队最容易陷入“报告太长不知道从哪看起”的窘境。我的干活顺序是第一步修所有红色结果。红色意味着分析器已经从代码逻辑上推出必然发生或必然违反检查项的地方这些是优先项第二步处理与安全强相关的橙色。比如除零、内存越界、未初始化、指针非法访问。如果橙色出现的位置在中断服务函数里优先级再提一级第三步处理规则类告警。例如 MISRA C 的违规、代码规范问题可以排到日常迭代里慢慢清。为什么 I 不把橙色全部忽略因为橙色往往代表“分析器无法证明而代码又确实存在某条危险路径”。等你把函数入口参数范围调整准确以后很多橙色会变成绿色没变绿的才需要人工写解释。4.2 代码注释层面的 defect 抑制与解释规则很多人会问 PolySpace 的“defect 注释规则”到底怎么写。这里分两个层面来看。第一个层面是源代码里加注释告诉工具这个位置不是缺陷。PolySpace 识别一套与规则检查工具类似的注释形式。比如在 MISRA C 检查里常见的注释范式是/* PRQA S 4500 */ int32_t map_value(int32_t v) { /* 确认输入范围已经被调用方限制 */ return v * 2; }不同版本、不同规则 ID 的具体注释语法会有差异但核心原则是一样的你要让注释能落到具体行、具体规则并且内容可解释。不要写那种“这不是问题”而不给理由的注释因为代码审查时会被打回去。第二个层面是在分析结果里添加说明。PolySpace 的结果管理界面支持对每条 defect 添加“审查注释”你可以标注状态比如误报已验证安全补充理由已知问题计划修复不适用外部条件保证。这个很重要。功能安全认证时需要的是证据链而不是单纯把报告清空。报告上写“已确认安全 为什么安全”的证据比“不报这个错”更值钱。所以从第一天开始我就要求团队所有橙色结果必须有处置意见要么修复要么写理由不许白屏留空。4.3 怎么避免“为了好看把报告刷绿”的歪路我见过一个反面案例团队为了把告警数量压到零把分析的入口函数去掉、把宏定义乱传、把某些文件排除在外最后报告确实清爽了但代码真正的问题还在那儿。PolySpace 不是一个刷绿的考试它是一个“找漏洞”的探测器。你把它范围缩小它看到的路径就变少颜色的可信度就降低。正确做法是分析范围必须包含真实运行路径尤其是中断入口和通信协议处理函数。一次跑不完可以分批先把最容易出事的模块跑掉再逐渐补充周边模块。禁用告警之前先问自己一句这条路径在真实运行时到底会不会走到如果会就不该一句话“误报”甩掉。5. 实战中那些猜不到的高频坑头文件、宏、浮点与编译选项5.1 头文件解析不全结果直接失真我见过最典型的误报来源不是 PolySpace 不行而是头文件没喂全。嵌入式工程里有大量寄存器定义、外设库结构体、CMSIS 头文件如果编译器头文件路径少配一个结构体的成员偏移就会对不上分析器看到的代码和实际编译产物根本不是同一个东西结果自然乱七八糟。解决方法是尽量用polyspace-configure去扫描真实构建命令而不是手动一次一次补-I。如果手动写至少要把工程的核心 include 目录、编译器自带头文件目录都列全。遇到厂商 SDK 的复杂宏不要改源代码去迎合工具应该先把编译宏配准确。5.2 编译器扩展与内联汇编的处理嵌入式 C 代码里到处是编译器扩展比如__attribute__((packed))、__asm、位域操作、中断函数修饰符。PolySpace 默认支持的编译器集合里GCC、ARMCC、IAR 这些常见编译器都能识别大部分扩展但厂商特有扩展偶尔会被解析成空或者报语法错误。遇到这种情况我的处理思路是对外设寄存器地址尽量通过标准的volatile指针访问而不是直接内联汇编对少量无法解析的__asm可以考虑在分析配置里单独隔离这些函数或者通过注释排除掉某一个文件但必须在报告里留痕不要为了让分析通过把volatile关键字全删掉。删掉之后PolySpace 可能就会把内存访问优化推断成错误结论。5.3 浮点模式与目标架构细节PolySpace 分析浮点运算时有不同精度和舍入模式的选择。如果你的固件大量使用浮点比如 FFT 频谱分析、PID 控制分析器默认的浮点建模方式和实际硬件的浮点行为不一致会产生一批让人摸不着头脑的橙色告警。一般建议把浮点行为配置和实际编译选型靠齐再用一套标准测试用例验证。还有一点容易被忽略字符是否有符号。很多 ARM 编译器默认char是无符号而 PC 上默认有符号。如果命令行里没有指定-unsigned-char这类选项PolySpace 可能按错误符号分析你的char变量最后标出完全不存在的负数判断告警。所以配置分析工程时要把编译器默认行为一项一项对过去。5.4 分析时间太长怎么办Code Prover 是出了名的能吃时间。全工程一上来就跑跑一晚上都可能不够。我的经验是分三步走先跑 Bug Finder它速度快能先把显式缺陷和规则问题扫一遍代码冻结后跑 Code Prover针对核心模块逐个入口分析使用增量分析第二次只重新分析有变更的模块和受影响路径而不是每次都全量重跑。Polyspace 有增量分析机制底层会缓存之前分析过的函数摘要否则小改动也全量跑的话团队根本等不起。第一次全量跑完后续每次改动几分钟到几十分钟这个进度才接得住开发节奏。6. 把 PolySpace 接入持续集成让安全防线自动运行6.1 为什么静态分析一定要进 CI大多数团队在本地安装 Polyspace 后都是“有人想起来才跑一次”。这会带来两个问题本地环境与分析机不一致报告无法统一追踪问题发现得晚等要合入主干时才看到大量红色告警修复成本已经上去了。把 PolySpace 接入 CI 以后每次提交或者每次合并请求都能自动跑一遍红色超阈值就直接门禁卡住。这条“安全防线”才真正立得起来不然它就只是一个个孤立的分析报告。6.2 一个可落地的流水线集成示例以一个 GitLab CI 的场景为例假设你的构建镜像里已经装好了 Polyspace并且许可证可以通过环境变量提供流水线大致长这样static-analysis: stage: test script: - polyspace-config -output-options-file options.txt make - polyspace-code-prover -options-file options.txt -results-dir results -report - polyspace-bug-finder -options-file options.txt -results-dir bf_results -report artifacts: paths: - results/ - bf_results/核心要点有三个使用options.txt保证 CI 里的分析选项和本地一致避免“本地能过、CI 挂了”的漂移把报告和结果目录作为流水线产物保存下来方便事后追溯设置质量门禁比如红色告警数量新增为 0或者关键告警不能超过某个阈值。如果你还要更严格的证据管理可以把分析结果导出成 JUnit 或自定义 XML 格式并在流水线中解析让“新增红色告警”直接导致合并请求失败。团队逐渐形成习惯后红黄告警就不会等到月底复盘时才冒出来了。6.3 团队分工谁看报告、谁改代码、谁背书最后聊一个容易被技术文章忽略的问题团队协作。PolySpace 报告不是“开发自己一个人看”的东西我建议这样分嵌入式开发工程师负责修复红色告警处理自己模块的橙色结果架构师/技术负责人负责定义分析入口、编译选项、规则集审批“确认安全”类注释测试或功能安全工程师负责定期抽取报告样本复核确保注释理由真实可信。这相当于把 PolySpace 从“一个工具”变成了“一个流程”。安全防线能不能撑得住并不取决于工具分析得多深而取决于团队怎么对待分析结果。每一份报告都有人看、有人解释、有人追踪防线才真正完整。我自己在项目里会有个习惯每次新模块接入 CI 前先跑一次 Code Prover人工把红色和橙色全部过一遍把确实安全的路径用注释和结果说明固化下来。这样后面每次增量分析这类“已确认安全”的路径就不会反复报警。等团队适应了这个节奏静态分析就不再是负担而成了给代码做健康体检的常规动作。
返回列表