交叉编译踩坑
本文假定所使用的是 Debian 系的系统,且使用
apt
作为包管理器。对于其他的系统/其他的包管理器,可能需要做相应的调整。
Always remember: Google is your best friend!
1. 交叉编译的基本概念
通常我们写的程序是在本机上编译运行的,这样的程序称为本地程序。而在开发嵌入式系统,或者运行平台和本地不一致的时,我们需要将程序编译成目标平台上可以运行的程序,这样的程序称为交叉编译程序。
一般来说,编译器识别平台所用的是一个三元组,即 arch-vendor-os
,例如我们通常的 x86
,我们可以使用 clang -v
来查看
1 | clang -v |
可以看到,当前的机器三元组为 x86_64-pc-linux-gnu
,即 arch=x86_64
,vendor=pc
,os=linux-gnu
。
交叉编译中,另外一个重要的概念是 build-host-target
,即编译器的构建平台、主机平台和目标平台。
- build: 构建平台,用来构建编译器的平台。
- host: 主机平台,即执行编译器的平台。
- target: 目标平台,即编译器生成的程序所运行的平台。
例如,我们在 x86_64
的机器上编译一个 arm
的程序,那么 build=x86_64
,host=x86_64
,target=arm
。
有以下几种比较常见的搭配:
- build==host==target:即本地编译,编译器和程序运行在同一平台。
- build==host!=target:即交叉编译,编译器和程序运行在不同平台。
- build!=host==target:即交叉编译工具链,编译器运行在不同平台,但生成的程序运行在本地。
2. 交叉编译的基本流程
当然通常情况下,上述的情况我们遇到最多的是第二种,即交叉编译。下面我们以交叉编译一个 arm
的程序为例,来介绍交叉编译的基本流程。
2.1. 安装交叉编译工具链
在 Linux
中,大部分的交叉工具都是前缀的区别,换言之,就是一个 arch-vendor-os
的前缀。例如 arm64
的 gcc
,前缀为 aarch64-linux-gnu-
,arm
的 gcc
,前缀为 arm-linux-gnueabi-
。
对于 Debian 系的系统,我们可以使用 apt
来安装交叉编译工具链,例如:
1 | sudo apt install binutils-aarch64-linux-gnu gcc-arm-linux-gnueabihf gcc-aarch64-linux-gnu -y |
其中的 binuils
是一些工具,例如 as
,ld
等,gcc
是编译器,同时 g++
,gdb
等也可以安装。
2.2. 目标库的安装
很多时候我们所编写的程序会依赖一些其他的库,最基础的 libc
,还可能有 libm
,libpthread
等。这些库在不同的平台上可能有不同的实现,所以我们需要安装目标平台的库。
一般对于 Debian 系,默认只启用本地的库,所以我们需要手动安装目标平台的库,以 aarch64
为例
1 | sudo dpkg --add-architecture arm64 |
之后是设置对应的镜像源,这里以 Ubuntu
为例,因为 Ubuntu
的 arm
的镜像组织形式和默认的差别较大,所以我们需要手动设置
2.2.1. 对于 Ubuntu 22.04 即之前的
对于这个版本,大部分仍然采用的是 sources.list
单行的形式,所以我们可以直接修改 /etc/apt/sources.list
文件,添加对应的源
例如对于 Ubuntu 18.04,采用清华的镜像源,对应的 /etc/apt/sources.list
文件如下
1 | deb [arch=amd64] http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic main restricted universe multiverse |
请务必为之前的本地源添加 arch=amd64
的标记,因为 Ubuntu
将 arm
部分的放在了 ubuntu-ports
下,不添加的话 apt
会尝试去 ubuntu-ports
下寻找 amd64
的包,导致错误。
2.2.2. 对于 Ubuntu 22.04 之后的
在这后面的版本中,Ubuntu
采用了 DEB822
的格式,我们可以直接新建一个 /etc/apt/sources.list.d/aarch64-cross-compile.sources
的源文件,内容如下
这里以 Ubuntu 24.04
为例
1 | Types: deb |
在按照上面的步骤添加源之后,我们可以使用 apt
来刷新源并安装对应的库,例如我们需要安装对应的 libc
库
1 | sudo apt update |
2.3. 对于不在官方源中的库
有时候我们并不一定能够从官方源中找到我们需要的库,这时候我们可能需要自行去编译这些库,一般来说,大部分开源的库都会提供对应的 configure
脚本,我们可以使用这个脚本来编译对应的库。
例如,我们可以使用:
1 | ./configure --host=aarch64-linux-gnu --prefix=/opt/aarch64/sysroot/usr |
这样会将库安装到 /opt/cross/aarch64-linux-gnu
下,我们可以使用 --prefix
来指定安装的位置。
2.4. 编译我们的程序
在安装好了交叉编译工具链和目标库之后,我们就可以开始编译我们的程序了,例如我们有一个简单的 hello.c
程序
1 |
|
其中的 extern_lib
假设放在 /opt/aarch64/sysroot/usr/lib
下,我们可以使用如下的命令来编译
1 | aarch64-linux-gnu-gcc hello.c -o hello --sysroot=/opt/aarch64/sysroot -lextern |
这里的 --sysroot
用来指定我们的目标库的位置,这样编译器就会去这个位置寻找对应的库,具体来说,编译器的查找路径为
默认 | --sysroot=<dir> |
|
---|---|---|
头文件搜索路径 | /usr/include |
<dir>/usr/include |
依赖库搜索路径 | /usr/lib |
<dir>/usr/lib |
通过 sysroot
能够很方便的指定我们的目标库的位置,这样我们就可以很方便的编译我们的程序了。
3. 对于自己写的库
假设你使用的是 GNU Make
作为你的构建系统,那么你可以参考我的方式,来管理交叉编译
1 | CC := gcc |
这样默认情况下,我们的编译器是本地的,如果我们需要交叉编译,只需要设置 CROSS_COMPILE
和 SYSROOT
即可。
因为大部分交叉编译工具仅仅是前缀的区别,所以我们可以很方便的通过设置 CROSS_COMPILE
来实现交叉编译。
例如,对于 aarch64
的编译,可以使用 make CROSS_COMPILE=aarch64-linux-gnu-
来完成
4. 最后
当然,交叉编译的坑还有很多,这里只是一个简单的入门,更多的内容还需要自己去摸索,希望这篇文章能够帮助到你。