本文是交叉编译入门及必要配置方法总结,目的为新手介绍如何进入交叉编译的世界,并附带两个重要列子:
第一个是使用cmake进行交叉编译
第二个是交叉编译Protobuf
交叉编译的目的是在一台架构A主机平台上编译另一种架构B目标平台的二进制文件或者库,交叉编译在目标系统平台(开发出来的应用程序序所运行的平台)难以或不容易编译时非常有用。主要体现在以下四个方面:
执行交叉编译最基本的就是明确自己主机平台和目标平台的架构。
要执行交叉编译就需要有对应的交叉编译工具链,称之为工具链就是编译都是一整套工具,并不是单一的工具。比较熟知的就是gcc/g++编译器和ld连接器。
通常交叉编译工具链命名规则为:arch-core-kernel-system。其中:
arch:目标平台架构,如上文提到的arm,mips等;
core:有两种种情况,第一是CPU Core,如Cortex A8;第二是指定工具链的供应商。如果没有特殊指定,则留空不填。这一组命名比较灵活,有以厂家名称命名的,有以开发者命名的,也有以开发板命名的,或者直接是none或cross的;
kernel: 目标平台的OS,见过的有linux,uclinux,bare-metal(无OS);
system:嵌入式应用二进制接口(Embedded Application Binary Interface),交叉编译工具链所选择的库函数和目标映像的规范,如gnu,gnueabi等。其中gnu等价于glibc+oabi;gnueabi等价于glibc+eabi。若不指定,则也可以留空不填;
上述命名规则并不是统一的规范,使用的时候作为参考就行。我使用的交叉编译工具链名称为:gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu。
获取到对应的交叉编译工具链之后,需要在主机平台配置交叉编译环境。
首先解压交叉编译工具链。一般工具链的目录结构如下:
drwxr-xr-x 7 4096 Dec 4 19:53 aarch64-linux-gnu/
drwxr-xr-x 2 4096 Dec 4 19:55 bin/
-rw-r--r-- 1 11300 Dec 4 19:53 gcc-linaro-7.5.0-2019.12-linux-manifest.txt
drwxr-xr-x 3 4096 Dec 4 19:51 include/
drwxr-xr-x 3 4096 Dec 4 19:55 lib/
drwxr-xr-x 3 4096 Dec 4 19:37 libexec/
drwxr-xr-x 8 4096 Dec 4 19:51 share/
我们重点需要关注的是aarch64-linux-gnu和bin这两个目录。
【aarch64-linux-gnu】可能不同的交叉编译工具链名称不同,主要是平台架构不同,其他名称可能为【arm-linux-gnueabihf】。根据交叉编译工具链的名称不同而不同。
这个目录下的文件结构如下:
drwxr-xr-x 2 4096 Dec 4 19:54 bin/
drwxr-xr-x 3 4096 Dec 4 19:48 include/
drwxr-xr-x 3 4096 Dec 4 19:33 lib/
drwxr-xr-x 3 4096 Dec 4 19:55 lib64/
drwxr-xr-x 7 4096 Dec 4 19:53 libc/
在【aarch64-linux-gnu/bin】目录下有部分交叉编译工具,但是不全。其他几个目录都是必要的依赖库和头文件。我们重点关注的是【aarch64-linux-gnu/libc】,以后编译的其他依赖都需要安装在这个目录下的usr目录。因为这个目录类似于Linux系统的根目录,文件结构如下:
drwxr-xr-x 2 4096 Dec 4 19:41 etc/
drwxr-xr-x 3 4096 Dec 4 19:55 lib/
drwxr-xr-x 2 4096 Dec 4 19:41 sbin/
drwxr-xr-x 10 4096 Dec 25 17:37 usr/
drwxr-xr-x 3 4096 Dec 4 19:41 var/
在【aarch64-linux-gnu/libc/usr】目录下有lib和include目录。在交叉编译其他依赖的时候,指定的安装目录【–prefix】一般指定到这个目录中。
在交叉编译工具链文件夹根目录下的【bin】文件夹是全部的交叉编译工具。比如gcc、g++编译器。可以在这个文件夹下执行以下编译器二进制文件,查看下版本,看看交叉编译器是否可用。
我们需要做的是将这个文件夹路径添加到环境变量PATH中:
export PATH=$PATH:/path/to/your/crosscompile-toolchain/bin
首先我们以一个传统的例子为例:
#include
void main()
{
printf("Hello world!!\n");
}
aarch64-linux-gnu-gcc helloworld.c -o helloworld
然后会生成一个可执行程序helloworld。你直接运行的话会报以下错误:
-bash: ./helloworld: cannot execute binary file: Exec format error
提示可执行程序格式错误。这是正常的。接着我们拷贝到目标平台上执行以下,检测是否正确输出“Hello world!!”。
这是基本的编译过程,稍微复杂点的也是一样,比如连接库文件的时候。只不过都需要使用交叉编译工具链来完成。
接下来我们讲两个复杂例子,第一个是使用cmake时的交叉编译方式,第二个是Protobuf的交叉编译。
在原项目使用的是cmake编译或者希望使用cmake进行交叉编译的时候。需要有几个变量进行定义。
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_SYSROOT /home/devel/rasp-pi-rootfs)
set(tools /home/devel/gcc-4.7-linaro-rpi-gnueabihf)
set(CMAKE_C_COMPILER ${tools}/bin/arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER ${tools}/bin/arm-linux-gnueabihf-g++)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
下面逐个介绍如下:
【CMAKE_SYSTEM_NAME】指定目标平台系统名称
【CMAKE_SYSTEM_PROCESSOR】指定目标平台CPU架构
【CMAKE_SYSROOT】这个很重要了,这个需要填写的是刚才交叉编译工具链根目录下的【aarch64-linux-gnu/libc】目录,这个目录当时介绍的时候说它类似主机平台Linux系统根目录,在这里指定的话,cmake交叉编译中会在这个目录下寻找安装的其他目标平台依赖程序。
【tools】【CMAKE_C_COMPILER】【CMAKE_CXX_COMPILER】这三个变量主要是为了指定交叉编译工具的gcc、g++位置。
【CMAKE_FIND_ROOT_PATH_MODE_PROGRAM】以及下面的【X_LIBRARY】【X_INCLUDE】【X_PACKAGE】三个变量都是指示CMake在执行find_X命令时的行为。有三个可选项,分别是NEVER,ONLY,BOTH。
上面的变量有两种方式跟原先的CMakeLists结合:
注:推荐第二种方式,但是第二种方式在进行交叉编译的时候,执行cmake容易出现无限循环检测,直到发送错误。
另外cmake在寻找依赖的时候一般去刚才指定的CMAKE_SYSROOT这个目录中找,通常是交叉编译工具链的【aarch64-linux-gnu/libc】目录。因此交叉编译的依赖最好都安装在这个目录中。
这里额外介绍Protobuf的原因是,在交叉编译二进制的时候有时候需要对应主机平台的二进制协助,而且最好是版本一致的。Protobuf就是这样的,还有libmysqlclient。
所以首先先编译一个主机平台的Protobuf,然后再开始目标平台的编译。如果主机平台的Protobuf之前就有就可以直接用(版本最好一致)。
目标平台的编译过程:
./configure --host=aarch64-linux-gnu CC=aarch64-linux-gnu-gcc CXX=aarch64-linux-gnu-g++ --with-protoc=/usr/bin/protoc --prefix=/path/to/your/toolchain/aarch64-linux-gnu/libc/usr
参数解释:
【–host】:就是用【config.guess】获取的;
【CC】【CXX】:交叉编译工具链gcc和g++名称;
【–with-protoc】:已经编译好的主机平台protoc二进制文件路径;
【–prefix】:交叉编译后安装目录。
交叉编译的入门介绍就结束了。其实主要就几个步骤:
其实第三步还是比较复杂的,不同的软件各有不同,但是也是有一些共通的地方,就是:
上文整体上是介绍C/C++ 为主要方向的交叉编译过程。其实还有很多其他语言也是需要交叉编译的,比如go,rust。基本的理念是一致的,就是在编译的时候告知目标平台架构。
go语言的交叉编译比较简单,主要涉及GOOS和GOARCH两个参数,GOOS设置目标平台操作系统,GOARCH设置目标平台架构:
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build
这里不展开讲了,大家遇到的话可以针对性的查。
以上就是全部内容了,觉得有用就点赞啊!!!