¿Qué es una interfaz binaria de aplicación (ABI)?


493

Nunca entendí claramente qué es un ABI. Por favor, no me señales un artículo de Wikipedia. Si pudiera entenderlo, no estaría aquí publicando una publicación tan larga.

Esta es mi mentalidad sobre las diferentes interfaces:

Un control remoto de TV es una interfaz entre el usuario y el TV. Es una entidad existente, pero inútil (no proporciona ninguna funcionalidad) por sí misma. Toda la funcionalidad para cada uno de esos botones en el control remoto se implementa en el televisor.

Interfaz: es una capa de "entidad existente" entre functionalityy consumerde esa funcionalidad. Una interfaz por sí sola no hace nada. Simplemente invoca la funcionalidad subyacente.

Ahora, dependiendo de quién sea el usuario, hay diferentes tipos de interfaces.

Los comandos de la interfaz de línea de comandos (CLI) son las entidades existentes, el consumidor es el usuario y la funcionalidad se encuentra detrás.

functionality: mi funcionalidad de software que resuelve algún propósito para el cual estamos describiendo esta interfaz.

existing entities: comandos

consumer: usuario

La ventana, los botones, etc. de la interfaz gráfica de usuario (GUI) son las entidades existentes, y nuevamente el consumidor es el usuario y la funcionalidad se encuentra detrás.

functionality: mi funcionalidad de software que resuelve algún problema al que estamos describiendo esta interfaz.

existing entities: ventana, botones etc.

consumer: usuario

Las funciones de la interfaz de programación de aplicaciones (API) (o para ser más correctos) (en la programación basada en interfaz) son las entidades existentes, el consumidor aquí es otro programa, no un usuario, y nuevamente la funcionalidad se encuentra detrás de esta capa.

functionality: mi funcionalidad de software que resuelve algún problema al que estamos describiendo esta interfaz.

existing entities: funciones, Interfaces (conjunto de funciones).

consumer: otro programa / aplicación.

Interfaz binaria de aplicación (ABI) Aquí es donde comienza mi problema.

functionality: ???

existing entities: ???

consumer: ???

  • Escribí software en diferentes idiomas y proporcioné diferentes tipos de interfaces (CLI, GUI y API), pero no estoy seguro de si alguna vez proporcioné alguna ABI.

Wikipedia dice:

Los ABI cubren detalles como

  • tipo de datos, tamaño y alineación;
  • la convención de llamada, que controla cómo se pasan los argumentos de las funciones y se recuperan los valores devueltos;
  • los números de llamada del sistema y cómo una aplicación debe hacer llamadas del sistema al sistema operativo;

Otros ABI estandarizan detalles como

  • el cambio de nombre de C ++,
  • propagación de excepciones, y
  • llamada de convención entre compiladores en la misma plataforma, pero no requiere compatibilidad multiplataforma.
  • ¿Quién necesita estos detalles? Por favor no digas el sistema operativo. Sé programación de ensamblaje. Sé cómo funciona la vinculación y carga. Sé exactamente lo que pasa adentro.

  • ¿Por qué entró el cambio de nombre en C ++? Pensé que estamos hablando a nivel binario. ¿Por qué entran los idiomas?

De todos modos, descargué la [ 4.1 ] Interfaz binaria de la aplicación System V Edición 4.1 (1997-03-18) para ver qué contiene exactamente. Bueno, la mayor parte no tenía ningún sentido.

  • ¿Por qué contiene dos capítulos (4to y 5to) para describir el formato de archivo ELF ? De hecho, estos son los únicos dos capítulos significativos de esa especificación. El resto de los capítulos son "específicos del procesador". De todos modos, pensé que es un tema completamente diferente. No diga que las especificaciones de formato de archivo ELF son ABI. No califica para ser una interfaz de acuerdo con la definición.

  • Lo sé, ya que estamos hablando a un nivel tan bajo que debe ser muy específico. Pero no estoy seguro de cómo es específica la "arquitectura de conjunto de instrucciones (ISA)".

  • ¿Dónde puedo encontrar el ABI de Microsoft Windows?

Entonces, estas son las principales consultas que me están molestando.


77
Los compiladores "Por favor no digas, OS" necesitan conocer el ABI. Los enlazadores necesitan conocer el ABI. El kernel necesita conocer el ABI para configurar el programa en RAM para que se ejecute correctamente. En cuanto a C ++, ver a continuación, convierte intencionalmente las etiquetas en galimatías debido a la sobrecarga y a métodos privados, y el enlazador y cualquier otro compilador deben tener un nombre compatible para trabajar con él, en otras palabras, el mismo ABI.
Justin Smith

8
Creo que la pregunta es muy clara; describiendo exactamente cuál es el formato de respuesta esperado y, sin embargo, ni una sola respuesta satisfactoria que pueda aceptarse.
legends2k

3
@ legends2k Mi opinión sobre el tema es que OP sí sabe qué es un ABI, pero no se da cuenta de eso. La gran mayoría de los programadores nunca diseñarán o proporcionarán un ABI, porque ese es el trabajo de los diseñadores de sistemas operativos / plataformas.
JesperE

44
@JesperE: estoy de acuerdo con tu punto. Pero probablemente el OP quiera saberlo claramente, en el formato que él o ella considere conveniente, aunque tal vez no necesite proporcionar una ABI.
legends2k

2
Yo era ignorante Recientemente mientras trabajaba con todas estas cosas. Me di cuenta de lo que realmente es ABI. Sí, estoy de acuerdo en que mi plantilla es defectuosa. No es apropiado encajar ABI en mi plantilla. Gracias @ JasperE. Solo tomó experiencia laboral darse cuenta de su respuesta.
garras

Respuestas:


536

Una manera fácil de entender "ABI" es compararlo con "API".

Ya estás familiarizado con el concepto de una API. Si desea utilizar las funciones de, por ejemplo, alguna biblioteca o su sistema operativo, programará contra una API. La API consta de tipos / estructuras de datos, constantes, funciones, etc. que puede usar en su código para acceder a la funcionalidad de ese componente externo.

