3. 环境搭建:Linux主机环境准备、安装依赖包、配置环境变量、验证工具链是否可用、第一个交叉编译程序(Hello World)
好,咱们正式开始动手了。
这一章,说白了就是“把家伙事儿备齐”。很多初学者一上来就急着写代码,结果编译报错半天找不到原因——十有八九是环境没搭对。我当年刚入行时也吃过这个亏,折腾了一整天,最后发现是路径写错了。嗯,咱们别走这个弯路。
3.1 Linux主机环境准备
交叉编译,主机必须是Linux。我个人习惯用Ubuntu 20.04 LTS或22.04 LTS,这两个版本对嵌入式工具链的支持最友好。如果你用的是CentOS或Debian,也没问题,但包管理命令要对应调整。
先确认你的系统是64位的:
uname -m
# 输出应为 x86_64
再看一下系统版本:
cat /etc/os-release
为什么要确认这些?因为很多交叉编译工具链只提供64位版本。我曾经在32位虚拟机上折腾过,结果工具链跑不起来,白白浪费了两个小时。
3.2 安装依赖包
交叉编译需要一些基础工具。别嫌多,这些都是必需品。
在Ubuntu上,执行:
sudo apt update
sudo apt install -y build-essential
sudo apt install -y git wget curl
sudo apt install -y libncurses5-dev
sudo apt install -y flex bison
sudo apt install -y texinfo
sudo apt install -y gawk
sudo apt install -y python3 python3-pip
这里我重点说一下 build-essential。它包含了gcc、g++、make等核心编译工具。如果你连这个都没装,后面所有操作都白搭。
另外,libncurses5-dev 是用于配置内核时的菜单界面。你想想看,如果没有它,配置内核时只能靠手写文本,那效率得多低。
3.3 配置环境变量
工具链装好后,需要让系统知道它在哪里。环境变量就是系统的“通讯录”。
假设你的交叉编译工具链安装在 /opt/toolchains/gcc-arm-none-eabi-10.3-2021.10 目录下。那么,在 ~/.bashrc 或 ~/.zshrc 中添加:
export PATH=$PATH:/opt/toolchains/gcc-arm-none-eabi-10.3-2021.10/bin
export CROSS_COMPILE=arm-none-eabi-
export ARCH=arm
然后执行:
source ~/.bashrc
这里有个坑,我必须要提醒你:
export PATH=/opt/toolchains/.../bin,注意是 $PATH: 开头,不是直接等号。否则系统原有的命令全找不到了,连ls、cd都会报错。那场面,真是欲哭无泪。
CROSS_COMPILE 这个变量,很多Makefile会用它来拼接编译器名字。比如 $(CROSS_COMPILE)gcc 就变成了 arm-none-eabi-gcc。你想想看,这样多方便,换一个平台只需要改这一个变量。
3.4 验证工具链是否可用
环境配好了,咱们得验证一下。别等到编译时才发现工具链有问题。
先看版本:
arm-none-eabi-gcc --version
正常会输出类似:
arm-none-eabi-gcc (GNU Arm Embedded Toolchain 10.3-2021.10) 10.3.1 20210824
Copyright (C) 2020 Free Software Foundation, Inc.
再看一下它能不能编译:
echo 'int main() { return 0; }' | arm-none-eabi-gcc -x c - -o /tmp/test.elf
file /tmp/test.elf
输出应该包含 ELF 32-bit LSB executable, ARM 字样。这说明你编译出来的确实是ARM架构的程序,不是x86的。
file 命令显示的是 x86-64,那说明你用的还是本机gcc,不是交叉编译器。赶紧检查PATH顺序。
3.5 第一个交叉编译程序(Hello World)
好了,环境没问题,咱们写个Hello World庆祝一下。
新建文件 hello.c:
#include <stdio.h>
int main()
{
printf("Hello from ARM!\n");
return 0;
}
然后编译:
arm-none-eabi-gcc -o hello.elf hello.c
等等,这里有个问题。如果你直接这样编译,大概率会报错:
undefined reference to `_exit'
undefined reference to `_sbrk'
...
为什么会这样?因为嵌入式环境没有操作系统帮你打理这些底层函数。你需要指定链接脚本和启动文件。
对于裸机程序,正确的编译方式应该是:
arm-none-eabi-gcc -c -mcpu=cortex-m3 -mthumb hello.c -o hello.o
arm-none-eabi-ld -T link.ld hello.o -o hello.elf
但咱们现在只是验证工具链,可以加个 --specs=nosys.specs 参数,让编译器用半主机模式(semihosting):
arm-none-eabi-gcc --specs=nosys.specs -o hello.elf hello.c
这样就能编译通过了。查看生成的elf文件:
arm-none-eabi-objdump -d hello.elf | head -20
你会看到ARM汇编指令,比如 push {r7, lr}、bl printf 等。这说明你的交叉编译工具链真的在工作了。
本章小结
咱们这一章做了四件事:
- 准备了Linux主机环境
- 安装了编译依赖包
- 配置了环境变量
- 验证了工具链并编译了第一个ARM程序
嗯,到这里,你的开发环境已经就绪了。下一章,咱们会深入Makefile,看看如何用自动化脚本管理这些编译过程。到时候你会发现,手动敲命令的日子一去不复返了。
公众号:蓝海资料掘金营,微信deep3321