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