2. 开发环境搭建:交叉编译工具链安装、QEMU模拟器配置、GDB调试环境搭建
好,咱们正式开始动手。这一章的目标很明确——把开发环境搭起来,让代码能编译、能运行、能调试。我见过不少学员卡在环境上,一卡就是一两天,其实说白了就是几个关键点没搞对。
咱们的目标平台是ARM Cortex-A系列,你的笔记本是x86架构。这就需要一个“翻译官”——交叉编译工具链。再加上QEMU模拟硬件,GDB负责调试。这三样配齐了,你就能在电脑上完整跑一遍ARMv7的程序。
2.1 交叉编译工具链:为什么需要它?
你的电脑是x86处理器,目标板是ARM处理器。x86的机器码ARM不认识,ARM的机器码x86也跑不了。交叉编译就是在x86上生成ARM机器码的过程。
我刚开始做嵌入式时犯过一个低级错误——在Ubuntu上直接用gcc编译,然后往ARM板上一拷,跑不起来。查了半天才发现编译出来的还是x86的指令。嗯,从那以后我每次编译前都会确认一下工具链前缀。
安装步骤(Ubuntu 20.04/22.04)
我个人习惯用ARM官方的gcc-arm-none-eabi,稳定,社区活跃。直接apt安装就行:
sudo apt update
sudo apt install gcc-arm-none-eabi
sudo apt install binutils-arm-none-eabi
装完验证一下:
arm-none-eabi-gcc --version
arm-none-eabi-objdump --version
如果能看到版本号,说明工具链装好了。这里有个坑——有些发行版源里的版本比较老,我建议去ARM官网下载最新的预编译包,解压后加到PATH里。
~/.bashrc 里,比如 export PATH=$PATH:/opt/gcc-arm-none-eabi/bin,这样每次开终端就不用重新设了。
工具链全家桶
| 工具 | 作用 | 常用参数 |
|---|---|---|
arm-none-eabi-gcc |
C/C++编译器 | -mcpu=cortex-a7 -mthumb |
arm-none-eabi-as |
汇编器 | -g 加入调试信息 |
arm-none-eabi-ld |
链接器 | -T linker.ld 指定链接脚本 |
arm-none-eabi-objcopy |
格式转换 | -O binary 转成裸机二进制 |
arm-none-eabi-objdump |
反汇编 | -d 反汇编代码段 |
2.2 QEMU模拟器:在电脑上跑ARM
QEMU是个好东西。它能模拟完整的ARM开发板,包括CPU、内存、UART、中断控制器。你不需要买实体板子,就能验证大部分代码逻辑。
我当年做裸机驱动时,就是先在QEMU上调通,再烧到板子上。这样调试效率高很多,不用反复插拔USB、按复位键。
安装QEMU
sudo apt install qemu-system-arm
qemu-system-arm --version
咱们课程主要用qemu-system-arm,模拟Cortex-A系列。具体到ARMv7,我推荐用virt机器类型,它支持Cortex-A7/A15,配置灵活。
启动一个最小系统
写一个最简单的汇编程序,验证环境:
// start.s
.global _start
_start:
mov r0, #1
mov r1, #2
add r2, r0, r1
loop:
b loop
编译和运行:
arm-none-eabi-as -g start.s -o start.o
arm-none-eabi-ld -Ttext=0x40000000 start.o -o start.elf
qemu-system-arm -machine virt -cpu cortex-a7 -kernel start.elf -nographic
这里-Ttext=0x40000000指定代码加载地址,virt机器的默认内存映射从0x40000000开始。如果没报错,QEMU就成功加载了你的ARM程序。
-nographic 参数,QEMU弹出一个空的GUI窗口,程序跑没跑完全看不到。加上这个参数后,输出直接走终端,清爽多了。
2.3 GDB调试环境:让代码“慢动作”执行
写嵌入式程序,最怕的就是“跑起来全黑,不知道哪错了”。GDB就是你的眼睛,能让你看到寄存器怎么变的、内存里存了什么。
安装GDB
sudo apt install gdb-multiarch
gdb-multiarch支持多种架构,包括ARM。我个人习惯用这个,一个GDB通吃所有。
GDB + QEMU联调
QEMU内置了GDB服务器,启动时加上-s和-S参数:
-s:在端口1234开启GDB远程调试服务-S:启动时暂停CPU,等待GDB连接
启动命令:
qemu-system-arm -machine virt -cpu cortex-a7 -kernel start.elf -nographic -s -S
然后另开一个终端,启动GDB:
gdb-multiarch start.elf
(gdb) target remote localhost:1234
(gdb) info registers
(gdb) stepi
(gdb) quit
你会看到QEMU停在了_start入口,寄存器初始值一目了然。用stepi单步执行,观察r0、r1、r2的变化——这就是调试的感觉。
layout asm 可以打开汇编窗口,边看代码边调试。输入 layout reg 显示寄存器窗口。两个窗口同时开,效果最好。
2.4 完整环境验证:一个ARMv7裸机程序
咱们写一个稍微完整点的程序,把工具链、QEMU、GDB串起来跑一遍。这个程序会初始化UART,打印一行"Hello ARMv7"。
// hello.c
void uart_init(void) {
// 简化版UART初始化,具体寄存器地址依赖virt板
volatile unsigned int *uart = (unsigned int *)0x09000000;
uart[0] = 0x01; // 使能UART
}
void uart_putc(char c) {
volatile unsigned int *uart = (unsigned int *)0x09000000;
while (uart[5] & 0x20); // 等待发送缓冲区空
uart[0] = c;
}
void uart_puts(const char *s) {
while (*s) uart_putc(*s++);
}
void _start(void) {
uart_init();
uart_puts("Hello ARMv7!\n");
while (1);
}
编译、链接、运行:
arm-none-eabi-gcc -c -mcpu=cortex-a7 hello.c -o hello.o
arm-none-eabi-ld -Ttext=0x40000000 hello.o -o hello.elf
qemu-system-arm -machine virt -cpu cortex-a7 -kernel hello.elf -nographic
如果一切正常,终端会输出"Hello ARMv7!"。看到这行字,说明你的开发环境完全搭好了。
- ✅ 交叉编译器能生成ARM机器码
- ✅ QEMU能加载并运行ARM程序
- ✅ GDB能远程连接到QEMU并单步调试
- ✅ UART输出正常,能看到打印信息
2.5 环境结构总览
下面这张图把整个开发环境的关系画清楚了。你写代码、编译、调试、运行的流程,一目了然。
从图上你能看到,整个流程是单向的:代码在开发机上编译,生成ARM可执行文件,交给QEMU运行。GDB则通过远程协议连接到QEMU内部,控制执行、读取寄存器、查看内存。这三者配合起来,就是一个完整的嵌入式开发闭环。