在 Linux 的一种“风格”上编译的 Linux 可执行文件会在不同的 Linux 上运行吗?

JCD*_*een 65 linux compiling compatibility architecture

一个非常简单的小程序的可执行文件,比如下面显示的,在一种 Linux 上编译的,它会在不同的 Linux 上运行吗?还是需要重新编译?

在这种情况下,机器架构重要吗?

int main()
{
  return (99);
}
Run Code Online (Sandbox Code Playgroud)

Dop*_*oti 72

简而言之:如果您使用相同(或兼容)架构将编译后的二进制文件从一台主机带到另一台主机,那么将它带到另一个发行版可能完全没问题。然而,随着代码复杂性的增加,链接到未安装的库的可能性;安装在另一个位置;或安装在不同的版本,增加。以您的代码为例lddgcc -o exit-test exit-test.c在(Debian 派生的)Ubuntu Linux 主机上编译时会报告以下依赖项:

$ ldd exit-test
    linux-gate.so.1 =>  (0xb7748000)
    libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb757b000)
    /lib/ld-linux.so.2 (0x8005a000)
Run Code Online (Sandbox Code Playgroud)

显然,如果我把它踢到 Mac ( ./exit-test: cannot execute binary file: Exec format error) 上,这个二进制文件就不会运行。让我们尝试将其移至 RHEL 框:

$ ./exit-test
-bash: ./exit-test: /lib/ld-linux.so.2: bad ELF interpreter: No such file or directory
Run Code Online (Sandbox Code Playgroud)

哦亲爱的。为什么会这样?

$ ls /lib/ld-l* # reference the `ldd` output above
ls: cannot access /lib/ld-l*: No such file or directory
Run Code Online (Sandbox Code Playgroud)

即使对于这个用例,由于缺少共享库,叉车也失败了。

但是,如果我用 编译它gcc -static exit-test-static exit-test.c,将它移植到没有库的系统中就可以了。当然,以磁盘空间为代价:

$ ls -l ./exit-test{,-static}
-rwxr-xr-x  1 username  groupname    7312 Jan 29 14:18 ./exit-test
-rwxr-xr-x  1 username  groupname  728228 Jan 29 14:27 ./exit-test-static
Run Code Online (Sandbox Code Playgroud)

另一个可行的解决方案是在新主机上安装必需的库。

与 U&L 世界中的许多事物一样,这是一只有很多皮肤的猫,上面概述了其中的两种。

  • 不幸的是,您的 Linux 示例相当人为,并且说明了您关于架构而不是发行版的观点:您在 Debian 上构建了一个 32 位二进制文​​件并尝试在 64 位 RHEL 上运行它;这些是不同的体系结构......可以很好地复制具有很少库依赖项的相同体系结构二进制文件。 (13认同)
  • 叉车……? (8认同)
  • @MSalters 我并不是说这是不合理的,我是说这是一个糟糕的例子,考虑到 DopeGhoti 试图提出的观点(你不能将二进制文件从一个发行版复制到另一个发行版——这是错误的)。当然,Intel 上的 64 位 Linux 也支持运行 32 位可执行文件,并具有适当的基础架构。在这种情况下,IMO 的一个有效示例是构建“amd64”二进制文件并在另一个“amd64”发行版上运行它,或者构建一个“i386”二进制文件并在另一个“i386”发行版上运行它。 (7认同)
  • 事实上,我忘记了静态二进制文件。一些供应商采用静态二进制文件,一些恶意软件作者也采用相同架构*的 Linux 版本之间的二进制兼容性最大化 (4认同)
  • @immibis 我认为这意味着将数据(可执行文件)从一个环境(发行版)复制到另一个环境,其中数据不是为目标环境设计的。 (2认同)
  • @StephenKitt:这不是不合理的。在 Windows 上,32 位二进制文​​件在 64 位系统上运行。和 Visual C++ 等价的 `libc` 是可再发行的。 (2认同)

