技术知识库
将 C 程序分阶段交叉编译并运行在 ARM64
将 C 程序分阶段交叉编译并运行在 ARM64
摘要
本实验在 Ubuntu 26.04 x86-64 宿主机上,用原生 GCC 建立运行基准,再用 aarch64-linux-gnu-gcc 显式完成预处理、编译、汇编和静态链接。最终 ELF 经 SSH 复制到自行构建的 ARM64 Debian QEMU Guest,成功输出字符串、分配 409,600 字节并返回状态 0。
原理见 GCC 从源文件到 ELF 的四个阶段。
环境与路径
宿主机:Ubuntu 26.04,x86-64
目标机:QEMU ARM64,Linux 5.0.0+,Debian 12
工作目录:/home/martin-zou/workspace/gcc-lab
交叉编译器:aarch64-linux-gnu-gcc 15.2.0
SSH 转发:127.0.0.1:2222 → ARM64 Guest:22
1. 创建测试程序
mkdir -p /home/martin-zou/workspace/gcc-lab
cd /home/martin-zou/workspace/gcc-lab
vim test.c
写入:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define PAGE_SIZE 4096
#define MAX_SIZE (100 * PAGE_SIZE)
int main(void)
{
char *buf;
puts("hello martin.zou");
buf = malloc(MAX_SIZE);
if (buf == NULL) {
puts("malloc error");
return EXIT_FAILURE;
}
memset(buf, 0, MAX_SIZE);
printf("buffer address=%p, size=%d bytes\n",
(void *)buf, MAX_SIZE);
free(buf);
return EXIT_SUCCESS;
}
2. 原生编译作为基准
gcc \
-Wall \
-Wextra \
-O0 \
-g \
test.c \
-o test-native
file test-native
./test-native
echo "status=$?"
本轮 file 识别为 x86-64 PIE、动态链接、包含调试信息。多次运行均成功,虚拟地址因 ASLR 而变化。
3. ARM64 预处理
aarch64-linux-gnu-gcc \
-E test.c \
-o test.i
检查宏和主函数:
ls -lh test.c test.i
grep -n 'int main' test.i
grep -n 'hello martin.zou' test.i
本轮 test.i 为 65,438 字节,明显大于源文件,因为标准库头文件声明已经展开。
4. 编译为 AArch64 汇编
aarch64-linux-gnu-gcc \
-S test.i \
-o test.s
检查:
sed -n '1,180p' test.s
本轮 test.s 为 1,052 字节,包含:
.arch armv8-a
stp x29, x30, [sp, -32]!
bl puts
bl malloc
bl memset
bl printf
bl free
ret
这些是 AArch64 指令与寄存器,证明目标架构已经在编译阶段确定。
5. 汇编为可重定位目标文件
aarch64-linux-gnu-gcc \
-c test.s \
-o test.o
验证:
ls -lh test.o
file test.o
aarch64-linux-gnu-readelf -h test.o
aarch64-linux-gnu-nm test.o
本轮 test.o 为 2,112 字节。它包含机器码,但 libc 外部符号尚待链接解析。
6. 静态链接 ARM64 ELF
aarch64-linux-gnu-gcc \
test.o \
-o test \
--static
验证:
file test
file test-native
ls -lh test test-native
本轮结果:
test:ARM aarch64,静态链接,面向 GNU/Linux 3.7.0 及以上
test-native:x86-64 PIE,动态链接,解释器 /lib64/ld-linux-x86-64.so.2
test 大小:约 689 KiB
静态链接使该程序不依赖 Guest 中与交叉工具链完全匹配的动态加载器和 libc。
7. 复制到 ARM64 Debian
先确保 ARM64 QEMU 使用如下转发:
hostfwd=tcp::2222-:22
宿主机执行:
scp \
-P 2222 \
test \
martin@127.0.0.1:~/
scp 使用大写 -P 指定端口;ssh 使用小写 -p。
8. 在 ARM64 Guest 运行
ssh -p 2222 martin@127.0.0.1
file /home/martin/test
/home/martin/test
echo "status=$?"
本轮两次实际输出:
hello martin.zou
buffer address=0xffff9ba2a010, size=409600 bytes
hello martin.zou
buffer address=0xa876430, size=409600 bytes
地址变化来自进程虚拟地址布局和内存分配策略,不影响实验成功。关键证据是 ARM64 ELF 能在 Guest 中执行、分配并清零 409,600 字节、正常释放并退出。
9. 待完成:Makefile
书中实验还要求编写一个简单 Makefile,把编译和清理操作自动化。完成该步骤并验证增量构建与 make clean 后,将本 Project 状态更新为 evergreen。
完成标准
- 保留
.i、.s、.o和最终 ELF。 - 能解释四阶段各自产物和职责。
-
file确认原生程序为 x86-64、交叉程序为 AArch64。 - ARM64 静态 ELF 在 Debian Guest 中运行成功。
- Makefile 能完成构建、增量判断和清理。