交叉编译器

Tutorial: 卓越工程师 Category: 嵌入式 Published: 2026-10-23 11:05:29 Views: 1 Likes: 0 Comments: 0

328.3.1交叉编译器

#include <stdio.h>            // 引入标准输入输出头文件,为 printf 函数提供声明。
int main()                   // 定义程序入口函数,返回 int 类型的状态值。
{                            // main 函数体开始。
    printf("i am wfeng!\n"); // 输出字符串,并使用 \n 换行。
    return 0;                // 返回 0,表示程序正常结束。
}                            // main 函数体结束。

本节没有修改 test.c,而是继续使用上一节通过 Samba 保存的上述最终版本,分别观察本机 GCC 与 ARM 交叉 GCC 生成的 ELF 文件。这里只整理格式并逐行补充注释,没有加入视频中不存在的业务代码。

本节在 x86-64 Ubuntu 20.04 虚拟机中安装预编译的 Linaro ARM GNU/Linux 工具链,再用它把 test.c 编译为 32 位 ARM 可执行文件。视频使用的完整工具链名称为:

gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf

其中,x86_64 表示这套编译器程序运行在 64 位 x86 Linux 主机上,arm-linux-gnueabihf 表示它生成面向 32 位 ARM GNU/Linux、采用 EABI hard-float ABI 的程序。GCC 官方也用“machine-gcc”概括交叉编译器的命名方式。GCC 官方文档

一、交叉编译器安装

1.1 交叉编译器概念

交叉编译是“编译器运行的平台”和“编译产物运行的平台”不同的构建方式。它并不以“必须装在虚拟机中”为定义;编译器也可以运行在实体机、容器或构建服务器里。本节各角色如下:

角色本节对应对象含义
Host(宿主平台)x86-64 Ubuntu实际运行 arm-linux-gnueabihf-gcc 的平台
Target(目标平台)32 位 ARM GNU/Linuxb.out 将来要运行的平台
本地编译x86-64 → x86-64Ubuntu 的 gcc test.c 生成 a.out
交叉编译x86-64 → ARMarm-linux-gnueabihf-gcc test.c -o b.out 生成 b.out

视频把 Intel 处理器口误说成“Intel ARM 处理器”,随即自行纠正。这里应理解为:当前虚拟机运行在 Intel/AMD 常见的 x86-64 指令集环境中,而课程开发板的 i.MX6UL/i.MX6ULL 系列处理器采用 32 位 ARM 架构。课程此前展示的是 iTOP-i.MX6ULL 开发板;i.MX6UL 与 i.MX6ULL 是不同型号,不能混写,但 NXP 官方资料确认二者都采用单核 Arm Cortex-A7,因此本工具链的 ARMv7-A 目标与课程实验方向相符。NXP i.MX6UL 资料、NXP i.MX6ULL 资料

架构不同会带来指令编码、寄存器、ABI、动态加载器等差异,因此 x86-64 Linux 不能把普通 ARM ELF 文件当成本机程序直接执行。反过来,本机 GCC 生成的 x86-64 程序也不能直接拿到 i.MX6ULL 上运行。

交叉工具链不只有一个 gcc 文件,通常还包含:

  • 编译器驱动 gcc、g++;
  • 汇编器 as、链接器 ld;
  • objcopy、objdump、readelf、strip 等二进制工具;
  • 目标平台头文件、运行库和 sysroot。

本项目内课程压缩包的清单实际包含这些组件,所以它是预编译二进制工具链,不是原笔记所说的“交叉编译器源码文件夹”。其内置清单和视频的 -v 输出都显示 GCC 版本为 4.9.4 (Linaro GCC 4.9-2017.01),目标为 arm-linux-gnueabihf。

系统编译部分在课程思维导图中分为交叉编译器、U-Boot、内核、设备树和根文件系统五块。它们与交叉工具链的关系要准确区分:

  • U-Boot、Linux 内核以及用户态 C/C++ 程序中的目标代码,主要由交叉编译工具链处理;
  • 设备树源文件 .dts/.dtsi 由设备树编译器 dtc 转为 .dtb,构建系统可能自动调用它,但它不是 GCC 的 C 编译阶段;
  • 根文件系统是目录、库、配置和应用程序的集合,需要构建或组装,其中的目标程序可以由交叉工具链编译,但“根文件系统”本身不是一个由 GCC 直接编出的可执行文件;
  • 编译产物是否需要烧写、写入哪种介质以及由谁加载,要由后续启动链路决定,不能统称为“全部烧进芯片才能运行”。

1.2 交叉编译器的安装方法

视频中的安装对象是压缩包:

gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz

画面显示其大小为 80,881,572 字节,约 77.1 MiB。项目内同名课程文件的 SHA-256 为:

