gcc usa i termini '' architettura '' per indicare il '' set di istruzioni '' di una CPU specifica, e "target" copre la combinazione di CPU e architettura, insieme ad altre variabili come ABI, libc, endian-ness e altro (possibilmente includendo "bare metal"). Un tipico compilatore ha un insieme limitato di combinazioni di destinazione (probabilmente un ABI, una famiglia di CPU, ma probabilmente sia a 32 che a 64 bit). Un compilatore incrociato di solito indica un compilatore con una destinazione diversa dal sistema su cui viene eseguito, oppure uno con più destinazioni o ABI (vedere anche questo ).
I binari sono portatili su architetture CPU diverse?
In generale, no. Un binario in termini convenzionali è un codice oggetto nativo per una particolare CPU o famiglia di CPU. Tuttavia, ci sono diversi casi in cui possono essere da moderatamente a altamente portatili:
- un'architettura è un superset di un'altra (comunemente i binari x86 hanno come target i386 o i686 anziché l'ultimo e il più grande x86, ad es.
-march=core2
)
- un'architettura fornisce l'emulazione o la traduzione nativa di un'altra (potresti aver sentito parlare di Crusoe ) o fornisce coprocessori compatibili (ad esempio PS2 )
- il sistema operativo e il runtime supportano il multiarch (ad esempio, la capacità di eseguire binari x86 a 32 bit su x86_64) o rendere la VM / JIT senza soluzione di continuità (Android utilizzando Dalvik o ART )
- c'è il supporto per i binari "fat" che essenzialmente contengono codice duplicato per ogni architettura supportata
Se in qualche modo riesci a risolvere questo problema, l' altro problema binario portatile di una miriade di librerie (glibc ti sto guardando) si presenterà. (La maggior parte dei sistemi embedded ti salva almeno da quel particolare problema.)
Se non l'hai già fatto, ora è un buon momento per correre gcc -dumpspecs
e gcc --target-help
vedere cosa stai affrontando.
I binari fat hanno vari svantaggi , ma hanno ancora potenziali usi ( EFI ).
Ci sono altre due considerazioni che mancano nelle altre risposte: ELF e l'interprete ELF e il supporto del kernel Linux per formati binari arbitrari . Non entrerò nei dettagli sui binari o sul bytecode per processori non reali qui, anche se è possibile trattarli come "nativi" ed eseguire binari di codice byte Java o Python compilati , tali binari sono indipendenti dall'architettura hardware (ma dipendono invece sulla versione VM pertinente, che alla fine esegue un file binario nativo).
Qualsiasi sistema Linux contemporaneo utilizzerà i binari ELF (dettagli tecnici in questo PDF ), nel caso dei binari ELF dinamici il kernel ha il compito di caricare l'immagine in memoria ma è il lavoro dell '"interprete" impostato nell'ELF intestazioni per fare il sollevamento pesante. Normalmente questo implica assicurarsi che tutte le librerie dinamiche dipendenti siano disponibili (con l'aiuto della sezione '' Dinamica '' che elenca le librerie e alcune altre strutture che elencano i simboli richiesti) - ma questo è quasi un livello di riferimento indiretto per scopi generali.
$ 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
è anche un file binario ELF, non ha un interprete ed è un codice binario nativo.)
Il problema con ELF è che l'intestazione nel binario ( readelf -h /bin/ls
) lo contrassegna per una specifica architettura, classe (32 o 64 bit), endian-ness e ABI (i binari fat "universali" di Apple usano un formato binario alternativo Mach-O invece che risolve questo problema, questo ha avuto origine su NextSTEP). Ciò significa che un eseguibile ELF deve corrispondere al sistema su cui deve essere eseguito. Un tratteggio di escape è l'interprete, questo può essere qualsiasi eseguibile (incluso uno che estrae o mappa le sottosezioni specifiche dell'architettura del file binario originario e le invoca), ma sei ancora vincolato dal tipo o dai tipi di ELF che il tuo sistema consentirà di eseguire . (FreeBSD ha un modo interessante di gestire i file ELF di Linux , brandelf
modifica il campo ABI ELF.)
C'è (usando binfmt_misc
) il supporto per Mach-O su Linux , c'è un esempio che ti mostra come creare ed eseguire un binario fat (32-64 bit). I fork di risorse / ADS , come originariamente fatto sul Mac, potrebbero essere una soluzione alternativa, ma nessun filesystem Linux nativo lo supporta.
Più o meno la stessa cosa vale per i moduli del kernel, i .ko
file sono anche ELF (anche se non hanno un set di interpreti). In questo caso c'è un livello aggiuntivo che utilizza la versione del kernel ( uname -r
) nel percorso di ricerca, qualcosa che teoricamente si potrebbe fare invece in ELF con il versioning, ma sospetto che abbia una certa complessità e poco guadagno.
Come notato altrove, Linux non supporta nativamente i binari fat, ma esiste un progetto fat-binary attivo: FatELF . È in circolazione da anni , non è mai stato integrato nel kernel standard in parte a causa di problemi di brevetto (ora scaduti). In questo momento richiede sia il kernel che il supporto della toolchain. Non usa l' binfmt_misc
approccio, questo fa da passo tra i problemi dell'intestazione ELF e consente anche moduli del kernel fat.
- Se ho un'applicazione compilata per essere eseguita su un "target x86, versione del sistema operativo xyz di Linux", posso semplicemente eseguire lo stesso binario compilato su un altro "sistema ARM, versione del sistema operativo xyz di Linux"?
Non con ELF, non ti permetterà di farlo.
- Se sopra non è vero, l'unico modo è quello di ottenere il codice sorgente dell'applicazione per ricostruire / ricompilare usando il toolchain '' ad esempio, arm-linux-gnueabi '?
La semplice risposta è sì. (Le risposte complicate includono emulazione, rappresentazioni intermedie, traduttori e JIT; ad eccezione del caso di "downgrade" di un binario i686 per utilizzare solo i codici operativi i386, probabilmente non sono interessanti qui, e le correzioni ABI sono potenzialmente tanto difficili quanto la traduzione di codice nativo. )
- Allo stesso modo, se ho un modulo kernel caricabile (driver di dispositivo) che funziona su un "target x86, versione del sistema operativo xyz di Linux", posso semplicemente caricare / utilizzare lo stesso .ko compilato su un altro sistema "target ARM, versione del sistema operativo xyz di Linux" ?
No, ELF non ti consente di farlo.
- Se sopra non è vero, l'unico modo è quello di ottenere il codice sorgente del driver per ricostruire / ricompilare usando il toolchain '' ad esempio, arm-linux-gnueabi '?
La semplice risposta è sì. Credo che FatELF ti consenta di creare una multiarchitettura .ko
, ma a un certo punto deve essere creata una versione binaria per ogni architettura supportata. Le cose che richiedono moduli del kernel spesso vengono fornite con il sorgente e sono costruite come richiesto, ad esempio VirtualBox lo fa.
Questa è già una lunga risposta sconclusionata, c'è solo un'altra deviazione. Il kernel ha già una macchina virtuale integrata, anche se dedicata: la VM BPF utilizzata per abbinare i pacchetti. Il filtro leggibile dall'uomo "host foo e non porta 22") viene compilato in un bytecode e il filtro del pacchetto del kernel lo esegue . Il nuovo eBPF non è solo per i pacchetti, in teoria che il codice VM è portatile su qualsiasi Linux contemporaneo e llvm lo supporta, ma per motivi di sicurezza probabilmente non sarà adatto a nient'altro che alle regole amministrative.
Ora, a seconda di quanto sei generoso con la definizione di un eseguibile binario, puoi (ab) usare binfmt_misc
per implementare il supporto binario fat con uno script di shell e file ZIP come formato contenitore:
#!/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
}
Chiamalo "wozbin" e configuralo con qualcosa del tipo:
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
Questo registra i .woz
file con il kernel, lo wozbin
script viene invocato invece con il suo primo argomento impostato sul percorso di un .woz
file richiamato .
Per ottenere un file portatile (fat) .woz
, è sufficiente creare un test.woz
file ZIP con una gerarchia di directory così:
i686/
\- Linux/
\- glibc-2.12/
armv6l/
\- Linux/
\- glibc-2.17/
All'interno di ogni directory arch / OS / libc (una scelta arbitraria) posizionare i file test
binari specifici dell'architettura e i componenti come i .so
file. Quando lo invochi, la sottodirectory richiesta viene estratta in un filesystem tmpfs in memoria ( /mnt/tmpfs
qui) e invocata.