2. 开发环境搭建:交叉编译工具链配置、QEMU虚拟化环境搭建、JTAG调试器连接与OpenOCD配置
做嵌入式底层开发,环境搭建往往是第一道坎。很多人觉得这步没什么技术含量,随便装个工具链就开始写代码。我刚开始做ATF移植时也这么想,结果编译出来的固件死活跑不起来,折腾了两天才发现是工具链版本不匹配。嗯,从那以后我再也不敢轻视环境搭建了。
这一章,我把开发环境拆成三个部分来讲:交叉编译工具链、QEMU虚拟化环境、JTAG+OpenOCD调试环境。每个部分我都会结合自己的踩坑经历,帮你把那些文档里不会写的细节补上。
2.1 交叉编译工具链配置
ATF和UEFI都是跑在ARM架构上的固件,我们的开发机通常是x86_64,所以必须用交叉编译工具链。说白了,就是在x86机器上编译出能在ARM处理器上执行的二进制文件。
2.1.1 工具链选择
我个人习惯用ARM官方提供的aarch64-none-elf-系列工具链。为什么不用Linaro的?其实都可以,但官方版本更新更及时,对最新ARM架构的支持也更好。
| 工具链类型 | 适用场景 | 我的推荐 |
|---|---|---|
| aarch64-none-elf- | 裸机开发(ATF、U-Boot) | ⭐⭐⭐⭐⭐ |
| aarch64-linux-gnu- | 带Linux内核的开发 | ⭐⭐⭐⭐ |
| arm-none-eabi- | 32位ARM裸机 | ⭐⭐⭐ |
2.1.2 安装与验证
安装其实很简单,解压后把bin目录加到PATH里就行。但我建议你做个软链接,方便以后切换版本:
# 下载并解压
wget https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x86_64-aarch64-none-elf.tar.xz
tar -xf arm-gnu-toolchain-13.2.rel1-x86_64-aarch64-none-elf.tar.xz -C /opt/
# 创建软链接
ln -sf /opt/arm-gnu-toolchain-13.2.rel1-x86_64-aarch64-none-elf /opt/aarch64-toolchain
# 配置环境变量
export PATH=/opt/aarch64-toolchain/bin:$PATH
# 验证安装
aarch64-none-elf-gcc --version
2.1.3 测试编译
装好之后,写个简单的启动代码测试一下:
// test_startup.c
void reset_handler(void) {
// 这里什么都不做,只是测试编译
while(1);
}
// 编译命令
aarch64-none-elf-gcc -c test_startup.c -o test_startup.o -O2 -Wall
// 查看生成的目标文件格式
aarch64-none-elf-objdump -d test_startup.o
如果能看到反汇编出来的ARM64指令,说明工具链工作正常。我记得第一次看到aarch64开头的反汇编时,心里还挺激动的——终于要开始搞底层了。
2.2 QEMU虚拟化环境搭建
没有开发板的时候,QEMU是最好的调试伙伴。你想想看,在QEMU上跑ATF+UEFI,可以随时打断点、看寄存器,比在真板上调试方便多了。
2.2.1 安装QEMU
我建议用源码编译安装,这样能确保支持最新的ARM特性:
# 安装依赖
sudo apt-get install ninja-build pkg-config libglib2.0-dev libpixman-1-dev
# 下载QEMU源码
git clone https://gitlab.com/qemu-project/qemu.git
cd qemu
# 配置,只编译ARM目标
./configure --target-list=aarch64-softmmu --enable-debug
# 编译安装
make -j$(nproc)
sudo make install
# 验证
qemu-system-aarch64 --version
--enable-debug选项,这样QEMU才支持GDB远程调试。我见过有人编译时没加这个,结果后面想单步调试ATF启动流程时才发现不行,又重编了一遍。
2.2.2 创建虚拟平台
QEMU支持多种ARM虚拟平台。我个人习惯用virt平台,它最灵活,可以自定义内存大小、CPU数量、外设配置:
# 启动一个基本的virt平台
qemu-system-aarch64 \
-machine virt \
-cpu cortex-a72 \
-smp 4 \
-m 2G \
-nographic \
-serial mon:stdio
这个命令启动了一个4核Cortex-A72的虚拟机器,2GB内存,没有图形界面。你会看到QEMU的UEFI固件(如果装了的话)开始运行。
2.2.3 加载ATF和UEFI固件
要让QEMU加载我们自己编译的ATF和UEFI,需要用-bios参数指定固件文件:
# 加载ATF BL1和FIP镜像
qemu-system-aarch64 \
-machine virt \
-cpu cortex-a72 \
-smp 2 \
-m 1G \
-bios /path/to/atf/build/qemu/release/bl1.bin \
-drive file=/path/to/fip.bin,if=none,id=fip,format=raw \
-device loader,file=/path/to/fip.bin,addr=0x04000000,force-raw=on \
-nographic
0x04000000是QEMU virt平台约定的加载地址。不同平台这个值不一样,一定要查平台手册确认。我刚开始做的时候,地址写错了,结果ATF加载UEFI时一直报错,查了半天才发现是地址不对。
2.3 JTAG调试器连接与OpenOCD配置
QEMU虽然方便,但真板调试还是得靠JTAG。JTAG调试器就像嵌入式开发的听诊器,能让你看到CPU内部的状态。
2.3.1 硬件连接
常用的JTAG调试器有J-Link、ST-Link、FT2232等。连接时要注意:
- TCK:时钟信号,调试器驱动
- TMS:状态机控制
- TDI:数据输入(到目标板)
- TDO:数据输出(从目标板)
- TRST:复位(可选)
- GND:共地,必须连接
2.3.2 OpenOCD配置
OpenOCD是开源调试器软件,支持多种调试器和目标芯片。下面是一个针对ARM Cortex-A72的配置示例:
# openocd.cfg
# 调试器配置(以J-Link为例)
source [find interface/jlink.cfg]
transport select swd
# 目标芯片配置
set CHIPNAME cortex-a72
set CPU_TAPID 0x4ba00477
# 创建CPU目标
target create $_CHIPNAME.cpu aarch64 \
-chain-position $_CHIPNAME.cti
# 配置复位方式
reset_config srst_nogate
# 初始化脚本
proc init_targets {} {
# 这里可以添加初始化代码
echo "Target initialized"
}
# 启动OpenOCD
init
targets
2.3.3 启动调试会话
配置好之后,启动OpenOCD:
# 启动OpenOCD
openocd -f openocd.cfg
# 在另一个终端启动GDB
aarch64-none-elf-gdb /path/to/atf/build/qemu/debug/bl1.elf
# 在GDB中连接OpenOCD
(gdb) target remote localhost:3333
(gdb) monitor reset halt
(gdb) load
(gdb) continue
2.4 本章知识体系
下面这张图总结了开发环境搭建的核心组件和它们之间的关系:
2.5 常见问题与避坑指南
原因:工具链没加到PATH,或者软链接失效。
解决:检查
export PATH=/opt/aarch64-toolchain/bin:$PATH是否执行成功。
原因:没有指定串口输出,或者固件加载地址不对。
解决:加上
-serial mon:stdio参数,检查-device loader的地址是否正确。
原因:JTAG接线错误,或者目标板没上电。
解决:用万用表测量TCK、TMS、TDI、TDO引脚电压,确认信号正常。我遇到过最奇葩的一次是JTAG线太长,信号衰减导致连接不稳定,换成15cm的短线就好了。
环境搭建这块,说白了就是细心加耐心。工具链版本、加载地址、接线顺序,任何一个细节出错都会导致后面调试时抓狂。我建议你每配好一个环节,就做个简单的测试验证,不要等到最后一起跑才发现问题。