在一种 Linux“风格”上编译的 Linux 可执行文件是否可以在另一种“风格”的 Linux 上运行?

在一种 Linux“风格”上编译的 Linux 可执行文件是否可以在另一种“风格”的 Linux 上运行?

在一种 Linux 风格上编译的一个小而极其简单的程序(如下所示)的可执行文件是否可以在不同风格的 Linux 上运行?还是需要重新编译?

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

int main()
{
  return (99);
}

答案1

简而言之:如果您使用相同(或兼容)的主机将编译后的二进制文件从一台主机传输到另一台主机建筑学,你可能完全可以把它带到另一个地方分配。然而,随着代码复杂性的增加,链接到未安装的库的可能性;安装在另一个位置;或安装在不同的版本,增加。以您的代码为例,在(Debian 派生的)Ubuntu Linux 主机上ldd编译时报告以下依赖关系:gcc -o exit-test exit-test.c

$ 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)

显然,如果我将它转移到 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

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

$ ls /lib/ld-l* # reference the `ldd` output above
ls: cannot access /lib/ld-l*: No such file or directory

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

但是,如果我用 编译它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

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

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

答案2

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

-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

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

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

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

main:
    pushl %ebp
    movl %esp,%ebp
    movl $99,%eax
    popl %ebp
    ret

amd64 系统可以处理(在 Linux 系统上——OpenBSD 相比之下在 amd64 上)才不是支持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

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

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

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

compat-glibc.x86_64                     1:2.12-4.el7.centos

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

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

答案3

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

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

至于从时间较远的旧版本 Linux 移植更高级的代码,或者更具体的程序(例如不同内核版本的内核模块),您可能必须调整和修改源代码以适应已弃用的库/API/ABI。

答案4

或许。

容易破坏它的事情包括。

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

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

相关内容