Un ABI es muy similar. Piense en ello como la versión compilada de una API (o como una API en el nivel de lenguaje de máquina). Cuando escribe el código fuente, accede a la biblioteca a través de una API. Una vez que se compila el código, su aplicación accede a los datos binarios en la biblioteca a través del ABI. El ABI define las estructuras y los métodos que usará su aplicación compilada para acceder a la biblioteca externa (tal como lo hizo la API), solo en un nivel inferior. Su API define el orden en que pasa los argumentos a una función. Su ABI define la mecánica de cómoSe pasan estos argumentos (registros, pila, etc.). Su API define qué funciones son parte de su biblioteca. Su ABI define cómo se almacena su código dentro del archivo de la biblioteca, de modo que cualquier programa que use su biblioteca pueda localizar la función deseada y ejecutarla.

Las ABI son importantes cuando se trata de aplicaciones que usan bibliotecas externas. Las bibliotecas están llenas de código y otros recursos, pero su programa debe saber cómo ubicar lo que necesita dentro del archivo de la biblioteca. Su ABI define cómo se almacenan los contenidos de una biblioteca dentro del archivo, y su programa usa la ABI para buscar a través del archivo y encontrar lo que necesita. Si todo en su sistema se ajusta a la misma ABI, entonces cualquier programa puede trabajar con cualquier archivo de biblioteca, sin importar quién los creó. Linux y Windows usan diferentes ABI, por lo que un programa de Windows no sabrá cómo acceder a una biblioteca compilada para Linux.

A veces, los cambios de ABI son inevitables. Cuando esto sucede, los programas que usan esa biblioteca no funcionarán a menos que se vuelvan a compilar para usar la nueva versión de la biblioteca. Si la ABI cambia pero la API no, entonces las versiones de biblioteca nuevas y antiguas a veces se denominan "fuente compatible". Esto implica que si bien un programa compilado para una versión de biblioteca no funcionará con el otro, el código fuente escrito para uno funcionará para el otro si se vuelve a compilar.

Por esta razón, los desarrolladores tienden a tratar de mantener su ABI estable (para minimizar las interrupciones). Mantener un ABI estable significa no cambiar las interfaces de función (tipo y número de retorno, tipos y orden de argumentos), definiciones de tipos de datos o estructuras de datos, constantes definidas, etc. Se pueden agregar nuevas funciones y tipos de datos, pero los existentes deben permanecer lo mismo. Si, por ejemplo, su biblioteca usa enteros de 32 bits para indicar el desplazamiento de una función y cambia a enteros de 64 bits, entonces el código ya compilado que usa esa biblioteca no accederá a ese campo (o ninguno lo seguirá) correctamente . El acceso a los miembros de la estructura de datos se convierte en direcciones de memoria y compensaciones durante la compilación y si la estructura de datos cambia,

Un ABI no es necesariamente algo que proporcionará explícitamente a menos que esté haciendo un trabajo de diseño de sistemas de muy bajo nivel. Tampoco es específico del lenguaje, ya que (por ejemplo) una aplicación C y una aplicación Pascal pueden usar el mismo ABI después de que se compilan.

Editar:Con respecto a su pregunta sobre los capítulos sobre el formato de archivo ELF en los documentos ABI de SysV: La razón por la que se incluye esta información es porque el formato ELF define la interfaz entre el sistema operativo y la aplicación. Cuando le dice al sistema operativo que ejecute un programa, espera que el programa se formatee de cierta manera y (por ejemplo) espera que la primera sección del binario sea un encabezado ELF que contenga cierta información en compensaciones de memoria específicas. Así es como la aplicación comunica información importante sobre sí misma al sistema operativo. Si crea un programa en un formato binario no ELF (como a.out o PE), entonces un sistema operativo que espera aplicaciones con formato ELF no podrá interpretar el archivo binario ni ejecutar la aplicación.

IIRC, Windows actualmente usa el formato ejecutable portátil (o PE). Hay enlaces en la sección de "enlaces externos" de esa página de Wikipedia con más información sobre el formato PE.

Además, con respecto a su nota sobre el cambio de nombre de C ++: Al ubicar una función en un archivo de biblioteca, la función generalmente se busca por nombre. C ++ le permite sobrecargar los nombres de las funciones, por lo que solo el nombre no es suficiente para identificar una función. Los compiladores de C ++ tienen sus propias formas de lidiar con esto internamente, llamado cambio de nombre . Un ABI puede definir una forma estándar de codificar el nombre de una función para que los programas creados con un lenguaje o compilador diferente puedan localizar lo que necesitan. Cuando lo usa extern "c"en un programa C ++, le indica al compilador que use una forma estandarizada de grabar nombres que sea comprensible para otro software.


2
@bta, gracias por la excelente respuesta. ¿La convención de llamadas es una especie de ABI? Gracias
camino

37
Buena respuesta. Excepto que esto no es lo que es un ABI. Un ABI es un conjunto de reglas que determina la convención de llamadas y reglas para diseñar estructuras. Pascal pasa argumentos en la pila en el orden inverso de las aplicaciones C, por lo que los compiladores pascal y C NO compilan en el mismo ABI. Los estándares respectivos para los compiladores de C y Pascal aseguran implícitamente que este sea el caso. Los compiladores de C ++ no pueden definir una forma "estándar" de alterar nombres, ya que no existe una forma estándar. Las convenciones de cambio de nombre de C ++ no eran compatibles entre los compiladores de C ++ cuando había compiladores de C ++ en Windows.
Robin Davies


1
@RobinDavies: en las plataformas donde los compiladores de Pascal habrían llamado argumentos de funciones pop dados por sus llamadores, los compiladores de C generalmente definirían los medios por los cuales un programador podría indicar que funciones particulares deberían usar, o debería esperarse que usen, las mismas convenciones de llamada que el Los compiladores de Pascal a pesar de que los compiladores de C generalmente usarían de manera predeterminada una convención en la que las funciones llamadas dejan en la pila todo lo que sus llamadores colocan allí.
supercat

