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


16

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

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

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

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

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

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


27
不,是,不,是。
霍布斯

7
它有助于认识到我们没有AMD目标和Intel目标,而两者都只有一个x86目标。那是因为Intel和AMD有足够的兼容性。显然,存在ARM目标是有特定原因的,即因为ARM CPU与Intel / AMD / x86不兼容。
MSalters '16

1
不可以,除非它的字节码旨在在Java Runtime之类的可移植运行时环境上运行。如果您正在编写用于嵌入式的代码,则您的代码可能将依赖于低级处理器特定的优化或功能,并且将非常难以移植,不仅需要针对目标平台进行编译(例如,汇编代码的更改,可能的重写)几个模块或整个程序)。
bwDraco '16

1
@MSalters:实际上,我们确实有一个AMD目标:amd64,通常将其标记为x86-64(而x86通常是对i386的重新标记)。幸运的是,英特尔复制(并随后扩展了)AMD体系结构,因此任何64位x86都可以运行amd64二进制文件。
slebetman '16

Answers:


42

不能。二进制文件必须针对目标体系结构进行(重新)编译,而Linux则提供了开箱即用的二进制文件。原因是因为代码被编译为特定体系结构的机器代码,并且大多数处理器系列之间的机器代码差异很大(例如ARM和x86差异很大)。

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

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

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

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


1
考虑到某些x86二进制文件可以在x64平台上运行,可能值得澄清一下x86和x64之间的兼容性(或缺乏兼容性)。(我不确定在Linux上是否是这种情况,但是例如在Windows上就是这种情况。)
jpmc26 '16

