跳到正文
Skyseek的数字工坊
返回

从 USB-Blaster DLL 逆向到 FTDI JTAG 插件:xtftdi 移植与实测记录

这次工作起于一个问题:能不能分析 jtag_hw_usb-blaster.dll,看看它到底支持哪些设备?随后又追到了 USB Blaster III 的实现位置,并将其中适用于普通 FTDI 四线 JTAG 的行为重构进现有 mbftdi 插件,最后整理成 xtftdi 项目。

工作主要完成于 2026 年 11 月 1 日。这篇文章记录排查依据、实现改动和验证结果。当前交付的是 Windows x64 四线 JTAG 预发布版,不是官方 USB Blaster III 全功能等价实现。

先分清下载器支持和 FPGA 器件支持

分析的 jtag_hw_usb-blaster.dll 为 AMD64 文件,版本资源标注为 Quartus Prime Standard 25.1.0.1129。

最初使用 Python、pefile 和 Capstone 检查 PE 结构、导出函数、字符串及代码引用,没有执行原始 DLL。它唯一的导出是 get_supported_hardware,实际只返回三类接口描述:

索引接口类型
0USB-Blaster
1Cubic Cyclonium
2Nios II Evaluation Board

内部还存在 12 条设备记录,包括重复名称、带 Altera 前缀的名称,以及 Cyclone III、Stratix III 开发板标签。识别路径先比较三个基本设备名称,未命中时再通过 FT_* 接口查询 USB ID,并与表中常量匹配。

这里容易误读:表中的 EP3C25、EP3SL150 等名称是开发板板载 USB-JTAG 接口的标签,不能据此得到“这个 DLL 支持哪些 FPGA 芯片”的完整清单。目标器件支持还涉及上层软件、器件数据库和配置算法。

表中还有一处值得保留原样的异常:Altera Cubic Cyclonium 对应常量是 0x009FB602,并非另一条 Cubic Cyclonium 记录的 0x09FB6002。疑似遗留笔误,但仅凭二进制无法确认原因,因此分析记录没有擅自更正。

usb-blaster-iii.zip 只提供了哪一层?

随后检查了 usb-blaster-iii.zip。这个包包含 INF、CAT 签名目录,以及 x32/x64 的 WDF、WinUSB 安装辅助 DLL,没有 jtag_hw_*.dll 插件。

INF 的关键作用是将指定 USB 设备绑定到 Windows 的 WinUSB.sys,并注册软件可枚举的设备接口 GUID:

{09FB6020-0000-0000-0000-00B0D03D3169}

设备匹配项使用 VID 09FB 和若干 602x PID,多数还限定了复合设备的 MI_00 接口。包中的辅助 DLL 导出的是安装相关函数。

所以,这个 ZIP 解决的是设备绑定与 USB 访问入口。Quartus 如何打开接口、组织 JTAG 命令、处理返回数据,还需要用户态实现。只安装这个驱动包,不能单凭驱动目录的存在就认定某套 Quartus 已具备完整的 Blaster III 支持。

找不到 blaster3 DLL,不代表没有实现

最初把独立文件名 jtag_hw_usb-blaster3.dll 当成了寻找实现的线索。实际检查提供的 Standard 安装目录后,发现 quartus/bin64 中确实没有这个文件。

继续分析 jtagserver.exe,才找到真正的实现位置:

这套安装文件的 version.txt 标注为 25.1std.0.1129。因此,最终结论应限定为这套实际检查过的文件:Blaster III 相关逻辑编在 JTAG Server 内,独立 blaster3 DLL 的缺失不能证明它不支持。

为了核对调用关系,后续使用 Ghidra 导出相关函数,并结合 RTTI、虚函数表和反汇编交叉检查。这里有一个实用经验:对包含嵌入数据的代码节做一次线性反汇编,可能中途停止。按 PE 异常表中的函数范围分别解码,更适合做后续函数覆盖检查。

与本次移植直接相关的参考位置包括:

参考 RVA作用
0x30096FTDI 接口枚举路径
0x26C60MPSSE TMS/数据命令选择与合并
0x34490JTAG 时钟分频设置
0x347C0数值参数设置
0x1B000数值参数读取

这些地址仅用于定位参考版本中的实现。新插件不加载参考 EXE,也不依赖这些绝对地址。

移植到现有 mbftdi 插件

现有项目源自 Marsohod MBFTDI JTAG programmer,已经能够通过 D2XX 访问普通 FT2232/FT4232 设备。

这次指定的目标是 FT4232H,只接 TCK、TDI、TDO、TMS;后续硬件测试改用本机连接的 Digilent FT2232H。因此选择保留现有 D2XX 传输层,对照内置实现重构适用的四线 JTAG 行为,而没有将 WinUSB 机器码直接搬进 DLL。

两条路径可以分别理解为:

所分析的内置路径:
Quartus → JTAG Server 内置 FTDI-Blaster → WinUSB → 下载器

xtftdi 路径:
Quartus → JTAG Server 插件接口 → xtftdi DLL → D2XX → FTDI MPSSE

原项目保留的设备标识为 0403:6010、0403:6011。这次没有把它改成官方 09FB:602x WinUSB 设备,也没有修改下载器 EEPROM。

MPSSE 编码与传输

新增的 blaster3_protocol.h 实现了参考行为中的 TMS/数据命令选择:在保持 TDI 不变的 TMS 命令与保持 TMS 的数据命令之间选择,并合并连续整字节传输。

移植过程中保留了之前已经修复的几项关键行为:

尤其是属性边界问题:一次 write_masked 回调可以同时包含忽略 TDO 的头部和需要采样的数据。不能只看回调起点的属性,就将整段都当成不需要返回数据。

接入 Quartus 参数接口

时钟功能还必须接入宿主真正调用的插件槽位。通过分析 JTAG_SERVER_EXTERNAL 的转发函数,确认了 x64 描述结构中的接口位置:

描述结构偏移用途
0x58数值参数设置
0x60数值参数读取
0xA8数据/字符串参数设置
0xB0数据/字符串参数读取

插件接入了 JtagClock、JtagClockAutoAdjust、固定频率控制,以及按长度返回的 SerialNumber。

实际集成测试发现了一个软件模型最初没有覆盖的问题:jtagconfig --setparam 返回成功,但下一条命令重新打开接口后,时钟恢复默认值。内置对象和插件会话的生命周期不同,参数不能只存在一次打开产生的对象里。

修复后,参数按插件端口保留在 DLL 的内存状态中,跨接口关闭、重开仍然有效;服务退出后则重新读取配置文件。这项行为又单独加入了回归测试。USB 重排与热插拔时的端口身份行为尚未完成验证。

自动校准

新增的 blaster3_calibration.h 采用低速基准、链延迟与图样比较的方式进行时钟资格检查:

  1. 在低速下重复检查 IR 长度与捕获结果。
  2. 检查 BYPASS 延迟。
  3. 通过 1024 位图样验证移位返回结果。
  4. 检查目标频率,失败时沿普通频率档位向下降低。

校准期间产生的采样数据只供内部判断使用,不交给 Quartus 的业务 TDO 回调。

这是参考行为的可移植重构,不是原始训练算法所有分支的逐项复制;原实现的另一组激进频率搜索分支没有移植。校准会改变 TAP 状态,显式校准应在操作之间执行,并随后重新扫描链路。

JTAG 时钟怎么配置

在实际运行的 jtagserver.exe 同目录放置 mbftdi.cfg:

channel=0
frequency=15000000
auto_adjust=1

frequency 单位是 Hz。新版默认请求 15 MHz,auto_adjust=1 允许检查后降频;需要固定时钟时设为 0。修改文件后重启对应的 JTAG Server。

运行期间可以设置第 1 个下载器:

jtagconfig --setparam 1 JtagClockAutoAdjust 0
jtagconfig --setparam 1 JtagClock 10M
jtagconfig --getparam 1 JtagClock

返回的是整数分频后的实际频率。例如使用 60 MHz 源、按 30 MHz / (divisor + 1) 分频时,请求 12 MHz 会得到 10 MHz,而不是任意连续可调的 12 MHz。

多版本 Quartus 共存时,要先确定当前运行的是哪个 JTAG Server。把 DLL 或配置文件放进另一套安装目录,不会自动改变当前服务的行为。

验证结果:软件模型与实板分开记录

软件回归使用独立 MPSSE 解释器和 TAP 模型,包括 120 组随机混合任务、百万时钟传输、TDO 属性边界、回调顺序、部分读写和故障处理。

新增测试还覆盖两个模拟器件的 IR/BYPASS 链,以及注入与频率有关的错误后,选择 15 MHz、10 MHz 或 100 kHz 的情况。软件测试通过只说明对应模型中的行为正确。

实板使用 Digilent FT2232H 通道 A,得到以下结果:

请求 TCK连续读链次数IDCODE
1 MHz302B220DD,一致
5 MHz302B220DD,一致
10 MHz302B220DD,一致
15 MHz302B220DD,一致

自动校准测得 IR=10、BYPASS=1,并通过了 15 MHz 的移位检查。

随后分别运行隔离的 Quartus 18.1 和 Standard 25.1.0.1129 JTAG Server,加载同一个新版 DLL。两者的 jtagconfig --debug 均读到:

Captured DR after reset = (02B220DD) [32]
Captured IR after reset = (155) [10]
Captured Bypass after reset = (0) [1]
Captured Bypass chain = (0) [1]

将 JtagClock 改为 10M、关闭自动校准后,后续独立命令仍读回 10M,链扫描也报告 10 MHz。

这些测试使用临时服务目录、私有配置文件和独立端口,测试后退出服务,没有替换已安装的 Quartus 插件,也没有下载 FPGA 配置或写入 Flash、EEPROM。

本次发布 DLL 的 SHA256 为:

889f4d95b87046f12a5a3684d78274c9e91cd0d5edb761cdc3cb05d58823dd31

测试记录按这个哈希关联。文件名相同,不代表可以套用另一个版本的验证结果。

交付与仍未完成的部分

项目已整理到 mediskyseek/xtftdi,首个版本为 v3.0.0-preview.1。仓库目前是私有的,查看源码或下载 Release 需要相应权限。

仓库上传了 150 个源码和文档文件,排除了历史预编译库、目标文件及本地构建缓存。DLL 放在 Release 中,同时提供 ZIP 和 SHA256 校验值。上游许可证及来源说明均保留;干净源码快照已重新构建并通过软件回归测试。

Windows x64 构建入口是:

python build_x64.py

它使用 VS2022 C++ Build Tools v143 和 Windows SDK,输出到 dist/blaster3_x64/。构建脚本本身不安装插件、不访问硬件。

当前仍有明确边界:

这次得到的是一个已经通过软件回归和实板读链测试、可以由两代 Quartus JTAG Server 调用的四线 FTDI JTAG 插件预发布版。后续验证应继续围绕具体操作和具体 DLL 哈希展开,不能将读链成功扩大解释为全部功能或长期稳定性已经成立。


分享这篇文章:

上一篇
测试GUI发布工具