gcc usa los términos '' arquitectura '' para referirse al '' conjunto de instrucciones '' de una CPU específica, y "objetivo" cubre la combinación de CPU y arquitectura, junto con otras variables como ABI, libc, endian-ness y más (posiblemente incluyendo "metal desnudo"). Un compilador típico tiene un conjunto limitado de combinaciones de destino (probablemente una ABI, una familia de CPU, pero posiblemente ambas de 32 y 64 bits). Un compilador cruzado generalmente significa un compilador con un objetivo distinto del sistema en el que se ejecuta, o uno con múltiples objetivos o ABI (ver también esto ).
¿Los binarios son portables en diferentes arquitecturas de CPU?
En general, no. Un binario en términos convencionales es el código de objeto nativo para una CPU o familia de CPU en particular. Pero, hay varios casos en los que pueden ser moderadamente a altamente portátiles:
- una arquitectura es un superconjunto de otra (comúnmente los binarios x86 se dirigen a i386 o i686 en lugar del último y mejor x86, por ejemplo
-march=core2
)
- una arquitectura proporciona emulación nativa o traducción de otra (es posible que haya oído hablar de Crusoe ) o proporciona coprocesadores compatibles (por ejemplo, PS2 )
- el sistema operativo y el tiempo de ejecución admiten multiarch (por ejemplo, la capacidad de ejecutar binarios x86 de 32 bits en x86_64), o hacer que el VM / JIT sea perfecto (Android usando Dalvik o ART )
- hay soporte para binarios "gordos" que esencialmente contienen código duplicado para cada arquitectura compatible
Si de alguna manera logras resolver este problema, el otro problema binario portátil de innumerables versiones de biblioteca (glibc te estoy mirando) se presentará. (La mayoría de los sistemas integrados lo salvan de ese problema en particular al menos).
Si aún no lo ha hecho, ahora es un buen momento para correr gcc -dumpspecs
y gcc --target-help
ver a qué se enfrenta.
Los binarios gordos tienen varios inconvenientes , pero aún tienen usos potenciales ( EFI ).
Sin embargo, faltan otras dos consideraciones en las otras respuestas: ELF y el intérprete ELF, y el soporte del kernel de Linux para formatos binarios arbitrarios . No entraré en detalles sobre los binarios o el código de bytes para procesadores no reales aquí, aunque es posible tratarlos como "nativos" y ejecutar Java o binarios compilados de código de bytes de Python , tales binarios son independientes de la arquitectura de hardware (pero dependen en la versión de VM relevante, que finalmente ejecuta un binario nativo).
Cualquier sistema Linux contemporáneo utilizará archivos binarios ELF (detalles técnicos en este PDF ), en el caso de los archivos binarios ELF dinámicos, el núcleo se encarga de cargar la imagen en la memoria, pero es el trabajo del "intérprete" establecido en el ELF encabezados para hacer el trabajo pesado. Normalmente esto implica asegurarse de que todas las bibliotecas dinámicas dependientes estén disponibles (con la ayuda de la sección '' Dinámica '' que enumera las bibliotecas y algunas otras estructuras que enumeran los símbolos requeridos), pero esta es casi una capa de indirección de propósito general.
$ 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
también es un binario ELF, no tiene un intérprete y es un código binario nativo).
El problema con ELF es que el encabezado en el binario ( readelf -h /bin/ls
) lo marca para una arquitectura específica, clase (32 o 64 bits), endian-ness y ABI (los binarios gordos "universales" de Apple usan un formato binario alternativo Mach-O en cambio, lo que resuelve este problema, esto se originó en NextSTEP). Esto significa que un ejecutable ELF debe coincidir con el sistema en el que se ejecutará. Un intérprete de escape es el intérprete, este puede ser cualquier ejecutable (incluido uno que extrae o asigna subsecciones específicas de la arquitectura del binario original y las invoca), pero aún está limitado por el tipo (s) de ELF que su sistema permitirá ejecutar . (FreeBSD tiene una forma interesante de manejar archivos ELF de Linux , brandelf
modifica el campo ELF ABI).
Hay (uso binfmt_misc
) de soporte para Mach-O en Linux , hay un ejemplo que muestra cómo crear y ejecutar un binario gordo (32 y 64 bits). Las bifurcaciones de recursos / ADS , como se hizo originalmente en Mac, podrían ser una solución, pero ningún sistema de archivos Linux nativo lo admite.
Más o menos lo mismo se aplica a los módulos del núcleo, los .ko
archivos también son ELF (aunque no tienen un conjunto de intérpretes). En este caso, hay una capa adicional que usa la versión del kernel ( uname -r
) en la ruta de búsqueda, algo que teóricamente podría hacerse en su lugar en ELF con versiones, pero sospecho que con cierta complejidad y poca ganancia.
Como se señaló en otra parte, Linux no admite de forma nativa binarios gordos, pero hay un proyecto activo binario gordo: FatELF . Ha existido durante años , nunca se integró en el núcleo estándar en parte debido a preocupaciones de patentes (ahora expiradas). En este momento requiere soporte de kernel y toolchain. No utiliza el binfmt_misc
enfoque, esto evita los problemas de encabezado ELF y también permite módulos de kernel gordos.
- Si tengo una aplicación compilada para ejecutarse en un 'x86 target, linux OS version xyz', ¿puedo ejecutar el mismo binario compilado en otro sistema 'ARM target, linux OS version xyz'?
No con ELF, no te permitirá hacer esto.
- Si lo anterior no es cierto, la única forma es obtener el código fuente de la aplicación para reconstruir / recompilar utilizando la cadena de herramientas relevante 'por ejemplo, arm-linux-gnueabi'?
La respuesta simple es sí. (Las respuestas complicadas incluyen emulación, representaciones intermedias, traductores y JIT; a excepción del caso de "degradar" un binario i686 para usar solo códigos de operación i386, probablemente no sean interesantes aquí, y las reparaciones ABI son potencialmente tan difíciles como traducir código nativo. )
- Del mismo modo, si tengo un módulo de kernel cargable (controlador de dispositivo) que funciona en un 'x86 target, linux OS versión xyz', ¿puedo cargar / usar el mismo .ko compilado en otro sistema 'ARM target, linux OS version xyz'? ?
No, ELF no te dejará hacer esto.
- Si lo anterior no es cierto, la única forma es obtener el código fuente del controlador para reconstruir / recompilar utilizando la cadena de herramientas relevante 'por ejemplo, arm-linux-gnueabi'?
La respuesta simple es sí. Creo que FatELF le permite construir una .ko
arquitectura múltiple, pero en algún momento se debe crear una versión binaria para cada arquitectura compatible. Las cosas que requieren módulos del núcleo a menudo vienen con la fuente y se compilan según sea necesario, por ejemplo, VirtualBox hace esto.
Esta ya es una respuesta larga, solo hay un desvío más. El núcleo ya tiene una máquina virtual integrada, aunque dedicada: la máquina virtual BPF que se usa para unir paquetes. El filtro legible por humanos "host foo and not port 22") se compila en un código de bytes y el filtro de paquetes del núcleo lo ejecuta . El nuevo eBPF no es solo para paquetes, en teoría que el código VM es portátil en cualquier linux contemporáneo, y llvm lo admite, sino que por razones de seguridad probablemente no sea adecuado para nada más que reglas administrativas.
Ahora, dependiendo de cuán generoso sea con la definición de un ejecutable binario, puede (ab) usarlo binfmt_misc
para implementar soporte binario gordo con un script de shell y archivos ZIP como formato contenedor:
#!/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
}
Llame a esto "wozbin" y configúrelo con algo como:
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
Esto registra .woz
archivos con el kernel, el wozbin
script se invoca en su lugar con su primer argumento establecido en la ruta de un .woz
archivo invocado .
Para obtener un .woz
archivo portátil (gordo) , simplemente cree un test.woz
archivo ZIP con una jerarquía de directorios para:
i686/
\- Linux/
\- glibc-2.12/
armv6l/
\- Linux/
\- glibc-2.17/
Dentro de cada directorio arch / OS / libc (una opción arbitraria) coloque el test
binario específico de la arquitectura y los componentes, como los .so
archivos. Cuando lo invoca, el subdirectorio requerido se extrae en un sistema de archivos en memoria tmpfs ( /mnt/tmpfs
aquí) y se invoca.