¿Puedo decir que los archivos obj generados por el compilador de C contienen ABI?
Mitu Raj

144

Si conoce el ensamblaje y cómo funcionan las cosas a nivel del sistema operativo, se está ajustando a un cierto ABI. El ABI gobierna cosas como cómo se pasan los parámetros, dónde se colocan los valores de retorno. Para muchas plataformas solo hay un ABI para elegir, y en esos casos el ABI es simplemente "cómo funcionan las cosas".

Sin embargo, el ABI también gobierna cosas como cómo se presentan las clases / objetos en C ++. Esto es necesario si desea poder pasar referencias de objeto a través de los límites del módulo o si desea mezclar código compilado con diferentes compiladores.

Además, si tiene un sistema operativo de 64 bits que puede ejecutar binarios de 32 bits, tendrá diferentes ABI para el código de 32 y 64 bits.

En general, cualquier código que enlace al mismo ejecutable debe cumplir con el mismo ABI. Si desea comunicarse entre códigos utilizando diferentes ABI, debe usar algún tipo de RPC o protocolos de serialización.

Creo que está tratando demasiado de introducir diferentes tipos de interfaces en un conjunto fijo de características. Por ejemplo, una interfaz no necesariamente tiene que dividirse en consumidores y productores. Una interfaz es solo una convención por la cual dos entidades interactúan.

Los ABI pueden ser (parcialmente) independientes de ISA. Algunos aspectos (como las convenciones de llamadas) dependen del ISA, mientras que otros aspectos (como el diseño de clase C ++) no.

Un ABI bien definido es muy importante para las personas que escriben compiladores. Sin un ABI bien definido, sería imposible generar código interoperable.

EDITAR: Algunas notas para aclarar:

  • "Binario" en ABI no excluye el uso de cadenas o texto. Si desea vincular una DLL que exporta una clase C ++, en algún lugar de ella deben codificarse los métodos y las firmas de tipo. Ahí es donde entra el cambio de nombre en C ++.
  • La razón por la que nunca proporcionó un ABI es que la gran mayoría de los programadores nunca lo harán. Las ABI son proporcionadas por las mismas personas que diseñan la plataforma (es decir, el sistema operativo), y muy pocos programadores tendrán el privilegio de diseñar una ABI ampliamente utilizada.

No estoy del todo convencido de que mi plantilla sea defectuosa. Porque en todas partes esta plantilla para la interfaz es verdadera. Entonces, sí, quiero, espero que ABI también se ajuste a esta plantilla, pero eso no es todo. Lo importante es que todavía no entiendo. No sé si soy tan tonto o algo más, pero simplemente no me viene a la cabeza. No puedo darme cuenta de las respuestas y el artículo wiki.
garras

2
@jesperE, "El ABI gobierna cosas como cómo se pasan los parámetros, dónde se colocan los valores de retorno", se refiere a "cdecl, stdcall, fastcall, pascal" ¿verdad?
camino

3
Si. El nombre propio es "convención de llamada", que forma parte de la ABI. en.wikipedia.org/wiki/X86_calling_conventions
JesperE

44
¡Esta es la respuesta correcta y precisa sin la verbosidad (más bien ruido )!
Nawaz

Recomiendo escribir un poco de montaje. Esto ayudará a las personas a comprender el ABI de una manera más tangible.
KunYu Tsai

40

En realidad , no necesitas un ABI si--

  • Su programa no tiene funciones y--
  • Su programa es un único ejecutable que se ejecuta solo (es decir, un sistema integrado) donde literalmente es lo único que se ejecuta y no necesita hablar con nada más.

Un resumen demasiado simplificado:

API: "Aquí están todas las funciones que puede llamar".

ABI: "Así es como llamar a una función".

El ABI es un conjunto de reglas a las que se adhieren los compiladores y vinculadores para compilar su programa para que funcione correctamente. Los ABI cubren múltiples temas:

  • Podría decirse que la parte más grande e importante de una ABI es el estándar de llamada a procedimiento, a veces conocido como "convención de llamada". Las convenciones de llamadas estandarizan cómo se traducen las "funciones" al código de ensamblaje.
  • Los ABI también dictan cómo se deben representar los nombres de las funciones expuestas en las bibliotecas para que otro código pueda llamar a esas bibliotecas y saber qué argumentos deben pasarse. Esto se llama "cambio de nombre".
  • Las ABI también dictan qué tipo de tipos de datos se pueden usar, cómo se deben alinear y otros detalles de bajo nivel.

Echando un vistazo más profundo a la convención de llamadas, que considero el núcleo de un ABI:

La máquina en sí no tiene concepto de "funciones". Cuando escribe una función en un lenguaje de alto nivel como c, el compilador genera una línea de código de ensamblaje como _MyFunction1:. Esta es una etiqueta , que eventualmente el ensamblador resolverá en una dirección. Esta etiqueta marca el "inicio" de su "función" en el código de ensamblaje. En el código de alto nivel, cuando "llama" a esa función, lo que realmente está haciendo es que la CPU salte a la dirección de esa etiqueta y continúe ejecutándose allí.