thr*_*rig 50

这取决于。为 IA-32(英特尔 32 位)编译的东西可以在 amd64 上运行,因为英特尔上的 Linux 保留了与 32 位应用程序(安装了合适的软件)的向后兼容性。这是您code在 RedHat 7.3 32 位系统(大约 2002 年,gcc 版本 2.96)上编译,然后将二进制文件复制到 Centos 7.4 64 位系统(大约 2017 年)并在其上运行:

-bash-4.2$ file code
code: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.2.5, not stripped
-bash-4.2$ ./code
-bash: ./code: /lib/ld-linux.so.2: bad ELF interpreter: No such file or directory
-bash-4.2$ sudo yum -y install glibc.i686
...
-bash-4.2$ ./code ; echo $?
99
Run Code Online (Sandbox Code Playgroud)

古老的 RedHat 7.3 到 Centos 7.4(本质上是 RedHat Enterprise Linux 7.4)保持在同一个“发行版”系列中,因此可能比从 2002 年的一些随机“从头开始安装 Linux”安装到 2018 年的其他一些随机 Linux 发行版具有更好的可移植性.

为 amd64 编译的东西不能在仅 32 位版本的 Linux 上运行(旧硬件不知道新硬件)。对于在现代系统上编译的旨在在古老的旧事物上运行的新软件也是如此,因为库甚至系统调用可能无法向后移植,因此可能需要编译技巧,或获得旧编译器等,或者可能改为在旧系统上编译。(这是保留古代旧事物的虚拟机的一个很好的理由。)

建筑确实很重要;amd64(或 IA-32)与 ARM 或 MIPS 有很大不同,因此其中之一的二进制文件不会在另一个上运行。在汇编级别main,IA-32 上的代码部分通过编译gcc -S code.c

main:
    pushl %ebp
    movl %esp,%ebp
    movl $99,%eax
    popl %ebp
    ret
Run Code Online (Sandbox Code Playgroud)

amd64 系统可以处理的问题(在 Linux 系统上——相比之下,在 amd64上的OpenBSD支持 32 位二进制文​​件;与旧架构的向后兼容性确实给攻击者提供了回旋余地,例如CVE-2014-8866和朋友)。同时在大端 MIPS 系统上 main编译为:

main:
        .frame  $fp,8,$31
        .mask   0x40000000,-4
        .fmask  0x00000000,0
        .set    noreorder
        .set    nomacro
        addiu   $sp,$sp,-8
        sw      $fp,4($sp)
        move    $fp,$sp
        li      $2,99
        move    $sp,$fp
        lw      $fp,4($sp)
        addiu   $sp,$sp,8
        j       $31
        nop
Run Code Online (Sandbox Code Playgroud)

英特尔处理器将不知道如何处理,对于 MIPS 上的英特尔组件也是如此。

您可能会使用 QEMU 或其他一些模拟器来运行外部代码(可能非常非常慢)。

然而!您的代码是非常简单的代码,因此可移植性问题比其他任何东西都要少;程序通常使用随时间变化的库(glibc、openssl 等);对于那些可能还需要安装各种库的旧版本的人(例如,RedHat 通常会将“compat”放在包名中的某处)

compat-glibc.x86_64                     1:2.12-4.el7.centos
Run Code Online (Sandbox Code Playgroud)

或者可能担心使用 glibc 的旧事物的 ABI 更改(应用程序二进制接口),或者最近由于 C++11 或其他 C++ 版本而发生的更改。还可以编译静态(大大增加磁盘上的二进制文件大小)以尽量避免库问题,尽管某些旧的二进制文件是否这样做取决于旧的 Linux 发行版是否正在编译大多数动态(RedHat:是)。另一方面,patchelf可以重新调整动态(ELF,但可能不是a.out格式)二进制文件以使用其他库。

