交叉编译

背景

linux下支持x86版本和arm版本的c++程序,每次需要在指定平台进行编译,希望能够统一平台。

比如在linux上可以编译linux和arm的程序。

编译平台和运行平台不同

目标

一个平台可以打出多个平台的包

技术

交叉编译

原理:

1 为什么要交叉编译

在开发过程中有宿主机目标机的角色之分:

  • 宿主机是执行编译链接的计算机
  • 目标机是运行程序的硬件平台。

可能会有以下场景需要进行交叉编译:

1、目标平台不具备编译环境:有时候,目标平台(例如嵌入式设备或移动设备)可能没有足够的计算资源或存储空间来支持源代码的本地编译,或者就不允许安装编译器,或者目的平台还没有建立,连操作系统都没有,根本谈不上运行什么编译器。在这种情况下,使用一台性能更强大的主机进行交叉编译可以更高效地生成目标平台上可执行的代码。

2、平台兼容性:不同的体系结构和操作系统具有不同的指令集和库,因此无法直接在一个平台上运行另一个平台的代码。通过交叉编译,可以将源代码编译为适用于目标平台的二进制文件,以确保代码在目标平台上正确运行。

3、开发效率:在软件开发过程中,通常会使用高性能的开发主机来编写、调试和测试代码。然而,最终的目标可能是在低功耗设备或嵌入式系统上运行代码。通过交叉编译,可以在开发主机上进行快速的开发、调试和测试,然后将生成的可执行文件部署到目标平台上,提高开发效率。

4、跨平台开发:对于跨平台应用程序或库的开发,交叉编译是一种常见的方法。它允许在一台主机上编译和构建适用于多个目标平台的代码,从而简化了跨平台开发的复杂性。

2 交叉编译工具链

2.1 通用命名规则

在使用交叉编译链时,常常会看到下面这样的名字:

arm-linux-gnueabihf-
arm-none-linux-gnueabi-
arm-cortex_a8-linux-gnueabi-
mips-malta-linux-gnu-

规则示例和说明如下:

arch-vender-os-abi
  • arch:目标cpu架构,比如mips、arm、x86、riscv等,该字段通常不会省略。
  • vendor:提供此编译工具链的厂商名称或是厂商特定信息,该字段只是标识信息,没有实际意义,可以为none、cross、unknow或是直接省略。
  • os:目标设备上运行的操作系统,常见的有linux、none(裸机)等。
  • abi:应用程序二进制接口(Application Binary Interface),交叉编译链所选择的库函数和目标映像的规范,该字段常见的值有abi 、eabi(embedded abi)、gun(glibc+oabi)、gnueabi(glibc+eabi)、gnueabihf (hf 指默认编译参数支持硬件浮点功能)等。

2.2 gcc交叉编译工具链

2.2.1 命名规则

命名约定通常使用目标体系结构作为前缀,并在后面加上"-"和ABI(Application Binary Interface)标识符

arch [-vendor] [-os] [-(gnu)eabi]-gcc

带 [] 的是可选部分。

比如 arm-linux-gnueabi-gcc ,arm-none-eabi-gcc, aarch64-linux-gnu-gcc

  • arch: 芯片架构,比如 32 位的 Arm 架构对应的 arch 为 arm,64 位的 Arm 架构对应的 arch 为 aarch64。
  • vendor :工具链提供商,大部分工具链名字里面都没有包含这部分。
  • os :编译出来的可执行文件(目标文件)针对的操作系统,比如 Linux。
  • 另外需要补充一点的是,32 位的 Arm 和 64 位的 Arm,它们的指令集是不同的,所以需要使用不同的工具链。当然,Arm64 为了保证前向兼容,提供了一个 32 位的兼容模式,所以我们用 arm-linux-gnueabi-gcc 编译的应用程序也是可以直接在Arm64 的系统上运行的,但是 Linux Kernel 和 U-Boot 就不行,除非你提前把 CPU 切换到 32 位模式。

示例:

1. arm-linux-gnueabi-gcc:用于ARM体系结构的交叉编译工具链。"arm"表示ARM体系结构,"linux"表示目标操作系统为Linux,"gnueabi"表示使用GNU的嵌入式ABI。
2. arm-linux-gnueabihf-gcc:与上面的工具链类似,但是添加了"hf"表示使用硬浮点(hard-float)的ABI。这意味着该工具链支持使用硬件浮点指令进行浮点运算。
3. x86_64-linux-gnu-gcc:用于x86-64体系结构(也称为AMD64或Intel 64)的交叉编译工具链。"x86_64"表示目标体系结构为x86-64,"linux"表示目标操作系统为Linux,"gnu"表示使用GNU的ABI。
4. mips-linux-gnu-gcc:用于MIPS体系结构的交叉编译工具链。"mips"表示MIPS体系结构,"linux"表示目标操作系统为Linux,"gnu"表示使用GNU的ABI。
这些工具链提供了gcc、g++等编译器和相关工具,可以用于编译C、C++等源代码,并生成适用于特定体系结构的可执行文件。您可以根据目标体系结构和所需的ABI选择适当的GCC交叉工具链。

2.2.2 组成

以下以工具链gcc-arm-8.2-2018.08-x86_64-aarch64-linux-gnu.tar.xz为示例:

解压后,目录如下所示:

|---aarch64-linux-gnu/			 <----特定于 ARM 架构的交叉编译工具链的二进制文件、库和头文件
|---bin/						<----Binutils:一组用于编译、汇编、链接等操作的工具集合
	|---aarch64-linux-gnu-gcc
	|---aarch64-linux-gnu-g++
	|---...
|---include/					<----和 C++ 标准库的头文件,以及 GCC 内部使用的头文件
|---lib/					    <----这三个lib通常包含库文件和辅助程序
|---lib64/
|---libexec/
|---share/                        <----包含一些额外的数据文件,如语言文件、man pages、GCC 的插件和配置脚本等

和程序编译链接等相关的gcc,binutils等工具按照先编译后链接等相关的编译程序的内在逻辑串起来,就成了我们所说的:工具链。工具链主要包括Binutils(汇编工具)、GCC(编译器)和Glibc(标准C函数库),主要用于把源代码编译连接生成可执行程序。

1 Binutils

Binutils 是一组用于编译、汇编、链接等操作的工具集合,是 GNU 工具链的一部分。它提供了一系列的工具,用于处理二进制文件,包括可执行文件、目标文件、共享库等。

常用交叉工具

|------交叉编译器 arm-linux-gcc

|------交叉链接器 arm-linux-ld

|------交叉转换器 arm-linux-readelf

|------交叉ELF文件工具 arm-linux-objdump

|------交叉反汇编器 arm-linux-objcopy

安装Binutils工具包含的程序有 addr2line、ar、as、c++filt、gprof、ld、nm、objcopy、objdump、ranlib、readelf、size、 strings、strip、libiberty、libbfd和libopcodes。

对应bin下的工具:这些都是x86格式的

aarch64-linux-gnu-addr2line
aarch64-linux-gnu-ar
aarch64-linux-gnu-as
...
  • ar:创建、修改和提取归档文件(静态库)中的成员文件。归档文件是包含多个文件内容的一个大文件,其结构保证了可以恢复原始文件内容。
  • as(汇编器): 将汇编语言代码转换为目标文件。它将 GNU C 编译器 gcc 输出汇编语言源文件转换为目标文件的工具。它将汇编代码翻译成机器码,生成目标文件,供后续链接器 ld 链接使用。
  • c++/g++:c++编译器。
  • gcc:C编译器,gcc-8.2.1为特定版本。
  • gfortran:Fortran 编译器
  • ld(链接器): 将多个目标文件和库文件链接成一个可执行文件或共享库的工具。它负责解析目标文件之间的依赖关系,并将它们合并成一个完整的可执行文件或共享库。通常,建立一个新编译程序的最后一步就是调用 ld。
  • nm: 用于列出目标文件中的符号表信息的工具。它可以显示目标文件中定义的符号、函数和变量的名称、地址等信息。
  • objcopy: 用于复制和转换目标文件的工具。它可以将目标文件的内容复制到另一个文件中,并可以进行格式转换、剪切、粘贴等操作。可以用于处理目标文件,包括可执行文件、目标文件和共享库等。
  • objdump: 用于反汇编目标文件和可执行文件的工具。它可以将二进制文件反汇编为汇编语言代码,方便开发人员进行调试和分析。。
  • readelf: 显示elf格式可执行文件的信息。
  • size:列出目标文件每一段的大小以及总体的大小。默认情况下,对于每个目标文件或者一个归档文件中的每个模块只产生一行输出。
  • strings:打印某个文件的可打印字符串,这些字符串最少4个字符长,也可以使用选项-n设置字符串的最小长度。默认情况下,它只打印目标文件初始化和可加载段中的可打印字符;对于其他类型的文件它打印整个文件的可打印字符。这个程序对于了解非文本文件的内容很有帮助。
  • strip: 用于删除目标文件中的符号表和调试信息的工具。它可以减小目标文件的体积,提高执行效率。