En preparación para el salto, el compilador debe hacer un montón de cosas importantes. La convención de llamadas es como una lista de verificación que el compilador sigue para hacer todo esto:

  • Primero, el compilador inserta un poco de código de ensamblaje para guardar la dirección actual, de modo que cuando finalice su "función", la CPU pueda volver al lugar correcto y continuar ejecutándose.
  • A continuación, el compilador genera código de ensamblaje para pasar los argumentos.
    • Algunas convenciones de llamadas dictan que los argumentos deben colocarse en la pila ( en un orden particular, por supuesto).
    • Otras convenciones dictan que los argumentos deben colocarse en registros particulares ( dependiendo de sus tipos de datos, por supuesto).
    • Aún otras convenciones dictan que se debe usar una combinación específica de pila y registros.
  • Por supuesto, si antes había algo importante en esos registros, esos valores ahora se sobrescriben y se pierden para siempre, por lo que algunas convenciones de llamada pueden dictar que el compilador debe guardar algunos de esos registros antes de poner los argumentos en ellos.
  • Ahora el compilador inserta una instrucción de salto que le dice a la CPU que vaya a la etiqueta que hizo anteriormente ( _MyFunction1:). En este punto, puede considerar que la CPU está "en" su "función".
  • Al final de la función, el compilador coloca un código de ensamblaje que hará que la CPU escriba el valor de retorno en el lugar correcto. La convención de llamada determinará si el valor de retorno debe colocarse en un registro particular (dependiendo de su tipo) o en la pila.
  • Ahora es el momento de la limpieza. La convención de llamada dictará dónde el compilador coloca el código de ensamblaje de limpieza.
    • Algunas convenciones dicen que la persona que llama debe limpiar la pila. Esto significa que después de que se realiza la "función" y la CPU vuelve a donde estaba antes, el siguiente código a ejecutar debería ser un código de limpieza muy específico.
    • Otras convenciones dicen que algunas partes particulares del código de limpieza deben estar al final de la "función" antes del salto hacia atrás.

Existen muchas ABI / convenciones de llamada diferentes. Algunos principales son:

  • Para la CPU x86 o x86-64 (entorno de 32 bits):
    • CDECL
    • STDCALL
    • FASTCALL
    • VECTORCALL
    • ESTA LLAMADA
  • Para la CPU x86-64 (entorno de 64 bits):
    • SYSTEMV
    • MSNATIVO
    • VECTORCALL
  • Para la CPU ARM (32 bits)
    • AAPCS
  • Para la CPU ARM (64 bits)
    • AAPCS64

Aquí hay una gran página que muestra las diferencias en el ensamblaje generado al compilar diferentes ABI.

Otra cosa a mencionar es que un ABI no solo es relevante dentro del módulo ejecutable de su programa. El enlazador también lo utiliza para asegurarse de que su programa llame a las funciones de la biblioteca correctamente. Tiene múltiples bibliotecas compartidas ejecutándose en su computadora, y siempre que su compilador sepa qué ABI usan cada una, puede llamar a sus funciones correctamente sin explotar la pila.

Su compilador que comprende cómo llamar a las funciones de la biblioteca es extremadamente importante. En una plataforma alojada (es decir, una en la que un sistema operativo carga programas), su programa ni siquiera puede parpadear sin hacer una llamada al núcleo.


19

Una interfaz binaria de aplicación (ABI) es similar a una API, pero la persona que llama no puede acceder a la función a nivel de código fuente. Solo una representación binaria es accesible / disponible.

Las ABI se pueden definir a nivel de arquitectura de procesador o a nivel de sistema operativo. Los ABI son estándares a seguir por la fase del generador de código del compilador. El estándar lo fija el sistema operativo o el procesador.

Funcionalidad: defina el mecanismo / estándar para hacer llamadas de función independientes del lenguaje de implementación o un compilador / enlazador / cadena de herramientas específico. Proporcione el mecanismo que permite JNI, o una interfaz Python-C, etc.

Entidades existentes: Funciones en forma de código de máquina.

Consumidor: otra función (incluida una en otro idioma, compilada por otro compilador o vinculada por otro vinculador).


¿Por qué la arquitectura definiría el ABI? ¿Por qué diferentes sistemas operativos en la misma arquitectura no podrían definir diferentes ABI?
Andreas Haferburg

10

Funcionalidad: conjunto de contratos que afectan al compilador, a los redactores de ensamblajes, al vinculador y al sistema operativo. Los contratos especifican cómo se presentan las funciones, dónde se pasan los parámetros, cómo se pasan los parámetros, cómo funcionan los retornos de función. Generalmente son específicos de una tupla (arquitectura de procesador, sistema operativo).

Entidades existentes: diseño de parámetros, semántica de funciones, asignación de registros. Por ejemplo, las arquitecturas ARM tienen numerosas ABI (APCS, EABI, GNU-EABI, no importa un montón de casos históricos): el uso de una ABI mixta hará que su código simplemente no funcione al llamar a través de los límites.

Consumidor: El compilador, los escritores de ensamblaje, el sistema operativo, la arquitectura específica de la CPU.

¿Quién necesita estos detalles? El compilador, los escritores de ensamblajes, los enlazadores que generan código (o los requisitos de alineación), el sistema operativo (manejo de interrupciones, interfaz syscall). Si hiciste la programación de ensamblaje, ¡te estabas ajustando a un ABI!

El cambio de nombre de C ++ es un caso especial, es un tema centrado en el enlazador y el enlazador dinámico, si el cambio de nombre no está estandarizado, entonces el enlace dinámico no funcionará. De ahora en adelante, el C ++ ABI se llama simplemente eso, el C ++ ABI. No es un problema de nivel de vinculador, sino un problema de generación de código. Una vez que tenga un binario C ++, no es posible hacerlo compatible con otro ABI C ++ (cambio de nombre, manejo de excepciones) sin volver a compilar desde la fuente.

ELF es un formato de archivo para el uso de un cargador y un vinculador dinámico. ELF es un formato contenedor para código binario y datos, y como tal especifica el ABI de un fragmento de código. No consideraría ELF como un ABI en sentido estricto, ya que los ejecutables de PE no son un ABI.

Todas las ABI son específicas del conjunto de instrucciones. Un ABM ARM no tendrá sentido en un procesador MSP430 o x86_64.

Windows tiene varias ABI, por ejemplo, fastcall y stdcall son dos ABI de uso común. El syscall ABI es diferente de nuevo.


9

Al menos déjame responder una parte de tu pregunta. Con un ejemplo de cómo la ABI de Linux afecta las llamadas al sistema y por qué eso es útil.

