交叉编译器
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/Linux | b.out 将来要运行的平台 |
| 本地编译 | x86-64 → x86-64 | Ubuntu 的 gcc test.c 生成 a.out |
| 交叉编译 | x86-64 → ARM | arm-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 公式化流程交叉编译器的安装方法
视频思维导图把过程归纳为七步:
- 建立
/usr/local/arm; - 把压缩包传入该目录;
- 解压;
- 把工具链的
bin目录加入PATH; - 重启系统并重新连接 SSH;
- 查看编译器版本;
- 编译
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 pwdpwd的画面输出是/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 $PATHenv和不带参数的printenv会列出导出的环境变量,因此画面输出很长。只检查PATH时更直接的命令是:printenv PATHPATH的多个目录用冒号:分隔。它保存的是目录列表而非某个编译器文件名。
-
-
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/binVim 的保存退出命令是
: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 -vGCC 官方定义的
-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
预期至少满足:
command -v指向/usr/local/arm/.../bin/arm-linux-gnueabihf-gcc;-dumpmachine输出arm-linux-gnueabihf;file b.out显示 32 位 ARM ELF,而不是 x86-64;- 编译器版本显示 Linaro GCC 4.9.4;
- 若要上板运行,还需单独验证开发板系统的动态加载器、库和执行权限。
三、本节关键纠正
- 交叉编译器的定义是 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处理,根文件系统由构建流程组装,三者不能混为一谈。
核对依据
- 视频:
videos/328.3.1交叉编译器.mp4 - 转录:
transcripts/328.3.1交叉编译器.txt、transcripts/328.3.1交叉编译器.srt - 课程工具链:
源码/5、ARM-Linux完全入门/代码&源代码/交叉编译器/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz - GCC:Invoking GCC
- GCC:Overall Options
- GNU Bash:Startup Files
- GNU Coreutils:Numeric Modes
- NXP i.MX6UL 官方资料
- NXP i.MX6ULL 官方资料