如果你是一名开发人员,正在创建二进制软件包,例如 RPM、DEB、Flatpak 或 Snap,则必须为各种不同的目标平台编译代码。典型的目标包括 32 位和 64 位 x86 和 ARM。你可以在不同的物理或虚拟机上进行构建,但这意味着要维护多个系统。相反,你可以使用 GNU 编译器集合(GCC)进行交叉编译,从单个构建机器为几种不同的架构生成二进制文件。
假设你有一个简单的掷骰子游戏,想要进行交叉编译。用 C 语言编写的东西在大多数系统上都相对容易,因此为了增加真实感,我用 C++ 编写了这个示例,因此该程序依赖于 C 语言中不存在的东西(特别是 iostream)。
#include <iostream>
#include <cstdlib>
using namespace std;
void lose (int c);
void win (int c);
void draw ();
int main() {
int i;
do {
cout << "Pick a number between 1 and 20: \n";
cin >> i;
int c = rand ( ) % 21;
if (i > 20) lose (c);
else if (i < c ) lose (c);
else if (i > c ) win (c);
else draw ();
}
while (1==1);
}
void lose (int c )
{
cout << "You lose! Computer rolled " << c << "\n";
}
void win (int c )
{
cout << "You win!! Computer rolled " << c << "\n";
}
void draw ( )
{
cout << "What are the chances. You tied. Try again, I dare you! \n";
}
在你的系统上使用 g++ 命令编译它
$ g++ dice.cpp -o dice
然后运行它以确认它工作正常
$ ./dice
Pick a number between 1 and 20:
[...]
你可以使用 file 命令查看你刚刚生成的二进制文件的类型
$ file ./dice
dice: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically
linked (uses shared libs), for GNU/Linux 5.1.15, not stripped
同样重要的是,使用 ldd 查看它链接到的库
$ ldd dice
linux-vdso.so.1 => (0x00007ffe0d1dc000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6
(0x00007fce8410e000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6
(0x00007fce83d4f000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6
(0x00007fce83a52000)
/lib64/ld-linux-x86-64.so.2 (0x00007fce84449000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1
(0x00007fce8383c000)
你已经从这些测试中确认了两件事:你刚刚运行的二进制文件是 64 位的,并且它链接到 64 位库。
这意味着,为了交叉编译为 32 位,你必须告诉 g++:
- 生成 32 位二进制文件
- 链接到 32 位库而不是默认的 64 位库
设置你的开发环境
要编译为 32 位,你需要在你的系统上安装 32 位库和头文件。如果你运行的是纯 64 位系统,那么你没有 32 位库或头文件,需要安装基本集。至少,你需要 C 和 C++ 库(glibc 和 libstdc++)以及 32 位版本的 GCC 库(libgcc)。这些软件包的名称可能因发行版而异。在 Slackware 上,一个纯 64 位发行版,其 32 位兼容性可从 Alien BOB 提供的 multilib 软件包中获得。在 Fedora、CentOS 和 RHEL 上
$ yum install libstdc++-*.i686
$ yum install glibc-*.i686
$ yum install libgcc.i686
无论你使用什么系统,你还必须安装你的项目使用的任何 32 位库。例如,如果你的项目包含 yaml-cpp,那么你必须安装 yaml-cpp 的 32 位版本,或者在许多系统上,安装 yaml-cpp 的开发包(例如,Fedora 上的 yaml-cpp-devel),然后再编译它。
一旦处理好这些,编译就相当简单了
$ g++ -m32 dice.cpp -o dice32 -L /usr/lib -march=i686
-m32 标志告诉 GCC 以 32 位模式编译。-march=i686 选项进一步定义了要使用的优化类型(有关选项列表,请参阅 info gcc)。-L 标志设置了你希望 GCC 链接到的库的路径。对于 32 位,这通常是 /usr/lib,尽管根据你的系统设置方式,它可能是 /usr/lib32,甚至是 /opt/usr/lib 或你所知的任何存放 32 位库的位置。
代码编译完成后,查看构建的证明
$ file ./dice32
dice: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV),
dynamically linked (uses shared libs) [...]
当然,ldd ./dice32 指向你的 32 位库。
不同的架构
在 64 位系统上为同一处理器系列编译 32 位代码允许 GCC 对如何编译代码做出许多假设。如果你需要为完全不同的处理器编译,则必须安装适当的交叉构建 GCC 实用程序。你安装哪个实用程序取决于你要编译的内容。此过程比为同一 CPU 系列编译要复杂一些。
当你为同一系列进行交叉编译时,你可以期望找到与 64 位库相同的一组 32 位库,因为你的 Linux 发行版正在维护两者。当为完全不同的架构编译时,你可能必须寻找你的代码所需的库。你需要的版本可能不在你的发行版的存储库中,因为你的发行版可能不提供针对你的目标系统的软件包,或者它可能不会在方便的位置镜像所有软件包。如果你要编译的代码是你自己的,那么你可能对它的依赖项以及可能在哪里找到它们有一个很好的了解。如果代码是你下载的并且需要编译的东西,那么你可能不太熟悉它的要求。在这种情况下,请调查代码正确构建所需的内容(它们通常列在 README 或 INSTALL 文件中,当然也在源代码本身中),然后收集组件。
例如,如果你需要为 ARM 编译 C 代码,你必须首先在 Fedora 或 RHEL 上安装 gcc-arm-linux-gnu(32 位)或 gcc-aarch64-linux-gnu(64 位),或者在 Ubuntu 上安装 arm-linux-gnueabi-gcc 和 binutils-arm-linux-gnueabi。这提供了构建(至少)一个简单的 C 程序所需的命令和库。此外,你需要你的代码使用的任何库。你可以将头文件放在通常的位置(大多数系统上的 /usr/include),或者你可以将它们放在你选择的目录中,并使用 -I 选项将 GCC 指向它。
编译时,不要使用标准的 gcc 或 g++ 命令。而是使用你安装的 GCC 实用程序。例如
$ arm-linux-gnu-g++ dice.cpp \
-I/home/seth/src/crossbuild/arm/cpp \
-o armdice.bin
验证你构建的内容
$ file armdice.bin
armdice.bin: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV) [...]
库和交付物
这是一个如何使用交叉编译的简单示例。在现实生活中,你的源代码可能产生的不只是单个二进制文件。虽然你可以手动管理它,但可能没有充分的理由这样做。在我的下一篇文章中,我将演示 GNU Autotools,它可以完成使你的代码可移植的大部分工作。
评论已关闭。