Una llamada al sistema es una forma para que un programa de espacio de usuario le pida algo al kernelspace. Funciona colocando el código numérico para la llamada y el argumento en un determinado registro y desencadenando una interrupción. Entonces, se produce un cambio al kernelspace y el kernel busca el código numérico y el argumento, maneja la solicitud, coloca el resultado nuevamente en un registro y activa un cambio nuevamente al espacio de usuario. Esto es necesario, por ejemplo, cuando la aplicación quiere asignar memoria o abrir un archivo (syscalls "brk" y "open").

Ahora las llamadas al sistema tienen nombres cortos "brk", etc. y los códigos de operación correspondientes, que se definen en un archivo de encabezado específico del sistema. Mientras estos códigos de operación permanezcan igual, puede ejecutar los mismos programas compilados de usuario con diferentes núcleos actualizados sin tener que volver a compilar. Entonces tiene una interfaz utilizada por binarios precompilados, de ahí ABI.


4

Para llamar al código en bibliotecas compartidas, o código de llamada entre unidades de compilación, el archivo objeto debe contener etiquetas para las llamadas. C ++ manipula los nombres de las etiquetas de métodos para forzar la ocultación de datos y permitir métodos sobrecargados. Es por eso que no puede mezclar archivos de diferentes compiladores de C ++ a menos que admitan explícitamente el mismo ABI.


4

La mejor manera de diferenciar entre ABI y API es saber por qué y para qué se utiliza:

Para x86-64 generalmente hay una ABI (y para x86 de 32 bits hay otro conjunto):

http://www.x86-64.org/documentation/abi.pdf

https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/LowLevelABI/140-x86-64_Function_Calling_Conventions/x86_64.html

http://people.freebsd.org/~obrien/amd64-elf-abi.pdf

Linux + FreeBSD + MacOSX lo siguen con algunas ligeras variaciones. Y Windows x64 tiene su propio ABI:

http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64/

Conocer el ABI y asumir que otro compilador también lo sigue, luego los binarios teóricamente saben cómo llamarse entre sí (bibliotecas API en particular) y pasar parámetros sobre la pila o por registros, etc. O qué registros se cambiarán al llamar a las funciones, etc. Esencialmente, este conocimiento ayudará al software a integrarse entre sí. Conociendo el orden de los registros / diseño de la pila, puedo armar fácilmente diferentes programas escritos en ensamblajes sin mucho problema.

Pero las API son diferentes:

Es un nombre de funciones de alto nivel, con un argumento definido, de modo que si se compilan diferentes piezas de software utilizando estas API, PUEDEN poder llamarse entre sí. Pero se debe cumplir un requisito adicional de SAME ABI.

Por ejemplo, Windows solía ser compatible con la API POSIX:

https://en.wikipedia.org/wiki/Windows_Services_for_UNIX

https://en.wikipedia.org/wiki/POSIX

Y Linux también es compatible con POSIX. Pero los archivos binarios no se pueden mover y ejecutar de inmediato. Pero debido a que usaron los mismos NOMBRES en la API compatible con POSIX, puede tomar el mismo software en C, recompilarlo en los diferentes sistemas operativos e inmediatamente ejecutarlo.

Las API están destinadas a facilitar la integración del software: etapa de precompilación. Entonces, después de la compilación, el software puede verse totalmente diferente, si los ABI son diferentes.

ABI está destinado a definir la integración exacta de software a nivel binario / ensamblador.


La convención de llamadas x86-64 de Windows no usa la convención de llamadas SysV que usan todos los demás sistemas operativos x86-64. Linux / OS X / FreeBSD comparten la misma convención de llamadas, pero no comparten la ABI completa. La ABI de un sistema operativo incluye números de llamadas al sistema. por ejemplo, freebsd.org/doc/en_US.ISO8859-1/books/developers-handbook/… dice que SYS_execvees 11 en Linux de 32 bits, pero 59 en FreeBSD.
Peter Cordes

gracias por su comentario, he modificado mi comentario para responder mejor a la diferencia entre ABI y API.
Peter Teoh

Todavía te falta la diferencia entre una convención de llamadas y un ABI completo (llamadas del sistema y todo). Puede ejecutar algunos binarios de FreeBSD en Linux, porque Linux (el núcleo) proporciona una capa de compatibilidad de FreeBSD. Incluso entonces, esto se limita a los archivos binarios que no intentan utilizar ninguna parte de la ABI de FreeBSD que Linux no proporciona. (por ejemplo, cualquier llamada al sistema de FreeBSD solamente). Compatible con ABI significa que puede ejecutar el mismo binario en ambos sistemas, no solo que se compilen de manera similar.
Peter Cordes

"Capa de compatibilidad de FreeBSD", nunca he oído hablar de eso. ¿Puede señalar el código fuente relevante del kernel de Linux? Pero existe lo contrario: freebsd.org/doc/en_US.ISO8859-1/books/handbook/linuxemu.html .
Peter Teoh

No es algo que yo uso. Me pareció algo así como que existía, pero tal vez no lo hace más. tldp.org/HOWTO/Linux+FreeBSD-6.html dice que no se ha mantenido y que el tutorial es de 2000. xD. unix.stackexchange.com/questions/172038/… confirma que se abandonó y nunca se volvió a hacer (ya que nadie lo deseaba lo suficiente). personality(2)puede establecer PER_BSD. Creo que recuerdo haber visto personality(PER_LINUX)en la stracesalida todo el tiempo, pero los binarios modernos de Linux de 64 bits ya no lo hacen.
Peter Cordes

4

Ejemplo de ABI ejecutable mínimo de biblioteca compartida de Linux

En el contexto de las bibliotecas compartidas, la implicación más importante de "tener un ABI estable" es que no necesita volver a compilar sus programas después de que la biblioteca cambie.

Así por ejemplo:

  • si está vendiendo una biblioteca compartida, le ahorra a sus usuarios la molestia de volver a compilar todo lo que depende de su biblioteca para cada nueva versión

  • si está vendiendo un programa de código cerrado que depende de una biblioteca compartida presente en la distribución del usuario, puede liberar y probar menos precompilaciones si está seguro de que ABI es estable en ciertas versiones del sistema operativo de destino.

    Esto es especialmente importante en el caso de la biblioteca estándar C, a la que se vinculan muchos programas en su sistema.

