Mi objetivo es poder desarrollar para Linux embebido. Tengo experiencia en sistemas integrados de metal desnudo con ARM.
Tengo algunas preguntas generales sobre el desarrollo para diferentes objetivos de CPU. Mis preguntas son las siguientes:
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 '?
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'?
Del mismo modo, si tengo un módulo de kernel cargable (controlador de dispositivo) que funciona en un ' x86 target, linux OS version xyz ', ¿puedo cargar / usar el mismo .ko compilado en otro sistema ' ARM target, linux OS version xyz '? ?
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'?
Respuestas:
No. Los binarios deben (re) compilarse para la arquitectura de destino, y Linux no ofrece nada como binarios gordos listos para usar . La razón es porque el código se compila en código de máquina para una arquitectura específica, y el código de máquina es muy diferente entre la mayoría de las familias de procesadores (ARM y x86, por ejemplo, son muy diferentes).
EDITAR: vale la pena señalar que algunas arquitecturas ofrecen niveles de compatibilidad con versiones anteriores (e incluso más raras, compatibilidad con otras arquitecturas); en las CPU de 64 bits, es común tener compatibilidad con versiones anteriores de las ediciones de 32 bits (pero recuerde: sus bibliotecas dependientes también deben ser de 32 bits, incluida su biblioteca estándar de C, a menos que enlace estáticamente ). También vale la pena mencionar es Itanium , donde fue posible ejecutar código x86 (solo 32 bits), aunque muy lentamente; La baja velocidad de ejecución del código x86 fue al menos parte de la razón por la que no tuvo mucho éxito en el mercado.
Tenga en cuenta que todavía no puede usar binarios compilados con instrucciones más recientes en CPU más antiguas, incluso en modos de compatibilidad (por ejemplo, no puede usar AVX en un binario de 32 bits en procesadores Nehalem x86 ; la CPU simplemente no lo admite.
Tenga en cuenta que los módulos del núcleo deben compilarse para la arquitectura relevante; Además, los módulos de kernel de 32 bits no funcionarán en kernel de 64 bits o viceversa.
Para obtener información sobre binarios de compilación cruzada (para que no tenga que tener una cadena de herramientas en el dispositivo ARM de destino), consulte la respuesta integral de grochmal a continuación.
fuente
Elizabeth Myers es correcta, cada arquitectura requiere un binario compilado para la arquitectura en cuestión. Para crear binarios para una arquitectura diferente a la que ejecuta su sistema, necesita a
cross-compiler
.En la mayoría de los casos, necesita compilar un compilador cruzado. Solo tengo experiencia
gcc
(pero creo quellvm
, y otros compiladores, tienen parámetros similares). Segcc
logra un compilador cruzado agregando--target
a la configuración:Se necesita compilar
gcc
,glibc
ybinutils
con estos parámetros (y proporciona las cabeceras del núcleo del núcleo en el equipo de destino).En la práctica, esto es considerablemente más complicado y aparecen diferentes errores de compilación en diferentes sistemas.
Existen varias guías sobre cómo compilar la cadena de herramientas GNU, pero recomendaré Linux From Scratch , que se mantiene continuamente y hace un muy buen trabajo al explicar lo que hacen los comandos presentados.
Otra opción es una compilación de arranque de un compilador cruzado. Gracias a la lucha de compilar compiladores cruzados para diferentes arquitecturas en diferentes arquitecturas
crosstool-ng
se creó. Da un arranque sobre la cadena de herramientas necesaria para construir un compilador cruzado.crosstool-ng
admite varios tripletes de destino en diferentes arquitecturas, básicamente es un programa de arranque donde las personas dedican su tiempo a resolver los problemas que ocurren durante la compilación de una cadena de herramientas de compilación cruzada.Varias distribuciones proporcionan compiladores cruzados como paquetes:
arch
proporciona un compilador cruzado mingw y un compilador cruzado eabi de brazo listo para usar . Aparte de otros compiladores cruzados en AUR.fedora
contiene varios compiladores cruzados empaquetados .ubuntu
proporciona un compilador cruzado de brazo también.debian
tiene un repositorio completo de cadenas de herramientas cruzadasEn otras palabras, verifique qué tiene disponible su distribución en términos de compiladores cruzados. Si su distribución no tiene un compilador cruzado para sus necesidades, siempre puede compilarlo usted mismo.
Referencias
Nota de módulos del núcleo
Si está compilando su compilador cruzado a mano, tiene todo lo que necesita para compilar los módulos del núcleo. Esto se debe a que necesita los encabezados del núcleo para compilar
glibc
.Pero, si está utilizando un compilador cruzado proporcionado por su distribución, necesitará los encabezados del núcleo que se ejecuta en la máquina de destino.
fuente
crosstool-ng
. Es posible que desee agregar eso a la lista. Además, configurar y compilar una cadena de herramientas cruzadas GNU a mano para cualquier arquitectura dada es increíblemente complicado y mucho más tedioso que solo las--target
banderas. Sospecho que eso es parte de por qué LLVM está ganando popularidad; Está diseñado de tal manera que no necesita una reconstrucción para apuntar a otra arquitectura; en su lugar, puede apuntar a múltiples backends usando las mismas bibliotecas frontend y optimizadora.Tenga en cuenta que, como último recurso (es decir, cuando no tiene el código fuente), puede ejecutar binarios en una arquitectura diferente utilizando emuladores como
qemu
,dosbox
oexagear
. Algunos emuladores están diseñados para emular sistemas que no sean Linux (por ejemplo,dosbox
está diseñado para ejecutar programas MS-DOS, y hay muchos emuladores para consolas de juegos populares). La emulación tiene una sobrecarga de rendimiento significativa: los programas emulados se ejecutan entre 2 y 10 veces más lento que sus contrapartes nativas.Si necesita ejecutar módulos de kernel en una CPU no nativa, deberá emular todo el sistema operativo, incluido el kernel para la misma arquitectura. AFAIK es imposible ejecutar código externo dentro del kernel de Linux.
fuente
No solo los binarios no son portátiles entre x86 y ARM, sino que también hay diferentes tipos de ARM .
El que probablemente encuentre en la práctica es ARMv6 vs ARMv7. Raspberry Pi 1 es ARMv6, las versiones posteriores son ARMv7. Por lo tanto, es posible compilar código en los posteriores que no funciona en Pi 1.
Afortunadamente, uno de los beneficios del software libre y de código abierto es tener el código fuente para que pueda reconstruirlo en cualquier arquitectura. Aunque esto puede requerir algo de trabajo.
(El control de versiones ARM es confuso, pero si hay una V antes del número, está hablando de la arquitectura del conjunto de instrucciones (ISA). Si no existe, es un número de modelo como "Cortex M0" o "ARM926EJS". Los números de modelo no tienen nada que ver. hacer con números ISA.)
fuente
Siempre necesitas apuntar a una plataforma. En el caso más simple, la CPU de destino ejecuta directamente el código compilado en el binario (esto corresponde aproximadamente a los ejecutables COM de MS DOS). Consideremos dos plataformas diferentes que acabo de inventar: Armistice e Intellio. En ambos casos, tendremos un programa simple hello world que genera 42 en la pantalla. También supondré que está utilizando un lenguaje multiplataforma de manera independiente de la plataforma, por lo que el código fuente es el mismo para ambos:
En Armistice, tiene un controlador de dispositivo simple que se encarga de imprimir números, por lo que todo lo que tiene que hacer es enviarlo a un puerto. En nuestro lenguaje ensamblador portátil, esto correspondería a algo como esto:
Sin embargo, o el sistema Intellio no tiene tal cosa, por lo que tiene que pasar por otras capas:
¡Vaya, ya tenemos una diferencia significativa entre los dos, incluso antes de llegar al código de máquina! Esto correspondería aproximadamente al tipo de diferencia que tiene entre Linux y MS DOS, o una PC IBM y un X-Box (aunque ambos pueden usar la misma CPU).
Pero para eso están los sistemas operativos. Supongamos que tenemos un HAL que se asegura de que todas las configuraciones de hardware diferentes se manejen de la misma manera en la capa de aplicación; básicamente, usaremos el enfoque Intellio incluso en Armistice, y nuestro código de "ensamblaje portátil" termina igual. Esto lo usan tanto los sistemas modernos tipo Unix como Windows, a menudo incluso en escenarios integrados. Bien, ahora podemos tener el mismo código de ensamblaje verdaderamente portátil tanto en Armistice como en Intellio. ¿Pero qué hay de los binarios?
Como hemos supuesto, la CPU necesita ejecutar el binario directamente. Veamos la primera línea de nuestro código
mov a, 10h
, en Intellio:Oh. Resulta que
mov a, constant
es tan popular que tiene sus propias instrucciones, con su propio código de operación. ¿Cómo maneja Armistice esto?Hmm Existe el código de operación para
mov.reg.imm
, por lo que necesitamos otro argumento para seleccionar el registro al que estamos asignando. Y la constante es siempre una palabra de 2 bytes, en notación big-endian: así es como se diseñó Armistice, de hecho, todas las instrucciones en Armistice tienen 4 bytes de longitud, sin excepciones.Ahora imagine ejecutar el binario desde Intellio en Armistice: la CPU comienza a decodificar la instrucción, encuentra el código de operación
20h
. En Armisticio, esto corresponde, por ejemplo, a laand.imm.reg
instrucción. Intenta leer la palabra constante de 2 bytes (que lee10XX
, ya es un problema), y luego el número de registro (otroXX
). Estamos ejecutando la instrucción incorrecta, con los argumentos incorrectos. Y lo que es peor, la próxima instrucción será completamente falsa, porque en realidad comimos otra instrucción, pensando que eran datos.La aplicación no tiene posibilidades de funcionar, y lo más probable es que se bloquee o cuelgue casi de inmediato.
Ahora, esto no significa que un ejecutable siempre necesite decir que se ejecuta en Intellio o Armistice. Solo necesita definir una plataforma que sea independiente de la CPU (como
bash
en Unix), o tanto la CPU como el sistema operativo (como Java o .NET, y hoy en día incluso JavaScript, más o menos). En este caso, la aplicación puede usar un ejecutable para todas las CPU y sistemas operativos diferentes, mientras que hay alguna aplicación o servicio en el sistema de destino (que apunta directamente a la CPU y / o sistema operativo correcto) que traduce el código independiente de la plataforma en algo que La CPU realmente puede ejecutarse. Esto puede o no tener un impacto en el rendimiento, el costo o la capacidad.Las CPU generalmente vienen en familias. Por ejemplo, todas las CPU de la familia x86 tienen un conjunto común de instrucciones que están codificadas exactamente de la misma manera, por lo que cada CPU x86 puede ejecutar todos los programas x86, siempre que no intente usar ninguna extensión (por ejemplo, operaciones de punto flotante u operaciones vectoriales). En x86, los ejemplos más comunes hoy en día son Intel y AMD, por supuesto. Atmel es una compañía bien conocida que diseña CPU en la familia ARM, bastante popular para dispositivos integrados. Apple también tiene CPU ARM propias, por ejemplo.
Pero ARM es totalmente incompatible con x86: tienen requisitos de diseño muy diferentes y tienen muy poco en común. Las instrucciones tienen códigos de operación completamente diferentes, se decodifican de manera diferente, las direcciones de memoria se tratan de manera diferente ... Es posible hacer un binario que se ejecute tanto en una CPU x86 como en una CPU ARM, utilizando algunas operaciones seguras para distingue entre los dos y salta a dos conjuntos de instrucciones completamente diferentes, pero aún así significa que tiene instrucciones separadas para ambas versiones, con solo un bootstrapper que elige el conjunto correcto en tiempo de ejecución.
fuente
Es posible volver a plantear esta pregunta en un entorno que podría ser más familiar. Por analogia:
"Tengo un programa Ruby que quiero ejecutar, pero mi plataforma solo tiene un intérprete de Python. ¿Puedo usar el intérprete de Python para ejecutar mi programa Ruby o tengo que reescribir mi programa en Python?"
Una arquitectura de conjunto de instrucciones ("destino") es un lenguaje, un "lenguaje de máquina", y diferentes CPU implementan diferentes idiomas. Entonces, pedirle a una CPU ARM que ejecute un binario Intel es muy parecido a intentar ejecutar un programa Ruby usando un intérprete de Python.
fuente
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 ).
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:
-march=core2
)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
ygcc --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.
(
/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.No con ELF, no te permitirá hacer esto.
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. )
No, ELF no te dejará hacer esto.
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:Llame a esto "wozbin" y configúrelo con algo como:
Esto registra
.woz
archivos con el kernel, elwozbin
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 untest.woz
archivo ZIP con una jerarquía de directorios para: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.fuente
Berry boot, resuelve algunos de tus problemas ... pero no resuelve el problema de cómo ejecutar en arm hf, normall / regullAr linux distro para x86-32 / 64bit.
Creo que debería estar integrado en isolinux (boatloader linux en usb) algún convertidor en vivo que pueda reconocer la distribución regular y en paseo / conversión en vivo a hf.
¿Por qué? Porque si cada linux se puede convertir mediante arranque de baya para trabajar en arm-hf, entonces podría ser capaz de construir un mecanismo de arranque bery en isolinux, lo que arrancamos usando eacher exaple o un disco de arranque de ubuntu creado.
fuente