交叉编译

我们日常使用的系统基本都是基于 x86 架构的,但是在嵌入式设备和 IOT 漏洞分析中,通常需要接触其他的架构,如:ARM、MIPS 等,而 x86 架构下编译的二进制程序是无法在 ARM、MIPS 架构下运行的

如果我们想要在自己的 x86 架构电脑上编译二进制程序,然后在嵌入式设备上运行,就需要配置交叉编译环境,即:在 x86 架构的系统上编译 ARM、MIPS 等架构的二进制程序

查看系统架构

学习多架构,首先当然需要了解一下自己的机器是什么架构

  • 查看内核版本
# 三选一即可
cat /proc/version
uname -a
uname -r

多架构与交叉编译3.png

  • 查看 Linux 版本信息
# 二选一即可
lsb_release -a
cat /etc/issue

多架构与交叉编译4.png

  • 查看系统位数
# 二选一即可
getconf LONG_BIT
file /bin/ls

多架构与交叉编译5.png

  • 查看系统架构
# 三选一即可
arch
dpkg --print-architecture
file /lib/systemd/systemd

多架构与交叉编译6.png


ARM 架构

ARM 架构在移动设备(如:智能手机、平板电脑等)和嵌入式系统中非常常见,而 x86 架构主要用于桌面和服务器领域

各种 ARM 架构名称的区别

如果刚开始接触 ARM 架构,大概会被它的各种名称弄混吧,例如:ARM、ARM64、AArch32、AArch64、ARMv7、ARMv8 等等

就像 x86 架构的 x86、x86_64、i386、amd64 等等,其实有些名称是同一个东西,只是人们的使用习惯问题

参考文章:技术|arm vs AArch64 vs amd64 vs x86_64 vs x86:有什么区别?

官方认定的 32 位和 64 位 ARM 架构的名称分别是 AArch32 和 AArch64(这里的 AArch 其实就是 ARM Architecture 的缩写)

实际符合 ARM 的 CPU ISA 的指令规范被命名为 ARMvX(其中 X 是规范版本的代表数字),目前已经有九个主要的规范版本:

  • ARMv1 到 ARMv7 是适用于 32 位 ARM CPU 的规范
  • ARMv8 和 ARMv9 是适用于 64 位 ARM CPU 的规范

通常来说,在 Linux 中 arm 指代 32 位,而 aarch64 指代 64 位

你可能会觉得困惑,为什么在 AArch64 正式被 ARM 认定为 64 位 ARM 架构后,有些人仍然称其为 arm64

原因主要有两点:

  1. arm64 这个名称在 ARM 决定采用 AArch64 之前就已经广为人知了,甚至 ARM 的一些官方文档也将 64 位的 ARM 架构称为 arm64
  2. Linus Torvalds 对 AArch64 这个名称表示不满,因此 Linux 的代码库主要将 AArch64 称为 arm64,然而,当你在系统中运行 uname -m 时,输出的仍然是 aarch64

因此,对于 32 位 ARM CPU,你应该寻找 AArch32 这个字符串,但有时也可能是 arm 或 armv7

类似的,对于 64 位 ARM CPU,你应该找 AArch64 这个字符串,但有时也可能会是 arm64ARMv8 或 ARMv9


交叉编译环境搭建

注意:

  • arm-linux-gnueabihf-gcc 是 32 位的 ARM 编译器
  • ​aarch64-linux-gnu-gcc 是 64 位的 ARM 编译器