Ahora quiero proporcionar un ejemplo ejecutable concreto mínimo de esto.

C Principal

#include <assert.h>
#include <stdlib.h>

#include "mylib.h"

int main(void) {
    mylib_mystruct *myobject = mylib_init(1);
    assert(myobject->old_field == 1);
    free(myobject);
    return EXIT_SUCCESS;
}

mylib.c

#include <stdlib.h>

#include "mylib.h"

mylib_mystruct* mylib_init(int old_field) {
    mylib_mystruct *myobject;
    myobject = malloc(sizeof(mylib_mystruct));
    myobject->old_field = old_field;
    return myobject;
}

mylib.h

#ifndef MYLIB_H
#define MYLIB_H

typedef struct {
    int old_field;
} mylib_mystruct;

mylib_mystruct* mylib_init(int old_field);

#endif

Compila y funciona bien con:

cc='gcc -pedantic-errors -std=c89 -Wall -Wextra'
$cc -fPIC -c -o mylib.o mylib.c
$cc -L . -shared -o libmylib.so mylib.o
$cc -L . -o main.out main.c -lmylib
LD_LIBRARY_PATH=. ./main.out

Ahora, supongamos que para v2 de la biblioteca, queremos agregar un nuevo campo a mylib_mystructllamadonew_field .

Si agregamos el campo antes old_fieldcomo en:

typedef struct {
    int new_field;
    int old_field;
} mylib_mystruct;

y reconstruyó la biblioteca pero no main.out , ¡entonces la afirmación falla!

Esto es porque la línea:

myobject->old_field == 1

había generado un ensamblaje que intenta acceder al primero intde la estructura, que ahora es en new_fieldlugar del esperadoold_field .

Por lo tanto, este cambio rompió el ABI.

Sin embargo, si agregamos new_fielddespués de old_field:

typedef struct {
    int old_field;
    int new_field;
} mylib_mystruct;

entonces el antiguo ensamblado generado aún accede al primero int de la estructura, y el programa aún funciona, porque mantuvimos el ABI estable.

Aquí hay una versión completamente automatizada de este ejemplo en GitHub .

Otra forma de mantener esta ABI estable habría sido tratarla mylib_mystructcomo una estructura opaca , y solo acceder a sus campos a través de métodos auxiliares. Esto hace que sea más fácil mantener estable la ABI, pero incurriría en una sobrecarga de rendimiento ya que haríamos más llamadas a funciones.

API vs ABI

En el ejemplo anterior, es interesante notar que agregar el new_fieldanterior old_fieldsolo rompió la ABI, pero no la API.

Lo que esto significa es que si hubiéramos compilado nuestro main.cprograma contra la biblioteca, habría funcionado de todos modos.

Sin embargo, también habríamos roto la API si hubiéramos cambiado, por ejemplo, la firma de la función:

mylib_mystruct* mylib_init(int old_field, int new_field);

ya que en ese caso, main.cdejaría de compilarse por completo.

API semántica vs API de programación

También podemos clasificar los cambios de API en un tercer tipo: cambios semánticos.

La API semántica, por lo general, es una descripción en lenguaje natural de lo que se supone que debe hacer la API, generalmente incluida en la documentación de la API.

Por lo tanto, es posible romper la API semántica sin romper la compilación del programa.

Por ejemplo, si hubiéramos modificado

myobject->old_field = old_field;

a:

myobject->old_field = old_field + 1;

entonces esto no habría roto ni la API de programación ni ABI, pero main.c la API semántica se rompería.

Hay dos formas de verificar mediante programación la API del contrato:

  • prueba un montón de casos de esquina. Fácil de hacer, pero siempre puedes perderte uno.
  • verificación formal . Es más difícil de hacer, pero produce una prueba matemática de corrección, esencialmente unificando la documentación y las pruebas en una forma "humana" / máquina verificable. Siempre que no haya un error en su descripción formal, por supuesto ;-)

    Este concepto está estrechamente relacionado con la formalización de las matemáticas en sí: /math/53969/what-does-formal-mean/3297537#3297537

Lista de todo lo que rompe las ABI de la biblioteca compartida de C / C ++

TODO: encuentra / crea la lista definitiva:

Ejemplo ejecutable mínimo de Java

¿Qué es la compatibilidad binaria en Java?

Probado en Ubuntu 18.10, GCC 8.2.0.


3

La ABI debe ser coherente entre la persona que llama y la persona que llama para asegurarse de que la llamada tiene éxito. Uso de pila, uso de registro, pop de pila al final de la rutina. Todas estas son las partes más importantes del ABI.


3

Resumen

Existen diversas interpretaciones y opiniones firmes sobre la capa exacta que define una ABI (interfaz binaria de aplicación).

En mi opinión, un ABI es una convención subjetiva de lo que se considera una plataforma / dado para una API específica. El ABI es el "resto" de convenciones que "no cambiarán" para una API específica o que serán atendidas por el entorno de ejecución: ejecutores, herramientas, vinculadores, compiladores, jvm y OS.

Definición de una interfaz : ABI, API

Si desea utilizar una biblioteca como joda-time, debe declarar una dependencia joda-time-<major>.<minor>.<patch>.jar. La biblioteca sigue las mejores prácticas y utiliza el control de versiones semántico . Esto define la compatibilidad API en tres niveles:

  1. Parche: no necesita cambiar en absoluto su código. La biblioteca solo corrige algunos errores.
  2. Menor: no necesita cambiar su código desde las adiciones
  3. Mayor: la interfaz (API) cambia y es posible que deba cambiar su código.

Para que pueda utilizar una nueva versión principal de la misma biblioteca, aún se deben respetar muchas otras convenciones:

  • El lenguaje binario utilizado para las bibliotecas (en casos de Java, la versión de destino de JVM que define el código de bytes de Java)
  • Convenciones de llamadas
  • Convenciones JVM
  • Vinculación de convenciones
  • Convenciones de tiempo de ejecución Todos estos son definidos y administrados por las herramientas que utilizamos.