2 GCC(编译器)

交叉编译器

3 Glibc

C函数库

GNU C Library:提供了许多 C 语言标准库函数的实现,这些函数包括了文件操作、字符串处理、内存管理、数学运算等等。

在执行辅助命令make命令时,会调用工具链里的编译器GCC进行编译,使用汇编器Binutils链接到C函数库Glibc,将源代码转换成可执行程序。

Glibc是C函数库是内核与应用程序的中间部分,主要提供C函数库文件。

安装Glibc就是在/lib安装一系列的库文件,直接在链接阶段就将库文件链接到可执行文件中,叫静态库。静态库文件是/lib目录下的.a文件;在程序运行时才被载入叫动态库,动态库文件是/lib下的.so文件。

Glibc是C函数库,Linux的命令执行过程中都要调用Glibc,其他的函数库也会调用Glibc,Glibc再去调用系统中的内核,内核再进行资源分配。Gcc和Binutils是应用程序,会引用里面的C函数库的库文件,同时使用工具链编译出来的程序软件也会调用Glibc函数库里的库文件。

arm下的动态库

在 ARM 架构下,glibc 库通常存放在 /lib/lib64 目录中,具体取决于系统的配置和版本。这些目录包含了标准 C 库、数学库以及其他与操作系统交互的库文件。通常,这些库文件的名称以 libc.solibm.so 等命名,分别对应标准 C 库和数学库。

在典型的 Linux 系统中,glibc 库可能会存放在以下位置之一:

  • /lib:这是默认的库存放目录,通常包含与操作系统的核心功能相关的库文件。
  • /lib64:在一些 64 位系统中,特别是在 x86_64 架构下,标准库文件可能会存放在 /lib64 目录中。
  • /usr/lib/usr/lib64:在某些系统中,用户安装的软件可能会将库文件存放在 /usr/lib/usr/lib64 目录中,这些库文件通常是非系统核心的。

要查找 glibc 库的确切路径,可以使用 find 命令或 locate 命令来搜索,例如:

find /lib /lib64 /usr/lib /usr/lib64 -name "libc.so*"

这将在 /lib/lib64/usr/lib/usr/lib64 目录中搜索以 libc.so 开头的文件,从而找到 glibc 库的位置。

3 使用示例

交叉编译器的安装方法,通常交叉编译器的打包发布方式有关:

①如果以 deb 包形式发布,则需要用 dpkg 命令进行安装。示例命令:

dpkg -i package.deb

②如果以 bin 方式打包发布,通常则需要为该文件加上可执行权限,然会运行这个文
件,完成安装。示例命令:

chmod +x package.bin
./package.bin

③如果以.tar.bz2 压缩包方式发布,则只需在某个目录下进行解压即可。

 tar xjvf package.tar.bz2

以上 3 条命令中,在实际中须将 package 替换为实际文件名称。

由于以 deb 或者 bin 方式发布的工具链对不同版本的操作系统适应性较差,所以大多数
都采用.tar.bz2 这样的压缩包形式发布。

目标平台是64位arm,小端

字节序的判断方式如下,运行程序查看

#include <stdio.h>

int main() 
{
    unsigned int num = 1;
    char *ptr = (char*)&num;

    if (*ptr == 1)
        printf("该系统是小端字节序(Little Endian)\n");
    else
        printf("该系统是大端字节序(Big Endian)\n");

    return 0;
}

使用压缩包方式如下:

1、下载arm-linux-gcc-3.4.1.tar.bz2到任意的目录下

网址:https://releases.linaro.org/components/toolchain/binaries/

选择:latest-7/aarch64-linux-gnu/下的gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz

下载下来是:gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar

2、解压 gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar

tar -xvf gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar

3、修改环境变量,把交叉编译器的路径加入到PATH

此处交叉编译器路径为:/home/work/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin

① 方法一:临时设置系统环境变量,是通过 export 命令,将交叉编译器的路径添加到系统 PATH 环境变量中。用法(多个值之间用冒号隔开):

export PATH=$PATH:/交叉编译器路径

使环境变量生效

source /root/.bashrc

局限:只能对当前终端有效,关闭终端再次打开将会失效,需要重新设置

② 方法二:修改全局配置文件

/etc/profile 是系统全局的配置文件,在该文件中设置交叉编译器的路径,能够让登录本机的全部用户都可以使用这个编译器。

打开终端,输入“sudo vi /etc/profile”命令,打开/etc/profile 文件,在文件末尾添加:

export PATH=$PATH:/交叉编译器路径

使环境变量生效

source /etc/profile

输入“. /etc/profile”(点+空格+文件名),执行 profile 文件,使刚才的改动生效。

③ 方法三:修改用户配置文件

“/etc/profile”是全局配置文件,会影响登录本机的全部用户。如果不希望影响其他用户,也可以只修改当前用户的配置文件,通常是“/.bashrc”或者“/.bash_profile”。

进行以上修改后,可以使用命令进行验证是否生效:打开终端,输入 aarch64-linux-gnu-,然后按键盘 TAB键,同样可以看到很多 aarch64-linux-gnu-开头的命令。也可输入echo $PATH检查路径是否已经被添加进去。

4、测试是否安装成功

aarch64-linux-gnu-gcc -v

④编写示例程序验证

hello.c

#include <stdio.h>

int main()
{
   printf("Hello World!\n");
   return 0;
}

⑤编译

aarch64-linux-gnu-gcc -o hello hello.c

查看生成可执行程序信息

# 输入
file hello
# 输出
hello: ELF 64-bit LSB executable, ARM aarch64, version7412 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, BuildID[sha1]=b7b57bfc9819ac5ab88752832fe918d977fdfd35, with debug_info, not stripped

⑥目标机器上运行

输出:Hello World!

4 cmake如何使用交叉编译

1、基础cmake程序

①目录结构

test
|---main.cpp
|---CMakeLists.txt

main.cpp

#include <iostream>
using namespace std;

int main()
{
    cout << "第一个CMake程序" << endl;
    return 0;
}

CMakeLists.txt

# CMake 最低版本号要求
cmake_minimum_required (VERSION 3.1)

# demo 代表项目名称
project (demo)

# 添加一个可执行程序,main是可执行程序名称,main.cpp是源文件
add_executable(main main.cpp)

②编译

mkdir build
cd build
cmake ..
make

2、使用交叉工具链

目标:编译arm64位的程序

如果你使用的是 cmake 工具来进行编译,那么你需要指定如下内容:

  • 目标系统:Linux —— 对应变量 CMAKE_SYSTEM_NAME
  • 目标架构:arm —— 对应变量 CMAKE_SYSTEM_PROCESSOR
  • gcc编译器: aarch64-linux-gnu-gcc —— 对应变量 CMAKE_C_COMPILER
  • g++编译器: aarch64-linux-gnu-g++ —— 对应变量 CMAKE_CXX_COMPILER

①在CMakeLists.txt 同级目录下新建一个 toolchain.cmake 文件

# 指定目标系统
set(CMAKE_SYSTEM_NAME Linux)
# 指定目标平台
set(CMAKE_SYSTEM_PROCESSOR arm)
 
# 指定交叉编译工具链的根路径
set(CROSS_CHAIN_PATH /home/work/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu)
# 指定C编译器
set(CMAKE_C_COMPILER "${CROSS_CHAIN_PATH}/bin/aarch64-linux-gnu-gcc")
# 指定C++编译器
set(CMAKE_CXX_COMPILER "${CROSS_CHAIN_PATH}/bin/aarch64-linux-gnu-g++")

②最后在编译的时候我们只需要告诉cmake上述文件的位置即可

mkdir build && cd build
# 格式:cmake -DCMAKE_TOOLCHAIN_FILE=刚才的编译配置文件 ..
cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain.cmake .. 

5 boost交叉编译

x86下系统安装boost,生成在系统默认路径中,cmake不用指定也可直接获取到头文件和动态库