这里以 32 位的 ARM 编译器为例64 位的 ARM 编译器将 arm 改为 arrch64 即可(同时要注意 AArch64 不再是 gnueabihf 而是 gnu

由于我本机是 64 位的 x86 架构 Kali Linux,首先需要安装 32 位库:

sudo dpkg --add-architecture i386
sudo apt update
sudo apt install libncurses5-dev lib32z1
sudo apt install libc6:i386 libstdc++6:i386
  • apt 安装

这种安装方式的优点是简单,但缺点是受版本的限制,如果需要编译特定的版本,则建议手动编译安装

搜索合适的版本:

# 这里主要搜索 arm 架构,可以按照需要修改
apt-cache search arm-linux | grep -E 'gcc|g\+\+'

多架构与交叉编译1.png

图中可以看到主要有 gnueabignueabihf 两个版本,它们的区别在于浮点运算的性能:

  • gnueabihf:使用硬浮点 ABI,要求目标设备具备硬件浮点单元,能够提高浮点运算的性能
  • gnueabi:使用软浮点 ABI,不要求目标设备具备硬件浮点单元,通过软件模拟浮点操作,性能较低

其他关于 abieabignueabignueabihf 的详细区别见:ARM工具链选择参考 - ArnoldLu - 博客园

安装 arm-linux-gccarm-linux-g++

# 也可以指定其他版本,参照上图,这里演示默认安装
sudo apt install gcc-arm-linux-gnueabihf
sudo apt install g++-arm-linux-gnueabihf

测试安装:

arm-linux-gnueabihf-gcc -v
arm-linux-gnueabihf-g++ -v

多架构与交叉编译2.png

卸载:

sudo apt remove gcc-arm-linux-gnueabihf
sudo apt remove g++-arm-linux-gnueabihf
  • 手动编译安装

因为我本机是 x86 架构,通过编译安装就需要用到交叉编译器

交叉编译器有很多,这里以 Linaro 出品的交叉编译器为例(这只是支持交叉编译的 GCC,能用就行,至于哪一家的无所谓)

首先下载 Linaro arm-linux-gnueabihf 架构的 GCC:Linaro GCC v7.5

多架构与交叉编译7.png

这里的位数根据本机来确定,我这里是 x86_64 的 Kali Linux,因此选择下面那个

其他版本的 Linaro GCC 下载:Linaro Releases(目前截止到 2019 年 GCC v7.5)
新版本的 Linaro GCC 下载:Linaro Snapshots,官网链接:Downloads | Linaro

多架构与交叉编译10.png

注意:

这个 Linaro GCC 的版本不是越新越好,不同的开发板的根文件系统的版本是不同的,高版本的编译器编译的程序在低版本的根文件系统中不能运行

如果出现不能运行的情况,要么降低交叉编译器的版本,要么升级开发板的根文件系统

创建一个文件夹用于存放 ARM 架构的交叉编译工具:

wget https://releases.linaro.org/components/toolchain/binaries/latest-7/arm-linux-gnueabihf/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz
sudo tar -vxf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz
sudo mkdir /usr/local/arm
sudo mv gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf /usr/local/arm

为了在终端直接使用交叉编译工具,接着添加环境变量:

sudo vim /etc/profile

# 在文件的最后,加入下面两个----之间的内容
-----------------------------------------------------------------
export PATH=$PATH:/usr/local/arm/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/arm/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/lib
-----------------------------------------------------------------

多架构与交叉编译8.png

验证环境变量:

source /etc/profile   # 使环境变量生效,如果重启机器,可能需要再执行一次
arm-linux-gnueabihf-gcc -v

多架构与交叉编译9.png

到这里就配置成功了


ARM 交叉编译

接下来测试一下 ARM 的交叉编译,编写一个简单的 C 程序:

#include<stdio.h>

int main()
{
	printf("hello ARM !!!\n");
	return 0;
}

然后使用 arm-linux-gnueabihf-gcc 编译并查看二进制文件:

arm-linux-gnueabihf-gcc test.c -o my_arm_file
file my_arm_file

如果没有出现报错,说明我们的环境是没有问题的

可以看到 my_arm_file 是一个 32 位的 ARM 架构的二进制文件(小端序):

多架构与交叉编译11.png

但是由于我们本机是 x86 架构,因此是无法运行该程序的:

多架构与交叉编译12.png

这里显示我们缺少 /lib/ld-linux-armhf.so.3 文件

为了避免出现 lib 库的问题,建议使用静态编译,即加上参数 -static

arm-linux-gnueabihf-gcc test.c -o my_arm_file -static

这样就不会再报这种缺少 lib 文件的错误了

安装相关库:

sudo apt install libc6-armhf-cross

成功后会有 /usr/arm-linux-gnueabihf 文件夹,这里面有我们缺少的文件:

多架构与交叉编译22.png

想要运行该 ARM 架构的程序,我们需要先安装 QEMU

详见本站的《IOT环境搭建与固件分析》一文的《安装 QEMU》部分

使用 QEMU 的用户级工具 qemu-arm-static 来执行 ARM 架构的二进制程序:

# 使用 -L 指定运行的 lib 环境
qemu-arm-static -L /usr/arm-linux-gnueabihf/ ./my_arm_file

多架构与交叉编译13.png

可以看到执行成功


MIPS 架构

MIPS 架构广泛被使用在许多电子产品、网络设备、个人娱乐设备与商业设备上,是一种采取精简指令集(RISC)的处理器架构

各种 MIPS 架构名称的区别

相比于 ARM 架构,MIPS 架构的名称就少多了

MIPS 的 32 位主要有 mipsmipsel 两种,它们的区别在于:

  • mips 是大端序的
  • mipsel 是小端序的

我们日常使用的 x86 架构的 Windows 系统通常也是小端序的

另外,mipsmipsel 对应的 64 位分别为 mips64mips64el,同样是对应大端序和小端序的关系

通常来说,在 Linux 中 mips 指代 32 位大端序,而 mipsel 指代 32 位小端序;mips64 指代 64 位大端序,而 mips64el 指代 64 位小端序


交叉编译环境搭建

注意:

与 ARM 和 AArch64 不同,MIPS 的交叉编译器可以直接通过 -mabi=32-mabi=64 参数来指定生成 32 位或 64 位的二进制程序(编译 64 位需要安装带 multilib 的版本)

但是,**mips 编译的二进制程序是大端序,mipsel 编译的是小端序**

这里以大端序的 MIPS 编译器为例小端序的 MIPS 编译器将 mips 改为 mipsel 即可,其他操作完全一致

由于我本机是 64 位的 x86 架构 Kali Linux,首先需要安装 32 位库:

sudo dpkg --add-architecture i386
sudo apt update
sudo apt install libncurses5-dev lib32z1
sudo apt install libc6:i386 libstdc++6:i386
  • apt 安装

与 ARM 架构的交叉编译环境搭建类似,这种安装方式的优点是简单,但缺点是受版本的限制,如果需要编译特定的版本,则建议手动编译安装

搜索合适的版本:

# 这里主要搜索 mips 架构,可以按照需要修改
apt-cache search mips-linux | grep -E 'gcc|g\+\+'

多架构与交叉编译14.png

图中可以看到主要有带 multilib 和不带 multilib 的两个版本,带 multilib 的版本适用于编译支持多种不同的目标架构和 ABI 的程序的场景,比如想要编译 64 位的程序就需要安装带 multilib 的版本

安装 mips-linux-gccmips-linux-g++

# 也可以指定其他版本,参照上图,这里演示默认安装
# 安装 32 位编译器
sudo apt install gcc-mips-linux-gnu
sudo apt install g++-mips-linux-gnu
# 安装 64 位编译器
sudo apt install gcc-multilib-mips-linux-gnu
sudo apt install g++-multilib-mips-linux-gnu

安装相关依赖:

sudo apt install linux-libc-dev-mips-cross libc6-mips-cross libc6-dev-mips-cross binutils-mips-linux-gnu

测试安装:

mips-linux-gnu-gcc -v
mips-linux-gnu-g++ -v

多架构与交叉编译15.png

卸载:

sudo apt remove gcc-mips-linux-gnu
sudo apt remove g++-mips-linux-gnu

MIPS 交叉编译

接下来测试一下 MIPS 的交叉编译,编写一个简单的 C 程序:

#include<stdio.h>

int main()
{
	printf("hello MIPS !!!\n");
	return 0;
}

然后使用 mips-linux-gnu-gcc 编译并查看二进制文件:

# 编译 32 位二进制程序
# 也可以不加 -mabi=32 参数,默认编译为 32 位程序
mips-linux-gnu-gcc -mabi=32 test.c -o my_mips32_file
file my_mips32_file

如果没有出现报错,说明我们的环境是没有问题的

可以看到 my_mips32_file 是一个 32 位的 MIPS 架构的二进制文件(大端序):

多架构与交叉编译16.png

# 编译 64 位二进制程序
mips-linux-gnu-gcc -mabi=64 test.c -o my_mips64_file
file my_mips64_file

在编译 64 位程序时,如果没有安装带 multilibmips-linux-gnu-gcc 版本,会出现如下报错:

In file included from /usr/mips-linux-gnu/include/features.h:514,
                 from /usr/mips-linux-gnu/include/bits/libc-header-start.h:33,
                 from /usr/mips-linux-gnu/include/stdio.h:27,
                 from test.c:1:
/usr/mips-linux-gnu/include/gnu/stubs.h:35:11: fatal error: gnu/stubs-n64_hard.h: 没有那个文件或目录
   35 | # include <gnu/stubs-n64_hard.h>
      |           ^~~~~~~~~~~~~~~~~~~~~~
compilation terminated.

安装了带 multilibmips-linux-gnu-gcc 版本后,可以看到 my_mips64_file 是一个 64 位的 MIPS 架构的二进制文件(大端序):

多架构与交叉编译17.png

但是由于我们本机是 x86 架构,因此是无法运行该程序的:

多架构与交叉编译18.png

这里显示我们缺少 /lib64/ld.so.1 文件

为了避免出现 lib 库的问题,建议使用静态编译,即加上参数 -static

mips-linux-gnu-gcc -mabi=32 test.c -o my_mips32_file -static  
mips-linux-gnu-gcc -mabi=64 test.c -o my_mips64_file -static

这样就不会再报这种缺少 lib 文件的错误了

安装相关库:

sudo apt install libc6-mips-cross

成功后会有 /usr/mips-linux-gnu 文件夹,这里面有我们缺少的文件:

多架构与交叉编译21.png

想要运行该 MIPS 架构的程序,我们需要先安装 QEMU

详见本站的《IOT环境搭建与固件分析》一文的《安装 QEMU》部分

使用 QEMU 的用户级工具 qemu-mips-staticqemu-mips64-static 来执行 MIPS 架构的二进制程序:

# 使用 -L 指定运行的 lib 环境
qemu-mips-static -L /usr/mips-linux-gnu/ ./my_mips32_file
# 使用 -L 指定运行的 lib 环境
qemu-mips64-static -L /usr/mips-linux-gnu/ ./my_mips64_file

多架构与交叉编译20.png

多架构与交叉编译19.png

可以看到执行成功