22914118FD963F953824B58107015C6953B5BBDCCBDCF25AD9FD9A2F9F11AC07

哈希可用于确认文件在传输前后是否一致;只有与可信来源公布的值比较,才能进一步验证来源,单独记录本地哈希不能证明文件本来就可信。

这是一套 2017 年发布的旧工具链。学习课程配套 BSP 时,版本匹配往往比盲目换用最新 GCC 更重要;用于新项目时则应根据芯片厂商 BSP、目标根文件系统 ABI 和项目构建说明重新选择,不要仅因命令能启动就认定兼容。

1.2.1 公式化流程交叉编译器的安装方法

视频思维导图把过程归纳为七步:

  1. 建立 /usr/local/arm;
  2. 把压缩包传入该目录;
  3. 解压;
  4. 把工具链的 bin 目录加入 PATH;
  5. 重启系统并重新连接 SSH;
  6. 查看编译器版本;
  7. 编译 test.c,再用 file 对比目标架构。

这套流程能复现视频结果,但其中的 chmod 777、传统 FTP、修改全局 /etc/profile 和整机重启都不是唯一做法,下面会分别说明课程原操作和更安全的替代方案。

1.2.1.1 公式化流程交叉编译器的安装方法用到的命令
  • 1.2.1.1.1 例题的起始时间

    • 1.2.1.1.1.1 例题:查看当前路径

      视频先进入 /usr/local,用 ls 查看内容,再以 pwd 确认当前位置:

      cd /usr/local
      ls
      pwd
      

      pwd 的画面输出是 /usr/local。这是系统管理员安装本地软件的传统层级,不会因为进入该目录就自动获得写权限;普通用户仍受目录所有者与权限位约束。Filesystem Hierarchy Standard:/usr/local

  • 1.2.1.1.2 例题:交叉编译器安装过程命令应用

    视频实际执行的核心命令如下,文件名和编译器前缀必须完整、准确:

    cd /usr/local
    sudo mkdir arm
    cd arm
    sudo tar -vxf gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz
    sudo vim /etc/profile
    sudo reboot
    arm-linux-gnueabihf-gcc -v
    cd ~/test
    arm-linux-gnueabihf-gcc test.c -o b.out
    file b.out
    file a.out
    

    注意以下 OCR/原笔记错误:

    • 路径是 /usr/local/arm,不是 r5 目录;
    • 工具链目录不是 jcc,编译器也不是 arm-linux-gcc,而是 arm-linux-gnueabihf-gcc;
    • PATH 必须全部大写,不是英文单词 pass;
    • -o b.out 中的 -o 用来指定输出文件名;若不写 -o,GCC 默认生成 a.out。GCC 输出选项说明
