二进制文件是否可以跨不同的 CPU 架构移植?

foo*_*oob 17 binary application portability cpu-architecture

我的目标是能够为嵌入式 Linux 进行开发。我有使用 ARM 的裸机嵌入式系统的经验。

我有一些关于针对不同 cpu 目标进行开发的一般问题。我的问题如下:

  1. 如果我有一个应用程序编译为在“ x86 目标,linux 操作系统版本 xyz ”上运行,我可以在另一个系统“ ARM 目标,linux 操作系统版本 xyz ”上运行相同的编译二进制文件吗?

  2. 如果上述情况不正确,唯一的方法是使用相关工具链“例如 arm-linux-gnueabi”获取应用程序源代码来重建/重新编译?

  3. 同样,如果我有一个可加载的内核模块(设备驱动程序)可以在“ x86 目标,linux 操作系统版本 xyz ”上运行,我是否可以在另一个系统“ ARM 目标,linux 操作系统版本 xyz ”上加载/使用相同的编译 .ko ?

  4. 如果以上不正确,唯一的方法是使用相关工具链“例如 arm-linux-gnueabi”获取驱动程序源代码来重建/重新编译?

Eli*_*fox 43

不可以。必须为目标体系结构(重新)编译二进制文件,而 Linux 没有像开箱即用的胖二进制文件那样提供任何东西。原因是代码被编译为特定架构的机器代码,而大多数处理器系列之间的机器代码非常不同(例如 ARM 和 x86 非常不同)。

编辑:值得注意的是,某些架构提供了向后兼容性级别(甚至更少见,与其他架构的兼容性);在 64 位 CPU 上,向后兼容 32 位版本是很常见的(但请记住:您的依赖库也必须是 32 位的,包括您的 C 标准库,除非您静态链接)。另外值得一提的是Itanium,它可以运行 x86 代码(仅限 32 位),尽管速度很慢;x86 代码执行速度不佳至少是它在市场上不太成功的部分原因。

