离线烧录前先审查 FLM:DAPLINK 文件边界实战
很多离线烧录器的工作流看起来很直白:从 TF 卡找到 .FLM,把算法搬进目标 RAM,再按页擦除、写入、读回校验。真正难排查的故障却往往发生在第一步——文件已经截断、符号表不完整,解析器仍然留下上一次加载的入口地址,后面才以“烧录失败”的形式暴露出来。
这次选取立创 DAPLINK 调试工具,重点不放在重复介绍 SWD/JTAG,而是把公开工程里的 FLM 装载边界单独拎出来,做成一个可以在主机上运行的结构检查器。它只回答一个问题:这个文件是否满足预先声明的 ELF/FLM 结构规则?通过后仍要检查目标芯片、页大小、算法 ABI 和实体烧录结果。

图 1|原项目公开的成品与屏幕照片。它说明产品形态和接口用途,不能代替本文新增检查器的运行结果。
先把原工程的装载链路画清楚
公开源码中,swd_download_update_flash_algo() 先调用 parse_flm_from_file()。解析成功后,才把五个函数入口、算法代码地址、代码长度和页缓冲区大小写入 program_target_t;swd_download_from_file() 随后执行初始化、擦除、分页写入和读回比较。

图 2|本文实现的边界。检查器停在“结构结果”这一层,不调用 SWD,也不把 ELF 内容当成已经可信的机器码。
原来的 flmparse.c 有几个很具体的风险:ELF 头、程序头和节表的读取返回值没有逐项检查;多个表被一次性读入固定 2048 字节缓冲区;字符串用子串搜索,UnInit 这类名字存在误匹配可能;五个入口不要求全部找到;失败时 Addr[] 可能保留前一次文件的值。还有一个容易被忽视的容量问题:RAM[2560] 一共 10240 字节,但算法从 &RAM[8] 开始写,前 32 字节已经被 halt 前导程序占用,真正可用容量只有 10208 字节。

图 3|容量是从源码中的数组和写入起点推导出的预算,不是示波器或实机测量值。p_memsz 也必须纳入检查,因为未初始化区同样会占用目标 RAM。
新检查器做了哪些收紧
我把核心写成不依赖 RT-Thread 的 C11 小模块,输入是一段只读文件快照,输出是结构化结果。支持范围主动收窄为:ELF32、小端、ARM、ET_EXEC,且只有一个位于虚拟地址 0 的可执行 PT_LOAD;符号表必须有对应字符串表,不能出现非空重定位节。程序头、节表、符号表、字符串表和整个文件都有上限,所有范围判断使用“长度不超过剩余空间”的写法,避免偏移加长度溢出。
五个入口严格按完整的 NUL 结尾名字匹配:Init、UnInit、EraseChip、EraseSector、ProgramPage。每个入口都必须是已定义的全局或弱函数,带 Thumb 标志,且落在可执行节范围内。解析一开始先清零输出结构,只有五个入口全部通过后才一次性提交;因此“先加载有效文件、再加载坏文件”不会继续拿到旧地址。
下面是核心接口的使用方式,完整源码和测试脚本放在附件中:
struct flm_result result;
enum flm_status status = flm_inspect(
file_bytes, file_length,
2560u * 4u - 8u * 4u, /* 10208-byte code/data budget */
&result);
if (status != FLM_OK) {
printf("reject: %s\n", flm_status_name(status));
return -1;
}
/* STRUCTURE_OK is not permission to execute or flash. */
失败分为 HEADER、RANGE、LIMIT、LAYOUT、SYMBOL 和 REQUIRED 等类别。这样 UI 可以告诉操作者是文件损坏、表越界,还是缺少烧录入口,而不是只显示一个笼统的“解析失败”。
原理图与 PCB:先确认接口,再谈软件
本文没有拿项目介绍页里的缩略图当原理图,而是在对应的嘉立创 EDA V1.0 工程中重新导出 4× PNG。PinOut 页能直接看到 SWD、串口、PWM、PD 测量和 LCD/按键的信号分配;接口页则把 33 Ω 串联电阻、TVS 和各类调试口画在同一页。