4
@ jpmc26在Linux上是可能的;但您可能需要先安装兼容性库。x86支持是Win64安装的非可选部分。在Linux中,它是可选的。而且由于Linux世界在提供所有内容的64位版本方面还有很长的路要走,因此某些发行版并不默认安装(全部?)32位库。(我不确定它的
普遍性

@ jpmc26我用你的笔记更新了答案;我想提一提,但不想使答案复杂化。
Elizafox '16

16

伊丽莎白·迈尔斯(Elizabeth Myers)是正确的,每种架构都需要针对该架构的编译二进制文件。要为不同于系统运行的体系结构构建二进制文件,您需要一个cross-compiler


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

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

你需要编译gccglibcbinutils用这些参数(在目标机器提供了内核的内核头文件)。

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

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

另一个选择是交叉编译器的引导编译。多亏了将交叉编译器编译为不同体系结构上的不同体系的努力crosstool-ng。它为构建交叉编译器所需的工具链提供了引导。

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


一些发行版将交叉编译器作为软件包提供:

换句话说,检查您的发行版在交叉编译器方面提供了哪些功能。如果您的发行版没有满足您需要的交叉编译器,则可以随时自己进行编译。

参考文献:


内核模块说明

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

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


FWIW Fedora也包括交叉编译器。
mattdm '16

@mattdm-谢谢,调整了答案,我相信我已经将fedora Wiki的正确部分链接了。
grochmal

2
比Linux更简单的方法是从头开始为另一体系结构获取Linux和工具链crosstool-ng。您可能要将其添加到列表中。而且,为任何给定的体系结构手动配置和编译GNU跨工具链的工作非常繁琐,而且比--target标志本身还要乏味得多。我怀疑这是LLVM越来越受欢迎的部分原因。它的架构方式使您无需重新构建即可针对另一种体系结构,而是可以使用相同的前端库和优化器库来针对多个后端。
Iwillnotexist Idonotexist

@IwillnotexistIdonotexist-谢谢,我进一步调整了答案。我以前从未听说过crosstool-ng,它看起来非常有用。您的评论实际上对我很有用。
grochmal

9

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

如果需要在非本机CPU上运行内核模块,则必须模拟整个操作系统,包括用于同一体系结构的内核。AFAIK不可能在Linux内核中运行外来代码。


3
仿真的速度损失通常甚至超过10倍,但是如果试图在4GHz的机器上运行为16Mhz机器编写的代码(速度差为250:1),则速度损失为50:1的仿真器可能仍然运行代码的速度比在原始平台上运行的速度快得多。
超级猫

7

二进制文件不仅不能在x86和ARM之间移植,而且还有不同的ARM版本

在实践中您可能会遇到的一个是ARMv6 vs ARMv7。Raspberry Pi 1是ARMv6,更高版本是ARMv7。因此,可以在无法在Pi 1上运行的更高版本上编译代码。

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

(ARM版本控制很容易混淆,但是如果在数字前加V,则表示指令集体系结构(ISA)。如果没有,则它是型号,例如“ Cortex M0”或“ ARM926EJS”。使用ISA编号。)


2
……对于相同的ARM风味,甚至还有不同的子风味,对于完全相同的硬件,甚至还有不同的ABI(我正在考虑整个ARM的soft / softfp / hard浮点运算)。
Matteo Italia

1
@MatteoItalia gh。多个ABI是一种灵丹妙药,可以治愈比这种疾病更严重的疾病。一些ARM根本没有VFP或NEON寄存器,有的只有16个,有32个。在Cortex-A8和更早的版本中,NEON引擎在内核的其余部分后面运行了十几个CC,因此将向量输出传输到GPR的成本为很多。ARM已经采取了正确的行动-规定了许多常见的功能子集。
Iwillnotexist Idonotexist

7

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

Print(42)

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

out 1234h, 42

但是,或者Intellio系统没有这样的东西,因此它必须经过其他层:

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

糟糕,我们甚至在获得机器代码之前,两者之间就已经有了微不足道的区别!这将大致对应于Linux和MS DOS或IBM PC和X-Box之间的差异(即使两者可能使用相同的CPU)。

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

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

20 10

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

36 01 00 10

嗯 有用于的操作码mov.reg.imm,因此我们需要另一个参数来选择要分配给的寄存器。常量始终是一个2字节的字,采用大尾数表示法-这正是停战协议的设计方式,实际上,停战协议中的所有指令均为4字节长,无一例外。

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

该应用程序无法正常工作,并且很有可能几乎立即崩溃或挂起。

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

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

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


3

可以将这个问题重新植入可能更熟悉的环境中。类推:

“我有一个要运行的Ruby程序,但是我的平台上只有一个Python解释器。我可以使用Python解释器运行我的Ruby程序,还是必须用Python重写程序?”

指令集体系结构(“目标”)是一种语言-一种“机器语言”,并且不同的CPU实现不同的语言。因此,要求ARM CPU运行Intel二进制文件非常类似于尝试使用Python解释器运行Ruby程序。


2

gcc使用术语“体系结构”来表示特定CPU的“指令集”,“目标”涵盖CPU和体系结构的组合以及其他变量,例如ABI,libc,endian-ness等(可能包括“裸机”)。典型的编译器具有有限的目标组合集(可能是一个ABI,一个CPU系列,但可能是32位和64位)。交叉编译器通常是指目标不是其运行系统的编译器,或者是具有多个目标或ABI的编译器(另请参见this)。

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

一般来说,没有。传统意义上的二进制是特定CPU或CPU系列的本机目标代码。但是,在某些情况下,它们可能中等到高度可移植:

  • 一种架构是另一种架构的超集(通常,x86二进制文件针对的是i386或i686,而不是最新,最出色的x86,例如-march=core2
  • 一种架构提供本机仿真或另一种架构的翻译(您可能听说过Crusoe),或者提供兼容的协处理器(例如PS2
  • 操作系统和运行时支持多体系结构(例如,能够在x86_64上运行32位x86二进制文件),或者使VM / JIT无缝(使用DalvikART的 Android )
  • 支持“胖”二进制文件,该文件实际上包含每种受支持体系结构的重复代码

如果您设法解决了这个问题,那么无数库版本的另一个可移植二进制问题(我正在看的glibc)将介绍自己。(大多数嵌入式系统至少可以使您免受该特定问题的困扰。)

如果您还没有,现在是时候运行gcc -dumpspecsgcc --target-help查看您遇到的挑战。

胖二进制文件有很多缺点,但仍有潜在用途(EFI)。

但是,其他答案缺少两个注意事项:ELF和ELF解释器,以及Linux内核对任意二进制格式的支持。尽管可以将它们视为“本机”并执行Java或已编译的Python字节码二进制文件,但我不会在这里详细介绍非真实处理器的二进制文件或字节代码,此类二进制文件与硬件体系结构无关(但取决于硬件)在相关的VM版本上运行,最终运行本机二进制文件)。

任何现代的Linux系统都将使用ELF二进制文件(此PDF中的技术细节),对于动态ELF二进制文件,内核负责将映像加载到内存中,但这是ELF中设置的“解释器”的工作。标头要做繁重的工作。通常,这涉及确保所有相关的动态库都可用(借助于“动态”部分,该部分列出了库以及列出所需符号的其他结构),但这几乎是通用的间接层。

$ file /bin/ls
/bin/ls: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses \
shared libs), stripped
$ readelf -p .interp /bin/ls
    String dump of section '.interp':
      [     0]  /lib/ld-linux.so.2

/lib/ld-linux.so.2也是ELF二进制文件,它没有解释程序,并且是本机二进制代码。)

