二进制文件可以在不同的 CPU 架构之间移植吗?

二进制文件可以在不同的 CPU 架构之间移植吗?

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

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

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

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

  3. 同样,如果我有一个可在“x86 目标,linux 操作系统版本 xyz',我可以在另一个系统上加载/使用相同的编译后的 .ko 'ARM 目标,linux 操作系统版本 xyz'?

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

答案1

不可以。必须针对目标架构(重新)编译二进制文件,而 Linux 不提供类似的功能胖二进制文件盒子外面。原因是因为代码被编译为特定架构的机器代码,而大多数处理器系列之间的机器代码有很大不同(例如 ARM 和 x86 有很大不同)。

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

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

请注意,内核模块必须针对相关架构进行编译;此外,32 位内核模块无法在 64 位内核上运行,反之亦然。

有关交叉编译二进制文件的信息(这样您就不必在目标 ARM 设备上拥有工具链),请参阅下面 grochmal 的综合答案。

答案2

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


大多数情况下你需要编译交叉编译器。我只有经验gcc(但我相信llvm,和其他编译器,有类似的参数)。通过在配置中gcc添加以下内容即可实现交叉编译器:--target

./configure --build=i686-arch-linux-gnu --target=arm-none-linux-gnueabi

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

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

有几个关于如何编译 GNU 工具链的指南,但我会推荐Linux 从头开始,它得到持续维护,并且很好地解释了所提供命令的作用。

另一种选择是交叉编译器的引导编译。感谢在不同架构上将交叉编译器编译到不同架构的斗争crosstool-ng被创建。它提供了构建交叉编译器所需的工具链的引导程序。

crosstool-ng支持几个目标三联体在不同的体系结构上,基本上它是一个引导程序,人们花时间来解决交叉编译器工具链编译过程中出现的问题。


一些发行版以包的形式提供交叉编译器:

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

参考:


内核模块注释

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

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

答案3

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

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

答案4

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

Print(42)

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

out 1234h, 42

然而,或者Intellio系统没有这样的东西,所以它必须经过其他层:

mov a, 10h
mov c, 42
int 13h

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

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

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

20 10

哦。事实证明,它mov a, constant非常受欢迎,它有自己的指令和操作码。停战协议如何处理这个问题?

36 01 00 10

唔。有 的操作码mov.reg.imm,因此我们需要另一个参数来选择要分配的寄存器。常量始终是一个 2 字节的字,采用大端表示法 - 这就是 Armistice 的设计方式,事实上,Armistice 中的所有指令都是 4 字节长,无一例外。

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

该应用程序没有机会运行,并且很可能会立即崩溃或挂起。

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

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

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

相关内容