Files
DCU_APP/docs/review/DCU_APP_专家评审_2026-10-19.html
T

86 lines
37 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>DCU_APP 专家评审 2026-10-19</title><style>
body{font-family:"PingFang SC","Microsoft YaHei",sans-serif;margin:0;background:#f5f6f8;color:#1d2330;line-height:1.6}
main{max-width:1280px;margin:0 auto;padding:24px 32px 64px;background:#fff}
h1{font-size:26px;border-bottom:3px solid #2a5bd7;padding-bottom:8px}h2{font-size:20px;margin-top:36px;border-left:5px solid #2a5bd7;padding-left:10px}h3{font-size:16px}
table{border-collapse:collapse;width:100%;font-size:13px;margin:12px 0}th,td{border:1px solid #d5d9e0;padding:6px 8px;vertical-align:top;text-align:left}th{background:#eef2fa}
tr:nth-child(even) td{background:#fafbfd}.loc{font-family:Consolas,monospace;font-size:12px;word-break:break-all;color:#334}
.sev,.st{display:inline-block;padding:1px 8px;border-radius:10px;font-size:12px;color:#fff;white-space:nowrap}
.s0{background:#8b0000}.s1{background:#d9480f}.s2{background:#e8a317}.s3{background:#5c7cfa}
.ok{background:#2b8a3e}.part{background:#e8a317}.bad{background:#c92a2a}.na{background:#868e96}
pre{background:#0f1729;color:#d6e2ff;padding:14px;border-radius:6px;font-size:12px;overflow-x:auto}
.kpi{display:flex;gap:12px;flex-wrap:wrap}.kpi div{flex:1;min-width:140px;border:1px solid #d5d9e0;border-radius:8px;padding:10px;text-align:center}.kpi b{font-size:24px;display:block}
.note{background:#fff8e6;border:1px solid #f0d58a;padding:10px 14px;border-radius:6px}
</style></head><body><main>
<h1>GD32E508 DCU_APP 固件专家评审</h1>
<p>仓库:<code>cbb/DCU_APP</code>(master @ <code>d8b3c97</code>,2026-10-11,APP 版本 1.7.4) 评审日期:2026-10-19 产出方式:多专家视角静态评审(未上板、未交叉编译)</p>
<div class="kpi"><div><b style="color:#8b0000">1</b>致命</div><div><b style="color:#d9480f">12</b>严重</div><div><b style="color:#e8a317">28</b>一般</div><div><b style="color:#5c7cfa">6</b>建议</div><div><b>48 / 100</b>综合评分</div></div>
<h2>1. 项目概述与架构</h2>
<p>DCU(GD32E508VE,Cortex-M33@180MHz,512KB Flash/128KB SRAM)是机器人整机的通信网关:EtherCAT 从站(LAN9252 经 EXMC 16 位 HBI,Beckhoff SSC 协议栈)、SPI0 从机连接 ESP32(WiFi/上位机)、3 路 CAN-FD 连接关节电机,并承担 DCU 本地 OTA 交接与电机固件中继 OTA。</p>
<pre>
EtherCAT 主站 ──100BASE-TX──▶ LAN9252 ESC ══EXMC HBI══╗
Web/上位机 ──WiFi──▶ ESP32 ──SPI0 从机(PA15 CS / PB6 READY)══╣
关节电机 ×N ◀──CAN-FD ×3 (CAN0/1/2, STB 脚)────────────────╣
▼
┌──────────────── GD32E508 DCU_APP ────────────────┐
│ ISR: EXTI0 SYNC0(p0) │ EXTI10_15 SPI CS(p0) │ CAN RX(p0)│
│ EXTI1 PDI/ESC IRQ(p1) │ EXTI2 SYNC1(p1) │ TIMER2(p1)│
│ CAN TX / SPI0 ERR(p2) │ USART0(p3) │ SysTick(p15) │
│ 主循环 TaskRunner: 喂狗 → SPI poll → CAN RX 解析 → │
│ EcatMotorOta/MotorOtaRelay → SSC MainLoop → 周期任务 │
│ Flash: BOOT 0x0800_0000 │ 备份 APP │ 主 APP 0x0804_8000 │
│ OTA 配置页 0x0807_E000 (word0..9, shared/ota_layout.h)│
└──────────────────────────────────────────────────┘
</pre>
<table><tr><th>目录</th><th>职责</th><th>规模/要点</th></tr>
<tr><td class="loc">sys/</td><td>初始化、调度、OTA 交接、电机 OTA 中继、AES、临界区、HardFault</td><td>motor_ota_relay.cpp 1284 行;config.h 集中配置</td></tr>
<tr><td class="loc">device/</td><td>CAN-FD、ADC、USART、Timer、看门狗、电机状态/版本</td><td>CANFD.cpp 2026 行(最大文件)</td></tr>
<tr><td class="loc">device/spi/</td><td>ESP32 SPI Link v2 命令总线与各命令处理器</td><td>app_spi_command.cpp 1375 行;协议核心 spi_link_v2 在仓库外</td></tr>
<tr><td class="loc">comm/</td><td>LAN9252/EXMC 驱动、SYNC、PDO、CAN 桥、电机 OTA 邮箱进料</td><td>esc_lan9252.cpp 718 行,关键路径 .ramfunc</td></tr>
<tr><td class="loc">comm/ssc/</td><td>Beckhoff SSC 协议栈 + 本地移植层(dc_app.c、ESI、EEPROM 生成)</td><td>ecatslv.c 2512 行;PDO 输出 384B / 输入 600B,ESI/EEPROM/代码一致</td></tr>
<tr><td class="loc">RTE/</td><td>GigaDevice 厂商库、启动文件、链接脚本</td><td>链接脚本含 .ramfunc 执行区;栈/堆定义三套并存</td></tr></table>
<h2>2. 评审范围与方法</h2>
<ul><li>范围:GD32E508_DCU_APP 全部应用代码(约 2.7 万行,不含厂商库)、构建配置、仓库根目录文件、docs/ 与 .trae/ 下全部既有评审记录、README。</li>
<li>专家组:①实时性/中断/DMA 与 SPI 链路;②OTA/Bootloader 与安全;③EtherCAT(LAN9252/SSC/CoE/PDO/DC);④CAN-FD/外设驱动/系统骨架与故障保护;⑤构建、仓库卫生与可维护性。</li>
<li>方法:逐条以当前 HEAD 的 <code>文件:行号</code> 为证据(grep/源码核对),既有审计结论逐条复核;BOOT 侧仅在 OTA 鉴权一项跨仓查阅 GD32E50X_BOOT,其余 BOOT/ESP32 行为标为本仓库不可验证。</li>
<li>局限:未交叉编译、未上板;时序数值为静态推算;共享协议 spi_link_v2 在仓库外未审。</li></ul>
<p class="note">安全提示:仓库根目录文件 “-” 含电机 OTA 主密钥常量。本报告只描述该风险,不转载任何密钥值。</p>
<h2>3. 问题清单</h2>
<table><tr><th>编号</th><th>严重度</th><th>领域</th><th style="width:16%">文件:行号</th><th>现象</th><th>风险</th><th>修改建议</th></tr><tr><td>R-01</td><td><span class='sev s0'>致命</span></td><td>OTA/安全</td><td class='loc'>comm/ssc/port/dc_app.c:330-351;跨仓 GD32E50X_BOOT sys/OTA.cpp:848-866、:960</td><td>SDO 0x2502 写 1 即触发回 BOOT 并把 EN 置 NOT_AVAL;BOOT 的 ecatArmed() 仅凭 CHECK/EN 不同时为 AVAL 即放行;之后 ecatFeed/ecatCommit 写入明文,CRC 由设备对自己写入的内容计算,不与发送方提供值比对</td><td>任何能接入 EtherCAT 网段的主站都可无鉴权把任意固件刷入 DCU(含恶意或错误镜像),整机失控/变砖</td><td>引入镜像签名(ECDSA/Ed25519)或至少 HMAC,BOOT 在 commit 前验签;0x2502 增加挑战-应答令牌并要求 AL≤PREOP、电机未运行</td></tr><tr><td>R-02</td><td><span class='sev s1'>严重</span></td><td>实时/SPI</td><td class='loc'>device/spi/app_spi_command.cpp:591、:1317;comm/esc_lan9252.cpp:366-383</td><td>EXTI10_15(SPI CS)配置为 preempt 0,与 SYNC0、CAN RX 同级;ESP32 全部命令在该 ISR 内同步执行,0xA2 唤醒诊断在 ISR 内以 50µs 步进轮询两段各最多 200 次(最长约 20ms)</td><td>SYNC0 抖动/丢拍导致 SM 看门狗或 Sync 错误掉 SAFEOP;CAN FIFO0(3 帧)溢出丢反馈帧</td><td>onCsRelease 只做快照+立即重装 DMA,命令处理移至主循环或 PendSV;EXTI10_15 降至 preempt≥1;0xA2 改为状态机异步完成</td></tr><tr><td>R-03</td><td><span class='sev s1'>严重</span></td><td>CAN/并发</td><td class='loc'>device/CANFD.cpp:284-317、:1255-1321;comm/ethercat_can_bridge.cpp:119、:337</td><td>CanTxConcurrencyGuard 只屏蔽 EXTI10_15 与本通道 TX 中断;enqueueTx 同时被 Sync0_Isr(preempt 0)/PDI_Isr(preempt 1) 经 EtherCAT 桥调用,可抢占 TX ISR 的 drainTxQueueIsr 与主循环 transmitFrame(注释 281 行声称可防 PDI,与实现不符)</td><td>软件队列 count 读改写丢失更新;两路同时选中同一空邮箱导致帧被覆盖/丢失</td><td>所有 enqueueTx/transmitFrame/drain 统一使用 PRIMASK 短临界区(仅包指针/计数与邮箱装填),或把 EtherCAT 侧发送改为单生产者无锁环形队列</td></tr><tr><td>R-04</td><td><span class='sev s1'>严重</span></td><td>安全/故障保护</td><td class='loc'>comm/ssc/port/dc_app.c:641-651;comm/esc_lan9252.cpp:552-568</td><td>OP→SAFEOP(主站丢失/SM 看门狗超时)时 APPL_StopOutputHandler 仅调用 EcatCan_ResetSequenceState;不清空 CAN 软件 TX 队列、不下发电机停机/零力矩;过程数据看门狗 0x0420 被放宽至 1s;SPI 链路丢失亦无超时处理</td><td>通信中断后电机继续执行最后指令或已排队指令最长约 1s 以上,存在机械安全风险(若电机侧无指令超时则升级为致命)</td><td>定义安全输出态:StopOutput 时清空各通道 TX 队列并广播停机帧;PD 看门狗恢复 100ms 量级;SPI/CAN 指令源分别加超时</td></tr><tr><td>R-05</td><td><span class='sev s1'>严重</span></td><td>OTA</td><td class='loc'>sys/config.h:359 对 shared/ota_layout.h:48;sys/ota_boot_request.cpp:62-80</td><td>CONFIG_OTA_CONFIG_WORD_COUNT=8,而布局真源 OTA_LAYOUT_WORD_COUNT=10;擦页重写路径只回写 word0..7</td><td>每次进 BOOT 请求都会抹掉 word8(ECAT arm 令牌)与 word9(崩溃计数),崩溃门失效、ECAT 刷写语义被破坏</td><td>直接以 OTA_LAYOUT_WORD_COUNT 定义该常量(删除 config.h 中重复的页地址常量)</td></tr><tr><td>R-06</td><td><span class='sev s1'>严重</span></td><td>OTA</td><td class='loc'>sys/ota_boot_request.cpp:64-80</td><td>配置页采用“读→擦整页→逐字写回”,无双页/日志式提交</td><td>擦除后写回前掉电,整页为 0xFF,BOOT 落入 stay_boot_wait_ota,完好 APP 不再启动,备份标志丢失</td><td>双页(A/B)+序号+CRC 的原子提交,或只做 1→0 的追加式写标志</td></tr><tr><td>R-07</td><td><span class='sev s1'>严重</span></td><td>OTA</td><td class='loc'>sys/ota_boot_request.cpp:199-213、:228-229</td><td>CAN 0x320 “OTA!” 路径在 taskCanRxParse 上下文中同步调用 requestEnterBoot();其余路径也仅关 SPI0/EXTI10_15,库函数 fmc_page_erase 期间 SYNC0/CAN/SysTick 仍开启</td><td>单 Bank Flash 擦除 20~30ms 期间取指停顿,所有 ISR 被阻塞,EtherCAT 掉线、CAN 丢帧;无鉴权的 CAN 帧即可让 DCU 回 BOOT</td><td>统一走 requestEnterBootAsync;擦写前先完成外设静默,再在关中断条件下执行;CAN 入口增加鉴权/状态门控</td></tr><tr><td>R-08</td><td><span class='sev s1'>严重</span></td><td>电机 OTA</td><td class='loc'>sys/motor_ota_relay.cpp:232-234、:1098-1108</td><td>start() 已在入口以 PRIMASK 加锁(VULN-14 部分修复),但 SPI cancel() 在 preempt 0 ISR 中执行,可插入主循环 process() 的“状态判断→enterState(kTransfer…)”之间;enterState(kIdle) 清锁后主循环又把状态写回活跃态</td><td>出现“无通道锁的活跃 OTA 会话”,其他总线可再次向升级通道混发;cancel 同时重置环形缓冲头尾,与 bufPop 竞争</td><td>cancel 只置请求标志,由主循环在单一上下文中执行状态迁移与解锁;锁与状态迁移放在同一临界区</td></tr><tr><td>R-09</td><td><span class='sev s1'>严重</span></td><td>电机 OTA</td><td class='loc'>device/spi/app_spi_cmd_motor_ota.cpp:79</td><td>SPI 0xD0 cancel 不检查会话来源 s_feed_src</td><td>ESP32 可中止由 EtherCAT 进料的电机 OTA;若电机已擦除,可能变砖</td><td>cancel 校验来源,仅允许会话所属通道取消,或要求显式 force 标志</td></tr><tr><td>R-10</td><td><span class='sev s1'>严重</span></td><td>安全</td><td class='loc'>sys/motor_ota_relay.cpp:23-26、:123-129;仓库根文件 “-”</td><td>电机 OTA 主密钥 K_MASTER 硬编码于源码(且为可预测常量),AES-128 使用 ECB;nonce 来自以毫秒节拍为种子的 LCG;keyed-CRC32 不是 MAC;根目录 “-” 文件的反汇编中原样包含该常量</td><td>密钥视同公开;可伪造/重放/降级电机固件(电机侧是否校验 nonce 本仓库不可验证)</td><td>视为已泄露并轮换;改为每设备派生密钥 + AES-CTR/GCM 或 CMAC;nonce 用 TRNG;从版本库移除 “-” 并评估历史清理</td></tr><tr><td>R-11</td><td><span class='sev s1'>严重</span></td><td>CAN</td><td class='loc'>device/CANFD.cpp:1473-1512、:1650-1663;device/spi/app_spi_cmd_can_feedback.cpp:228、:237</td><td>CAN0→CAN1/2 桥接模式(可由 SPI 0xC2 mode=2 远程打开)在 CAN0 RX ISR(preempt 0) 中调用 bridgeTransmitRaw:不检查 TX 静默、不检查 MotorOtaRelay 通道锁、无并发守卫,且会 abort 其他发送者的在途帧</td><td>升级期间可打断电机 OTA 固件包;与 transmitFrame 争抢邮箱</td><td>桥接发送改入软件队列并复用统一守卫;检查静默与 OTA 锁;禁止 abort 他人帧</td></tr><tr><td>R-12</td><td><span class='sev s1'>严重</span></td><td>系统</td><td class='loc'>device/spi/app_spi_cmd_can_feedback.cpp:88-93、:196;RTE/.../startup_gd32e50x_cl.s:33;regions_GD32E508VE.h:96-97</td><td>0xC1 处理器在 EXTI ISR 中于栈上分配 IdSnapshotTable(~1.35KB)+payload+快照(单次约 1.7KB);栈/堆有三套定义,实际生效栈仅 2KB 且紧贴 .bss 向下增长</td><td>嵌套中断时栈溢出静默踩坏 .bss 全局变量,表现为偶发、不可复现的故障</td><td>大缓冲改静态;统一栈/堆定义为单一来源,栈放 RAM 顶部并加大(RAM 约 100KB 空闲),加栈水印/MPU 守护</td></tr><tr><td>R-13</td><td><span class='sev s1'>严重</span></td><td>仓库卫生</td><td class='loc'>仓库根目录文件 “-”(cfe7725 引入,1,343,675 B)</td><td>fromelf --text 对 Release .axf 的完整反汇编+符号表被误提交(推断为 -o - 被当作文件名后 git add -A)</td><td>泄露完整符号/地址布局与密钥常量;仓库膨胀</td><td>git rm --cached ./- 并在 .gitignore 加 /-;评估 filter-repo 清理历史(本次评审未删除任何文件)</td></tr><tr><td>R-14</td><td><span class='sev s2'>一般</span></td><td>EtherCAT</td><td class='loc'>comm/ssc/port/dc_app.c:861-872、:892-948;comm/ssc/src/ecatslv.c:951</td><td>自愈守卫假设“OP 且 bEscIntEnabled=false 不可能”并强制置位;但 Free-Run 模式下该状态合法(ESI 也宣告 Free Run);另 5ms 新鲜度兜底会在 SYNC0 丢失时由主循环强行跑 PDO</td><td>Free-Run 模式输入仅靠 5ms 兜底刷新;DC 丢失被掩盖、从站不掉 SAFEOP 而继续用陈旧输出(需上板确认)</td><td>守卫按同步类型区分;兜底仅记诊断不代跑应用,让 SSC 看门狗按规范降级</td></tr><tr><td>R-15</td><td><span class='sev s2'>一般</span></td><td>EtherCAT/安全</td><td class='loc'>comm/ssc/port/dc_app.c:330-351、:355-393</td><td>0x2502(回 BOOT)与 0x2503(ESP32 EN)SDO 写不检查 AL 状态与电机运行态(0x2502 仅在 ota_boot_request.cpp:160 拒绝电机 OTA 进行中)</td><td>OP 运行中被误写即重启 DCU 或切断 WiFi</td><td>要求 AL≤PREOP 且电机使能关闭,否则返回 SDO abort</td></tr><tr><td>R-16</td><td><span class='sev s2'>一般</span></td><td>实时</td><td class='loc'>comm/ethercat_sync.cpp:142-147、:161-166</td><td>PDI_Isr/Sync1 执行期间显式屏蔽 EXTI0(为保护 HBI 两步事务)</td><td>大块 SDO 时 SYNC0 被延迟上百 µs(VULN-03)</td><td>HBI 访问改为 ISR 统一代访或使用直接寻址模式缩短事务</td></tr><tr><td>R-17</td><td><span class='sev s2'>一般</span></td><td>SPI</td><td class='loc'>device/spi/app_spi_command.cpp:1317-1320</td><td>armSpiDmasForNextCs() 仍在 processCommand() 之后调用</td><td>命令处理慢时 ESP32 下一笔事务落入 DMA 未装窗口,RXORERR/帧损坏(VULN-04)</td><td>CS 释放后先重装 DMA 再处理命令;ESP32 侧发送前检查 DATA_READY</td></tr><tr><td>R-18</td><td><span class='sev s2'>一般</span></td><td>SPI</td><td class='loc'>device/spi/app_spi_command.cpp:1245-1250</td><td>按铃待发事件在 ESP32 发起查询帧时被清空,无重发暂存</td><td>0xC6/0xD2 主动推送事件丢失(VULN-05,影响面较原审计时缩小)</td><td>引入持久化重发槽并在空闲期重新按铃</td></tr><tr><td>R-19</td><td><span class='sev s2'>一般</span></td><td>SPI</td><td class='loc'>device/spi/app_spi_command.cpp:1135-1147、:1042</td><td>poll() 中按铃超时处理用 BASEPRI 保护,挡不住 preempt 0 的 CS ISR;cancelRing 不重装 DMA</td><td>ISR 刚装填的新响应被 cancelRing 清掉且 PB6 被拉高,ESP32 收不到通知;陈旧帧留在 TX 缓冲</td><td>该段改 PRIMASK 短临界区;cancelRing 内重装 DMA</td></tr><tr><td>R-20</td><td><span class='sev s2'>一般</span></td><td>SPI</td><td class='loc'>device/spi/app_spi_command.cpp:339-374</td><td>帧解析对每个 SOF 候选重算整帧 CRC,畸形 256B 帧可触发约 236 次 CRC</td><td>在 preempt 0 ISR 中放大耗时(spi_link_v2 CRC 实现在仓库外,耗时待测)</td><td>先校验长度/版本再算 CRC,限制候选数</td></tr><tr><td>R-21</td><td><span class='sev s2'>一般</span></td><td>实时</td><td class='loc'>device/CANFD.cpp:1134、:1215、:1374、:1387、:1414;device/usart0.cpp:76-95;app_spi_cmd_motor_ota.cpp:65;motor_ota_relay.cpp:1135</td><td>printf/LOG 在 EXTI、PDI、CAN TX ISR 中执行;UART 缓冲满时按字节自旋</td><td>单行日志数百 µs 的 ISR 阻塞;ISR 与主循环并发写环形缓冲索引竞争</td><td>ISR 内只写无锁日志环或计数器,由主循环输出;满则丢弃并计数</td></tr><tr><td>R-22</td><td><span class='sev s2'>一般</span></td><td>CAN</td><td class='loc'>device/CANFD.cpp:1904、:1965-1984</td><td>refreshFeedbackSnapshot 在临界区外拷贝 s_channelStats;copyIdSnapshotTable 用 BASEPRI 包住约 1.35KB 拷贝,而 RX ISR 为 preempt 0</td><td>快照撕裂(id 与 data 不配);同时白白压住 preempt≥1 中断</td><td>双缓冲/序号锁(seqlock)读取</td></tr><tr><td>R-23</td><td><span class='sev s2'>一般</span></td><td>CAN</td><td class='loc'>device/CANFD.cpp:1588-1591</td><td>内容校验 s_lastMcu/s_lastMot 仅按节点号索引,三通道共用</td><td>不同通道同号节点互相误判</td><td>按[通道][节点]索引</td></tr><tr><td>R-24</td><td><span class='sev s2'>一般</span></td><td>CAN</td><td class='loc'>device/CANFD.cpp:1861-1871、:1891、:1928</td><td>空通道自动静默条件为“无帧且有错”;正常通道断线 5s 后错误日志与 BUS_OFF 切换日志也被静默;静默状态未经 0xC1 上报</td><td>真实总线故障被掩盖</td><td>区分“从未有节点”与“曾在线后掉线”;静默状态上报</td></tr><tr><td>R-25</td><td><span class='sev s2'>一般</span></td><td>CAN</td><td class='loc'>device/CANFD.cpp:1294-1321、:1390-1411</td><td>setTxSilent 不清空该通道软件 TX 队列</td><td>静默期排队帧在 drain 时以 kErrSilent 无计数丢弃</td><td>进入静默时清队列并计数</td></tr><tr><td>R-26</td><td><span class='sev s2'>一般</span></td><td>故障保护</td><td class='loc'>sys/task_runner.cpp:545</td><td>全系统唯一喂狗点在主循环入口且无条件执行,无任务存活检查</td><td>调度定时器停摆、ESC 楔死、SPI 死链时仍持续喂狗</td><td>任务心跳位图全部到齐才喂狗</td></tr><tr><td>R-27</td><td><span class='sev s2'>一般</span></td><td>OTA</td><td class='loc'>sys/ota_boot_request.cpp:246-257;sys/config.h:338-345</td><td>复位进 BOOT 前无外设优雅关停(LAN9252 未降级、CAN STB 未置位、SPI/DMA 未停);FWDGT 仍为 DIV64/1874(约 3s)跨复位运行</td><td>复位瞬间 CAN 在途帧被腰斩;BOOT 等待窗口内若不喂狗将连续看门狗复位(BOOT 侧喂狗需跨仓确认)</td><td>实现 teardown;复位前延展 FWDGT 或确认 BOOT 周期喂狗</td></tr><tr><td>R-28</td><td><span class='sev s2'>一般</span></td><td>OTA</td><td class='loc'>device/spi/app_spi_cmd_boot_request.cpp:205-210</td><td>0xB0 总是回 ACK 0xB5,即使随后因电机 OTA 进行中被拒绝</td><td>上位机误以为已进入 BOOT</td><td>请求结果同步判定后再回 ACK/NACK</td></tr><tr><td>R-29</td><td><span class='sev s2'>一般</span></td><td>电机 OTA</td><td class='loc'>sys/motor_ota_relay.cpp:649、:681-692</td><td>processTransferAck 在任意 kTransfer 状态接受 next_pkt=0xFFFFFFFF,且不拒绝 next16&gt;s_packets_acked+1</td><td>电机异常/伪造 ACK 可使包号与数据失步</td><td>严格校验 ACK 范围与状态</td></tr><tr><td>R-30</td><td><span class='sev s2'>一般</span></td><td>EtherCAT</td><td class='loc'>comm/esc_lan9252.cpp:604-635、:654-659、:683-688</td><td>pramRead/pramWrite 超时直接 break,不 abort、不返回错误;csrRead/csrWrite 超时静默</td><td>PRAM 引擎残留 busy,下一事务未定义,数据部分写入</td><td>超时时写 ABORT 位并返回错误,计数上报</td></tr><tr><td>R-31</td><td><span class='sev s2'>一般</span></td><td>EtherCAT</td><td class='loc'>comm/ssc/inc/el9800hw.h:45-67;sys/task_runner.cpp:310、:330、:627</td><td>ENABLE_ESC_INT/ENABLE_AL_EVENT_INT 无条件重新使能 EXTI0/1,抹掉连续性测试或 maskEnter 的屏蔽;DISABLE_AL_EVENT_INT 不屏蔽 EXTI2</td><td>诊断/测试模式下中断被意外重开,HBI 访问冲突</td><td>改为引用计数式屏蔽 API</td></tr><tr><td>R-32</td><td><span class='sev s2'>一般</span></td><td>EtherCAT</td><td class='loc'>comm/esc_lan9252.cpp:366-383(nrstDrive 相关)</td><td>诊断 nrstDrive 释放复位后不重新初始化 IRQ_CFG/INT_EN、看门狗与 SSC</td><td>诊断后 PDI 通路失效,需整机重启</td><td>复位后走完整 ESC 重新初始化流程</td></tr><tr><td>R-33</td><td><span class='sev s2'>一般</span></td><td>EtherCAT</td><td class='loc'>comm/ssc/src/mailbox.c:243-280;comm/ssc/port/dc_app.c:794-813</td><td>SM0/SM1 地址或长度与编译期常量不符时只打 WARN;随后守卫(0)又用寄存器值覆盖缓存,与“几何常量化”设计矛盾</td><td>主站错误配置不会被拒绝,邮箱行为不确定</td><td>不匹配时以 INVALIDMBXCFGINPREOP 拒绝</td></tr><tr><td>R-34</td><td><span class='sev s2'>一般</span></td><td>EtherCAT</td><td class='loc'>comm/ssc/port/hw_gd32exmc.cpp:183-191</td><td>HW_SetLed 对奇地址 0x0139 做 2 字节 CSR 写;对 0x0138 的 2 字节写会覆盖 0x0139;写值 0x0001 未置 override 使能位</td><td>LED 指示错误或改写相邻寄存器</td><td>按字节写并设置正确位</td></tr><tr><td>R-35</td><td><span class='sev s2'>一般</span></td><td>构建</td><td class='loc'>GD32E508_DCU_APP/GD32E508_DCU_APP.cproject.yml</td><td>源文件名大小写与磁盘不符(adc.cpp/canfd.cpp/timer.cpp/timer.h vs ADC.cpp/CANFD.cpp/Timer.cpp/Timer.h)</td><td>只能在 Windows 大小写不敏感文件系统构建,CI/Linux 失败</td><td>统一文件名大小写</td></tr><tr><td>R-36</td><td><span class='sev s2'>一般</span></td><td>构建</td><td class='loc'>cproject.yml(-IG:/Robot_v2/DCU/spi_link 与 ../../spi_link);_syntax_check.bat</td><td>共享协议 spi_link_v2 位于仓库外并以绝对路径引用</td><td>仓库无法独立复现构建;帧格式/CRC 无法随本仓审查</td><td>作为 git submodule 或 vendored 目录纳入</td></tr><tr><td>R-37</td><td><span class='sev s2'>一般</span></td><td>构建</td><td class='loc'>cproject.yml</td><td>未启用 -Wall/-Wextra/-Werror(仅 -Wno-register)</td><td>潜在缺陷不被编译器暴露</td><td>逐步开启告警并清零</td></tr><tr><td>R-38</td><td><span class='sev s2'>一般</span></td><td>仓库卫生</td><td class='loc'>.gitignore;comm/ssc/port/__pycache__/*.pyc;_syntaxcheck_stubs/;.trae/</td><td>.trae/ 被忽略但 10 个文件已跟踪;*.html 全局忽略(docs 下 HTML 本地需 -f);pyc、本机路径脚本进仓</td><td>仓库噪声与规则失真</td><td>清理跟踪文件,*.html 规则收窄到根目录产物</td></tr><tr><td>R-39</td><td><span class='sev s2'>一般</span></td><td>构建</td><td class='loc'>comm/ssc/port/ecat_def.h:620-655;DCU_ESI.xml;sys/config.h:177-180</td><td>APP 版本 1.7.4 单一定义良好,但 ESI RevisionNo/DEVICE_SW_VERSION 未随版本更新;ota_layout.h 在 APP/BOOT 各存一份</td><td>主站无法依 ESI 区分固件版本;布局副本漂移风险</td><td>构建时生成 ESI 版本;ota_layout.h 用子模块共享</td></tr><tr><td>R-40</td><td><span class='sev s2'>一般</span></td><td>文档</td><td class='loc'>README.md</td><td>路径 /workzone/…、temp_sensor 模块、整套 C 风格 API(app_init、task_runner_run_pending 等)均已不存在;未覆盖 comm/、OTA、AES、看门狗</td><td>新成员被误导</td><td>按当前 C++ 静态类结构重写</td></tr><tr><td>R-41</td><td><span class='sev s2'>一般</span></td><td>质量</td><td class='loc'>(仓库整体)</td><td>无 tests/、无 CI 工作流;提交 cfe7725 一次混合约 10 个主题并夹带 “-” 文件</td><td>回归无法自动发现;历史难以二分定位</td><td>建立主机侧单元测试(协议解析、OTA 状态机、配置页)与 Gitea Actions 构建</td></tr><tr><td>R-42</td><td><span class='sev s3'>建议</span></td><td>SPI</td><td class='loc'>device/spi/app_spi_command.cpp:764-812</td><td>err_crc_mismatch 等分原因计数器仅在 CONFIG_SPI_APP_LOG_ENABLE(当前 0)时自增</td><td>0xA4 诊断恒报 0</td><td>计数与日志开关解耦</td></tr><tr><td>R-43</td><td><span class='sev s3'>建议</span></td><td>实时</td><td class='loc'>device/CANFD.cpp:599、:601</td><td>CAN 中断设置了不同子优先级,在 PRE4_SUB0 分组下无效</td><td>误导读者</td><td>去掉子优先级参数并注释说明</td></tr><tr><td>R-44</td><td><span class='sev s3'>建议</span></td><td>故障保护</td><td class='loc'>sys/hardfault.cpp:15-20、:94-137</td><td>rawPutc 等待 TBE 无超时;未使能 MemManage/BusFault/UsageFault;不保存崩溃现场</td><td>故障难以事后定位</td><td>使能细分故障,保存寄存器快照到 noinit RAM 后复位</td></tr><tr><td>R-45</td><td><span class='sev s3'>建议</span></td><td>质量</td><td class='loc'>device/canfd.h:36、42-43、65-67;CANFD.cpp:386、501、1132-1136</td><td>注释与实现不一致多处(kErrBusOff 已死码、时钟打印仍写 2Mbps 等);遗留“幽灵帧 0x100”调试打印在热路径</td><td>可维护性下降</td><td>清理注释与调试打印</td></tr><tr><td>R-46</td><td><span class='sev s3'>建议</span></td><td>EtherCAT</td><td class='loc'>DCU_ESI.xml;gen_eeprom.py;ethercat_pdo_size.h 注释</td><td>Vendor ID 为 0x00000000;gen_eeprom docstring 过时;注释称 SM3 止于 0x1F67,实际 0x1F87(PDO 大小 384/600B 在 ESI/EEPROM/代码间一致)</td><td>量产与 ETG 一致性认证受阻</td><td>申请 ETG Vendor ID 并同步注释</td></tr><tr><td>R-47</td><td><span class='sev s3'>建议</span></td><td>实时</td><td class='loc'>sys/critical.h:69-75</td><td>新增 RT_CRITICAL_PREEMPT0_SAVE 仅在 motor_ota_relay.cpp:1099 使用一次</td><td>与 preempt 0 共享数据的其余路径仍用 BASEPRI</td><td>梳理所有与 preempt 0 ISR 共享的数据并统一临界区策略</td></tr></table>
<h2>4. 既有评审结论跟踪</h2>
<h3>4.1 docs/audit_ecat_spi_wifi_ota_compatibility.md(2026-09-18,v2.0)</h3>
<p>修订跟踪已另存为 <code>docs/audit_ecat_spi_wifi_ota_compatibility.rev2026-10-19.md</code>,原文件保留并在文首加注指向本次评审。</p>
<table><tr><th>原编号</th><th>原结论</th><th>当前状态</th><th>证据</th><th>本次对应</th></tr><tr><td>VULN-01</td><td>SPI CS ISR 与 SYNC0 同处 preempt 0 且同步执行耗时命令</td><td><span class='st bad'>仍存在</span></td><td class='loc'>app_spi_command.cpp:591、:1317;承诺的“&lt;2µs 解耦”未实施</td><td>R-02</td></tr><tr><td>VULN-02</td><td>Preempt 0 阻塞导致 CAN FIFO0 溢出</td><td><span class='st part'>部分修复</span></td><td class='loc'>CAN RX 提升为 preempt 0(config.h:373),FIFO 改为丢新不覆旧(CANFD.cpp:448);阻塞源 VULN-01 仍在</td><td>R-02</td></tr><tr><td>VULN-03</td><td>PDI_Isr 显式屏蔽 SYNC0</td><td><span class='st bad'>仍存在</span></td><td class='loc'>ethercat_sync.cpp:142-147、:161-166(原文引用行号已漂移)</td><td>R-16</td></tr><tr><td>VULN-04</td><td>DMA 在 processCommand 之后才重装</td><td><span class='st bad'>仍存在</span></td><td class='loc'>app_spi_command.cpp:1317→1320</td><td>R-17</td></tr><tr><td>VULN-05</td><td>按铃事件被查询帧抹除</td><td><span class='st bad'>仍存在(影响面缩小)</span></td><td class='loc'>app_spi_command.cpp:1245-1250;m_savedEventBuf 不存在</td><td>R-18</td></tr><tr><td>VULN-06</td><td>m_logBuf 被 preempt 0 撕裂</td><td><span class='st na'>原结论不再适用</span></td><td class='loc'>日志改为单生产者环形缓冲,且 CONFIG_SPI_APP_LOG_ENABLE=0</td><td>—</td></tr><tr><td>VULN-07</td><td>Flash 擦写未关中断致 BusFault 死锁</td><td><span class='st bad'>仍存在,但机理描述有误</span></td><td class='loc'>仍用库函数擦写、仅关 SPI0/EXTI10_15(ota_boot_request.cpp:62-64、228-229);单 Bank 擦除时取指为停顿而非 BusFault,实际后果为 20~30ms 中断阻塞</td><td>R-07</td></tr><tr><td>VULN-08</td><td>CAN 0x320 路径同步 requestEnterBoot</td><td><span class='st bad'>仍存在</span></td><td class='loc'>ota_boot_request.cpp:199-213</td><td>R-07</td></tr><tr><td>VULN-09</td><td>复位前无外设优雅关停</td><td><span class='st bad'>仍存在</span></td><td class='loc'>ota_boot_request.cpp:246-257</td><td>R-27</td></tr><tr><td>VULN-10</td><td>FWDGT 未延展,BOOT 10s 窗口崩溃锁死</td><td><span class='st bad'>仍存在(BOOT 侧喂狗需跨仓确认)</span></td><td class='loc'>文件内无 fwdgt_*;config.h:338-345 为 DIV64/1874</td><td>R-27</td></tr><tr><td>VULN-11</td><td>word count=8 抹除 word8/9;clearCrashCount 栈越界</td><td><span class='st na'>前半仍存在;“栈越界”原结论不成立</span></td><td class='loc'>config.h:359=8;clearCrashCount 走 writeWord 路径(ota_boot_request.cpp:52-53、133),不触达 words[] 下标——与 ecat-dcu-flash-crypto-audit 结论一致</td><td>R-05</td></tr><tr><td>VULN-12</td><td>EtherCAT 桥未检查 OTA 通道锁</td><td><span class='st ok'>已修复</span></td><td class='loc'>ethercat_can_bridge.cpp:112、:332</td><td>—</td></tr><tr><td>VULN-13</td><td>SPI 桥/突发未检查 OTA 通道锁</td><td><span class='st ok'>已修复(新发现 CAN0 桥接模式旁路)</span></td><td class='loc'>app_spi_cmd_can_bridge.cpp:83;app_spi_cmd_can_burst.cpp:85;旁路见 R-11</td><td>R-11</td></tr><tr><td>VULN-14</td><td>start() 加锁前存在漏包窗口</td><td><span class='st part'>部分修复</span></td><td class='loc'>motor_ota_relay.cpp:1098-1108 入口 PRIMASK 加锁;残留 cancel 竞态</td><td>R-08</td></tr><tr><td>VULN-15</td><td>ACK 处理跨圈滞后(调度反序)</td><td><span class='st ok'>已修复</span></td><td class='loc'>task_runner.cpp:550-553 在 relay process 之前先 hasRxFrame→taskCanRxParse</td><td>—</td></tr><tr><td>VULN-16</td><td>RX 软件队列深度 16 过小</td><td><span class='st ok'>已修复</span></td><td class='loc'>canfd.h:277 深度改为 64</td><td>—</td></tr><tr><td>审计总评</td><td>自评“98.6 分 / PRODUCTION READY / 全部闭环”</td><td><span class='st na'>原结论不成立</span></td><td class='loc'>第 6 章补丁套件大部分未合入;引用的 tests/test_adversarial_*.py 在仓库中不存在</td><td>—</td></tr></table>
<h3>4.2 compose/spec、.trae 与 README</h3>
<p>汇总于 <code>docs/compose/spec/REVIEW_STATUS_2026-10-19.md</code>。</p>
<table><tr><th>记录</th><th>原结论/内容</th><th>当前状态</th></tr><tr><td class='loc'>docs/compose/spec/ecat-dcu-flash-crypto-audit.md</td><td>DCU 刷写无载荷加密;word count=8;CAN 路径同步;无 teardown/fwdgt;BOOT 侧不可验证</td><td>仍成立;补充:BOOT 侧经跨仓核实 arm 门可被 0x2502 无鉴权打开(R-01)</td></tr><tr><td class='loc'>docs/compose/spec/lan9252-perf-audit.md</td><td>P1 差分写 ESC、P2 csrRead verify 分级</td><td>已实现:config.h:134 SPARSE=1、:143 DIFF=1;esc_lan9252.h:84 csrRead(verify)。上机对拍仍待做;front-matter updated 日期与提交时间不符</td></tr><tr><td class='loc'>docs/compose/spec/ecat-wkc-periodic-dip.md</td><td>主因 B:输出侧每 256 拍强制全量读</td><td>机制仍存在(ethercat_pdo.cpp:114-134),未见修复提交;仍待现场 A/B 对拍</td></tr><tr><td class='loc'>docs/compose/spec/tiered-pdo-mapping.md</td><td>status: designed,按电机数分档收缩 SM3</td><td>未实现:全仓无 tier 相关代码,SM3 仍为 600B</td></tr><tr><td class='loc'>docs/compose/spec/warm-jump-ecat-probe.md</td><td>起板探针改为 BYTE_TEST 放行</td><td>已实现并保留(esc_lan9252.cpp 使用 LAN9252_BYTE_TEST_VALUE)</td></tr><tr><td class='loc'>.trae/specs/fix-ethercat-realtime-comm-risks</td><td>s_periodicSeq、safe* 接口、PDO 三缓冲</td><td>被后续 SSC 集成取代:periodicIsr/三缓冲桥已删除,PDO 由 SSC 3-buffer SM 完成</td></tr><tr><td class='loc'>.trae/specs/fix-ecat-postaudit-risks</td><td>H1 safe* 收窄;M1 SYNC0 RAM 化;M2 NVIC 分组前移;M3 删 readBlock</td><td>H1/M3 已落地(safe*、readBlock 均已不存在);M1 已落地(ethercat_sync.cpp:112、:188 .ramfunc);M2 已落地(initial.cpp:222)</td></tr><tr><td class='loc'>.trae/specs/fix-ecat-pdo-and-runtime-issues</td><td>S1 PDI 偏移分离、S2 process 桥、S3 AL 守卫、M4-M7</td><td>S1/S2 被 SSC 架构取代;S3 部分保留(ethercat_slave.h:32 getAlState);M4-M7 随旧路径删除</td></tr><tr><td class='loc'>.trae/documents/cleanup-residual-low-risk-optimizations.md</td><td>13 项低风险清理</td><td>抽查均已落地:canfd_diag_t、m_ringTxLen、s_pushCount 已删除,sys/hardfault.h 已删除,spinDelay/armRingPending 已提取,ADC 匿名 namespace 已合并</td></tr><tr><td class='loc'>README.md</td><td>工程结构说明</td><td>大面积过时(见 R-40)</td></tr></table>
<h2>5. 优先级修复路线图</h2>
<table><tr><th>阶段</th><th>时限建议</th><th>事项</th><th>对应问题</th></tr>
<tr><td>P0 止血</td><td>1 周内</td><td>轮换电机 OTA 密钥并移出版本库;配置页 word 数改为 10;0x2502/CAN 0x320 加状态门控与临时口令;StopOutput 清空 CAN 队列并下发停机;PD 看门狗回到 100ms 量级</td><td>R-01 R-05 R-07 R-04 R-10 R-13 R-15</td></tr>
<tr><td>P1 实时性重构</td><td>2~4 周</td><td>SPI 命令处理移出 preempt 0 ISR(CS ISR 只快照+重装 DMA);统一 CAN TX 并发模型(含 CAN0 桥接);ISR 内禁 printf;0xC1 大缓冲静态化、统一栈定义并加栈守护</td><td>R-02 R-03 R-11 R-12 R-16~R-22</td></tr>
<tr><td>P2 OTA 可靠性</td><td>1~2 月</td><td>镜像签名验签(BOOT 协同);配置页 A/B 原子提交;电机 OTA cancel 改主循环单上下文;AES-CTR/GCM+TRNG nonce;复位前外设 teardown 与看门狗策略</td><td>R-01 R-06 R-08 R-09 R-10 R-27~R-29</td></tr>
<tr><td>P3 EtherCAT 规范性</td><td>1~2 月</td><td>Free-Run/DC 丢失处理按规范降级;PRAM 超时 abort;中断屏蔽改引用计数;邮箱几何不符即拒绝;Vendor ID/ESI 版本</td><td>R-14 R-30~R-34 R-46</td></tr>
<tr><td>P4 工程化</td><td>持续</td><td>文件名大小写、spi_link 纳入子模块、开启 -Wall、.gitignore 清理、README 重写、主机侧单元测试 + Gitea Actions CI、提交按主题拆分</td><td>R-35~R-41</td></tr></table>
<h2>6. 总结评分</h2>
<table><tr><th>维度</th><th>权重</th><th>得分</th><th>理由</th></tr>
<tr><td>实时性与中断设计</td><td>20%</td><td>45</td><td>SPI 命令仍在 preempt 0 同步执行;临界区策略与中断级别不匹配</td></tr>
<tr><td>通信协议(EtherCAT/SPI/CAN)</td><td>20%</td><td>60</td><td>PDO 尺寸三方一致、差分写/RAM 化扎实;CAN TX 并发与 SSC 边界处理有缺口</td></tr>
<tr><td>安全与故障保护</td><td>20%</td><td>30</td><td>刷写无鉴权、密钥硬编码泄露、通信丢失无安全输出态</td></tr>
<tr><td>OTA/Bootloader 可靠性</td><td>15%</td><td>40</td><td>通道锁已加,但配置页非原子、word 数错配、cancel 竞态</td></tr>
<tr><td>代码质量与可维护性</td><td>15%</td><td>60</td><td>注释详实、无 TODO/#if 0 残留、清理计划已落实;大文件与注释漂移、无测试</td></tr>
<tr><td>构建与仓库卫生</td><td>10%</td><td>45</td><td>“-” 产物与密钥进仓、外部绝对路径依赖、大小写不一致、无告警</td></tr>
<tr><th>综合</th><th>100%</th><th>48</th><th>功能完整、调试可观测性强,但距可量产尚有 P0/P1 级工作</th></tr></table>
<h3>亮点</h3><ul>
<li>EtherCAT 关键路径(SYNC0 入口、HBI 访问)已 RAM 化并改直接寄存器操作,抖动意识强。</li>
<li>ESI、EEPROM 生成脚本与代码常量的 PDO 布局(输出 384B/输入 600B,SM2 0x1400、SM3 0x1880 三缓冲)完全一致。</li>
<li>VULN-12/13/15/16 已修复:EtherCAT/SPI 桥均检查 OTA 通道锁,ACK 当拍消费,RX 队列 64 深。</li>
<li>APP 版本号单一来源并下发至 CoE 0x100A 与 SPI ping;诊断计数与 DWT 剖分完善。</li>
<li>.trae 清理计划 13 项已落实,代码中无 TODO/FIXME 与 #if 0 残留。</li></ul>
<p style="color:#868e96;font-size:12px">本报告由多专家视角静态评审生成;所有行号基于 d8b3c97。代码变化后请以最新代码复核。</p>
</main></body></html>