1.2.1.2 公式化流程交叉编译器的安装方法用到的操作
  • 1.2.1.2.1 例题:文件传输操作

    视频在 FileZilla 左侧找到课程的“代码&源代码/交叉编译器”目录,在右侧进入 Ubuntu 的 /usr/local/arm,双击压缩包上传。第一次和第二次上传均失败,FileZilla 返回:

    553 Could not create file.
    

    原因可由终端的 ls -l /usr/local 看出:arm 由 root:root 所有,权限为 drwxr-xr-x。普通用户 wfeng 只能读取和进入,不能在其中新建文件。视频随后执行:

    sudo chmod 777 /usr/local/arm
    

    上传才成功,FileZilla 最终显示传输了 80,881,572 字节。这里不是“队列数字从 1 变成 3 就证明内容正确”,而应同时确认“成功的传输”列表、远端文件大小,最好再比较哈希。

    777 表示所有者、同组用户和其他用户都具有读、写、执行/进入权限,会让任何本机用户都能替换工具链文件,权限明显过宽。GNU Coreutils 权限说明 更合理的做法有两种:

    # 方案一:让课程用户拥有专用安装目录,其他用户只有读取和进入权限。
    sudo chown wfeng:wfeng /usr/local/arm
    sudo chmod 0755 /usr/local/arm
    
    # 方案二:先上传到自己的家目录,再由管理员解压到受保护的目录。
    sudo tar -xJf ~/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz -C /usr/local/arm
    

    第二种方式能让解压后的工具链继续由 root 管理。上一节已说明传统 FTP 默认不加密;在非隔离网络上应使用 SFTP/SCP,并核对主机密钥和文件哈希。

  • 1.2.1.2.2 例题:交叉编译器解压操作

    视频在 /usr/local/arm 中使用:

    sudo tar -vxf gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz
    

    各参数含义:

    参数作用
    -x从归档中提取文件
    -v逐项显示文件名;只影响输出详细程度
    -f后一个参数是归档文件名

    该环境中的 GNU tar 能自动识别 xz 压缩。想显式写出压缩格式可用 -J:

    sudo tar -xJf gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz
    

    -v 会输出大量条目,不是成功解压所必需。输入文件名前几个字符后按的是 Tab 键自动补全,不是转录中的 “type”。解压后 ls 显示压缩包和同名目录:

    gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf
    gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz
    

    进入解压目录后,视频看到 arm-linux-gnueabihf、bin、include、lib、libexec、share 和工具链清单等内容。安装的是预编译工具链,解压本身没有从源码构建编译器。

  • 1.2.1.2.3 例题:查看环境变量操作

    • 1.2.1.2.3.1 例题:查看交叉编译器文件

      Shell 收到 arm-linux-gnueabihf-gcc 这个不含斜杠的命令名时,会依次到 PATH 列出的目录中查找可执行文件。工具链真正需要加入 PATH 的目录是:

      /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin
      

      也可以不改 PATH,而是每次使用绝对路径;修改 PATH 只是为了让命令更短,不是“向 Linux 注册编译器”。

  • 1.2.1.2.4 例题:查看环境变量操作

    • 1.2.1.2.4.1 例题:env命令应用

      视频演示:

      env
      printenv
      echo $PATH
      

      env 和不带参数的 printenv 会列出导出的环境变量,因此画面输出很长。只检查 PATH 时更直接的命令是:

      printenv PATH
      

      PATH 的多个目录用冒号 : 分隔。它保存的是目录列表而非某个编译器文件名。

  • 1.2.1.2.5 例题:echo命令应用

    正确命令是:

    echo "$PATH"
    

    $ 表示取变量值,PATH 区分大小写,echo 与参数之间必须有空格。原笔记中的 echo pass 只会原样输出单词 pass;echo $pass 则读取另一个名为 pass 的小写变量,二者都不是本节目标。

  • 1.2.1.2.6 例题:修改环境变量操作

    视频执行:

    sudo vim /etc/profile
    

    并在末尾加入这一行:

    export PATH=$PATH:/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin
    

    Vim 的保存退出命令是 :wq,冒号与字母之间没有空格。export 让随后启动的子进程继承更新后的 PATH。目录名任何一个字符出错,Shell 都无法从该位置找到编译器。

    /etc/profile 是系统范围的登录 Shell 启动文件,修改它会影响多个用户,必须保留原内容并谨慎编辑。Bash 官方说明:登录 Shell 会读取 /etc/profile,普通交互式非登录 Shell 则主要读取 ~/.bashrc。Bash 启动文件文档

    单用户实验更适合把配置写入 ~/.profile:

    export PATH=/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin:$PATH
    

    推荐把新目录放在 $PATH 前面,这样同名命令优先使用指定工具链;本节的前缀足够独特,放前放后通常不会改变结果。若暂时不想编辑文件,也可在当前 Shell 直接执行这一行,只对当前会话及其子进程生效。

  • 1.2.1.2.7 例题:重启操作

    视频执行的是:

    sudo reboot
    

    虚拟机重启会中断当前 SSH 连接。系统重新启动后,MobaXterm 显示 Press R to restart session;按 R 只是让客户端重新建立 SSH 会话,不是再次重启 Ubuntu。原转录把按键识别成了数字 2。

    仅为了让 PATH 生效通常不必重启整台虚拟机。重新登录即可触发 /etc/profile/~/.profile,也可以在确认文件可信且语法正确后,在当前 Shell 中执行:

    source /etc/profile
    
  • 1.2.1.2.8 例题:交叉编译器测试操作

    视频先照思维导图输入了错误命令:

    arm-linux-gnueabihf-gcc -V
    

    终端明确返回:

    error: unrecognized command line option '-V'
    fatal error: no input files
    compilation terminated.
    

    随后讲师把大写 -V 改为小写 -v,成功显示 Target: arm-linux-gnueabihf 和 gcc version 4.9.4 (Linaro GCC 4.9-2017.01):

    arm-linux-gnueabihf-gcc -v
    

    GCC 官方定义的 -v 会打印版本以及编译阶段的详细信息;若只看简短版本,可用 --version。GCC 没有这里所写的大写 -V 选项。GCC 总体选项文档

    编译测试过程是:

    cd ~/test
    arm-linux-gnueabihf-gcc test.c -o b.out
    ls
    ./b.out
    file b.out
    file a.out
    

    交叉编译成功后,ls 中出现 b.out。在 x86-64 虚拟机直接运行它,终端报错:

    -bash: ./b.out: cannot execute binary file: Exec format error
    

    这是预期的架构不匹配,不是 b.out 没有执行权限。file 命令随后给出关键区别:

    文件视频中的关键识别结果来源
    b.outELF 32-bit LSB executable, ARM, EABI5,动态加载器 /lib/ld-linux-armhf.so.3ARM 交叉 GCC
    a.outELF 64-bit LSB shared object, x86-64,动态加载器 /lib64/ld-linux-x86-64.so.2Ubuntu 本机 GCC

    Ubuntu 的本机 GCC 默认可能生成 PIE 可执行文件,所以 file a.out 显示 shared object 并不等于它是供其他程序链接的普通共享库;它仍可作为主机程序执行。file 会依据文件内容和 ELF 标识判断类型,而不是只看 .out 后缀。file(1) 手册

    b.out 能在开发板运行还需要同时满足:处理器指令集兼容、系统采用 hard-float EABI、根文件系统存在 /lib/ld-linux-armhf.so.3 及所需动态库。arm-linux-gnueabihf 中的 hf 表示 hard-float;该 GNU triplet 对应 32 位 ARM EABI hard-float。Debian ARM 架构说明 若目标根文件系统缺少动态加载器,即使文件存在,也可能得到容易误解的 “No such file or directory”。可进一步检查:

    arm-linux-gnueabihf-gcc -dumpmachine
    arm-linux-gnueabihf-gcc -dumpversion
    file b.out
    readelf -h b.out
    readelf -l b.out | grep 'interpreter'
    

    原笔记说它“只能在 ARM 环境运行”过于绝对:它可以在 ABI 和运行库兼容的 ARM Linux 上原生运行,也可以在正确配置的 QEMU 用户态模拟环境中运行;但视频没有配置模拟器,所以在当前 x86-64 Ubuntu 上直接执行必然失败。