ELF的问题在于,二进制文件(readelf -h /bin/ls)中的标头将其标记为特定的体系结构,类(32位或64位),字节序和ABI(Apple的“通用”胖二进制文件使用替代的二进制格式Mach-O而是解决了这个问题,它起源于NextSTEP。这意味着ELF可执行文件必须与要在其上运行的系统匹配。一个逃生舱口是解释器,它可以是任何可执行文件(包括提取或映射原始二进制文件的体系结构特定子部分并调用它们的可执行文件),但是您仍然受到系统允许运行的ELF类型的限制。(FreeBSD有一种有趣的处理Linux ELF文件的方式,它brandelf修改了ELF ABI字段。)

在Linux上(使用binfmt_misc对Mach-O的支持,那里有一个示例,向您展示了如何创建和运行胖(32位和64位)二进制文件。如最初在Mac上所做的那样,资源派生/ ADS可能是一种解决方法,但是没有本机Linux文件系统支持此功能。

内核模块或多或少都具有相同的含义,.ko文件也是ELF(尽管没有设置解释器)。在这种情况下,会有一个额外的层uname -r在搜索路径中使用内核版本(),理论上可以在带有版本控制的ELF中完成此操作,但我认为这样做有些复杂且几乎没有收益。

如其他地方所述,Linux本身并不支持胖二进制文件,但是有一个活跃的胖二进制项目:FatELF。它已经存在多年了,部分由于专利问题(现已过期)而从未集成到标准内核中。目前,它需要内核和工具链支持。它不使用binfmt_misc方法,这回避了ELF标头问题,也允许使用胖内核模块。

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

如果没有ELF,它将无法让您执行此操作。

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

简单的答案是。(复杂的答案包括仿真,中间表示,转换器和JIT;除了将i686二进制文件“降级”以仅使用i386操作码的情况外,它们在这里可能没有什么用处,而且ABI修复可能与翻译本机代码一样困难。 )

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

不,ELF不允许您这样做。

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

简单的答案是。我相信FatELF可以让您构建一个.ko多架构的架构,但是在某个时候,必须为每种受支持的架构创建一个二进制版本。需要内核模块的内容通常随源一起提供,并根据需要进行构建,例如VirtualBox就是这样做的。

这已经是一个漫长的漫漫答案,只有一个弯路。内核已经内置了一个虚拟机,尽管它是专用的:用于匹配数据包的BPF VM。人类可读的过滤器“ host foo而不是端口22”被编译为字节码,内核数据包过滤器执行该字节码。新的eBPF不仅适用于数据包,从理论上讲,VM代码可在任何现代linux上移植,而llvm支持它,但出于安全原因,它可能不适合管理规则以外的任何其他条件。


现在,根据您对二进制可执行文件的定义的慷慨程度,您可以(滥用)使用binfmt_miscShell脚本和ZIP文件作为容器格式来实现对二进制文件的支持:

#!/bin/bash

name=$1
prog=${1/*\//}      # basename
prog=${prog/.woz/}  # remove extension
root=/mnt/tmpfs
root=$(TMPDIR= mktemp -d -p ${root} woz.XXXXXX)
shift               # drop argv[0], keep other args

arch=$(uname -m)                  # i686
uname_s=$(uname -s)               # Linux
glibc=$(getconf GNU_LIBC_VERSION) # glibc 2.17
glibc=${glibc// /-}               # s/ /-/g

# test that "foo.woz" can unzip, and test "foo" is executable
unzip -tqq "$1" && {
  unzip -q -o -j -d ${root} "$1"  "${arch}/${uname_s}/${glibc}/*" 
  test -x ${root}/$prog && ( 
    export LD_LIBRARY_PATH="${root}:${LD_LIBRARY_PATH}"
    #readlink -f "${root}/${prog}"   # for the curious
    exec -a "${name}" "${root}/${prog}" "$@" 
  )
  rc=$?
  #rm -rf -- "${root}/${prog}"       # for the brave
  exit $rc
}

将此称为“ wozbin”,并使用类似以下内容进行设置:

mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc
printf ":%s:%s:%s:%s:%s:%s:%s" \
  "woz" "E" "" "woz" "" "/path/to/wozbin" ""  > /proc/sys/fs/binfmt_misc/register

这会.woz向内核注册文件,wozbin而是通过将其第一个参数设置为被调用.woz文件的路径来调用脚本。

要获取可移植(胖).woz 文件,只需创建test.woz具有目录层次结构的ZIP文件,即可:

i686/ 
    \- Linux/
            \- glibc-2.12/
armv6l/
    \- Linux/
            \- glibc-2.17/

在每个arch / OS / libc目录中(任意选择),放置特定于体系结构的test二进制文件和组件(例如.so文件)。调用它时,所需的子目录被提取到tmpfs内存文件系统中(在/mnt/tmpfs此处)并被调用。


0

浆果启动,解决了您的一些问题..但解决了如何在x86-32 / 64bit的normall / regullAr linux发行版上运行arm hf并没有解决问题。

我认为应该在isolinux(usb上的boatloader linux)中内置一些实时转换器,该转换器可以识别常规发行版并在ride / live转换为hf中。

为什么?因为如果每个linux都可以通过berry boot转换为可以在arm-hf上运行,那么它就可以将bery引导机制构建到isolinux中,例如,我们用于引导每个人的引导或内置的ubuntu创建启动磁盘。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.