现代数字吉他音箱内置复杂的 DSP 处理链,从音箱建模到箱体模拟,从混响到调制效果,这些功能都由固件中的算法实现。然而,厂商提供的功能往往无法满足所有使用场景 —— 比如无法单独关闭箱体模拟以连接真实吉他箱体,或者希望在插入耳机时仍然保持内部扬声器输出。当硬件本身具备能力但软件限制使用时,逆向工程成为解锁设备潜力的可行路径。
本文以 Yamaha THR10c 数字吉他音箱为例,系统介绍如何通过 JTAG 接口提取固件、分析 ARM7TDMI 架构代码、实施二进制补丁,并最终通过标准 MIDI 协议分发更新。
硬件接口识别与 JTAG 连接
逆向工程的第一步是找到与设备通信的物理接口。通过查阅服务手册的电路原理图,可以在主 PCB 上发现两个关键接口:UART 串口(CB4,2mm 间距)和 JTAG 调试口(CB3,1mm 间距 FFC 连接器)。UART 接口在启动时无输出,可能是未启用或需要特定触发条件。相比之下,JTAG 接口提供了更底层的硬件调试能力。
JTAG(Joint Test Action Group)是一种用于芯片测试和调试的串行接口,包含 TCK(时钟)、TMS(状态机控制)、TDI(数据输入)、TDO(数据输出)四个核心信号,以及可选的 TRST(复位)。对于 THR10c,其主控芯片 SSP2 的 JTAG ID 为 0x4F1F0F0F,与 NXP LPC 2xxx 系列一致,推测基于 ARM7TDMI-S 核心。
硬件连接使用 FTDI FT2232H Mini Module 作为 JTAG 适配器,该芯片提供双通道多功能接口,可同时支持 JTAG 和 UART。关键接线配置如下:AD0 接 TCK,AD1 接 TDI,AD2 接 TDO,AD3 接 TMS,AD4 接 TRST,AD5 接 SRST(系统复位)。注意 TRST 为低电平有效,需要通过 GPIO 拉高以启用 TAP 控制器。
OpenOCD 配置与内存转储
OpenOCD(Open On-Chip Debugger)是连接 JTAG 硬件与调试工具的关键软件。针对 FT2232H Mini Module 的自定义配置需设置 TRST 和 SRST 信号引脚:
adapter driver ftdi
ftdi device_desc "FT2232H MiniModule"
ftdi vid_pid 0x0403 0x6010
ftdi layout_init 0x0008 0x000b
ftdi layout_signal nTRST -data 0x0010 -oe 0x0010
ftdi layout_signal nSRST -data 0x0020 -oe 0x0020
目标配置文件需声明 TAP 控制器和处理器核心:
transport select jtag
jtag newtap ssp2 cpu -irlen 4 -expected-id 0x4F1F0F0F -ircapture 0x1 -irmask 0xF
target create ssp2.cpu arm7tdmi -chain-position ssp2.cpu -endian big
adapter speed 2000
reset_config trst_and_srst
注意 SSP2 使用大端模式(big-endian),这与 ARM7TDMI 默认的小端模式不同,需要在 GDB 中通过 set endian big 进行配置。
通过 GDB 连接到 OpenOCD 后(target extended-remote :3333),可以读取和写入内存、寄存器,甚至单步执行代码。为了分析固件,需要转储地址空间。THR10c 配备 2MiB Flash 芯片,从地址 0x2000000 开始。通过 GDB 命令 dump memory memory.bin 0 0x4000000 可以转储 64MiB 地址空间,耗时约 15 分钟。
固件结构与内存布局分析
将转储的内存加载到 Ghidra 进行分析,设置处理器为 ARM:BE:32:v4t(ARMv4 大端模式)。通过分析启动代码,可以识别出固件包含两个独立的程序镜像:
- DTAb(Bootloader):位于
0x2000000,大小约 64KiB,负责固件更新和系统初始化 - DTAm(Main Firmware):位于
0x2010000,大小约 1MiB,包含主要应用逻辑
DTAm 的启动流程包括:设置各 ARM 异常模式(IRQ、Abort、Undefined、FIQ、Supervisor)的栈指针,清零 RAM 区域(0x1000000 和 0x4000000 处的 SDRAM),然后复制数据段并跳转到主程序。
完整的内存布局如下:
| 地址 | 长度 | 用途 |
|---|---|---|
| 0x2000000 | 0x100 | Bootloader 第一阶段 |
| 0x2000100 | 0xFF00 | Bootloader (DTAb) |
| 0x2010000 | 0x100000 | 主固件 (DTAm) |
| 0x1008000 | 0x3AD0 | RAM0 数据段 |
| 0x100BAD0 | 0xA49C | RAM0 BSS 段 |
| 0x4000000 | 0x10100 | RAM1 数据段 |
| 0x4010100 | 0x118B4 | RAM1 BSS 段 |
ELF 重链接与补丁系统
为了在原始固件中注入自定义代码,需要将扁平的二进制固件转换为可重链接的 ELF 格式。首先通过汇编文件将固件分割为对应内存布局的段:
.macro fwdata addr:req,size:req
.incbin "thr10_ver104c_20120803.bin",\addr-0x2010000,\size
.endm
.text
fwdata 0x2010000,0x7645C
.section .data.ram1,"aw"
fwdata 0x208645C,0x10100
链接器脚本定义了各段的加载地址和运行地址,同时预留了 .text.patch、.data.patch、.bss.patch 等自定义段,从 0x20A0000 开始存放新代码。
补丁系统通过 .patch.text 段实现,每个补丁符号对应一个需要覆盖的原始代码位置。宏定义简化了补丁编写:
.macro patch addr:req,body:vararg
.section .patch.text,"xo",.text
.org \addr-0x2010000
patch\+:
.ifnb \body
\body
endpatch
.endif
.endm
补丁工具使用 libelf 库读取 .patch.* 段,将符号内容复制到对应原始段的位置,并调整重定位信息,最后移除补丁段。这使得可以通过简单的 Makefile 调用完成固件构建:make 即可生成包含自定义代码的完整固件镜像。
功能实现:箱体模拟旁路与强制扬声器输出
通过深入分析 Ghidra 反编译代码,可以理解固件的多线程架构:USB MIDI 处理、面板 I/O(LED、电位器、按键)分别运行在不同线程中,通过事件机制通信。DSP 核心独立于 ARM 主核运行,即使 ARM 核心被调试中断,吉他信号仍能通过 DSP 链处理。
内存映射 I/O 区域位于 0x7000000,通过对比按键按下前后的内存转储,可以定位按键状态寄存器(0x7001D60)和电位器 ADC 值(0x7001800)。类似方法也用于发现 UART 寄存器(0x7002000/0x7002001),波特率为 32000。
实现箱体模拟旁路功能需要拦截按键处理函数(0x2028CB8)和 DSP 命令发送函数。THR10c 使用组合按键触发特殊功能:长按 TAP 同时按下 PRESET1 切换箱体旁路,按下 PRESET2 切换强制扬声器模式。LED 指示灯复用调音器区域的左右箭头(旁路状态)和中心点(扬声器状态)进行状态显示。
DSP 命令通过 dsp_command 函数发送,支持多种命令类型:类型 0 用于编程 DSP 块(设置初始参数),类型 1 用于设置特定参数值。箱体模拟对应 DSP 块 2,参数 0 选择箱体类型(0=Flat,1=Brit Blues 2x12,以此类推)。在旁路模式下,需要拦截所有对块 2 的编程和参数设置命令,将箱体类型强制替换为 Flat。
分析还发现各箱体类型的增益补偿系数,Flat 模式的增益为 0dB,而其他模式有 -10dB 左右的衰减,这解释了为什么切换到 Flat 时音量会突然增大。通过将 Flat 模式的增益系数从 0 改为 -10,可以修正这一问题。
固件更新格式与分发
为了让其他用户无需硬件修改即可使用补丁,需要利用设备原有的固件更新机制。THR10c 支持通过 USB MIDI 接收 SysEx(System Exclusive)消息进行固件更新。
固件转储命令的 SysEx 格式为:F0 43 7D 50 44 54 41 31 52 4F 4D 52 02 F7,其中 43 是 Yamaha 的厂商 ID,7D 50 是设备类型和命令码,DTA1ROMR 是命令名,02 选择 Flash 区域。
固件更新数据通过 DTA1MAIN 消息传输,每条消息包含:前缀 F0 43 7D 40、剩余长度(7 位编码)、命令名、块编号、两个保留字节(固定 7F 7F)、以及编码后的固件数据。数据编码采用 MIDI 1.0 规范变体:每 7 个字节数据后跟第 8 个字节存储这 7 个字节的最高位。校验和为所有字节和的补码的低 7 位。
更新流程为:发送 DTA1ERASE 擦除固件区域,等待 16 秒,然后发送一系列 DTA1MAIN 消息传输固件数据(每条间隔 50ms),最后发送 DTA1CSUM 进行整体校验。
通过编写 bintomid 和 midtobin 工具,可以在原始二进制和 MIDI 文件格式之间转换。使用 Standard MIDI File 格式,设置 500 ticks/beat 和 120 BPM,使每个 tick 对应 1 毫秒,便于时间计算。
用户更新固件时,只需在开机时按住 TAP 按钮并按 5 次进入更新模式(LED 显示 "U"),然后通过 aplaymidi -p THR10 thr10.mid 播放 MIDI 文件即可完成更新。
工程实践要点
此类嵌入式固件逆向工程需要掌握的关键技能包括:
- 硬件调试接口:熟悉 JTAG 信号定义和 TAP 控制器状态机,能够阅读电路原理图识别调试端口
- 处理器架构:理解 ARM/Thumb 指令集切换机制(通过 BX 指令的最低位),掌握 ARM7TDMI 的异常模式和栈布局
- 固件分析工具:熟练使用 Ghidra 进行静态分析,编写脚本修复交叉引用(如处理 ARM/Thumb 切换辅助函数的调用关系)
- 链接与重定位:理解 ELF 格式、链接器脚本语法,能够将扁平二进制转换为可重链接格式
- 嵌入式通信协议:理解 MIDI SysEx 消息结构,掌握 7 位编码和校验和计算
风险方面,固件修改可能导致设备变砖(无法启动),因此在进行 Flash 写入前应确保能够通过 JTAG 恢复。建议保留原始固件备份,并在修改前验证补丁的正确性。
参考资料
- Michael Forney, "Patching my guitar amp's firmware", mforney.org, 2026-05-28. https://mforney.org/blog/2026-05-28-patching-my-guitar-amps-firmware.html
- THR10 固件补丁源码与工具: https://github.com/michaelforney/thr10
- wrongbaud, "JTAG Debugging", 硬件调试入门指南. https://wrongbaud.github.io/posts/jtag-hdd/
内容声明:本文无广告投放、无付费植入。
如有事实性问题,欢迎发送勘误至 i@hotdrydog.com。