二、完整流程速查

下面把课程原流程整理成一条可核对的链路。若已经按视频创建并上传文件,可从相应步骤继续,不要重复覆盖配置:

# 1. 进入安装位置并创建目录。
cd /usr/local
sudo mkdir arm

# 2. 课程在此处用 FileZilla 把压缩包上传到 /usr/local/arm。
#    不建议 chmod 777;应使用合适的所有权,或先传到家目录再由 sudo 解压。

# 3. 解压课程工具链。
cd /usr/local/arm
sudo tar -xJf gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz

# 4. 按课程路径设置当前会话的 PATH;永久设置时再写入合适的启动文件。
export PATH=/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin:$PATH

# 5. 核验 Shell 找到的程序、目标三元组与版本。
command -v arm-linux-gnueabihf-gcc
arm-linux-gnueabihf-gcc -dumpmachine
arm-linux-gnueabihf-gcc --version

# 6. 编译课程已有的 test.c。
cd ~/test
arm-linux-gnueabihf-gcc test.c -o b.out

# 7. 只检查文件格式;不要在 x86-64 主机上直接把它当本机程序执行。
file b.out
readelf -h b.out

预期至少满足:

  1. command -v 指向 /usr/local/arm/.../bin/arm-linux-gnueabihf-gcc;
  2. -dumpmachine 输出 arm-linux-gnueabihf;
  3. file b.out 显示 32 位 ARM ELF,而不是 x86-64;
  4. 编译器版本显示 Linaro GCC 4.9.4;
  5. 若要上板运行,还需单独验证开发板系统的动态加载器、库和执行权限。

三、本节关键纠正

  • 交叉编译器的定义是 host 与 target 不同,不是“安装在虚拟机里的编译器”。
  • 当前 Ubuntu 虚拟机是 x86-64,开发板为 Arm Cortex-A7;“Intel ARM 处理器”是视频中已经纠正的口误。
  • 压缩包是预编译工具链,不是工具链源码。
  • 工具链及编译器全名分别是 gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf 和 arm-linux-gnueabihf-gcc,不能缩写成 jcc 或 arm-linux-gcc。
  • 环境变量是大写 PATH;查看单个值可用 echo "$PATH" 或 printenv PATH。
  • 思维导图的大写 -V 在视频中实际报错,正确测试参数是小写 -v 或长选项 --version。
  • sudo chmod 777 /usr/local/arm 虽解决了上传问题,却让目录对所有本机用户可写;应改所有权或先上传到家目录。
  • sudo reboot 会重启虚拟机;MobaXterm 的 R 仅重连会话。修改 PATH 通常不需要整机重启。
  • Exec format error 的直接原因是 ARM 与 x86-64 架构不匹配,不是文件名或 .out 后缀问题。
  • file 显示 ARM 只能确认 ELF 架构等元数据,不能单独证明板上动态加载器和运行库齐全。
  • GCC 负责编译目标程序的 C/C++ 等部分;设备树由 dtc 处理,根文件系统由构建流程组装,三者不能混为一谈。

核对依据