请记住,即使在兼容模式下,您仍然不能在较旧的 CPU 上使用使用较新指令编译的二进制文件(例如,您不能在Nehalem x86 处理器上的 32 位二进制文​​件中使用 AVX ;CPU 只是不支持它。

请注意,必须为相关架构编译内核模块;此外,32 位内核模块不能在 64 位内核上工作,反之亦然。

有关交叉编译二进制文件的信息(因此您不必在目标 ARM 设备上安装工具链),请参阅下面的 Grochmal 综合回答。

  • @jpmc26 在 Linux 上是可能的;但您可能需要先安装兼容性库。x86 支持是 Win64 安装的非可选部分。在 Linux 中它是可选的;并且因为 Linux 世界在制作 64 位版本的所有可用版本方面走得更远,因此某些发行版并未默认安装(全部?)32 位库。(我不确定它有多普遍;但之前已经看到运行主流发行版的人对它提出了一些疑问。) (4认同)

gro*_*mal 16

Elizabeth Myers 是正确的,每个架构都需要针对相关架构的编译二进制文件。要为与系统运行的架构不同的架构构建二进制文件,您需要一个cross-compiler.


在大多数情况下,您需要编译交叉编译器。我只有经验gcc(但我相信llvm和其他编译器具有类似的参数)。甲gcc交叉编译器通过将实现--target到配置:

./configure --build=i686-arch-linux-gnu --target=arm-none-linux-gnueabi
Run Code Online (Sandbox Code Playgroud)

您需要编译gcc,glibc并binutils使用这些参数(并在目标机器上提供内核的内核头文件)。

实际上,这要复杂得多,并且在不同的系统上会出现不同的构建错误。

有几个关于如何编译 GNU 工具链的指南,但我会推荐Linux From Scratch,它不断维护并且在解释所提供的命令的作用方面做得非常好。

另一种选择是交叉编译器的引导编译。由于在不同架构上将交叉编译器编译为不同架构的努力crosstool-ng而产生。它提供了构建交叉编译器所需的工具链的引导程序。

crosstool-ng支持不同架构上的多个目标三元组,基本上它是一个引导程序,人们花时间整理交叉编译器工具链编译过程中发生的问题。


几个发行版提供交叉编译器作为包:

换句话说,检查您的发行版在交叉编译器方面可用的内容。如果您的发行版没有满足您需求的交叉编译器,您可以随时自行编译。

参考:


内核模块说明

如果您手动编译交叉编译器,则您拥有编译内核模块所需的一切。这是因为您需要内核头文件来编译glibc.

但是,如果您使用的是发行版提供的交叉编译器,则需要在目标机器上运行的内核的内核头文件。

  • 比 Linux From Scratch 更容易获得 Linux 和用于其他架构的工具链的方法是 `crosstool-ng`。您可能希望将其添加到列表中。此外,为任何给定的体系结构手动配置和编译 GNU 跨工具链非常复杂,而且比仅仅使用 `--target` 标志要繁琐得多。我怀疑这就是 LLVM 越来越受欢迎的部分原因;它的架构方式使您不需要重新构建以定位另一个架构——相反,您可以使用相同的前端和优化器库来定位多个后端。 (2认同)

Dmi*_*yev 9

请注意,作为最后的手段(即当您没有源代码时),您可以使用qemu,dosbox或等模拟器在不同的体系结构上运行二进制文件exagear。一些模拟器旨在模拟 Linux 以外的系统(例如dosbox,旨在运行 MS-DOS 程序,并且有大量模拟器可用于流行的游戏机)。仿真具有显着的性能开销:仿真程序的运行速度比原生程序慢 2-10 倍。

如果您需要在非本机 CPU 上运行内核模块,则必须模拟整个操作系统,包括相同架构的内核。AFAIK 在 Linux 内核中运行外部代码是不可能的。

  • 仿真的速度损失通常甚至高于 10 倍,但如果试图在 4GHz 机器上运行为 16Mhz 机器编写的代码(速度差异为 250:1),速度损失为 50:1 的模拟器可能仍然存在运行代码比在原始平台上运行的速度要快得多。 (3认同)

pjc*_*c50 7

不仅二进制文件不能在 x86 和 ARM 之间移植,还有不同风格的 ARM。

您在实践中可能会遇到的是 ARMv6 与 ARMv7。Raspberry Pi 1 是 ARMv6,以后的版本是 ARMv7。因此,可以在后来在 Pi 1 上不起作用的代码上编译代码。

幸运的是,开源和自由软件的一个好处是拥有源代码,因此您可以在任何架构上重建它。虽然这可能需要一些工作。

(ARM 版本控制令人困惑,但如果数字前有 V,则表示指令集体系结构 (ISA)。如果没有,则是型号,如“Cortex M0”或“ARM926EJS”。型号没有任何意义使用 ISA 号码。)

  • ...然后对于相同的 ARM 风格甚至有不同的子风格,甚至对于完全相同的硬件有不同的 ABI(我正在考虑整个 ARM 软/softfp/硬浮点混乱)。 (2认同)

Lua*_*aan 7

你总是需要瞄准一个平台。在最简单的情况下,目标 CPU 直接运行二进制编译的代码(这大致对应于 MS DOS 的 COM 可执行文件)。让我们考虑一下我刚刚发明的两个不同的平台——Armistice 和 Intellio。在这两种情况下,我们都会有一个简单的 hello world 程序,它在屏幕上输出 42。我还将假设您以与平台无关的方式使用多平台语言,因此两者的源代码相同:

Print(42)
Run Code Online (Sandbox Code Playgroud)

在 Armistice 上,您有一个简单的设备驱动程序来处理打印数字,因此您所要做的就是输出到端口。在我们的可移植汇编语言中,这将对应于以下内容:

out 1234h, 42
Run Code Online (Sandbox Code Playgroud)

但是,或者Intellio系统没有这样的东西,所以还得经过其他层:

mov a, 10h
mov c, 42
int 13h
Run Code Online (Sandbox Code Playgroud)

哎呀,在我们甚至在使用机器代码之前,我们已经有了两者之间的显着差异!这大致对应于您在 Linux 和 MS DOS 之间或 IBM PC 和 X-Box 之间的差异(即使两者可能使用相同的 CPU)。

但这就是操作系统的用途。让我们假设我们有一个 HAL,它确保所有不同的硬件配置在应用程序层上以相同的方式处理 - 基本上,即使在停战协议上,我们也会使用 Intellio 方法,并且我们的“可移植程序集”代码最终是相同的。这被现代类 Unix 系统和 Windows 使用,通常甚至在嵌入式场景中。很好 - 现在我们可以在 Armistice 和 Intellio 上拥有相同的真正可移植的汇编代码。但是二进制文件呢?

正如我们所假设的,CPU 需要直接执行二进制文件。让我们看看mov a, 10hIntellio 上代码的第一行:

20 10
Run Code Online (Sandbox Code Playgroud)

哦。事实证明,它mov a, constant是如此流行,它有自己的指令,有自己的操作码。停战协议如何处理?

36 01 00 10
Run Code Online (Sandbox Code Playgroud)

唔。有操作码mov.reg.imm,所以我们需要另一个参数来选择我们分配给的寄存器。并且常量总是一个 2 字节的字,采用大端符号——这就是 Armistice 的设计方式,事实上,Armistice 中的所有指令都是 4 个字节长,没有例外。

现在想象在 Armistice 上运行 Intellio 的二进制文件:CPU 开始解码指令,找到 opcode 20h。在停战协议上,这对应于and.imm.reg指令。它尝试读取 2 字节字常量(读取10XX,已经有问题),然后是寄存器编号(另一个XX)。我们正在使用错误的参数执行错误的指令。更糟糕的是,下一条指令将完全是假的,因为我们实际上吃了另一条指令,认为它是数据。

应用程序没有机会工作,它很可能会立即崩溃或挂起。

现在,这并不意味着可执行文件总是需要说它在 Intellio 或 Armistice 上运行。您只需要定义一个独立于 CPU(例如bash在 Unix 上)或 CPU 和操作系统(例如 Java 或 .NET,现在甚至是 JavaScript 之类)的平台。在这种情况下,应用程序可以为所有不同的 CPU 和操作系统使用一个可执行文件,而目标系统上有一些应用程序或服务(直接针对正确的 CPU 和/或操作系统)将独立于平台的代码转换为CPU实际上可以执行。这可能会也可能不会对性能、成本或能力造成影响。

CPU 通常以系列形式出现。例如,x86 系列的所有 CPU 都有一组以完全相同方式编码的通用指令,因此每个 x86 CPU 都可以运行每个 x86 程序,只要它不尝试使用任何扩展(例如,浮点运算或向量运算)。在 x86 上,今天最常见的例子当然是 Intel 和 AMD。Atmel 是一家著名的设计 ARM 系列 CPU 的公司,在嵌入式设备中非常流行。例如,Apple 也有自己的 ARM CPU。

但是 ARM 与 x86 完全不兼容——它们有非常不同的设计要求,并且几乎没有共同点。这些指令具有完全不同的操作码,它们以不同的方式解码,内存地址的处理方式不同......通过使用一些安全操作,可以制作在 x86 CPU 和 ARM CPU 上运行的二进制文件区分两者并跳转到两个完全不同的指令集,但这仍然意味着您对两个版本都有单独的指令,只有一个引导程序在运行时选择正确的指令集。