二. LLVM交叉编译

发布时间 2023-07-12 11:22:58作者: 种玫瑰的小刘

 

前言:交叉编译最重要的是生成具有与编译机不同架构的指令,除此之外,编译过程还需要完整的工具链,包括编译器、链接器、库、头文件等。

  GCC会针对每个编译主机和目标架构提供一套完整的套件,包含了二进制、头文件和库等。所以一般使用起来比较简单,下载对应的安装包,解压到一个合适的目录就可以使用了,编译器会使用自带的头文件和库。

  不过 LLVM 有所不同,LLVM 是复用一套编译系统负责多个目标架构的编译任务,通过 -target 选项来区分。这样的好处是开发者可以维护一套编译环境即可编译多个平台和架构的程序。但是这样会给开发者带来使用上的麻烦,因为要不同的目标架构和系统的程序会需要链接不同的库,而编译器需要足够的参数被告知去哪里寻找这些库和头文件的位置。不仅如此,这些编译所需的库通常也需要开发者自己去准备。


1. 准备工作

  如果希望进行交叉编译,就需要我们的编译器支持目标架构,即拥有对应的控制台。LLVM支持哪些目标架构是在其编译的时候决定的,而前一章在编译LLVM的时候我们,并没有指定相关的参数,即 LLVM_TARGETS_TO_BUILD。不过该参数有一个默认值 LLVM_ALL_TARGETS,即所有支持的架构,所以我们编译的 LLVM 说明已经是支持很多架构的了。

如果想知道我们的LLVM支持哪些架构,可以通过llc -version命令查看

注:ARM是32位的arm架构,AARCH64和ARMV64是64位的arm架构,thumb是16位的arm架构,X86包含了32位和64位,而arm架构系列又分了大端和小端,于是出现了这么多的组合。

查看Target目录

cd llvm/lib/Target/
ls #查看文件目录

从目录结构可以看出,这么多目标架构,实际上总共就这几个实现,即ARM列的大端和小端,X86系列的32位和64位,实际上是同一套代码实现的。

ls ARM/进入ARM文件夹查看

可以看出,thumb架构实际上是合用了ARM 32-bit的实现。如果参数LLVM_TARGETS_TO_BUILD只设置ARM,编译结果支持了ARM和Thumb的大端和小端,即arm 32-bit和16-bit系列共四种架构。

接下来我们测试一下是否可以在X86环境下编译出arm64的程序。

2. 交叉编译 X86→ARM

编写一个C程序

#include <stdio.h>

int main()
{
    int a = 1;
    int b = 2;
    printf("%d + %d = %d\n", a, b, a + b);
    return 0;
}

使用 Clang 进行交叉编译需要用 -target 参数来指定编译目标,这里我们使用 aarch64-linux-gnu,即所谓的三段式目标表示形式。

clang -target aarch64-linux-gnu cctest.c -o cctest

报错

错误是因为编译器找不到某些头文件。我们前面文已经提到过,交叉编译环境需要目标架构所需的相关库和头文件。这部分应该由硬件厂商提供,我们需要自行获取。那么我们需要获取哪些库呢?无非是C/C++标准库和链接器,我们尝试通过apt的搜索命令apt search arm64来查询。

根据当前环境的GCC版本,需要安装以下库

sudo apt install binutils-aarch64-linux-gnu 
sudo apt install cpp-5-aarch64-linux-gnu 
sudo apt install g++-5-aarch64-linux-gnu 
sudo apt install linux-libc-dev-arm64-cross 
sudo apt install libstdc++-5-dev-arm64-cross 
sudo apt install libstdc++6-5-dbg-arm64-cross 
sudo apt install libstdc++6-arm64-cross 
sudo apt install libgcc-5-dev-arm64-cross 
sudo apt install crossbuild-essential-arm64

继续

clang -target aarch64-linux-gnu --sysroot=/usr/aarch64-linux-gnu cctest.c -o cctest
# Clang通过--sysroot=/pat来获取安装的库的位置

报错链接器找不到某个.o文件

通过llvm开发的链接器 lld 解决这个问题,首先下载lld,解压到/llvm/tools,接下来重新编译llvm,注意此时需要将编译目录清空,否则不会把新加的代码引入编译

重新编译之后再回来。这次我们需要用lld需要使用参数 -fuse-ld=lld 来指定

 

clang -target aarch64-linux-gnu --sysroot=/usr/aarch64-linux-gnu -fuse-ld=lld cctest.c -o cctest

用了 lld 似乎完全没有变化,问题还在。不过我们可以看到,这里说找不到的文件,crtbegin.o 在 /usr/aarch64-linux-gnu 中确实不存在。而相关的库该装的都已经装好了,到底这个文件有没有?在哪里呢?那我们来搜索看看。

有两个目录存在,第一个是 x86 的,另一个才是我们需要的。这个目录并不在 /usr/aarch64-linux-gnu 里面,或许需要我们手动指定来告诉 lld

clang -target aarch64-linux-gnu --sysroot=/usr/aarch64-linux-gnu -L/usr/lib/gcc-cross/aarch64-linux-gnu/5 -fuse-ld=lld cctest.c -o cctest

这次问题变少了,虽然 crtbegin.o 文件依然找不到,但后面的一些报错是没有了。为了搞清楚问题,我们给编译命令加上更多的输出来看看。我们可以通过添加 -v 参数来获得更详细的输出。

我们看最后,从这个参数可以看出,似乎是要求把当前目录的 crtbegin.o 和 crtend.o 拿来静态链接,而非像一般的系统库一样动态链接。也就是说这两个文件在一般系统上是不会提供动态链接库的版本,或许是因为功能的特殊性(在进程启动之初,动态链接库加载之前使用),要求它们只能静态链接到可执行文件中。

在 llvm 的源码中搜索这两个文件,并把这两个文件拷贝到当前目录,再编译试试。

通过 file cctestreadelf cctest 来查看生成的可执行文件的信息,看起来没有什么问题。接下来就需要来实际检验可执行文件在 arm64 的物理机上的运行效果了。

 

 


参考:play_with_llvm/ch03.md at master · tuoxie007/play_with_llvm · GitHub