也可直接放置在系统中:以ubuntu为例

copy -r boost_1_65_1/boost /usr/include/boost/

copy -r boost_1_65_1/stage/lib /usr/local/lib/boost/boost_1_65_1/stage/lib/

设置环境变量:配置文件

export LIBRARY_PATH=$LIBRARY_PATH:/usr/local/lib/boost/boost_1_65_1/stage/lib/

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib/boost/boost_1_65_1/stage/lib/

使用arm进行交叉编译时需要指定头文件和动态库

1、需要编译boost下的动态库

版本:boost_1_65_1

手段:使用交叉编译工具编译出arm版本的动态库

下载:https://sourceforge.net/projects/boost/files/boost/1.65.1/boost_1_65_1.tar.gz

# 解压
tar -xvf boost_1_65_1.tar.gz
# 切换到里面的目录
cd boost_1_65_1

使用以下脚本进行安装:主要用途:更改编译器并编译

#!/bin/sh

#编译工具路径
QL_CROSSTOOLS=/home/work/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu

#SYSROOT路径
QL_SYSROOT=/home/work/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/aarch64-linux-gnu/
COMPILER_SYSROOT="--sysroot=$QL_SYSROOT"

#编译工具
COMPILER_CC=$QL_CROSSTOOLS/bin/aarch64-linux-gnu-gcc

#编译选项
#COMPILER_FLAGS="-march=armv7-a -marm -mfpu=neon -mfloat-abi=hard"
COMPILER_FLAGS="";

#安装位置
COMPILER_PREFIX=$(pwd)/../target_cross

rm -rf project-config.jam*
./bootstrap.sh --with-libraries=all --prefix=$COMPILER_PREFIX

#将gcc替换成交叉编译工具的gcc并添加编译选项和sysroot路径
# 实践发现增加COMPILER_SYSROOT会报错
# sed -i "/using gcc/c using gcc : : $COMPILER_CC $COMPILER_FLAGS $COMPILER_SYSROOT ;" project-config.jam
sed -i "/using gcc/c using gcc : : $COMPILER_CC ;" project-config.jam

#./b2 install variant=release abi=aapcs address-model=32 architecture=arm binary-format=elf threading=multi toolset=gcc 
./b2

如果不使用脚本:需要手动修改配置

# 生成配置
./bootstrap.sh
# 修改生成的配置文件project-config.jam中的using gcc : : 那一行为:特别注意空格不能省
using gcc : : /home/work/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc ;
# 编译库
./b2

编译结果

头文件:boost
库文件:stage/lib #file查看动态库,确实是arm架构

总结

注意选取交叉编译工具链的gcc版本、libc.so版本、libstdc++.so版本

相关版本选择参照:Arm GNU Toolchain Downloads – Arm Developer

实际项目使用中选取的gcc-arm-8.2-2018.08-x86_64-aarch64-linux-gnu.tar.xz(libc.so.2.28版本需要),并替换了其中libstdc++.so.6.0.24为需要的库(报错:缺少GLIBCXX依赖)

交叉编译工具设置编译器后,之所以通常不需要再单独设置头文件和库文件的路径,是因为交叉编译工具链本身已经包含了这些路径信息。交叉编译工具链是一个专门为交叉编译设计的工具集合,它包含了编译器、链接器、库文件以及头文件等必要组件,并且这些组件都已经被配置为能够针对目标平台(而非主机平台)进行编译。
在安装交叉编译工具链时,这些路径通常会被自动配置好。编译器在编译源代码时,会根据工具链中的配置信息自动查找所需的头文件和库文件。因此,一般情况下,用户只需要设置好交叉编译器的路径,并确保工具链被正确安装和配置,就可以开始进行交叉编译,而无需额外设置头文件和库文件的路径。
然而,需要注意的是,如果项目使用了非标准路径下的头文件或库文件,或者需要链接特定的库版本,那么可能仍然需要手动指定这些路径。此外,不同的交叉编译工具链和不同的项目可能有不同的配置要求,因此在实际使用中,用户可能需要根据具体情况进行调整和配置。

参考

1、交叉编译工具链命名规则

2、安装交叉编译工具链

3、Linux 系统下 CMake 示 例_linux cmake

posted on 2024-03-21 14:00  circlelll  阅读(174)  评论(0编辑  收藏  举报