图 4|EDA V1.0 PinOut 4× 导出后的局部。信号名用于核对代码和接口说明,截图本身不是本文新增硬件设计。

图 5|EDA V1.0 Interface 页局部,保留调试线、串联电阻和保护器件的对应关系。
PCB 分层图仍按 EDA 的顶视方向保存,底层没有擅自镜像,避免读者把观察方向误当成生产 Gerber 方向。

图 6|PCB 顶层顶视图。

图 7|PCB 底层顶视图;用于走线和器件区域核对,不替代制造文件。
让测试先于“兼容所有 FLM”
测试工程使用 Zig 0.16.0 编译主机 DLL、命令行工具和 Cortex-M4 裸机目标对象,再由 Python 3.12.13 驱动合成 ELF。合成文件中的指令只是占位字节,不能拿去烧录。

图 8|实际测试记录:26 个命名案例、496 个截断前缀和 5000 次固定种子变异检查全部按预期完成。变异中仍有 1121 个通过结构检查,恰好说明检查器不验证机器码行为。
测试覆盖了错误 ELF 魔数、错误机器类型、表格超出文件、程序段超过容量、无效字符串表链接、入口缺少 Thumb 位、入口越过代码节、重复入口、五个入口不齐,以及同一个输出对象连续经历“有效→无效”两次加载。最后一项专门验证旧地址不会残留。
STRUCTURE_OK
code_offset=128 code_bytes=64 memory_bytes=64
entry[0]=0x00000001
entry[1]=0x00000005
entry[2]=0x00000009
entry[3]=0x0000000D
entry[4]=0x00000011
Structural checks only. No algorithm executed; no target programmed.
这组输出来自合成样本,不是真实厂商算法的运行日志。当前没有把整套 RT-Thread 固件重新链接,也没有在 DAPLINK、STM32 或 GD32 实物上执行 FLM。公开仓库的真实 STM32F4xx_512.FLM 已确认存在并记录了下载地址,但本轮下载未完成,因此不把“兼容厂商 FLM”写成已验证结论。
附件怎么用
解压 flm-audit-1.0.0.zip,准备 Zig 0.16.0 和 Python 3 后运行 python build.py --zig <zig.exe>。命令行工具返回 0 只代表结构规则通过;返回 1 是拒绝,返回 2 是文件读取或参数错误。README.md 列出了支持范围、容量计算、验证限制和许可边界。

图 9|原项目公开装配照片,帮助读者把接口图和实际外壳对应起来;它不是本文改版后的焊接实测。
来源、许可与本次修改
- 项目:立创 DAPLINK 调试工具;原团队:立创开发板;项目页:https://oshwhub.com/li-chuang-kai-fa-ban/li-chuang-daplink-diao-shi-gong-ju
- 公开源码:https://gitee.com/lcsc/LCKFB-DAPLINK-DEBUG-TOOL
- 参考文件:
1_Code/GD32F407/applications/flmparse/flmparse.c、elf.h、applications/offline_download/swd_download_file.c;观察到的 master 提交为582bf4c0a805bdfdf0e4b0aee4292142400eeeff。 - 项目工程页声明 GPL-3.0;仓库根目录声明 Apache-2.0;
flmparse目录有单独 MIT 声明。照片和 EDA 截图作为原项目材料使用,保留来源与处理记录;附件中的flm-audit是重新实现的 MIT 工具,不重新标记上游固件、算法二进制或完整工程。 - 本文新增:有界 ELF 结构读取、精确函数名和入口范围检查、容量预算、失败清零与事务式结果提交;已完成主机测试和 Cortex-M4 对象编译,未完成厂商 FLM 兼容性、整机固件链接和实体烧录测试。
技术交流,欢迎关注微信公众号:美男子玩编程。