Ejemplos

Caso de estudio de Java

Por ejemplo, Java estandarizó todas estas convenciones, no en una herramienta, sino en una especificación JVM formal. La especificación permitió a otros proveedores proporcionar un conjunto diferente de herramientas que pueden generar bibliotecas compatibles.

Java proporciona otros dos estudios de caso interesantes para ABI: las versiones de Scala y la máquina virtual Dalvik .

Dalvik virtual machine rompió el ABI

Dalvik VM necesita un tipo diferente de bytecode que el bytecode de Java. Las bibliotecas de Dalvik se obtienen convirtiendo el código de bytes de Java (con la misma API) para Dalvik. De esta forma, puede obtener dos versiones de la misma API: definida por el original joda-time-1.7.2.jar. Podríamos llamarme joda-time-1.7.2.jary joda-time-1.7.2-dalvik.jar. Utilizan un ABI diferente para el Java vms estándar orientado a la pila: el de Oracle, el de IBM, Java abierto o cualquier otro; y el segundo ABI es el que está alrededor de Dalvik.

Los sucesivos lanzamientos de Scala son incompatibles

Scala no tiene compatibilidad binaria entre versiones menores de Scala: 2.X. Por esta razón, la misma API "io.reactivex" %% "rxscala"% "0.26.5" tiene tres versiones (en el futuro más): para Scala 2.10, 2.11 y 2.12. ¿Qué ha cambiado? No lo sé por ahora , pero los binarios no son compatibles. Probablemente, las últimas versiones agregan elementos que hacen que las bibliotecas sean inutilizables en las máquinas virtuales antiguas, probablemente elementos relacionados con las convenciones de vinculación / nomenclatura / parámetros.

Las versiones sucesivas de Java son incompatibles

Java también tiene problemas con las principales versiones de la JVM: 4,5,6,7,8,9. Ofrecen solo compatibilidad con versiones anteriores. Jvm9 sabe cómo ejecutar código compilado / dirigido ( -targetopción de javac ) para todas las demás versiones, mientras que JVM 4 no sabe cómo ejecutar código dirigido para JVM 5. Todo esto mientras tiene una biblioteca joda. Esta incompatibilidad vuela por debajo del radar gracias a diferentes soluciones:

  1. Versiones semánticas: cuando las bibliotecas se dirigen a JVM más altas, generalmente cambian la versión principal.
  2. Use JVM 4 como ABI y estará a salvo.
  3. Java 9 agrega una especificación sobre cómo puede incluir bytecode para JVM específico en la misma biblioteca.

¿Por qué comencé con la definición de API?

API y ABI son solo convenciones sobre cómo se define la compatibilidad. Las capas inferiores son genéricas con respecto a una plétora de semántica de alto nivel. Por eso es fácil hacer algunas convenciones. El primer tipo de convenciones se trata de la alineación de la memoria, la codificación de bytes, las convenciones de llamada, las codificaciones endian grandes y pequeñas, etc. Además de ellas, se obtienen las convenciones ejecutables como otras descritas, las convenciones de enlace, el código de bytes intermedios como el utilizado por Java o LLVM IR utilizado por GCC. Tercero, obtienes convenciones sobre cómo encontrar bibliotecas, cómo cargarlas (ver cargadores de clases Java). A medida que avanzas más y más en conceptos, tienes nuevas convenciones que consideras como un hecho. Es por eso que no llegaron a las versiones semánticas .versión. Podríamos modificar las versiones semánticas con <major>-<minor>-<patch>-<platform/ABI>. Esto es lo que realmente está sucediendo ya: plataforma ya es un rpm, dll, jar(JVM bytecode), war(JVM + servidor web), apk, 2.11(versión específica Scala) y así sucesivamente. Cuando dices APK, ya hablas de una parte ABI específica de tu API.

API se puede portar a diferentes ABI

