/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */
#content_views .toc,
/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */
#content_views.markdown_views > p:empty:has(+ .toc),
#content_views.markdown_views > .toc + p:empty,
/* 富文本旧版目录标记 */
#content_views.htmledit_views #main-toc,
#content_views.htmledit_views #hr-toc,
#content_views.htmledit_views p[id*=\”-toc\”] {
display: none !important;
}
/* 目录去掉后,紧跟的首个标题不再多出一块上边距 */
#content_views.markdown_views > .toc + h1,
#content_views.markdown_views > .toc + h2,
#content_views.markdown_views > .toc + h3,
#content_views.markdown_views > .toc + h4,
#content_views.markdown_views > .toc + p:empty + h1,
#content_views.markdown_views > .toc + p:empty + h2,
#content_views.markdown_views > .toc + p:empty + h3,
#content_views.markdown_views > .toc + p:empty + h4 {
margin-top: 0 !important;
}
CentOS 7 搭建 AArch64 交叉编译环境全记录:从 EPEL 到 QEMU 7.2 的层层闯关
在 CentOS 7.9 上搭建 AArch64 Linux 用户态交叉编译环境,我先后遇到了缺少 stdio.h、找不到动态加载器、旧版 QEMU 报 Illegal instruction,最后通过 Arm GNU 工具链和源码编译的 QEMU 7.2,成功运行了 Hello from AArch64!。
本文记录这条完整路径,也区分了已经观察到的现象与尚未证实的故障原因。无需真实 ARM 开发板,就能在 x86_64 宿主机上完成这个示例的交叉编译与运行验证。
时间提醒:CentOS 7 已于 2024 年 6 月 30 日结束维护。本文面向仍需维护旧环境的场景,使用的是历史版本,不代表新项目的推荐选型。原有 CentOS、EPEL、SCL 镜像可能失效;安装前需要确认已配置可信的归档源或内部镜像。
一、环境与最终结论
| 宿主机 | CentOS Linux release 7.9.2009 (Core) |
| 内核 | 3.10.0-1160.el7.x86_64 |
| 宿主机架构 | x86_64 |
| 目标平台 | AArch64(ARM64)Linux 用户态 |
| 最终交叉编译器 | Arm GNU Toolchain 10.3-2021.07,前缀 aarch64-none-linux-gnu |
| 最终验证工具 | QEMU 7.2.0,源码编译的 aarch64-linux-user |
| QEMU 构建环境 | Python 3.6、Ninja、devtoolset-11 |
本文验证成功的组合是:Arm GCC 10.3 + 静态链接的示例程序 + QEMU 7.2.0。
静态链接减少了运行示例时对目标动态加载器和共享库的依赖,但不是唯一选择。动态链接程序也可以通过 QEMU 的 -L 参数指定匹配的 ARM64 sysroot。
二、准备测试程序
建立一个固定工作目录,避免后续在下载、构建目录之间切换时找不到 hello.c:
mkdir -p "$HOME/aarch64-demo"
cd "$HOME/aarch64-demo"
cat > hello.c <<'EOF'
#include <stdio.h>
int main(void)
{
printf("Hello from AArch64!\\n");
return 0;
}
EOF
后续编译示例都在这个目录中进行。宿主机 GCC 用于构建 QEMU,交叉 GCC 用于构建 ARM64 程序,两者职责不同,切勿混用。
三、第一轮:EPEL 工具链缺少目标 C 库
1. 安装并查看版本
在已配置可用 EPEL 源的环境中执行:
sudo yum install -y epel-release
sudo yum install -y gcc-aarch64-linux-gnu
aarch64-linux-gnu-gcc –version
本次环境中的编译器版本为 GCC 4.8.5。
2. 尝试编译
cd "$HOME/aarch64-demo"
aarch64-linux-gnu-gcc hello.c -o hello_arm64
报错的关键信息如下,具体行号可能不同:
hello.c:1:19: fatal error: stdio.h: No such file or directory
#include <stdio.h>
^
compilation terminated.
3. 原因与结论
当前安装的交叉编译器没有可用的 AArch64 用户态 C 库头文件和库文件。编译器、目标 C 库及其 sysroot 是不同的组成部分:安装了 GCC,不等于拥有完整的 Linux 用户态开发环境。
尝试通过以下名称安装目标 glibc,当前仓库也没有对应软件包:
sudo yum install -y glibc-aarch64-linux-gnu
# No package glibc-aarch64-linux-gnu available.
可以进一步检查编译器配置及头文件搜索路径:
aarch64-linux-gnu-gcc -print-sysroot
aarch64-linux-gnu-gcc -v -E hello.c -o /dev/null
结论:本次 EPEL 安装结果不足以直接编译依赖 glibc 的 ARM64 用户态程序。 若自行提供匹配的 sysroot,仍有可能使用该编译器;不能据此认定它完全不能用于用户态开发。因此这里改用自带目标库的 Arm 官方 Linux 工具链。
四、第二轮:Arm 官方工具链能编译,动态程序运行失败
1. 下载 Arm GNU Toolchain 13.3
先下载到普通用户可写目录,再使用管理员权限解压到 /opt:
mkdir -p "$HOME/aarch64-downloads"
cd "$HOME/aarch64-downloads"
wget https://developer.arm.com/-/media/Files/downloads/gnu/13.3.rel1/binrel/arm-gnu-toolchain-13.3.rel1-x86_64-aarch64-none-linux-gnu.tar.xz
sudo tar -xJf arm-gnu-toolchain-13.3.rel1-x86_64-aarch64-none-linux-gnu.tar.xz -C /opt
export ARM13=/opt/arm-gnu-toolchain-13.3.rel1-x86_64-aarch64-none-linux-gnu
"$ARM13/bin/aarch64-none-linux-gnu-gcc" –version
下载后应按发布方提供的校验信息核验文件。历史下载链接如失效,请到 Arm 官方归档页寻找同名发行包。
2. 编译并确认目标架构
cd "$HOME/aarch64-demo"
"$ARM13/bin/aarch64-none-linux-gnu-gcc" hello.c -o hello_arm64
file hello_arm64
"$ARM13/bin/aarch64-none-linux-gnu-readelf" -l hello_arm64
file 输出包含 ARM aarch64 和 dynamically linked;readelf 可以查看程序请求的动态加载器路径。
file hello_arm64
hello_arm64: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 3.7.0, not stripped
3. 使用已有 QEMU 运行
本次机器上已有的 QEMU 为 2.0.0。不同机器的安装来源和版本可能不同,应以实际输出为准:
qemu-aarch64 –version
qemu-aarch64 ./hello_arm64
运行时报错:
/lib/ld-linux-aarch64.so.1: No such file or directory
这是目标动态加载器缺失的问题。宿主机的 x86_64 glibc 不能替代 ARM64 glibc。
动态链接的正确方向是提供与程序匹配的目标 sysroot,例如:
ARM13_SYSROOT=$("$ARM13/bin/aarch64-none-linux-gnu-gcc" -print-sysroot)
printf '%s\\n' "$ARM13_SYSROOT"
ls "$ARM13_SYSROOT/lib/ld-linux-aarch64.so.1"
qemu-aarch64 -L "$ARM13_SYSROOT" ./hello_arm64
如果输出的 sysroot 为空,或该目录下没有目标加载器,需要先定位工具链中的实际目标运行库目录,不能继续照搬这个路径。即使加载器问题解决,旧 QEMU 仍可能存在其他兼容性问题。
五、第三轮:静态链接后,QEMU 2.0 报 SIGILL
1. 使用 GCC 13 静态编译
cd "$HOME/aarch64-demo"
"$ARM13/bin/aarch64-none-linux-gnu-gcc" hello.c -o hello_arm64 -static
qemu-aarch64 ./hello_arm64
本次运行结果:
qemu: uncaught target signal 4 (Illegal instruction) – core dumped
Illegal instruction (core dumped)
2. 换用 Arm GCC 10.3,仍然失败
cd "$HOME/aarch64-downloads"
wget https://developer.arm.com/-/media/Files/downloads/gnu-a/10.3-2021.07/binrel/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu.tar.xz
sudo tar -xf gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu.tar.xz -C /opt
export ARM10=/opt/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu
"$ARM10/bin/aarch64-none-linux-gnu-gcc" –version
cd "$HOME/aarch64-demo"
"$ARM10/bin/aarch64-none-linux-gnu-gcc" hello.c -o hello_arm64 -static
qemu-aarch64 ./hello_arm64
这次仍然出现 Illegal instruction。
3. 不要把现象直接当成根因
SIGILL 表示执行遇到了非法指令,但仅凭这条信息无法确定具体原因。可能涉及 QEMU 的指令模拟、CPU 特性、目标运行库或其他兼容性问题,需要结合故障地址、反汇编及执行日志分析。
尤其不能直接断言“glibc 调用了 membarrier(),宿主机内核不支持,所以 QEMU 将其转成 SIGILL”。不支持的系统调用通常表现为 ENOSYS;若要建立它与 SIGILL 的因果关系,还需要额外证据。 需要继续定位时,可以收集日志:
qemu-aarch64 -strace ./hello_arm64
qemu-aarch64 -d in_asm -D qemu-arm64.log ./hello_arm64
本次可以确认的是:两个工具链生成的静态示例都未能在 QEMU 2.0 上运行,后续同一 GCC 10.3 示例在 QEMU 7.2 上运行成功。 这支持升级 QEMU 的处理方向,但不证明某个具体系统调用或指令就是根因,也不确定最低可用 QEMU 版本。
六、第四轮:源码编译 QEMU 7.2,验证成功
这里只构建 AArch64 Linux 用户态模拟器,不构建整机模拟器。
1. 安装基础依赖
sudo yum install -y gcc gcc-c++ make git wget tar xz bzip2 \\
pkgconfig glib2-devel zlib-devel
对这个用户态构建目标,通常不需要用于图形显示的 pixman-devel。依赖是否满足,以 QEMU configure 检查结果为准。
2. 准备 Python 3.6
QEMU 7.2 要求 Python 3.6 或更高版本;CentOS 7 默认的 Python 2.7 不满足要求:
sudo yum install -y python36
python3.6 –version
command -v python3.6
不要替换系统 /usr/bin/python,以免影响 yum 等系统工具。若归档源没有 python36,应通过可信来源另行安装,并在配置 QEMU 时指定其真实路径。
3. 准备 Ninja
优先尝试软件包安装:
sudo yum install -y ninja-build
ninja –version
如果仓库没有可用版本,可以固定到历史版本 Ninja 1.10.2,避免直接克隆最新分支引入新的编译器或 Python 要求:
cd "$HOME/aarch64-downloads"
git clone –branch v1.10.2 –depth 1 https://github.com/ninja-build/ninja.git ninja-1.10.2
cd ninja-1.10.2
python3.6 ./configure.py –bootstrap
sudo install -m 0755 ninja /usr/local/bin/ninja
export PATH="/usr/local/bin:$PATH"
hash -r
ninja –version
两种安装方式选择一种即可。如果 bootstrap 提示宿主编译器过旧,可以先完成下一步,再返回构建 Ninja。
4. 使用 devtoolset-11 构建 QEMU
QEMU 7.2 要求 GCC 至少为 7.4,CentOS 7 默认 GCC 4.8.5 不满足要求。本次使用 devtoolset-11:
sudo yum install -y centos-release-scl
sudo yum install -y devtoolset-11-gcc devtoolset-11-gcc-c++ \\
devtoolset-11-binutils
source /opt/rh/devtoolset-11/enable
gcc –version
g++ –version
这些命令以可用的 CentOS 7 SCL 归档源为前提。若出现镜像失效、404 或找不到软件包,应先修复软件源;换成某个普通镜像地址并不保证其仍保留 CentOS 7 内容。使用可信归档源时保留 GPG 校验,不建议用 gpgcheck=0 绕过问题。
source 只影响当前 shell。重新打开终端后,需要重新启用 devtoolset-11;它不会替换系统 GCC,也不会替换 ARM64 交叉编译器。
5. 下载并编译 QEMU 7.2.0
cd "$HOME/aarch64-downloads"
wget https://download.qemu.org/qemu-7.2.0.tar.xz
tar -xJf qemu-7.2.0.tar.xz
mkdir -p qemu-7.2.0/build
cd qemu-7.2.0/build
source /opt/rh/devtoolset-11/enable
export PATH="/usr/local/bin:$PATH"
../configure \\
–target-list=aarch64-linux-user \\
–prefix=/usr/local \\
–python="$(command -v python3.6)"
make -j"$(nproc)"
sudo make install
下载后同样应核验发布方提供的签名或校验信息。构建时如发生内存不足,可以降低并行度,例如使用 make -j2。
查看新版本:
/usr/local/bin/qemu-aarch64 –version
本次输出的版本为:
qemu-aarch64 version 7.2.0
这里使用绝对路径,避免误调用原来的 QEMU 2.0。
6. 最终验证
export ARM10=/opt/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu
cd "$HOME/aarch64-demo"
"$ARM10/bin/aarch64-none-linux-gnu-gcc" hello.c -o hello_arm64 -static
file hello_arm64
/usr/local/bin/qemu-aarch64 ./hello_arm64
file 输出包含 ARM aarch64 和 statically linked。
file hello_arm64
hello_arm64: ELF 64-bit LSB executable, ARM aarch64, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.7.0, not stripped
程序输出:
Hello from AArch64!
至此,交叉编译器、目标 C 库、生成的 ARM64 可执行文件以及 QEMU 用户态执行链路,都通过了这个最小示例的验证。
七、踩坑总结
| 1 | EPEL GCC 4.8.5 | 编译失败 | 当前安装缺少可用的目标 C 库头文件 |
| 2 | Arm GCC 13 + QEMU 2.0,动态链接 | 运行失败 | 未提供目标动态加载器及匹配的运行库 |
| 3 | Arm GCC 13 + QEMU 2.0,静态链接 | SIGILL | 具体触发原因未定位 |
| 4 | Arm GCC 10.3 + QEMU 2.0,静态链接 | SIGILL | 该示例在旧模拟器上仍然失败 |
| 5 | Arm GCC 10.3 + QEMU 7.2,静态链接 | 成功 | 该组合通过最小用户态示例验证 |
这次搭建中,最重要的经验有三点:
此外,QEMU Linux 用户态模拟会借助宿主机内核处理系统调用,并不会启动一个 ARM64 Linux 内核。Hello World 跑通不能代替真实开发板测试,也不能证明所有 ARM64 应用、驱动或硬件相关功能都兼容。 GCC 版本本身也不能决定目标内核兼容性;还要考虑工具链所带目标 glibc 的构建配置、程序实际使用的接口以及 QEMU 的支持情况。本文不据此推导“必须 GCC 10”或“QEMU 5 以上都可以”的通用结论。
八、后续计划
- 编写 CMake 交叉编译工具链文件 toolchain-aarch64.cmake。
- 交叉编译 OpenSSL、curl 和 SQLite,验证更完整的依赖链。
- 用 qemu-aarch64 -L <sysroot> 验证动态链接程序。
- 在真实开发板上验证运行库、系统调用和硬件相关行为。
- 有板子后继续实践 U-Boot、设备树及内核驱动开发。
希望这份记录能帮助仍在维护 CentOS 7 环境的同学,把“能编译”和“能运行”之间的几个关键环节理清楚。
参考资料
- Arm GNU Toolchain 下载与历史版本
- QEMU 源码下载
- QEMU 7.2 构建平台要求(此链接为当前文档,历史版本要求应以 7.2.0 源码中的文档和 configure 检查为准)
- QEMU Linux 用户态模拟文档
- CentOS Linux 生命周期说明
标签:嵌入式、Linux、CentOS7、交叉编译、AArch64、ARM64、QEMU



