gcc verwendet die Begriffe "Architektur", um den "Befehlssatz" einer bestimmten CPU zu bezeichnen, und "Ziel" deckt die Kombination von CPU und Architektur zusammen mit anderen Variablen wie ABI, libc, endian-ness und mehr ab (möglicherweise einschließlich "Bare Metal"). Ein typischer Compiler verfügt über eine begrenzte Anzahl von Zielkombinationen (wahrscheinlich eine ABI, eine CPU-Familie, aber möglicherweise sowohl 32- als auch 64-Bit). Ein Cross-Compiler ist in der Regel entweder ein Compiler mit einem anderen Ziel als dem System, auf dem er ausgeführt wird, oder einer mit mehreren Zielen oder ABIs (siehe auch dies ).
Sind Binärdateien über verschiedene CPU-Architekturen portierbar?
Im Allgemeinen nicht. Eine Binärdatei ist im herkömmlichen Sinne der native Objektcode für eine bestimmte CPU oder CPU-Familie. Es gibt jedoch mehrere Fälle, in denen sie mäßig bis hochgradig portabel sind:
- Eine Architektur ist eine Obermenge einer anderen (normalerweise sind x86-Binärdateien auf i386 oder i686 ausgerichtet und nicht auf die neueste und beste x86-Architektur, z. B.
-march=core2
)
- Eine Architektur bietet native Emulation oder Übersetzung einer anderen (Sie haben vielleicht schon von Crusoe gehört ), oder sie bietet kompatible Co-Prozessoren (z. B. PS2 ).
- Das Betriebssystem und die Laufzeit unterstützen Multiarch (z. B. die Möglichkeit, 32-Bit-x86-Binärdateien auf x86_64 auszuführen) oder das VM / JIT nahtlos zu machen (Android mit Dalvik oder ART ).
- Es gibt Unterstützung für "fat" -Binaries, die im Wesentlichen doppelten Code für jede unterstützte Architektur enthalten
Wenn Sie es irgendwie schaffen, dieses Problem zu lösen, wird sich das andere tragbare Binärproblem von unzähligen Bibliotheksversionen (glibc ich sehe Sie an) zeigen. (Die meisten eingebetteten Systeme bewahren Sie zumindest vor diesem speziellen Problem.)
Wenn Sie es noch nicht getan haben, ist jetzt ein guter Zeitpunkt, um zu rennen gcc -dumpspecs
und gcc --target-help
zu sehen, gegen was Sie antreten.
Fat Binaries haben verschiedene Nachteile , haben aber immer noch potenzielle Verwendungen ( EFI ).
Bei den anderen Antworten fehlen jedoch zwei weitere Überlegungen: ELF und der ELF-Interpreter sowie die Unterstützung des Linux-Kernels für beliebige Binärformate . Ich werde hier nicht näher auf Binärdateien oder Bytecode für nicht-reale Prozessoren eingehen, obwohl es möglich ist, diese als "native" zu behandeln und Java- oder kompilierte Python-Bytecode-Binärdateien auszuführen. Solche Binärdateien sind unabhängig von der Hardwarearchitektur (hängen jedoch stattdessen ab) auf der entsprechenden VM-Version, auf der letztendlich eine native Binärdatei ausgeführt wird).
Jedes moderne Linux-System verwendet ELF-Binärdateien (technische Details in diesem PDF ). Bei dynamischen ELF-Binärdateien ist der Kernel dafür verantwortlich, das Image in den Speicher zu laden, aber es ist die Aufgabe des in der ELF festgelegten Interpreters Header, um das schwere Heben zu tun. Normalerweise muss dafür gesorgt werden, dass alle abhängigen dynamischen Bibliotheken verfügbar sind (mithilfe des Abschnitts '' Dynamisch '', in dem die Bibliotheken und einige andere Strukturen mit den erforderlichen Symbolen aufgeführt sind) - dies ist jedoch fast eine allgemeine Indirektionsebene.
$ 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
Ist auch eine ELF-Binärdatei, hat keinen Interpreter und ist nativer Binärcode.)
Das Problem bei ELF ist, dass der Header in binary ( readelf -h /bin/ls
) ihn für eine bestimmte Architektur, Klasse (32- oder 64-Bit), Endian-Ness und ABI kennzeichnet (Apples "universelle" Fat-Binaries verwenden ein alternatives Binärformat Mach-O) Stattdessen wurde dieses Problem durch NextSTEP gelöst. Dies bedeutet, dass eine ausführbare ELF-Datei mit dem System übereinstimmen muss, auf dem sie ausgeführt werden soll. Eine Escape-Luke ist der Interpreter. Dies kann eine beliebige ausführbare Datei sein (einschließlich einer, die architekturspezifische Unterabschnitte der ursprünglichen Binärdatei extrahiert oder abbildet und sie aufruft), Sie sind jedoch weiterhin durch die Art (en) der ELF eingeschränkt, die Ihr System ausführen darf . (FreeBSD hat eine interessante Art und Weise von Linux ELF - Dateien verarbeitet werden , der brandelf
ändert das ELF ABI Feld.)
Unter Linux wird Mach-O (mit binfmt_misc
) unterstützt. Dort finden Sie ein Beispiel, das zeigt, wie Sie eine Fat-Binärdatei (32- und 64-Bit) erstellen und ausführen. Resource Forks / ADS , wie es ursprünglich auf dem Mac ausgeführt wurde, könnte eine Problemumgehung sein, aber kein natives Linux-Dateisystem unterstützt dies.
Mehr oder weniger dasselbe gilt für Kernelmodule, .ko
Dateien sind ebenfalls ELF (obwohl sie keinen Interpreter-Satz haben). In diesem Fall gibt es eine zusätzliche Ebene, die die Kernel-Version ( uname -r
) im Suchpfad verwendet. Dies könnte theoretisch stattdessen in ELF mit Versionierung erfolgen, aber mit einer gewissen Komplexität und wenig Gewinn, wie ich vermute.
Wie bereits an anderer Stelle erwähnt, unterstützt Linux Fat-Binaries nicht von Haus aus, es gibt jedoch ein aktives Fat-Binary-Projekt: FatELF . Es gibt es schon seit Jahren , es wurde nicht mehr in den Standard-Kernel integriert, was zum Teil auf (inzwischen abgelaufene) Patentprobleme zurückzuführen ist. Zu diesem Zeitpunkt sind sowohl Kernel- als auch Toolchain-Unterstützung erforderlich. Es wird nicht der binfmt_misc
Ansatz verwendet, der die ELF-Header-Probleme umgeht und auch Fat-Kernel-Module zulässt.
- Wenn ich eine Anwendung kompiliert habe, um sie auf einem 'x86-Zielsystem, Linux-OS-Version xyz' auszuführen, kann ich dann dieselbe kompilierte Binärdatei auf einem anderen 'ARM-Zielsystem, Linux-OS-Version xyz' ausführen?
Nicht bei ELF, das lässt du nicht zu.
- Wenn dies nicht zutrifft, besteht die einzige Möglichkeit darin, den Quellcode der Anwendung mithilfe der entsprechenden Toolchain 'zum Beispiel arm-linux-gnueabi' neu zu erstellen / zu kompilieren.
Die einfache Antwort lautet ja. (Komplizierte Antworten umfassen Emulation, Zwischendarstellungen, Übersetzer und JIT. Abgesehen von der "Herabstufung" einer i686-Binärdatei zur Verwendung von i386-Opcodes sind sie hier wahrscheinlich nicht interessant, und die ABI-Korrekturen sind möglicherweise so schwierig wie die Übersetzung von nativem Code. )
- Ebenso kann ich, wenn ich ein ladbares Kernelmodul (Gerätetreiber) habe, das auf einem 'x86-Ziel, Linux-OS-Version xyz' funktioniert, dasselbe kompilierte .ko auf einem anderen System 'ARM-Ziel, Linux-OS-Version xyz' laden / verwenden. ?
Nein, ELF lässt dich das nicht tun.
- Wenn dies nicht zutrifft, besteht die einzige Möglichkeit darin, den Treiber-Quellcode mithilfe der entsprechenden Toolchain 'zum Beispiel arm-linux-gnueabi' neu zu erstellen / zu kompilieren.
Die einfache Antwort lautet ja. Ich glaube, mit FatELF können Sie eine .ko
Multi-Architektur erstellen , aber irgendwann muss eine Binärversion für jede unterstützte Architektur erstellt werden. Dinge, die Kernelmodule erfordern, kommen oft mit der Quelle und werden nach Bedarf erstellt, z. B. VirtualBox.
Dies ist bereits eine lange Antwort, es gibt nur noch einen Umweg. Im Kernel ist bereits eine virtuelle Maschine integriert, allerdings eine dedizierte: die BPF-VM , mit der Pakete abgeglichen werden. Der vom Menschen lesbare Filter "Host foo und nicht Port 22") wird zu einem Bytecode kompiliert und vom Kernel-Paketfilter ausgeführt . Das neue eBPF ist nicht nur für Pakete gedacht. Theoretisch ist VM-Code für alle gängigen Linux- Betriebssysteme portierbar und wird von llvm unterstützt. Aus Sicherheitsgründen ist es jedoch wahrscheinlich nicht für andere Zwecke als für Verwaltungsregeln geeignet.
Abhängig davon, wie großzügig Sie mit der Definition einer ausführbaren Binärdatei umgehen, können Sie (ab) verwenden binfmt_misc
, um die Unterstützung für Fat Binary mit einem Shell-Skript und ZIP-Dateien als Container-Format zu implementieren:
#!/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
}
Nenne dies "wozbin" und richte es so ein:
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
Dadurch werden .woz
Dateien beim Kernel registriert. wozbin
Stattdessen wird das Skript mit dem ersten Argument aufgerufen , das auf den Pfad einer aufgerufenen .woz
Datei festgelegt ist.
Um eine portable (fette) .woz
Datei zu erhalten, erstellen Sie einfach eine test.woz
ZIP-Datei mit einer Verzeichnishierarchie.
i686/
\- Linux/
\- glibc-2.12/
armv6l/
\- Linux/
\- glibc-2.17/
Platzieren Sie in jedem arch / OS / libc-Verzeichnis (eine beliebige Auswahl) die architekturspezifische test
Binärdatei und Komponenten wie z. B. .so
Dateien. Wenn Sie es aufrufen, wird das erforderliche Unterverzeichnis in ein speicherinternes tmpfs-Dateisystem ( /mnt/tmpfs
hier aktiviert) extrahiert und aufgerufen.