使用 Buildroot 交叉编译 Vitetris 到 ARM 开发板的完整实践与经验总结

最近翻出很久以前买的 Luckfox pico mini板子,这玩意买回来电都没通过吃灰了好几年。
这几天我尝试使用 Buildroot 将 Vitetris 游戏交叉编译到我的 ARM 开发板上。
这个过程涉及多次试错、Makefile 调整和交叉编译的理解,最终成功完成。

现在我将整个过程整理如下,希望对同样做交叉编译的朋友有所帮助。


1. 初步验证:Hello World

在正式编译 Vitetris 之前,我先验证交叉编译链是否可用。

  1. 编写一个最简单的 hello.c:
#include <stdio.h>
int main() {
    printf("Hello ARM!\n");
    return 0;
}
  1. 使用 Buildroot 提供的交叉编译链编译:
arm-linux-gcc -o hello hello.c
  1. file 检查生成文件:
file hello
# 输出:
# ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV)

✅ 成功生成 ARM 可执行文件,可在开发板上运行,输出 “Hello ARM!”。

通过这个小实验,我确认了交叉编译链可用,并能正确生成目标架构的 ELF 文件。


2. Vitetris 编译问题

初次在 Buildroot 中编译 Vitetris 后,将生成的可执行文件拷贝到 ARM 开发板上运行时,报错:

/usr/bin/tetris: line 1: syntax error: unexpected "|"

使用 file 命令检查可执行文件:

file tetris
# 输出:
# ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2

发现可执行文件是 x86_64 架构,而开发板是 ARM。说明编译过程中没有使用交叉编译链,而是使用了本机 gcc。


3. 问题分析

  1. Buildroot 提供了 ARM 交叉编译链。
  2. Vitetris 源码顶层 Makefile 中 没有显式定义 CC,直接使用 $(CC)
  3. 当顶层 Makefile 进入 src 目录调用子 Makefile 时,如果不显式传递 CC,子 Makefile 会使用系统默认 gcc,导致生成错误架构的可执行文件。

4. 解决过程

4.1 修改 vitetris.mk

原来调用子目录 Make 的命令:

$(MAKE) -C $(@D)

修改为:

$(MAKE) CC="$(TARGET_CC)" CFLAGS="$(TARGET_CFLAGS)" LDFLAGS="$(TARGET_LDFLAGS)" -C $(@D)

这样顶层 Makefile 的 $(CC) 会被交叉编译链覆盖,并传递到子 Makefile。

4.2 子 Makefile 继承问题

重新编译后,出现:

main.o: file not recognized: file format not recognized
collect2: error: ld returned 1 exit status

原因是子 Makefile 仍然使用系统默认 gcc,没有继承父 Make 的 CC

4.3 显式传递 CC

在顶层 Makefile 调用 src 目录时显式传递:

cd src; $(MAKE) CC="$(CC)" CFLAGS="$(CCFLAGS)" LDFLAGS="$(LDFLAGS)" tetris

子 Makefile 中的 $(CC) 就会使用传递过来的交叉编译链。


5. 编译验证

使用 file 检查最终生成的可执行文件:

file sysdrv/source/buildroot/buildroot-2023.02.6/output/target/usr/bin/tetris
# 输出:
# ELF 32-bit LSB pie executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, not stripped

✅ 成功生成 ARM 32-bit 可执行文件,可直接在开发板上运行。


6. 知识点总结

  1. 交叉编译链必须显式传递给所有子 Makefile

    • 在父 Makefile 中设置 CC="$(TARGET_CC)"CFLAGS="$(TARGET_CFLAGS)" 等,并传递到子 Makefile。
    • 否则子 Makefile 默认使用本机 gcc,会生成错误架构的可执行文件。
  2. Makefile 中 CC 的继承规则

    • 子 Makefile 不会自动继承父 Makefile 的变量值(除非通过环境变量)。
    • 使用 $(MAKE) CC="$(CC)" ... -C subdir 可以显式传递。
  3. 检查架构和库类型

    • file <executable> 是最直接检查生成文件架构的方法。
    • 注意 Buildroot 默认使用 uClibc 动态链接库。
  4. 交叉编译调试技巧

    • 小程序先验证交叉编译链是否生效。
    • 对复杂项目,先理解 Makefile 层级和变量传递。
    • 编译报错时,检查每个对象文件是否是正确架构。
  5. 优化

    • 最终生成可执行文件可使用 strip 去掉调试符号,减小体积。
    • Makefile 可统一管理交叉编译参数,避免重复传递。

7. 收获与经验

通过这次实践,我深入理解了:

  • Makefile 变量作用域和继承机制
  • Buildroot 的交叉编译流程
  • 如何排查架构不匹配问题
  • 子 Makefile 与父 Makefile 的交互
  • 实战中通过 file 检查 ELF 架构的重要性

总之,这次尝试不仅让我成功将 Vitetris 移植到 ARM 开发板,也让我对交叉编译和复杂 Makefile 的工作机制有了系统的理解。

系列文章

Last modification:October 25, 2025
If you think my article is useful to you, please feel free to appreciate