95 lines
40 KiB
HTML
95 lines
40 KiB
HTML
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>GD32E50X_BOOT 专家评审 2026-10-19</title><style>
|
||
body{font-family:-apple-system,"PingFang SC","Microsoft YaHei",sans-serif;margin:0;background:#f5f6f8;color:#1f2328;line-height:1.6}
|
||
.wrap{max-width:1180px;margin:0 auto;padding:28px 24px 60px}
|
||
h1{font-size:26px;margin:0 0 6px}h2{font-size:20px;margin:36px 0 12px;padding-bottom:6px;border-bottom:2px solid #d0d7de}
|
||
h3{font-size:16px;margin:20px 0 8px}
|
||
.meta{color:#57606a;font-size:13px}
|
||
table{border-collapse:collapse;width:100%;background:#fff;font-size:13px;margin:8px 0}
|
||
th,td{border:1px solid #d0d7de;padding:6px 8px;vertical-align:top;text-align:left}
|
||
th{background:#eef1f4;white-space:nowrap}
|
||
code,.loc{font-family:Consolas,Menlo,monospace;font-size:12px;word-break:break-all}
|
||
.sev{display:inline-block;padding:1px 8px;border-radius:10px;color:#fff;font-weight:600;font-size:12px;white-space:nowrap}
|
||
.s0{background:#a40e26}.s1{background:#d1242f}.s2{background:#bf8700}.s3{background:#1f6feb}
|
||
.card{background:#fff;border:1px solid #d0d7de;border-radius:8px;padding:14px 18px;margin:10px 0}
|
||
.grid{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}
|
||
.kpi{background:#fff;border:1px solid #d0d7de;border-radius:8px;padding:12px;text-align:center}
|
||
.kpi b{display:block;font-size:26px}
|
||
pre{background:#0d1117;color:#e6edf3;padding:14px;border-radius:8px;overflow:auto;font-size:12px;line-height:1.45}
|
||
.ok{color:#1a7f37;font-weight:600}.no{color:#d1242f;font-weight:600}.warn{color:#9a6700;font-weight:600}
|
||
</style></head><body><div class="wrap">
|
||
<h1>GD32E50X_BOOT 专家评审报告</h1>
|
||
<div class="meta">仓库 cbb/GD32E50X_BOOT · 分支 NO_compress · 基线提交 644c8fb(2026-10-13)· 评审日期 2026-10-19 · BOOT 版本 1.1.0.25</div>
|
||
|
||
<div class="grid" style="margin-top:18px">
|
||
<div class="kpi"><b style="color:#a40e26">3</b>致命</div>
|
||
<div class="kpi"><b style="color:#d1242f">13</b>严重</div>
|
||
<div class="kpi"><b style="color:#bf8700">18</b>一般</div>
|
||
<div class="kpi"><b style="color:#1f6feb">6</b>建议</div>
|
||
</div>
|
||
|
||
<div class="card"><b>结论速览:</b>架构思路成熟(A/B 双区 + CRC + 崩溃门 + 三链路 OTA + 共享布局头),但<b>完整性与鉴权链条存在可绕过的缺口</b>:ECAT 门在 APP 崩溃后自动敞开且无鉴权、CRC 擦除态即放行、CRC 只对写后 Flash 盖章、密钥硬编码可重放。建议先完成路线图 P0 再进入量产。综合评分 <b>4.7 / 10</b>。
|
||
<ol style="margin:8px 0 0"><li><a href="#R01">R01</a> <span class="sev s0">致命</span> ECAT 刷写门在 APP 任一标志无效时自动敞开,且该路径无任何鉴权</li><li><a href="#R02">R02</a> <span class="sev s0">致命</span> SPI/CAN 通道共用硬编码主密钥,会话密钥仅由主机 nonce 决定,握手可整段重放</li><li><a href="#R03">R03</a> <span class="sev s0">致命</span> CRC 擦除态即判通过,CRC 写失败被吞——无 CRC 镜像可直接启动</li><li><a href="#R04">R04</a> <span class="sev s1">严重</span> CRC 是“写完后对 Flash 现状盖章”,不与主机提供的整镜像校验值比对</li><li><a href="#R06">R06</a> <span class="sev s1">严重</span> 配置页读-擦-写无掉电保护</li></ol></div>
|
||
|
||
<h2>1. 项目概述与架构</h2>
|
||
<p>GD32E508VE 的 Bootloader,常驻三条升级链路:ESP32 经 SPI0 从机(v2 帧协议)中继、CAN-FD 直连、以及 1.1.0.x 起新增的 LAN9252 原生 EtherCAT(CoE SDO)刷写。Flash 布局由 <code>shared/ota_layout.h</code> 统一定义,供 BOOT/APP 共用。</p>
|
||
<pre>
|
||
┌──────────────── GD32E508VE (Cortex-M33 @180MHz, 512KB Flash) ────────────────┐
|
||
ESP32 ──SPI0 从机(PA15 CS/EXTI15, DMA)+PB6 DATA_READY──► SpiOta (v2: SEQ/ACK/CREDIT) ─┐
|
||
CAN-FD 总线 ──CAN0 RX0/TX 中断──────────────────────► CAN_transmiter ─────────────────┤
|
||
EtherCAT 主站 ──LAN9252 (EXMC/HBI) ─► SSC 5.11 (CoE SDO 0x25xx) ─► boot_ecat_app ──────┤
|
||
▼
|
||
OTAHandler:握手(K_master→K_session) → SIZE/擦除
|
||
→ DATA(AES-ECB 48B + 带钥 CRC32) → FINISH(CRC 盖章/置标志)
|
||
ECAT:ecatBegin/Feed/Commit(明文,令牌或“救砖”门)
|
||
▼
|
||
Flash:0x08000000 BOOT 72KB │ 0x08012000 备份区 216KB │ 0x08048000 主应用区 216KB (VTOR)
|
||
0x0807E000 OTA 配置页(10 字:AVAL/EN/CHECK×2、BOOT 版本、ECAT_ARM、崩溃计数) │ 0x0807E800 睡眠页
|
||
</pre>
|
||
<h3>源码构成</h3>
|
||
<table><tr><th>模块</th><th>文件</th><th>行数</th><th>说明</th></tr>
|
||
<tr><td>启动/跳转</td><td>main.cpp, sys/initial.*, sys/systick.*</td><td>~1,100</td><td>启动判定、崩溃门、深睡、延时</td></tr>
|
||
<tr><td>OTA 核心</td><td>sys/OTA.*, sys/aes128.*, sys/config.h, shared/ota_layout.h</td><td>~2,770</td><td>握手、解密、写 Flash、CRC、配置页、ECAT 刷写接口</td></tr>
|
||
<tr><td>SPI 链路</td><td>device/SpiOta.*(+仓库外 spi_link_v2)</td><td>~1,170</td><td>DMA 收发、CS EXTI、SEQ/ACK/CREDIT</td></tr>
|
||
<tr><td>CAN 链路</td><td>device/Can.*</td><td>~530</td><td>CAN-FD 收发、软件发送队列</td></tr>
|
||
<tr><td>EtherCAT</td><td>comm/ecat/boot_ecat_*.cpp + Beckhoff SSC 5.11</td><td>~1,140 自研 + ~10,000 SSC</td><td>EXMC/HBI 驱动、CoE 对象 0x25xx、跳转</td></tr>
|
||
<tr><td>厂商库</td><td>RTE/Device/GD32E508VE</td><td>—</td><td>GD32E50x 标准外设库、启动文件、链接脚本(未评审内容,仅核对区段配置)</td></tr></table>
|
||
<h3>启动流程</h3><table><tr><th>阶段</th><th>行为(main.cpp)</th></tr>
|
||
<tr><td>1. 初始化</td><td>时钟/SysTick/USART 调试口 → 回写 BOOT 版本号到配置页 → 深睡检查(BootSleepManager::check)</td></tr>
|
||
<tr><td>2. 复位原因</td><td>FWDGT 复位 → 崩溃计数 +1,A 区 EN 置无效;计数 ≥ 3 → crash_gate,禁止自动跳转</td></tr>
|
||
<tr><td>3. 跳转判定</td><td>A 区 CHECK==AVAL && EN==AVAL && !crash_gate → verifyAppCrc → jumpToApplication</td></tr>
|
||
<tr><td>4. 留守</td><td>否则启动 SPI/CAN/ECAT 三条 OTA 链路,APP_DELAY_BOOT_TIME 等待窗口;超时后可尝试 B 区恢复(同样受 crash_gate 约束)</td></tr>
|
||
<tr><td>5. 刷写完成</td><td>validateApplication(向量表)→ 计算并写 CRC → 置 CHECK/AVAL/EN → 跳转</td></tr></table>
|
||
|
||
<h2>2. 评审范围与方法</h2>
|
||
<ul>
|
||
<li><b>范围:</b>NO_compress 分支全部自研源码与工程配置(cproject/csolution/cbuild-pack/vcpkg、链接区段、.gitignore);SSC 原厂文件只核查配置宏与本地改动(git 显示 46adcb7 之后未再修改);RTE 厂商库只核对区段与中断向量。</li>
|
||
<li><b>专家组:</b>① Bootloader/OTA 可靠性与固件安全;② 实时性、中断/DMA 与 SPI/CAN 协议;③ EtherCAT(SSC/LAN9252/CoE);④ 代码质量、构建与仓库卫生。各组并行逐行阅读,结论由主评审在代码中逐条复核行号后汇总。</li>
|
||
<li><b>既有记录:</b>全仓搜索 .md/.html,仅发现 <code>.trae/specs/prune-boot-dead-code/</code>(spec/tasks/checklist)。另据 .gitignore 推断曾有未入库的 SECURITY_AUDIT_REPORT.*,无法获取。</li>
|
||
<li><b>限制:</b>本次为静态评审,未编译、未上板;外部依赖 spi_link_v2 不在仓库,无法评审;标“待确认”的条目需上板或查看 APP 侧代码验证。</li>
|
||
<li><b>严重度:</b>致命=可变砖/不可恢复或可被远程植入任意固件;严重=现场可能故障或明显安全缺陷;一般=特定条件下出错或维护隐患;建议=改进项。</li>
|
||
</ul>
|
||
|
||
<h2>3. 问题清单(40 条)</h2>
|
||
<table><tr><th>编号</th><th>严重度</th><th>模块</th><th>问题 / 位置(文件:行号)</th><th>现象</th><th>风险</th><th>修改建议</th></tr><tr id="R01"><td>R01</td><td><span class="sev s0">致命</span></td><td>安全/ECAT</td><td><b>ECAT 刷写门在 APP 任一标志无效时自动敞开,且该路径无任何鉴权</b><br><span class="loc">GD32E50X_BOOT/sys/OTA.cpp:850-865;GD32E50X_BOOT/main.cpp:246,266,294,408</span></td><td>ecatArmed() 除令牌外,只要 LATEST_CHECK 或 LATEST_EN 有一个不是 AVAL 就返回 true。FWDGT 复位(main.cpp:246)、超时/崩溃路径(266/294/408)都会把 EN/CHECK 置为 NOT_AVAL。ECAT SDO 下发的是明文镜像,无 AES、无签名、无主机侧 CRC。</td><td>APP 只要崩溃一次(或被诱发看门狗复位),总线上任何主站都可经 CoE 0x25xx 对象刷入任意固件,属于可远程植入。</td><td>救砖门应改为“仅当 A/B 两区都无可启动镜像”才开;ECAT 镜像必须带签名(推荐 Ed25519/ECDSA-P256,公钥固化在 BOOT)或至少带主机预置 CRC/HMAC 并在 commit 前比对。</td></tr><tr id="R02"><td>R02</td><td><span class="sev s0">致命</span></td><td>安全/OTA</td><td><b>SPI/CAN 通道共用硬编码主密钥,会话密钥仅由主机 nonce 决定,握手可整段重放</b><br><span class="loc">GD32E50X_BOOT/sys/config.h:120-123;GD32E50X_BOOT/sys/OTA.cpp:1250-1265</span></td><td>K_master 以常量写死在 BOOT,所有设备相同;K_session = AES(K_master, nonce_server),设备端不贡献随机数,设备 UID 不参与派生。</td><td>录下任意一次合法升级流量即可对任意设备原样重放(含降级到有漏洞的旧版);一旦从任一块板 dump 出 BOOT 即可伪造全部设备的固件。</td><td>改为“签名 + 可选加密”:镜像用私钥签名,BOOT 仅存公钥;加密密钥按设备 UID 派生(KDF(K_master, UID))并由设备贡献 nonce;加入单调版本号防降级。</td></tr><tr id="R03"><td>R03</td><td><span class="sev s0">致命</span></td><td>OTA 可靠性</td><td><b>CRC 擦除态即判通过,CRC 写失败被吞——无 CRC 镜像可直接启动</b><br><span class="loc">GD32E50X_BOOT/sys/OTA.cpp:1523-1549(verifyAppCrc)、1559-1577(writeAppCrc)</span></td><td>stored_crc==0xFFFFFFFF 时 verifyAppCrc 直接返回 true;writeAppCrc 写失败只打印、无返回值,调用方(OTA.cpp:385、ECAT commit 959)随后照常置 CHECK/AVAL/EN。</td><td>CRC 槽写失败、或被最后一包数据覆盖时,损坏/不完整镜像依旧被放行启动,设备可能反复崩溃直至触发崩溃门而停机。</td><td>调试烧录的豁免改用显式标志(例如配置页单独一个 DEBUG_NO_CRC 字,量产 BOOT 编译期禁用);writeAppCrc 返回状态并回读校验,失败则不置标志、回 ERROR。</td></tr><tr id="R04"><td>R04</td><td><span class="sev s1">严重</span></td><td>OTA 可靠性</td><td><b>CRC 是“写完后对 Flash 现状盖章”,不与主机提供的整镜像校验值比对</b><br><span class="loc">GD32E50X_BOOT/sys/OTA.cpp:376-389(SPI/CAN FINISH)、GD32E50X_BOOT/sys/OTA.cpp:956-960(ECAT commit)</span></td><td>FINISH 时只做向量表合法性检查(validateApplication),随后 calculateAppCrc 对 Flash 当前内容计算并写入。主机从未下发整镜像 CRC/摘要。</td><td>丢包、跳号、写失败留下的空洞都会被计算进“合法 CRC”,CRC 只能防后续位翻转,防不住传输/写入错误。</td><td>协议在 SIZE 阶段同时下发镜像 CRC32(或 SHA-256),FINISH 时比对一致后才写 CRC 与标志。</td></tr><tr id="R05"><td>R05</td><td><span class="sev s1">严重</span></td><td>OTA 可靠性</td><td><b>收包不强制连续序号、可提前 IFEND、Flash 写失败与擦除失败被静默忽略</b><br><span class="loc">GD32E50X_BOOT/sys/OTA.cpp:1077-1160(DATA 处理)、818-838(writeApplicationData 失败仅 return)、347-349(擦除后无条件进入 RECEIVING)</span></td><td>DATA 不检查 seq==next_expected;任意包可带 IFEND 结束;writeApplicationData 遇 FMC 错误直接 return,不置错误码;eraseApplicationArea 结果未检查即切到 OTA_STATE_RECEIVING_FIRMWARE。</td><td>与上一条叠加,部分写入的镜像可被判为成功。</td><td>按序号严格推进并在 FINISH 时校验 received_size==expected_size;writeApplicationData/erase 返回 bool,失败转 ERROR 并通知主机。</td></tr><tr id="R06"><td>R06</td><td><span class="sev s1">严重</span></td><td>OTA 可靠性</td><td><b>配置页读-擦-写无掉电保护</b><br><span class="loc">GD32E50X_BOOT/sys/OTA.cpp:133-224(OTA_SET);GD32E50X_BOOT/main.cpp:75-128(版本号回写)、153-160(崩溃计数)</span></td><td>10 个配置字放在单页 0x0807E000,任何 1→0 以外的修改都要整页擦除后回写,无双页/日志结构,写完无回读校验。崩溃计数每次 FWDGT 都会擦一次页。</td><td>擦除窗口内掉电,整页变 0xFF:A/B 区标志全失效,好 APP 也停在 BOOT;同时 ecatArmed 判为已 arm(叠加致命#1)。频繁擦写也加速该页磨损。</td><td>改为 A/B 双页 + 序号/CRC 的日志式配置(或至少“先写新页、再擦旧页”);崩溃计数改用备份寄存器/RTC BKP 或日志追加。</td></tr><tr id="R07"><td>R07</td><td><span class="sev s1">严重</span></td><td>OTA 可靠性</td><td><b>新固件 CRC 合法但运行崩溃时无法回滚到 B 区</b><br><span class="loc">GD32E50X_BOOT/sys/OTA.cpp:548-549;GD32E50X_BOOT/main.cpp:258-260,282,430</span></td><td>jumpToApplication 每次跳转前都把 AVAL/EN 重新写成 AVAL,使 FWDGT 后置的 EN=NOT_AVAL 门失效;崩溃次数达到 OTA_CRASH_STAY_MAX(3) 后 crash_gate 同时挡住了 B 区恢复分支(main.cpp:430 条件含 !crash_gate)。</td><td>坏版本 APP 会反复启动-崩溃,最终停在 BOOT 等外部灌写,而 B 区里的好版本永远用不上。</td><td>崩溃门触发时应优先尝试从 B 区恢复(恢复后清零计数),仅当 B 区也不可用才停在 BOOT;EN 标志仅由 APP 自检通过后置位。</td></tr><tr id="R08"><td>R08</td><td><span class="sev s1">严重</span></td><td>并发/ISR</td><td><b>OTA 状态机与 Flash 写入在 ISR 与主循环两个上下文并发执行</b><br><span class="loc">GD32E50X_BOOT/device/SpiOta.cpp:502;GD32E50X_BOOT/sys/OTA.cpp:1120(ISR 内 drainFlashBufferOne);GD32E50X_BOOT/device/Can.cpp:182-189</span></td><td>SPI 非 64B 帧在 CS 上升沿 EXTI ISR 内直接调用 handleLinkMessage;缓冲满时会在 ISR 里写 Flash 并 fmc_lock,而主循环可能正处于 writeApplicationData 中途。ota_state、pending_flash_erase、pending_app_jump 等共享变量非 volatile;CAN 从主循环进入,形成第二生产者。</td><td>主循环写到一半 FMC 被 ISR 锁上,后续字写入失败(且被吞,见上条);状态机字段撕裂。</td><td>ISR 只做帧校验与入队,所有 handleLinkMessage/Flash 操作统一在主循环单线程执行;共享标志 volatile + 临界区。</td></tr><tr id="R09"><td>R09</td><td><span class="sev s1">严重</span></td><td>SPI 协议</td><td><b>DATA 队列满时序号已推进,帧被永久丢失;credit 初值大于队列深度</b><br><span class="loc">GD32E50X_BOOT/device/SpiOta.cpp:82,406-408,489-494;GD32E50X_BOOT/sys/config.h:140-142</span></td><td>s_expectedRxSeq 在入队前就 +1;队列满(kOtaDataSlots=4)时只回 WAIT。ESP32 重发同 SEQ 会被判为 DUP(409-427),只回放缓存响应,该包永不处理。g_localCredit 初值/重置为 CONFIG_OTA_FLASH_BUF_SLOTS=8,大于 ISR 队列 4 槽,对端严格遵守 credit 也会溢出。</td><td>高吞吐时丢包且不报错,配合“不强制连续序号”问题,可产出带空洞的镜像。</td><td>入队成功后再推进期望序号;credit 取 min(ISR 槽, Flash 槽);队列满回专用 BUSY 且不推进 SEQ。</td></tr><tr id="R10"><td>R10</td><td><span class="sev s1">严重</span></td><td>SPI 协议</td><td><b>isSpiTransport() 判断恒假,CAN 帧可在 SPI 会话中抢走回包通道</b><br><span class="loc">GD32E50X_BOOT/sys/OTA.cpp:323-325;GD32E50X_BOOT/device/SpiOta.cpp:485;GD32E50X_BOOT/device/Can.cpp:174;GD32E50X_BOOT/sys/OTA.cpp:308</span></td><td>SPI ISR 路径设置的是 SpiOta::sender,而 isSpiTransport() 比较的是 ota_spi_sender,两者不同,因此 Can.cpp:174 与 OTA.cpp:308 的“SPI 会话中忽略 CAN”保护在多数时间不生效。</td><td>SPI 升级过程中总线上一帧 CAN OTA 报文即可把 transport_send 切回 CAN,ACK 发错通道,ESP32 侧超时(代码 OTA.cpp:349-355 已出现过同类症状并局部打补丁)。</td><td>用显式枚举 transport_kind 记录当前会话链路,而不是比较函数指针。</td></tr><tr id="R11"><td>R11</td><td><span class="sev s1">严重</span></td><td>SPI 驱动</td><td><b>SPI 从机 NSS 常低,永不按 CS 重新同步位计数;EXTI 双边沿靠读电平判方向</b><br><span class="loc">GD32E50X_BOOT/device/SpiOta.cpp:721,768-771,941-946</span></td><td>spi_nss_internal_low 使 SPI 永远处于选中态;CS 用 EXTI_TRIG_BOTH,ISR 中读引脚电平判断沿方向。ISR 延迟时两个边沿可能合并成一次中断。</td><td>一次 SCK 毛刺或半字节事务后所有后续字节永久错位直至复位;释放沿丢失时两笔事务拼进同一 RX 缓冲。</td><td>使用硬件 NSS(或在 CS 释放时复位 SPI 外设/DMA);上升沿/下降沿分别用独立状态位,处理时以 DMA 剩余计数判帧长。</td></tr><tr id="R12"><td>R12</td><td><span class="sev s1">严重</span></td><td>SPI 驱动</td><td><b>主循环可在 CS 有效、DMA 正在发送时改写 TX 缓冲</b><br><span class="loc">GD32E50X_BOOT/device/SpiOta.cpp:300-308(submitResponse)、954/1012/1020(armSpiDmasForNextCs 前仅检查 !s_csActive)、881-887</span></td><td>主循环检查 !s_csActive 与 armSpiDmasForNextCs 之间无临界区;submitResponse 虽关中断,但关中断挡不住 DMA,仍可在 CS 有效、DMA 读取 s_txBuf 时 memcpy 覆盖;CS 释放时把只发出一半的 ACK 标记为已交付。</td><td>ACK 损坏或丢失,导致对端重传/超时,严重时会话死锁。</td><td>TX 用双缓冲(乒乓),DMA 只读“已提交”缓冲;提交与 armDma 放在同一临界区内。</td></tr><tr id="R13"><td>R13</td><td><span class="sev s1">严重</span></td><td>CAN</td><td><b>CAN 接收只有单槽缓冲,旧帧被覆盖、新帧被重复处理;软件发送队列可能卡死</b><br><span class="loc">GD32E50X_BOOT/device/Can.cpp:167-189,324-337(单个 receive_message + 计数器)、292-293(入队后未使能 TME 中断)</span></td><td>ISR 每收一帧覆盖 receive_message 并 count++;主循环按 count 次数处理同一缓冲。count-- 非原子。入队后未开 TMEIE,排队的 ACK 可能永不发出;直发路径绕过队列造成乱序。</td><td>CAN OTA 在突发流量下丢帧/重复写同一包,或 ACK 永不发出导致会话超时。</td><td>改为 N 槽环形队列(ISR 写 head、主循环读 tail);发送统一走队列并在入队时使能 TME 中断。</td></tr><tr id="R14"><td>R14</td><td><span class="sev s1">严重</span></td><td>CAN</td><td><b>Bus-off 不自动恢复,且无错误中断</b><br><span class="loc">GD32E50X_BOOT/device/Can.cpp:76</span></td><td>auto_bus_off_recovery = DISABLE,代码中无 bus-off/错误被动处理逻辑。</td><td>一次总线干扰进入 bus-off 后 CAN 通道永久失效,只能复位。</td><td>启用自动恢复,或开启 ERR/BOF 中断并在主循环中按策略恢复。</td></tr><tr id="R15"><td>R15</td><td><span class="sev s1">严重</span></td><td>ECAT/跳转</td><td><b>跳转 APP 前不复位 ESC,EXMC 引脚保持 AF 推挽(待上板确认)</b><br><span class="loc">GD32E50X_BOOT/comm/ecat/boot_ecat_hw.cpp:542-556,332-335</span></td><td>escResetForJump() 被注释掉成为死代码;跳转前只关闭 EXMC 时钟,GPIO 仍为 GPIO_MODE_AF_PP,注释“PD/PE 回浮空”与实际不符。INT_EN/IRQ_CFG 残值与 PREOP 状态遗留给 APP。</td><td>APP 起板前 LAN9252 片选/写使能电平不确定,存在误选通与状态残留;这正是历史上 1.1.0.18/22 反复修 warm jump 的根源。</td><td>跳转前把 EXMC 相关 GPIO 显式重置为浮空输入(或拉高 NE/NWE),并按 HBI 使用情况执行 ESC 软复位(0x01F8 / RESET_CTL),在 APP 侧做对称初始化。</td></tr><tr id="R16"><td>R16</td><td><span class="sev s1">严重</span></td><td>安全/资源</td><td><b>主栈仅 1KB,ISR 内叠加 printf、AES、128B 局部数组与 SSC</b><br><span class="loc">GD32E50X_BOOT/RTE/Device/GD32E508VE/regions_GD32E508VE.h:96(__STACK_SIZE 0x400)</span></td><td>EXTI ISR 中调用 handleLinkMessage(含 printf、AES 解密、多个 64~128B 局部缓冲),SSC 邮箱处理也在主循环深调用链上。</td><td>栈溢出会静默覆盖 .bss/堆(SDO 分段缓冲来自 malloc),表现为随机死机或写错 Flash。</td><td>栈至少提高到 4KB,并用栈水位填充(0xDEADBEEF)或 MPU 栈守卫页实测;ISR 中禁止 printf。</td></tr><tr id="R17"><td>R17</td><td><span class="sev s2">一般</span></td><td>OTA</td><td><b>最后一包固定写满 48 字节,镜像接近上限时会覆盖 CRC 槽;SPI 与 ECAT 尺寸上限不一致</b><br><span class="loc">GD32E50X_BOOT/sys/OTA.cpp:1173-1175(new_size <= APP_MAX_SIZE);GD32E50X_BOOT/sys/OTA.cpp:890(ECAT 用 APP_FIRMWARE_SIZE);GD32E50X_BOOT/sys/config.h:38</span></td><td>SPI/CAN 路径允许 size 到 APP_MAX_SIZE(0x35FFC),而 CRC 存放在 start+APP_FIRMWARE_SIZE(0x35FF8);ECAT 用 APP_FIRMWARE_SIZE。末 4 字节闲置,与注释“末尾 CRC32”不符。</td><td>大镜像的最后一包会写入 CRC 槽,随后 writeAppCrc 必然 PGERR(被吞)。</td><td>统一上限为 APP_FIRMWARE_SIZE 并把 48B 对齐余量计入;修正注释。</td></tr><tr id="R18"><td>R18</td><td><span class="sev s2">一般</span></td><td>OTA</td><td><b>B 区恢复后不置 CHECK,可能每次上电重复恢复;恢复全程关中断约 108 页</b><br><span class="loc">GD32E50X_BOOT/sys/OTA.cpp:1397-1446;GD32E50X_BOOT/main.cpp:430</span></td><td>恢复成功后经 jumpToApplication 只置 AVAL/EN,未置 LATEST_CHECK;CRC 字随数据一起被拷贝,随后 writeAppCrc 对已编程字再写必报 PGERR。(待确认 APP 侧是否会置 CHECK)</td><td>上电时间被拉长且反复擦写 A 区;恢复期间 SPI/CAN/ECAT 全部无响应。</td><td>恢复完成后置 CHECK 并跳过重复写 CRC;分段擦写间开中断喂狗。</td></tr><tr id="R19"><td>R19</td><td><span class="sev s2">一般</span></td><td>OTA</td><td><b>validateApplication 栈顶上界判断用 <,排除了 MSP=RAM 顶端这一常见合法值</b><br><span class="loc">GD32E50X_BOOT/sys/OTA.cpp:500</span></td><td>stack_ptr < 0x20020000U;链接器默认 __initial_sp 正好等于 RAM 末地址。</td><td>APP 栈放在 RAM 末端时会被误判无效,停在 BOOT。</td><td>改为 <= 0x20020000 且要求 8 字节对齐。</td></tr><tr id="R20"><td>R20</td><td><span class="sev s2">一般</span></td><td>跳转</td><td><b>跳转前清理不完整</b><br><span class="loc">GD32E50X_BOOT/sys/OTA.cpp:515-652</span></td><td>未清 EXTI pending、SysTick/PendSV 挂起位,未关闭 USART0 等外设时钟,并在 BX 之前就清 PRIMASK。</td><td>APP 开中断瞬间可能进入 BOOT 遗留的挂起中断向量。</td><td>跳转前:关所有 NVIC 使能+清 pending、SysTick->CTRL=0、清 SCB->ICSR PENDSV/PENDST、复位所用外设(rcu_periph_reset),PRIMASK 留给 APP 自己打开。</td></tr><tr id="R21"><td>R21</td><td><span class="sev s2">一般</span></td><td>OTA</td><td><b>OTA 超时为循环计数,ECAT 会话无超时</b><br><span class="loc">GD32E50X_BOOT/sys/OTA.cpp:426-436;GD32E50X_BOOT/sys/config.h:130</span></td><td>OTA_TIMEOUT=50000 次 × 20us 延时 + 循环体,实际时长随负载变化;ECAT begin 后若主站断开,ecat_session 永久为 true,阻塞 SPI/CAN 会话。</td><td>会话无法可靠回收。</td><td>统一用 SysTick 毫秒时间戳计算超时;ECAT 会话加空闲超时自动 abort。</td></tr><tr id="R22"><td>R22</td><td><span class="sev s2">一般</span></td><td>OTA</td><td><b>REQ 分支先读 data[0]/data[1] 再判 len;SIZE 帧非对齐强转</b><br><span class="loc">GD32E50X_BOOT/sys/OTA.cpp:1239;GD32E50X_BOOT/sys/OTA.cpp:1173</span></td><td>if (data[0]==0x00 && data[1]==0xAC && len==2) 先解引用再判长度;*(u32*)data 对任意偏移 payload 做 32 位读。</td><td>len<2 时越界读;未对齐访问在 M33 上一般可容忍,但若开启 UNALIGN_TRP 会 HardFault。</td><td>先判 len;用 memcpy 读取多字节字段。</td></tr><tr id="R23"><td>R23</td><td><span class="sev s2">一般</span></td><td>睡眠</td><td><b>深睡唤醒写 DISABLE 时可能对非擦除字再编程(待确认)</b><br><span class="loc">GD32E50X_BOOT/sys/initial.cpp:150-180</span></td><td>当 SLEEP_ENABLE 位于页首字时,循环落入 else 分支 writeAddress(address, SLEEP_DISABLE),对已编程字再编程;GD32 FMC 对非 0xFFFFFFFF 字编程报 PGERR,返回值被忽略。</td><td>DISABLE 写不进去,下次上电 check() 仍读到 ENABLE 再次深睡,且 check() 在 OTA 通道启动前执行,只能靠 SWD 救回。</td><td>日志写入一律写到下一个擦除态字;写后回读;check() 前预留按键/串口逃生窗口。</td></tr><tr id="R24"><td>R24</td><td><span class="sev s2">一般</span></td><td>中断</td><td><b>全工程未设置 NVIC 优先级分组,两个中断都配成最高优先级 0</b><br><span class="loc">GD32E50X_BOOT/sys/initial.cpp:117(EXTI5_9 0,0);GD32E50X_BOOT/comm/ecat/boot_ecat_hw.cpp:448(ESC IRQ 0,0);GD32E50X_BOOT/device/SpiOta.cpp:773;GD32E50X_BOOT/device/Can.cpp:125-126</span></td><td>未调用 nvic_priority_group_set,抢占/子优先级的划分依赖复位默认值;ESC IRQ 在 BOOT 中实际不工作(见 ECAT 条目),却占用最高优先级。</td><td>SPI CS 边沿(1,1)可能被延迟,加剧边沿合并问题。</td><td>启动时显式设置分组(如 NVIC_PRIGROUP_PRE2_SUB2),并按 SPI CS > CAN > 其他 规划。</td></tr><tr id="R25"><td>R25</td><td><span class="sev s2">一般</span></td><td>CAN</td><td><b>标准帧 rx_efid 未初始化;接收白名单包含本机发送的 ACK/WAIT ID;滤波器全放行、FIFO 溢出静默清除</b><br><span class="loc">GD32E50X_BOOT/device/Can.cpp:324-334,112-122,346-348,79</span></td><td>temp_message 为栈变量,驱动只在扩展帧时写 efid;ACK/WAIT 也会覆盖待处理帧;mask 全 0,RFO 只清不计数,rec_fifo_overwrite=ENABLE。</td><td>偶发把垃圾值当作 OTA ID 处理;高负载时静默丢帧。</td><td>先判 rx_ff==CAN_FF_EXTENDED;硬件滤波器只放行 REQ/DATA/CANCEL;溢出计数上报。</td></tr><tr id="R26"><td>R26</td><td><span class="sev s2">一般</span></td><td>CAN</td><td><b>CANFD_AsyncSend 用调试冻结位 CAN_CTL_DFZ 判断 FD 模式</b><br><span class="loc">GD32E50X_BOOT/device/Can.cpp:251</span></td><td>DFZ 是 Debug Freeze 位,与 FD 使能无关,当前逻辑碰巧成立。</td><td>调试器配置变化时发送路径行为改变。</td><td>改为检查 FDCTL 的 FDEN 位或用驱动 API。</td></tr><tr id="R27"><td>R27</td><td><span class="sev s2">一般</span></td><td>ECAT</td><td><b>空闲限速使 SSC 1ms 时基变为约 5ms</b><br><span class="loc">GD32E50X_BOOT/comm/ecat/boot_ecat_app.cpp:411,445-449</span></td><td>空闲时每 5ms 才调用一次 MainLoop,而 ECAT_CheckTimer 每次只累加 1ms。</td><td>PREOP/邮箱等 SSC 超时被放大 5 倍,与主站期望不一致。</td><td>按实际经过毫秒数补调 ECAT_CheckTimer,或改用 SysTick ISR 驱动。</td></tr><tr id="R28"><td>R28</td><td><span class="sev s2">一般</span></td><td>ECAT</td><td><b>PRAM FIFO / CSR busy 超时不发 abort</b><br><span class="loc">GD32E50X_BOOT/comm/ecat/boot_ecat_hw.cpp:172-194,213-214,239-240</span></td><td>超时仅 break,未写 0x030C/0x0314 abort 位;CSR busy 超时静默忽略。</td><td>下一笔 PRAM 事务与残留 FIFO 错位,读写到错误数据。</td><td>超时置 abort 并返回错误,上层重试。</td></tr><tr id="R29"><td>R29</td><td><span class="sev s2">一般</span></td><td>ECAT</td><td><b>0x25xx SDO 写回调不校验 dataSize,错误码累积,会话中可改写期望长度</b><br><span class="loc">GD32E50X_BOOT/comm/ecat/boot_ecat_app.cpp:199-243</span></td><td>dlLoadU32 固定读 4 字节;begin 失败时 u32Phase |= 0x10000 不清旧错误;会话进行中仍可改写 sub2 expected。</td><td>短写时越界读;状态码难以诊断;会话中途改长度使 commit 判据失真。</td><td>校验 dataSize==4;错误码改为赋值;ecat_session 期间拒绝写 expected。</td></tr><tr id="R30"><td>R30</td><td><span class="sev s2">一般</span></td><td>ECAT</td><td><b>死代码 HW_Enable/DisableSyncManChannel 写错寄存器,HW_SetLed 对奇地址做 16 位写</b><br><span class="loc">GD32E50X_BOOT/comm/ecat/boot_ecat_hw.cpp:649-665</span></td><td>写 0x0806(PDI 只读),应为 0x0807 bit0;SSC 5.11 实际使用 ecatslv.c 内版本,故未生效。HW_SetLed 对 0x0139 做 size=2 写,违反本文件第 257 行约束。</td><td>日后有人启用这些函数会引入隐蔽故障。</td><td>删除或修正这些未用函数。</td></tr><tr id="R31"><td>R31</td><td><span class="sev s2">一般</span></td><td>ECAT</td><td><b>VENDOR_ID 为 0(未注册),与 SII 一致性待确认</b><br><span class="loc">GD32E50X_BOOT/comm/ecat/ecat_def.h:629</span></td><td>0x1018:01 厂商号为 0x00000000。</td><td>不符合 ETG 一致性要求,多厂商设备共网时主站识别混淆。</td><td>申请 ETG Vendor ID,BOOT/APP/SII 三处统一。</td></tr><tr id="R32"><td>R32</td><td><span class="sev s2">一般</span></td><td>构建</td><td><b>仓库不自包含:依赖仓库外 spi_link 源码与 G: 盘绝对路径</b><br><span class="loc">GD32E50X_BOOT/GD32E50X_BOOT.cproject.yml:27-29,96</span></td><td>spi_link_v2.h/.cpp 不在本仓库,也不在 Gitea 任何仓库;-IG:/Robot_v2/DCU/spi_link 写死。</td><td>克隆即无法编译;SPI v2 帧解析(payload_len 校验等)无法评审,协议版本可能与 APP/ESP32 漂移。</td><td>spi_link 以 submodule 或 shared/ 目录纳入版本管理,使用相对路径。</td></tr><tr id="R33"><td>R33</td><td><span class="sev s2">一般</span></td><td>构建</td><td><b>Release 镜像仍编入全部调试 printf;版本注释悬空、无 CHANGELOG</b><br><span class="loc">GD32E50X_BOOT/sys/config.h:14,19;GD32E50X_BOOT.csolution.yml:28</span></td><td>DEBUG_PRINT 恒为 1,target-set 烧录 Release;644c8fb 删除了约 200 行版本说明,“见上方注释块”已无所指;1.1.0.22→25 无变更记录。</td><td>阻塞式串口输出拉长 ISR 与关键路径;版本追溯困难。</td><td>DEBUG_PRINT 由 build-type 宏控制;新增 CHANGELOG.md。</td></tr><tr id="R34"><td>R34</td><td><span class="sev s2">一般</span></td><td>构建</td><td><b>.gitignore 与实际跟踪内容矛盾</b><br><span class="loc">.gitignore:90-94</span></td><td>忽略 *.html 与 .trae/,但 .trae/specs 已入库;SECURITY_AUDIT_REPORT.* 被忽略,说明曾有本地安全审计稿未入库。</td><td>docs/ 下 HTML 评审在本地无法 git add;有价值的审计结论游离于仓库外。</td><td>改为 !docs/**/*.html;决定 .trae 规格是否入库;将审计结论纳入 docs/。</td></tr><tr id="R35"><td>R35</td><td><span class="sev s3">建议</span></td><td>安全</td><td><b>数据块使用 AES-ECB,带密钥 CRC32 不是 MAC,且不覆盖 seq</b><br><span class="loc">GD32E50X_BOOT/sys/OTA.cpp:1093-1098,1470-1498</span></td><td>ECB 下同会话内块可重排;CRC32 线性可伪造。</td><td>完整性依赖 CRC,抗篡改能力弱(在签名方案落地前)。</td><td>改 AES-CTR/GCM 或 CMAC,并把 seq/session 纳入认证数据。</td></tr><tr id="R36"><td>R36</td><td><span class="sev s3">建议</span></td><td>性能</td><td><b>每个数据包都重新做 AES 密钥扩展</b><br><span class="loc">GD32E50X_BOOT/sys/OTA.cpp:1093</span></td><td>aes128_set_key 在每包解密前调用。</td><td>浪费 CPU,延长主循环处理时间。</td><td>会话建立时扩展一次并缓存轮密钥。</td></tr><tr id="R37"><td>R37</td><td><span class="sev s3">建议</span></td><td>ECAT</td><td><b>未使用 ETG 推荐的 BOOT 状态 + FoE,ESC 中断在 BOOT 中实际无效</b><br><span class="loc">GD32E50X_BOOT/comm/ecat/ecat_def.h:273,286;GD32E50X_BOOT/comm/ecat/boot_ecat_app.cpp:379-388;GD32E50X_BOOT/comm/ecat/boot_ecat_hw.cpp:441-449</span></td><td>BOOTSTRAPMODE_SUPPORTED=0、FOE_SUPPORTED=0,固件在 PREOP 经自定义 CoE 对象写入;BOOT 永不进 SAFEOP,PDI_Isr 不做事却配置为最高优先级。</td><td>与通用主站工具(TwinCAT/SOEM FoE)不兼容。</td><td>中长期迁移到 BOOT 状态 + FoE;去掉 BOOT 中无用的 ESC IRQ。</td></tr><tr id="R38"><td>R38</td><td><span class="sev s3">建议</span></td><td>可维护性</td><td><b>SSC 5.11 版本较旧、文件头缺许可证声明;量产代码残留诊断逻辑</b><br><span class="loc">GD32E50X_BOOT/comm/ecat/ecatslv.c:12;GD32E50X_BOOT/comm/ecat/ecatslv.c:1361,2437;GD32E50X_BOOT/comm/ecat/boot_ecat_hw.cpp:394-403</span></td><td>bring-up 期 printf 无条件编译;exmcInit 中留有 D4/D5 短路排查写入。</td><td>代码体积与时序受影响;许可合规风险。</td><td>升级至 SSC 5.13 并保留 Beckhoff 许可头;诊断代码收进 DEBUG 宏。</td></tr><tr id="R39"><td>R39</td><td><span class="sev s3">建议</span></td><td>可维护性</td><td><b>注释与代码不一致、死代码、宏重复定义</b><br><span class="loc">GD32E50X_BOOT/shared/ota_layout.h:5;GD32E50X_BOOT/sys/config.h:41-42;GD32E50X_BOOT/sys/OTA.cpp:25-37,138,957-958;GD32E50X_BOOT/comm/ecat/el9800hw.h:37 与 ecat_def.h:165</span></td><td>ota_layout.h 注释写 ../../../shared,实际 include ../shared;SLEEP_CONFIG 硬编码未引用 OTA_LAYOUT_SLEEP_PAGE;branchToApplication 未使用;OTA_SET 注释“6 个配置字”实为 10 个;ECAT commit 注释引用 OTA.cpp:1327 已过时(实际 1531);ECAT_TIMER_INC_P_MS 两处定义文本不同;崩溃计数清零条件 config.h 与 ota_layout.h 说法不一。</td><td>误导后续维护者。</td><td>统一引用 ota_layout.h;注释改为引用符号名而非行号;删除死代码。</td></tr><tr id="R40"><td>R40</td><td><span class="sev s3">建议</span></td><td>工程</td><td><b>缺 README/协议文档/CI</b><br><span class="loc">仓库根目录</span></td><td>无 README、Flash 布局/OTA 协议/救砖流程文档;无 CI,.trae 规格的编译验证项至今无法从仓库证实。vcpkg-configuration.json 已锁定工具链版本。</td><td>新人上手与回归成本高。</td><td>补充 docs/;Gitea Actions 跑 cbuild + cppcheck。</td></tr></table>
|
||
|
||
<h2>4. 既有评审结论跟踪</h2>
|
||
<p>来源:<code>.trae/specs/prune-boot-dead-code/</code>(随提交 3377a2e,2026-07-15 入库)。原文件保留不动,逐条复核见下表,修订版另存为
|
||
<a href="prune-boot-dead-code_复核_2026-10-19.html">docs/review/prune-boot-dead-code_复核_2026-10-19.html</a>。</p>
|
||
<table><tr><th>原条目</th><th>结论</th><th>2026-10-19 复核(基于 644c8fb)</th><th>状态</th></tr><tr><td>A</td><td>删除 OTAHandler::ota_stateota_time</td><td>全仓 grep 0 命中</td><td class="ok">仍成立</td></tr><tr><td>B</td><td>删除 handleOTAMessage 旧接口</td><td>0 命中;链路统一走 handleLinkMessage</td><td class="ok">仍成立</td></tr><tr><td>C</td><td>删除 sendDATA_ACK 两个重载</td><td>0 命中</td><td class="ok">仍成立</td></tr><tr><td>D</td><td>删除 can_dma_init 占位</td><td>0 命中;canfd_config 中无调用</td><td class="ok">仍成立</td></tr><tr><td>E</td><td>删除 BootPlatform 及其 4 个预留接口</td><td>0 命中</td><td class="ok">仍成立</td></tr><tr><td>F</td><td>删除 SPI0_IRQHandler 空壳 ISR</td><td>仅启动文件 [WEAK] 处出现;SpiOta.cpp:725-727 仍禁用 SPI0 中断,未使能 SPI0_IRQn</td><td class="ok">仍成立</td></tr><tr><td>G</td><td>删除 .bak 备份与 RTE 冲突副本</td><td>仓库中已不存在</td><td class="ok">仍成立</td></tr><tr><td>H</td><td>C 包装层审计(保留 delay_1ms/1us、system_soft_reset、EXTI/CAN IRQ)</td><td>保留项仍有调用方;spi_ota_* 仅剩类型名 spi_ota_frame_type_t</td><td class="ok">仍成立</td></tr><tr><td>I</td><td>注释一致性(已删符号 0 命中)</td><td>已删符号仍 0 命中;但出现新的注释漂移(见 R39)</td><td class="warn">部分退化</td></tr><tr><td>J</td><td>行为零变化</td><td>该次提交(3377a2e)之后又有 10 个功能提交,行为已演进,此项仅对当时快照有意义</td><td class="warn">不再适用</td></tr><tr><td>K/G2.2</td><td>编译验证(当时未执行)</td><td>仓库仍无 CI 与构建产物,无法从仓库证实;且构建依赖仓库外 spi_link(R31),克隆后无法独立编译</td><td class="no">仍未完成</td></tr><tr><td>L</td><td>“不漏不错”自检</td><td>当时范围内成立;之后新增 branchToApplication(未使用)、HW_*SyncManChannel/HW_SetLed 等死代码(R30、R39)</td><td class="warn">需续做</td></tr></table>
|
||
|
||
<h2>5. 优先级修复路线图</h2>
|
||
<table><tr><th>优先级</th><th>时间</th><th>事项</th><th>关联问题</th></tr>
|
||
<tr><td><span class="sev s0">P0</span></td><td>1 周内</td><td>收紧 ECAT 救砖门(A/B 均不可启动才开);verifyAppCrc 取消擦除态豁免、writeAppCrc 返回并回读;FINISH/commit 比对主机下发的整镜像 CRC;擦除/写入失败转 ERROR</td><td>R01 R03 R04 R05</td></tr>
|
||
<tr><td><span class="sev s1">P1</span></td><td>2–4 周</td><td>ISR 只入队、OTA/Flash 单线程化;SPI 队列满不推进 SEQ、credit 取 min;修正 isSpiTransport;TX 双缓冲;CAN 环形队列 + bus-off 恢复;崩溃门优先回滚 B 区;栈扩到 4KB 并测水位</td><td>R07–R14 R16</td></tr>
|
||
<tr><td><span class="sev s1">P1</span></td><td>2–4 周</td><td>配置页改双页日志结构;跳转前完整外设/中断清理与 EXMC 引脚复位、ESC 复位</td><td>R06 R15 R20</td></tr>
|
||
<tr><td><span class="sev s2">P2</span></td><td>1–2 月</td><td>固件签名(ECDSA-P256/Ed25519)+ 防降级版本号 + 按 UID 派生密钥,替换硬编码 K_master 与 ECB/CRC 方案</td><td>R02 R35 R36</td></tr>
|
||
<tr><td><span class="sev s2">P2</span></td><td>1–2 月</td><td>spi_link 纳入仓库、Release 关闭 DEBUG_PRINT、CHANGELOG、.gitignore 修正、Gitea Actions CI</td><td>R32–R34 R40</td></tr>
|
||
<tr><td><span class="sev s3">P3</span></td><td>后续</td><td>ECAT 迁移 BOOT 状态 + FoE;SSC 升级 5.13;申请 Vendor ID;清理死代码与注释漂移</td><td>R30 R31 R37–R39</td></tr></table>
|
||
|
||
<h2>6. 总结评分</h2>
|
||
<table><tr><th>维度</th><th>评分</th><th>说明</th></tr><tr><td>Bootloader/OTA 可靠性</td><td><b>4.5</b> / 10</td><td>A/B 分区、CRC、崩溃门思路完整,但校验可绕过、配置页无掉电保护、回滚失效</td></tr><tr><td>固件安全</td><td><b>2.5</b> / 10</td><td>硬编码共享密钥、无签名、可重放;ECAT 门在崩溃后敞开</td></tr><tr><td>实时性与中断/DMA</td><td><b>4.5</b> / 10</td><td>已做 DMA 与 ISR 分流,但 ISR 内仍跑状态机/Flash,竞态较多</td></tr><tr><td>通信协议(SPI v2 / CAN-FD)</td><td><b>5.0</b> / 10</td><td>SEQ/ACK/CREDIT 设计合理,实现存在丢包与通道串扰</td></tr><tr><td>EtherCAT 集成</td><td><b>6.0</b> / 10</td><td>SSC 集成可用,刷写流程清晰;跳转前 ESC/引脚处理与时基存疑</td></tr><tr><td>代码质量与可维护性</td><td><b>6.5</b> / 10</td><td>注释详尽、常量集中(ota_layout.h 唯一真源);存在注释漂移与死代码</td></tr><tr><td>构建与仓库卫生</td><td><b>4.0</b> / 10</td><td>依赖仓库外源码与绝对路径,无 README/CI,.gitignore 矛盾</td></tr>
|
||
<tr><td><b>综合</b></td><td><b>4.7</b> / 10</td><td>完成 P0+P1 后预计可达 7 分以上</td></tr></table>
|
||
<div class="card"><b>亮点:</b>Flash 布局单一真源、配置字以擦除态为“无效”的设计、崩溃门与 ECAT 救砖思路、SPI v2 的 SEQ/CREDIT 流控、ECAT commit 不在 SSC 回调里直接跳转、注释中保留了大量现场问题来由。<br>
|
||
<b>主要短板:</b>完整性校验“可绕过/后盖章”、鉴权缺失、ISR 与主循环共享状态机、单页配置无掉电保护、仓库不自包含。</div>
|
||
<p class="meta">本报告仅新增 docs/ 下文件,未改动任何源码。</p>
|
||
</div></body></html> |