El nivel superior de una abstracción (las fuentes escritas en la API más alta se pueden volver a compilar / portar a cualquier otra abstracción de nivel inferior.

Digamos que tengo algunas fuentes para rxscala. Si se cambian las herramientas de Scala, puedo volver a compilarlas. Si la JVM cambia, podría tener conversiones automáticas de la máquina vieja a la nueva sin molestarme con los conceptos de alto nivel. Si bien la transferencia puede ser difícil, ayudará a cualquier otro cliente. Si se crea un nuevo sistema operativo utilizando un código de ensamblador totalmente diferente, se puede crear un traductor.

API portadas a través de idiomas

Hay API que se portan en varios idiomas, como secuencias reactivas . En general, definen asignaciones a lenguajes / plataformas específicos. Yo diría que la API es la especificación maestra definida formalmente en lenguaje humano o incluso un lenguaje de programación específico. Todos los otros "mapeos" son ABI en cierto sentido, más API que el ABI habitual. Lo mismo está sucediendo con las interfaces REST.


1

En resumen y en filosofía, solo las cosas de un tipo pueden llevarse bien, y el ABI podría verse como el tipo de cosas de software que funcionan juntas.


1

También estaba tratando de entender ABI y la respuesta de JesperE fue muy útil.

Desde una perspectiva muy simple, podemos tratar de entender ABI considerando la compatibilidad binaria.

El wiki de KDE define una biblioteca como compatible con binarios "si un programa vinculado dinámicamente a una versión anterior de la biblioteca continúa ejecutándose con versiones más nuevas de la biblioteca sin la necesidad de volver a compilar". Para más información sobre el enlace dinámico, consulte Enlace estático vs enlace dinámico

Ahora, tratemos de ver solo los aspectos más básicos necesarios para que una biblioteca tenga compatibilidad binaria (suponiendo que no haya cambios en el código fuente de la biblioteca):

  1. La misma arquitectura de conjunto de instrucciones compatible con versiones anteriores (instrucciones del procesador, estructura del archivo de registro, organización de la pila, tipos de acceso a la memoria, junto con tamaños, diseño y alineación de los tipos de datos básicos a los que el procesador puede acceder directamente)
  2. Las mismas convenciones de llamadas
  3. Convención de manipulación del mismo nombre (esto podría ser necesario si, por ejemplo, un programa Fortran necesita llamar a alguna función de biblioteca C ++).

Claro, hay muchos otros detalles, pero esto es principalmente lo que cubre el ABI.

Más específicamente para responder a su pregunta, de lo anterior, podemos deducir:

Funcionalidad ABI: compatibilidad binaria

entidades existentes: programa / bibliotecas / SO existentes

consumidor: bibliotecas, SO

¡Espero que esto ayude!


1

Interfaz binaria de aplicación (ABI)

Funcionalidad

  • Traducción del modelo del programador al tipo de datos de dominio del sistema subyacente, tamaño, alineación, la convención de llamada, que controla cómo se pasan los argumentos de las funciones y se recuperan los valores de retorno; los números de llamada del sistema y cómo una aplicación debe hacer llamadas del sistema al sistema operativo; el esquema de manipulación de nombres de los compiladores de idiomas de alto nivel, la propagación de excepciones y la convención de llamadas entre compiladores en la misma plataforma, pero no requieren compatibilidad entre plataformas ...

Entidades existentes:

  • Bloques lógicos que participan directamente en la ejecución del programa: ALU, registros de propósito general, registros para la asignación de memoria / E / S de E / S, etc.

consumidor:

  • Procesador de lenguaje enlazador, ensamblador ...

Quienes los necesiten deben asegurarse de que las cadenas de herramientas de construcción funcionen como un todo. Si escribe un módulo en lenguaje ensamblador, otro en Python, y en lugar de que su propio cargador de arranque quiera usar un sistema operativo, sus módulos de "aplicación" están trabajando a través de límites "binarios" y requieren el acuerdo de dicha "interfaz".

Cambio de nombre de C ++ porque los archivos de objetos de diferentes lenguajes de alto nivel pueden estar obligados a vincularse en su aplicación. Considere usar la biblioteca estándar de GCC para realizar llamadas al sistema a Windows creadas con Visual C ++.

ELF es una expectativa posible del vinculador desde un archivo de objeto para interpretación, aunque JVM podría tener alguna otra idea.

Para una aplicación de Windows RT Store, intente buscar ARM ABI si realmente desea que algunas cadenas de herramientas de compilación funcionen juntas.


1

El término ABI se usa para referirse a dos conceptos distintos pero relacionados.

Cuando se habla de compiladores, se refiere a las reglas utilizadas para traducir de construcciones de nivel fuente a construcciones binarias. ¿Qué tan grandes son los tipos de datos? ¿Cómo funciona la pila? ¿Cómo paso los parámetros a las funciones? ¿Qué registros deben ser guardados por la persona que llama frente a la persona que llama?

Cuando se habla de bibliotecas, se refiere a la interfaz binaria presentada por una biblioteca compilada. Esta interfaz es el resultado de una serie de factores que incluyen el código fuente de la biblioteca, las reglas utilizadas por el compilador y, en algunos casos, las definiciones recogidas de otras bibliotecas.

Los cambios en una biblioteca pueden romper la ABI sin romper la API. Considere, por ejemplo, una biblioteca con una interfaz como.

void initfoo(FOO * foo)
int usefoo(FOO * foo, int bar)
void cleanupfoo(FOO * foo)

y el programador de aplicaciones escribe código como

int dostuffwithfoo(int bar) {
  FOO foo;
  initfoo(&foo);
  int result = usefoo(&foo,bar)
  cleanupfoo(&foo);
  return result;
}

El programador de la aplicación no se preocupa por el tamaño o el diseño de FOO, pero el binario de la aplicación termina con un tamaño codificado de foo. Si el programador de la biblioteca agrega un campo adicional a foo y alguien usa el nuevo binario de la biblioteca con el viejo binario de la aplicación, entonces la biblioteca puede hacer accesos de memoria fuera de los límites.

OTOH si el autor de la biblioteca hubiera diseñado su API como.

FOO * newfoo(void)
int usefoo(FOO * foo, int bar)
void deletefoo((FOO * foo, int bar))

y el programador de aplicaciones escribe código como

int dostuffwithfoo(int bar) {
  FOO * foo;
  foo = newfoo();
  int result = usefoo(foo,bar)
  deletefoo(foo);
  return result;
}

Entonces, el binario de la aplicación no necesita saber nada sobre la estructura de FOO, todo eso puede estar oculto dentro de la biblioteca. Sin embargo, el precio que paga por eso es que están involucradas las operaciones de almacenamiento dinámico.


0

ABI- Application Binary Interfacese trata de una comunicación de código de máquina en tiempo de ejecución entre dos partes de programas binarios como - aplicación, biblioteca, sistema operativo ... ABIdescribe cómo se guardan los objetos en la memoria y cómo se llaman las funciones ( calling convention)

Un buen ejemplo de API y ABI es el ecosistema iOS con lenguaje Swift .

  • Application- Cuando crea una aplicación usando diferentes idiomas. Por ejemplo, puede crear aplicaciones usando Swifty Objective-C[Mezclando Swift y Objective-C]

  • Application - OS- tiempo de ejecución - Swift runtimey standard librariesson partes del sistema operativo y no deben incluirse en cada paquete (por ejemplo, aplicación, marco). Es lo mismo que utiliza Objective-C

  • Library- Module Stabilitycaso - tiempo de compilación - podrá importar un marco creado con otra versión del compilador de Swift. Significa que es seguro crear un binario de código cerrado (precompilación) que será consumido por una versión diferente del compilador ( .swiftinterfacese usa con .swiftmodule) y no obtendrá

    Module compiled with _ cannot be imported by the _ compiler
    
  • Library- Library Evolutioncaso

    1. Tiempo de compilación: si se cambió una dependencia, no se debe volver a compilar un cliente.
    2. Tiempo de ejecución: una biblioteca del sistema o un marco dinámico pueden intercambiarse en caliente por uno nuevo.

[API vs ABI]

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.