多架构与交叉编译
交叉编译
我们日常使用的系统基本都是基于 x86 架构的,但是在嵌入式设备和 IOT 漏洞分析中,通常需要接触其他的架构,如:ARM、MIPS 等,而 x86 架构下编译的二进制程序是无法在 ARM、MIPS 架构下运行的
如果我们想要在自己的 x86 架构电脑上编译二进制程序,然后在嵌入式设备上运行,就需要配置交叉编译环境,即:在 x86 架构的系统上编译 ARM、MIPS 等架构的二进制程序
查看系统架构
学习多架构,首先当然需要了解一下自己的机器是什么架构
- 查看内核版本
# 三选一即可
cat /proc/version
uname -a
uname -r
- 查看 Linux 版本信息
# 二选一即可
lsb_release -a
cat /etc/issue
- 查看系统位数
# 二选一即可
getconf LONG_BIT
file /bin/ls
- 查看系统架构
# 三选一即可
arch
dpkg --print-architecture
file /lib/systemd/systemd
ARM 架构
ARM 架构在移动设备(如:智能手机、平板电脑等)和嵌入式系统中非常常见,而 x86 架构主要用于桌面和服务器领域
各种 ARM 架构名称的区别
如果刚开始接触 ARM 架构,大概会被它的各种名称弄混吧,例如:ARM、ARM64、AArch32、AArch64、ARMv7、ARMv8 等等
就像 x86 架构的 x86、x86_64、i386、amd64 等等,其实有些名称是同一个东西,只是人们的使用习惯问题
官方认定的 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
原因主要有两点:
arm64
这个名称在 ARM 决定采用AArch64
之前就已经广为人知了,甚至 ARM 的一些官方文档也将 64 位的 ARM 架构称为arm64
- Linus Torvalds 对
AArch64
这个名称表示不满,因此 Linux 的代码库主要将AArch64
称为arm64
,然而,当你在系统中运行uname -m
时,输出的仍然是aarch64
因此,对于 32 位 ARM CPU,你应该寻找
AArch32
这个字符串,但有时也可能是arm
或armv7
类似的,对于 64 位 ARM CPU,你应该找
AArch64
这个字符串,但有时也可能会是arm64
、ARMv8
或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\+\+'
图中可以看到主要有 gnueabi
和 gnueabihf
两个版本,它们的区别在于浮点运算的性能:
gnueabihf
:使用硬浮点 ABI,要求目标设备具备硬件浮点单元,能够提高浮点运算的性能gnueabi
:使用软浮点 ABI,不要求目标设备具备硬件浮点单元,通过软件模拟浮点操作,性能较低
其他关于
abi
与eabi
、gnueabi
与gnueabihf
的详细区别见:ARM工具链选择参考 - ArnoldLu - 博客园
安装 arm-linux-gcc
和 arm-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
卸载:
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
这里的位数根据本机来确定,我这里是 x86_64 的 Kali Linux,因此选择下面那个
其他版本的 Linaro GCC 下载:Linaro Releases(目前截止到 2019 年 GCC v7.5)
新版本的 Linaro GCC 下载:Linaro Snapshots,官网链接:Downloads | Linaro
注意:
这个 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
-----------------------------------------------------------------
验证环境变量:
source /etc/profile # 使环境变量生效,如果重启机器,可能需要再执行一次
arm-linux-gnueabihf-gcc -v
到这里就配置成功了
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 架构的二进制文件(小端序):
但是由于我们本机是 x86 架构,因此是无法运行该程序的:
这里显示我们缺少 /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
文件夹,这里面有我们缺少的文件:
想要运行该 ARM 架构的程序,我们需要先安装 QEMU
详见本站的《IOT环境搭建与固件分析》一文的《安装 QEMU》部分
使用 QEMU 的用户级工具 qemu-arm-static
来执行 ARM 架构的二进制程序:
# 使用 -L 指定运行的 lib 环境
qemu-arm-static -L /usr/arm-linux-gnueabihf/ ./my_arm_file
可以看到执行成功
MIPS 架构
MIPS 架构广泛被使用在许多电子产品、网络设备、个人娱乐设备与商业设备上,是一种采取精简指令集(RISC)的处理器架构
各种 MIPS 架构名称的区别
相比于 ARM 架构,MIPS 架构的名称就少多了
MIPS 的 32 位主要有 mips
和 mipsel
两种,它们的区别在于:
mips
是大端序的mipsel
是小端序的
我们日常使用的 x86 架构的 Windows 系统通常也是小端序的
另外,mips
和 mipsel
对应的 64 位分别为 mips64
和 mips64el
,同样是对应大端序和小端序的关系
通常来说,在 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\+\+'
图中可以看到主要有带 multilib
和不带 multilib
的两个版本,带 multilib
的版本适用于编译支持多种不同的目标架构和 ABI 的程序的场景,比如想要编译 64 位的程序就需要安装带 multilib
的版本
安装 mips-linux-gcc
和 mips-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
卸载:
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 架构的二进制文件(大端序):
# 编译 64 位二进制程序
mips-linux-gnu-gcc -mabi=64 test.c -o my_mips64_file
file my_mips64_file
在编译 64 位程序时,如果没有安装带 multilib
的 mips-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.
安装了带 multilib
的 mips-linux-gnu-gcc
版本后,可以看到 my_mips64_file
是一个 64 位的 MIPS 架构的二进制文件(大端序):
但是由于我们本机是 x86 架构,因此是无法运行该程序的:
这里显示我们缺少 /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
文件夹,这里面有我们缺少的文件:
想要运行该 MIPS 架构的程序,我们需要先安装 QEMU
详见本站的《IOT环境搭建与固件分析》一文的《安装 QEMU》部分
使用 QEMU 的用户级工具 qemu-mips-static
和 qemu-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
可以看到执行成功