然而!能够运行程序是一回事,实际上用它做一些有用的事情是另一回事。如果旧的 32 位 Intel 二进制文件依赖于其中存在一些可怕且未向后移植的安全问题的 OpenSSL 版本,则它们可能存在安全问题,或者该程序可能根本无法与现代 Web 服务器进行协商(如现代服务器拒绝旧程序的旧协议和密码),或不再支持 SSH 协议版本 1,或...

  • 关于第一段:不,英特尔称其为“英特尔 64”(这些天,在早些时候经历了一些其他名称之后)。IA-64 指的是 Itanium,而不是任何与 x86 兼容的东西。 (14认同)
  • 为什么不提到静态链接? (3认同)
  • 不仅仅是库 ABI 发生了变化——内核的系统调用接口也随着时间的推移而扩展。注意`file /usr/bin/ls` 输出中的`for GNU/Linux 2.6.32`(或类似的)。 (2认同)

Rui*_*iro 25

添加到优秀的 @thrig 和 @DopeGhoti 答案:Unix 或类 Unix 操作系统,包括 Linux,传统上总是为了源代码的可移植性而不是二进制文件进行设计和调整。

如果没有任何特定于硬件的东西或像您的示例中那样是一个简单的源代码,只要目标服务器安装了 C 开发包,您就可以毫无问题地从几乎任何版本的 Linux 或体系结构之间移动它作为源代码,必要的库,并安装相应的开发库。

至于从较早版本的 Linux 中移植更高级的代码,或者更具体的程序,如用于不同内核版本的内核模块,您可能需要调整和修改源代码以解决不推荐使用的库/API/ABI。


bta*_*bta 21

通过默认情况下,你几乎肯定会碰到与外部库的问题。其他一些答案详细介绍了这些问题,因此我不会重复他们的工作。

但是,您可以编译许多程序——甚至是非平凡的程序——以便在 Linux 系统之间移植。关键是称为Linux Standard Base 的工具包。该LSB是专为刚刚创建这些类型的便携式应用。为 LSB v5.0 编译一个应用程序,它将在任何其他实现 LSB v5.0 的 Linux 环境(相同架构)上运行。一些 Linux 发行版符合 LSB,其他发行版包括 LSB 工具包/库作为可安装包。如果您使用 LSB 工具(如 的lsbcc包装器gcc)构建您的应用程序并链接到库的 LSB 版本,您将创建一个可移植的应用程序。

  • @BlackJack-发行版本身并未将其 100% 实现为核心操作系统的一部分,但您可以将 LSB 兼容库和工具包安装为可选包。我已经使用 Ubuntu(例如)构建了兼容 LSB 的程序,然后可以在 Suse 和 Centos 上运行,你只需要 `apt-get install` 几个包。 (2认同)

plu*_*ash 11

也许。

容易破坏它的事情包括。

  1. 不同的架构。显然完全不同的架构是行不通的(除非你有类似用户模式 ​​qemu 和 binfmt_misc 的东西,但这几乎不是一个正常的配置)。x86 二进制文件可以在 amd64 上运行,但前提是所需的 32 位库可用。
  2. 库版本。如果 soversion 是错误的,那么它根本找不到库。如果 soversion 是相同的,但二进制文件是针对比它正在运行的库的更新版本构建的,那么它可能会由于新符号或符号的新版本而无法加载。特别是 glibc 是符号版本控制的重度用户,因此针对较新的 glibc 构建的二进制文件很可能会因使用较旧的 glibc 而失败。

如果您避免使用任何快速变化的库,避免更改架构并构建在您想要定位的最旧的发行版上,那么您很有可能在许多发行版上制作一个二进制文件。


小智 5

除了前面提到的一些东西之外,可执行文件格式也发生了一些变化。大多数情况下,linux 使用 ELF,但旧版本使用 a.out 或 COFF。

维基洞的开始:

https://en.wikipedia.org/wiki/Comparison_of_executable_file_formats

可能有一种方法可以让旧版本运行新格式,但我个人从未研究过它。