L'exécutable d'un petit programme extrêmement simple, tel que celui illustré ci-dessous, qui est compilé sur une version de Linux, s'exécutera-t-il sur une version différente? Ou aurait-il besoin d'être recompilé?
L'architecture de la machine est-elle importante dans un cas comme celui-ci?
int main()
{
return (99);
}
Ça dépend. Quelque chose compilé pour IA-32 (Intel 32 bits) peut fonctionner sur AMD64 car Linux sur Intel conserve la compatibilité descendante avec les applications 32 bits (avec un logiciel approprié installé). Voici votre code
compilé sur le système RedHat 7.3 32 bits (vers 2002, gcc version 2.96), puis le binaire copié et exécuté sur un système Centos 7.4 64 bits (vers 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
L'ancien RedHat 7.3 à Centos 7.4 (essentiellement RedHat Enterprise Linux 7.4) reste dans la même famille de "distribution", donc aura probablement une meilleure portabilité que de passer d'une installation aléatoire "Linux from scratch" de 2002 à une autre distribution Linux aléatoire en 2018 .
Quelque chose compilé pour AMD64 ne fonctionnerait pas uniquement sur les versions 32 bits de Linux (l'ancien matériel ne connaît pas le nouveau matériel). Cela est également vrai pour les nouveaux logiciels compilés sur des systèmes modernes destinés à être exécutés sur d'anciennes choses anciennes, car les bibliothèques et même les appels système peuvent ne pas être portables en arrière, donc peuvent nécessiter des astuces de compilation, ou obtenir un ancien compilateur et ainsi de suite, ou éventuellement à la place la compilation sur l'ancien système. (C'est une bonne raison de garder des machines virtuelles d'anciennes choses anciennes.)
L'architecture est importante; AMD64 (ou IA-32) est très différent de ARM ou MIPS, de sorte que le binaire de l'un de ceux-ci ne devrait pas s'exécuter sur un autre. Au niveau de l'assembly, le main
section de votre code sur IA-32 compile via gcc -S code.c
à
main:
pushl %ebp
movl %esp,%ebp
movl $99,%eax
popl %ebp
ret
avec lequel un système AMD64 peut gérer (sur un système Linux - OpenBSD en revanche sur AMD64 ne prend pas en charge les binaires 32 bits; compatibilité descendante avec les anciennes arches donne aux attaquants une marge de manœuvre, par exemple CVE-2014-8866 et amis). Pendant ce temps sur un système MIPS big-endianmain
se compile à la place pour:
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
dont un processeur Intel n'aura aucune idée de quoi faire, et de même pour l'assemblage Intel sur MIPS.
Vous pouvez éventuellement utiliser QEMU ou un autre émulateur pour exécuter du code étranger (peut-être très, très lentement).
Toutefois! Votre code est un code très simple, il aura donc moins de problèmes de portabilité qu'autre chose; les programmes utilisent généralement des bibliothèques qui ont changé au fil du temps (glibc, openssl, ...); pour ceux-là, il peut également être nécessaire d'installer des versions plus anciennes de diverses bibliothèques (RedHat, par exemple, place généralement "compat" quelque part dans le nom du package)
compat-glibc.x86_64 1:2.12-4.el7.centos
ou peut-être vous inquiéter des changements ABI (Application Binary Interface) pour des choses bien anciennes qui utilisent la glibc, ou plus récemment des changements dus à C++ 11 ou à d'autres versions de C++. On pourrait également compiler de la statique (augmentant considérablement la taille des binaires sur le disque) pour essayer d'éviter les problèmes de bibliothèque, bien que le fait que certains anciens binaires le fassent dépend de si l'ancienne distribution Linux compilait la plupart des éléments dynamiques (RedHat: oui) ou non. D'un autre côté, des choses comme patchelf
peuvent relancer la dynamique (ELF, mais probablement pas a.out
format) binaires pour utiliser d'autres bibliothèques.
Toutefois! Être capable d'exécuter un programme est une chose, et en faire quelque chose d'utile en est une autre. Les anciens binaires Intel 32 bits peuvent avoir des problèmes de sécurité s'ils dépendent d'une version d'OpenSSL qui a un problème de sécurité horrible et non rétroporté, ou le programme peut ne pas être en mesure de négocier du tout avec des serveurs Web modernes (comme les modernes les serveurs rejettent les anciens protocoles et chiffrements de l'ancien programme), ou la version 1 du protocole SSH n'est plus prise en charge, ou ...
En bref: si vous prenez un binaire compilé d'un hôte à un autre en utilisant le même (ou un compatible) architecture, vous pouvez être parfaitement bien en le prenant à un autre distribution . Cependant, à mesure que la complexité du code augmente, la probabilité d'être lié à une bibliothèque qui n'est pas installée; installé dans un autre emplacement; ou installé dans une version différente, augmente. Prenons par exemple votre code, pour lequel ldd
signale les dépendances suivantes lorsqu'il est compilé avec gcc -o exit-test exit-test.c
sur un hôte Ubuntu Linux (dérivé de Debian):
$ 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)
Évidemment, ce binaire ne fonctionnera pas si je le lance sur, disons, un Mac (./exit-test: cannot execute binary file: Exec format error
). Essayons de le déplacer vers une boîte RHEL:
$ ./exit-test
-bash: ./exit-test: /lib/ld-linux.so.2: bad ELF interpreter: No such file or directory
Oh cher. Pourquoi est-ce possible?
$ ls /lib/ld-l* # reference the `ldd` output above
ls: cannot access /lib/ld-l*: No such file or directory
Même pour ce cas d'utilisation, le chariot élévateur a échoué en raison de bibliothèques partagées manquantes.
Cependant, si je le compile avec gcc -static exit-test-static exit-test.c
, le porter sur le système sans les bibliothèques fonctionne très bien. Au détriment, bien sûr, de l'espace disque:
$ 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
Une autre solution viable serait d'installer les bibliothèques requises sur le nouvel hôte.
Comme pour beaucoup de choses dans l'univers U&L, il s'agit d'un chat avec de nombreuses peaux, dont deux sont décrites ci-dessus.
Ajout aux excellentes réponses @thrig et @DopeGhoti: les OS Unix ou Unix, y compris Linux, étaient traditionnellement toujours conçus et alignés davantage pour la portabilité du code source que les binaires.
Si vous n'avez rien de spécifique au matériel ou si vous êtes une simple source comme dans votre exemple, vous pouvez le déplacer sans aucun problème entre à peu près n'importe quelle version de Linux ou architecture en tant que code source tant que les packages de développement C étant installés sur les serveurs de destination, les bibliothèques nécessaires et les bibliothèques de développement correspondantes sont installées.
En ce qui concerne le portage de code plus avancé à partir de versions plus anciennes de Linux éloignées dans le temps, ou de programmes plus spécifiques comme les modules du noyau pour différentes versions du noyau, vous devrez peut-être adapter et modifier le code source pour tenir compte des bibliothèques/API/ABI obsolètes.
Par par défaut , vous rencontrerez presque certainement des problèmes avec les bibliothèques externes. Certaines des autres réponses entrent dans plus de détails sur ces problèmes, donc je ne reproduirai pas leur travail.
Vous pouvez , cependant, compiler de nombreux programmes - même non triviaux - pour être portables entre les systèmes Linux. La clé est une boîte à outils appelée Linux Standard Base . LSB est conçu pour créer uniquement ces types d'applications portables. Compilez une application pour LSB v5.0 et elle s'exécutera sur tout autre environnement Linux (de la même architecture) qui implémente LSB v5.0. Quelques distributions Linux sont conformes à LSB, et d'autres incluent des boîtes à outils/bibliothèques LSB en tant que package installable. Si vous créez votre application à l'aide des outils LSB (comme le wrapper lsbcc
pour gcc
) et que vous liez à la version LSB des bibliothèques, vous créerez une application portable.
Peut être.
Les choses qui ont tendance à le casser incluent.
Si vous évitez d'utiliser des bibliothèques à évolution rapide, évitez les changements d'architecture et construisez sur la distribution la plus ancienne que vous souhaitez cibler, vous avez de bonnes chances de faire un travail binaire sur de nombreuses distributions.
En plus de certaines des choses mentionnées précédemment, il y a eu quelques changements dans le format de fichier exécutable. Pour la plupart, Linux utilise ELF, mais les anciennes versions utilisaient a.out ou COFF.
Le début d'un wikihole:
https://en.wikipedia.org/wiki/Comparison_of_executable_file_formats
Il pourrait y avoir un moyen d'obtenir des versions plus anciennes pour exécuter de nouveaux formats, mais personnellement, je ne l'ai jamais étudié.