Qu'est-ce qu'une interface binaire d'application (ABI)?


493

Je n'ai jamais clairement compris ce qu'est un ABI. Veuillez ne pas me diriger vers un article Wikipedia. Si je pouvais comprendre, je ne serais pas ici pour publier un article aussi long.

C'est mon état d'esprit sur les différentes interfaces:

Une télécommande TV est une interface entre l'utilisateur et le téléviseur. C'est une entité existante, mais inutile (ne fournit aucune fonctionnalité) par elle-même. Toutes les fonctionnalités de chacun de ces boutons de la télécommande sont implémentées dans le téléviseur.

Interface: Il s'agit d'une couche "entité existante" entre le functionalityet consumerde cette fonctionnalité. Une interface seule ne fait rien. Il invoque simplement la fonctionnalité qui se trouve derrière.

Maintenant, selon qui est l'utilisateur, il existe différents types d'interfaces.

Les commandes CLI (Interface de ligne de commande) sont les entités existantes, le consommateur est l'utilisateur et la fonctionnalité est derrière.

functionality: ma fonctionnalité logicielle qui résout un objectif auquel nous décrivons cette interface.

existing entities: commandes

consumer: utilisateur

La fenêtre de l' interface utilisateur graphique (GUI) , les boutons, etc. sont les entités existantes, et encore une fois le consommateur est l'utilisateur et la fonctionnalité est derrière.

functionality: ma fonctionnalité logicielle qui résout un problème auquel nous décrivons cette interface.

existing entities: fenêtre, boutons etc.

consumer: utilisateur

Les fonctions d'interface de programmation d'application (API) (ou, pour être plus correct), les interfaces (dans la programmation basée sur l'interface) sont les entités existantes, le consommateur est un autre programme et non un utilisateur, et là encore, la fonctionnalité se trouve derrière cette couche.

functionality: ma fonctionnalité logicielle qui résout un problème auquel nous décrivons cette interface.

existing entities: fonctions, Interfaces (tableau de fonctions).

consumer: un autre programme / application.

Interface binaire d'application (ABI) Voici où commence mon problème.

functionality: ???

existing entities: ???

consumer: ???

  • J'ai écrit des logiciels dans différentes langues et fourni différents types d'interfaces (CLI, GUI et API), mais je ne sais pas si j'ai déjà fourni une ABI.

Wikipédia dit:

Les ABI couvrent des détails tels que

  • type, taille et alignement des données;
  • la convention d'appel, qui contrôle la façon dont les arguments des fonctions sont passés et les valeurs de retour récupérées;
  • les numéros d'appel système et comment une application doit effectuer des appels système vers le système d'exploitation;

D'autres ABI standardisent des détails tels que

  • le changement de nom C ++,
  • propagation d'exception, et
  • convention d'appel entre compilateurs sur la même plate-forme, mais ne nécessite pas de compatibilité entre plates-formes.
  • Qui a besoin de ces détails? Veuillez ne pas dire le système d'exploitation. Je connais la programmation d'assemblage. Je sais comment fonctionne le lien et le chargement. Je sais exactement ce qui se passe à l'intérieur.

  • Pourquoi le changement de nom C ++ est-il entré? Je pensais que nous parlons au niveau binaire. Pourquoi les langues entrent-elles?

Quoi qu'il en soit, j'ai téléchargé [PDF] System V Application Binary Interface Edition 4.1 (1997-03-18) pour voir ce qu'il contient exactement. Eh bien, la plupart n'avaient aucun sens.

  • Pourquoi contient-il deux chapitres (4e et 5e) pour décrire le format de fichier ELF ? En fait, ce sont les deux seuls chapitres importants de cette spécification. Les autres chapitres sont "spécifiques au processeur". Quoi qu'il en soit, je pensais que c'était un sujet complètement différent. Veuillez ne pas dire que les spécifications du format de fichier ELF sont l'ABI. Il ne remplit pas les conditions requises pour être une interface selon la définition.

  • Je sais, puisque nous parlons à un niveau aussi bas, cela doit être très précis. Mais je ne sais pas en quoi est-ce que "l'architecture de jeu d'instructions (ISA)" est spécifique?

  • Où puis-je trouver l'ABI de Microsoft Windows?

Ce sont donc les principales requêtes qui me dérangent.


7
"S'il vous plaît ne dites pas, OS" Les compilateurs doivent connaître l'ABI. Les éditeurs de liens doivent connaître l'ABI. Le noyau doit connaître l'ABI afin de configurer le programme en RAM pour qu'il fonctionne correctement. En ce qui concerne C ++, voir ci-dessous, il transforme intentionnellement les étiquettes en charabia à cause de la surcharge et des méthodes privées, et l'éditeur de liens et tout autre compilateur doivent avoir un changement de nom compatible pour fonctionner avec, en d'autres termes le même ABI.
Justin Smith

8
Je pense que la question est si claire; décrivant exactement quel est le format de réponse attendu et pourtant pas une seule réponse satisfaisante ce qui peut être accepté.
legends2k

3
@ legends2k Mon point de vue sur la question est qu'OP sait en effet ce qu'est un ABI, mais ne le réalise pas. La grande majorité des programmeurs ne concevront ou ne fourniront jamais d'ABI, car c'est le travail des concepteurs de systèmes d'exploitation / plateformes.
JesperE

4
@JesperE: J'accepte votre point. Mais l'OP veut probablement le savoir clairement, dans le format qu'il / elle juge approprié, même s'il / elle n'a peut-être pas besoin de fournir un ABI.
legends2k

2
J'étais ignorant. Récemment en travaillant avec toutes ces choses. J'ai réalisé ce qu'est réellement ABI. Ouais, je suis d'accord que mon modèle est défectueux. Ce n'est pas approprié d'intégrer ABI dans mon modèle. Merci @ JasperE. Il a juste fallu de l'expérience professionnelle pour réaliser votre réponse.
griffes

Réponses:


536

Une façon simple de comprendre "ABI" est de le comparer à "API".

Vous connaissez déjà le concept d'une API. Si vous souhaitez utiliser les fonctionnalités de, disons, une bibliothèque ou votre système d'exploitation, vous programmez contre une API. L'API se compose de types / structures de données, constantes, fonctions, etc. que vous pouvez utiliser dans votre code pour accéder aux fonctionnalités de ce composant externe.

Un ABI est très similaire. Considérez-le comme la version compilée d'une API (ou comme une API au niveau du langage machine). Lorsque vous écrivez du code source, vous accédez à la bibliothèque via une API. Une fois le code compilé, votre application accède aux données binaires de la bibliothèque via l'ABI. L'ABI définit les structures et les méthodes que votre application compilée utilisera pour accéder à la bibliothèque externe (comme l'a fait l'API), uniquement à un niveau inférieur. Votre API définit l'ordre dans lequel vous passez des arguments à une fonction. Votre ABI définit la mécanique de la façon dontces arguments sont passés (registres, pile, etc.). Votre API définit les fonctions qui font partie de votre bibliothèque. Votre ABI définit comment votre code est stocké dans le fichier de bibliothèque, de sorte que tout programme utilisant votre bibliothèque puisse localiser la fonction souhaitée et l'exécuter.

Les ABI sont importants en ce qui concerne les applications qui utilisent des bibliothèques externes. Les bibliothèques sont pleines de code et d'autres ressources, mais votre programme doit savoir comment localiser ce dont il a besoin dans le fichier de bibliothèque. Votre ABI définit la façon dont le contenu d'une bibliothèque est stocké dans le fichier, et votre programme utilise l'ABI pour rechercher dans le fichier et trouver ce dont il a besoin. Si tout dans votre système est conforme au même ABI, n'importe quel programme peut fonctionner avec n'importe quel fichier de bibliothèque, peu importe qui les a créés. Linux et Windows utilisent des ABI différents, donc un programme Windows ne saura pas comment accéder à une bibliothèque compilée pour Linux.

Parfois, les changements ABI sont inévitables. Lorsque cela se produit, tous les programmes qui utilisent cette bibliothèque ne fonctionneront pas, sauf s'ils sont recompilés pour utiliser la nouvelle version de la bibliothèque. Si l'ABI change mais l'API ne change pas, alors l'ancienne et la nouvelle version de la bibliothèque sont parfois appelées "compatible avec la source". Cela implique que même si un programme compilé pour une version de bibliothèque ne fonctionnera pas avec l'autre, le code source écrit pour l'une fonctionnera pour l'autre s'il est recompilé.

Pour cette raison, les développeurs ont tendance à essayer de maintenir leur ABI stable (pour minimiser les perturbations). Garder un ABI stable signifie ne pas changer les interfaces de fonction (type et nombre de retour, types et ordre des arguments), les définitions des types de données ou des structures de données, les constantes définies, etc. De nouvelles fonctions et types de données peuvent être ajoutés, mais les existants doivent rester le même. Si, par exemple, votre bibliothèque utilise des entiers 32 bits pour indiquer le décalage d'une fonction et que vous basculez vers des entiers 64 bits, le code déjà compilé qui utilise cette bibliothèque n'accédera pas correctement à ce champ (ou à tout autre suivi) . L'accès aux membres de la structure de données est converti en adresses de mémoire et décalages lors de la compilation et si la structure de données change,

Un ABI n'est pas nécessairement quelque chose que vous fournirez explicitement, sauf si vous effectuez un travail de conception de systèmes de très bas niveau. Il n'est pas spécifique au langage non plus, car (par exemple) une application C et une application Pascal peuvent utiliser le même ABI après leur compilation.

Éditer:Concernant votre question sur les chapitres concernant le format de fichier ELF dans les documents SysV ABI: La raison pour laquelle ces informations sont incluses est que le format ELF définit l'interface entre le système d'exploitation et l'application. Lorsque vous dites au système d'exploitation d'exécuter un programme, il s'attend à ce que le programme soit formaté d'une certaine manière et (par exemple) s'attend à ce que la première section du binaire soit un en-tête ELF contenant certaines informations à des décalages de mémoire spécifiques. C'est ainsi que l'application communique des informations importantes sur elle-même au système d'exploitation. Si vous créez un programme dans un format binaire non ELF (tel que a.out ou PE), un système d'exploitation qui attend des applications au format ELF ne pourra pas interpréter le fichier binaire ni exécuter l'application.

IIRC, Windows utilise actuellement le format Portable Executable (ou PE). Il y a des liens dans la section "liens externes" de cette page Wikipedia avec plus d'informations sur le format PE.

En outre, en ce qui concerne votre remarque sur le changement de nom C ++: lors de la localisation d'une fonction dans un fichier de bibliothèque, la fonction est généralement recherchée par son nom. C ++ vous permet de surcharger les noms de fonctions, donc le nom seul n'est pas suffisant pour identifier une fonction. Les compilateurs C ++ ont leurs propres façons de gérer cela en interne, appelés mangling de nom . Un ABI peut définir une manière standard d'encoder le nom d'une fonction afin que les programmes construits avec un langage ou un compilateur différent puissent localiser ce dont ils ont besoin. Lorsque vous utilisez extern "c"dans un programme C ++, vous demandez au compilateur d'utiliser une manière standardisée d'enregistrer des noms compréhensibles par d'autres logiciels.


2
@bta, Merci pour la bonne réponse. La convention d'appel est-elle une sorte d'ABI? Merci
camino

37
Bonne réponse. Sauf que ce n'est pas ce qu'est un ABI. Un ABI est un ensemble de règles qui détermine la convention d'appel et les règles de disposition des structures. Pascal transmet les arguments sur la pile dans l'ordre inverse des applications C, donc les compilateurs pascal et C ne compilent PAS vers le même ABI. Les normes respectives pour les compilateurs C et Pascal garantissent implicitement que ce sera le cas. Les compilateurs C ++ ne peuvent pas définir une manière "standard" de modifier les noms, car il n'y a pas de méthode standard. Les conventions de manipulation de nom C ++ n'étaient pas compatibles entre les compilateurs C ++ lorsqu'il existait des compilateurs C ++ concurrents sur Windows.
Robin Davies


1
@RobinDavies: Sur les plates-formes où les compilateurs Pascal auraient appelé les fonctions des arguments pop donnés par leurs appelants, les compilateurs C définiraient généralement les moyens par lesquels un programmeur pourrait indiquer que des fonctions particulières devraient utiliser, ou devraient utiliser, les mêmes conventions d'appel que les Les compilateurs Pascal, même si les compilateurs C utilisent généralement par défaut une convention où les fonctions appelées laissent sur la pile tout ce qui y est placé par leurs appelants.
supercat

Puis-je dire que les fichiers obj générés par le compilateur C contiennent des ABI?
Mitu Raj

144

Si vous connaissez l'assemblage et comment les choses fonctionnent au niveau du système d'exploitation, vous vous conformez à un certain ABI. L'ABI régit des choses comme la façon dont les paramètres sont passés, où les valeurs de retour sont placées. Pour de nombreuses plates-formes, il n'y a qu'un seul ABI parmi lequel choisir, et dans ces cas, l'ABI est simplement "comment les choses fonctionnent".

Cependant, l'ABI régit également des choses comme la façon dont les classes / objets sont disposés en C ++. Ceci est nécessaire si vous voulez pouvoir passer des références d'objet à travers les limites du module ou si vous voulez mélanger du code compilé avec différents compilateurs.

De plus, si vous avez un système d'exploitation 64 bits qui peut exécuter des binaires 32 bits, vous aurez différents ABI pour le code 32 et 64 bits.

En général, tout code que vous liez au même exécutable doit être conforme au même ABI. Si vous souhaitez communiquer entre du code utilisant différents ABI, vous devez utiliser une certaine forme de protocole RPC ou de sérialisation.

Je pense que vous essayez trop fort de presser différents types d'interfaces dans un ensemble fixe de caractéristiques. Par exemple, une interface ne doit pas nécessairement être divisée en consommateurs et en producteurs. Une interface n'est qu'une convention par laquelle deux entités interagissent.

Les ABI peuvent être (partiellement) agnostiques à l'ISA. Certains aspects (tels que les conventions d'appel) dépendent de l'ISA, tandis que d'autres aspects (tels que la disposition des classes C ++) n'en dépendent pas.

Un ABI bien défini est très important pour les personnes qui écrivent des compilateurs. Sans un ABI bien défini, il serait impossible de générer du code interopérable.

EDIT: Quelques notes pour clarifier:

  • "Binaire" dans ABI n'exclut pas l'utilisation de chaînes ou de texte. Si vous souhaitez lier une DLL exportant une classe C ++, quelque part, les méthodes et les signatures de type doivent être codées. C'est là qu'intervient la manipulation de noms C ++.
  • La raison pour laquelle vous n'avez jamais fourni d'ABI est que la grande majorité des programmeurs ne le feront jamais. Les ABI sont fournis par les mêmes personnes qui conçoivent la plate-forme (c'est-à-dire le système d'exploitation), et très peu de programmeurs auront jamais le privilège de concevoir un ABI largement utilisé.

Je ne suis pas du tout convaincu que mon modèle soit défectueux. Parce que partout où ce modèle d'interface est vrai. Donc, oui, je veux que je m'attende à ce qu'ABI s'intègre également dans ce modèle, mais ce n'est pas le cas. La chose IMPORTANTE est que je ne comprends toujours pas. Je ne sais pas si je suis si stupide ou quelque chose d'autre, mais ça ne me vient pas à l'esprit. Je n'arrive pas à réaliser les réponses et l'article wiki.
griffes

2
@jesperE, "L'ABI régit des choses comme la façon dont les paramètres sont passés, où les valeurs de retour sont placées." fait référence à "cdecl, stdcall, fastcall, pascal" non?
camino

3
Oui. Le nom propre est «convention d'appel», qui fait partie de l'ABI. en.wikipedia.org/wiki/X86_calling_conventions
JesperE

4
c'est la réponse correcte et précise sans la verbosité (plutôt le bruit )!
Nawaz

Je recommande d'écrire un peu d'assemblage. Cela aidera les gens à comprendre l'ABI d'une manière plus tangible.
KunYu Tsai

40

En fait, vous n'avez pas du tout besoin d'un ABI si ...

  • Votre programme n'a pas de fonctions, et--
  • Votre programme est un exécutable unique qui s'exécute seul (c'est-à-dire un système embarqué) où il est littéralement la seule chose en cours d'exécution et n'a pas besoin de parler à autre chose.

Un résumé trop simplifié:

API: "Voici toutes les fonctions que vous pouvez appeler."

ABI: "Voici comment appeler une fonction."

L'ABI est un ensemble de règles auxquelles les compilateurs et les éditeurs de liens adhèrent afin de compiler votre programme afin que cela fonctionne correctement. Les ABI couvrent plusieurs sujets:

  • On peut dire que la partie la plus importante et la plus importante d'un ABI est la norme d'appel de procédure parfois connue sous le nom de «convention d'appel». Les conventions d'appel normalisent la façon dont les «fonctions» sont traduites en code assembleur.
  • Les ABI dictent également la façon dont les noms des fonctions exposées dans les bibliothèques doivent être représentés afin que d'autres codes puissent appeler ces bibliothèques et savoir quels arguments doivent être passés. C'est ce qu'on appelle le "mutilation des noms".
  • Les ABI dictent également quel type de types de données peuvent être utilisés, comment ils doivent être alignés et d'autres détails de bas niveau.

Un examen plus approfondi de la convention d'appel, que je considère comme le cœur d'un ABI:

La machine elle-même n'a pas de concept de "fonctions". Lorsque vous écrivez une fonction dans un langage de haut niveau comme c, le compilateur génère une ligne de code assembleur comme _MyFunction1:. Il s'agit d'une étiquette qui sera finalement résolue en une adresse par l'assembleur. Cette étiquette marque le "début" de votre "fonction" dans le code d'assemblage. Dans le code de haut niveau, lorsque vous "appelez" cette fonction, ce que vous faites vraiment, c'est que le CPU saute à l'adresse de cette étiquette et continue de s'exécuter là-bas.

En préparation pour le saut, le compilateur doit faire un tas de choses importantes. La convention d'appel est comme une liste de contrôle que le compilateur suit pour faire tout cela:

  • Tout d'abord, le compilateur insère un peu de code assembleur pour enregistrer l'adresse actuelle, de sorte que lorsque votre "fonction" est terminée, le CPU peut revenir au bon endroit et continuer à s'exécuter.
  • Ensuite, le compilateur génère du code d'assembly pour passer les arguments.
    • Certaines conventions d'appel dictent que les arguments doivent être placés sur la pile ( dans un ordre particulier bien sûr).
    • D'autres conventions exigent que les arguments soient placés dans des registres particuliers (en fonction de leurs types de données bien sûr de de ).
    • D'autres conventions encore exigent qu'une combinaison spécifique de pile et de registres soit utilisée.
  • Bien sûr, s'il y avait quelque chose d'important dans ces registres auparavant, ces valeurs sont maintenant écrasées et perdues à jamais, donc certaines conventions d'appel peuvent dicter que le compilateur doit enregistrer certains de ces registres avant d'y mettre les arguments.
  • Maintenant, le compilateur insère une instruction de saut indiquant au CPU d'aller à l'étiquette qu'il a faite précédemment ( _MyFunction1:). À ce stade, vous pouvez considérer que le CPU est "dans" votre "fonction".
  • À la fin de la fonction, le compilateur place du code d'assemblage qui obligera le CPU à écrire la valeur de retour au bon endroit. La convention d'appel déterminera si la valeur de retour doit être placée dans un registre particulier (selon son type), ou sur la pile.
  • Il est maintenant temps de nettoyer. La convention d'appel dictera où le compilateur place le code d'assembly de nettoyage.
    • Certaines conventions stipulent que l'appelant doit nettoyer la pile. Cela signifie qu'après que la "fonction" est terminée et que le CPU revient à l'endroit où il était auparavant, le code suivant à exécuter doit être un code de nettoyage très spécifique.
    • D'autres conventions disent que certaines parties particulières du code de nettoyage devraient être à la fin de la "fonction" avant le saut en arrière.

Il existe de nombreuses ABI / conventions d'appel différentes. Certains principaux sont:

  • Pour le processeur x86 ou x86-64 (environnement 32 bits):
    • CDECL
    • STDCALL
    • FASTCALL
    • VECTORCALL
    • THISCALL
  • Pour le processeur x86-64 (environnement 64 bits):
    • SYSTEMV
    • MSNATIVE
    • VECTORCALL
  • Pour le processeur ARM (32 bits)
    • AAPCS
  • Pour le processeur ARM (64 bits)
    • AAPCS64

Ici une excellente page qui montre réellement les différences dans l'assembly généré lors de la compilation pour différents ABI.

Une autre chose à mentionner est qu'un ABI n'est pas seulement pertinent à l' intérieur du module exécutable de votre programme. Il est également utilisé par l'éditeur de liens pour s'assurer que votre programme appelle correctement les fonctions de la bibliothèque. Vous avez plusieurs bibliothèques partagées en cours d'exécution sur votre ordinateur, et tant que votre compilateur sait quel ABI il utilise, il peut appeler des fonctions correctement sans faire exploser la pile.

Votre compilateur comprenant comment appeler les fonctions de bibliothèque est extrêmement important. Sur une plate-forme hébergée (c'est-à-dire une plate-forme où un système d'exploitation charge des programmes), votre programme ne peut même pas clignoter sans effectuer d'appel du noyau.


19

Une interface binaire d'application (ABI) est similaire à une API, mais la fonction n'est pas accessible à l'appelant au niveau du code source. Seule une représentation binaire est accessible / disponible.

Les ABI peuvent être définis au niveau de l'architecture du processeur ou au niveau du système d'exploitation. Les ABI sont des normes à suivre par la phase de génération de code du compilateur. La norme est fixée soit par l'OS soit par le processeur.

Fonctionnalité: Définissez le mécanisme / la norme pour effectuer des appels de fonction indépendamment du langage d'implémentation ou d'un compilateur / éditeur de liens / chaîne d'outils spécifique. Fournissez le mécanisme qui autorise JNI, ou une interface Python-C, etc.

Entités existantes: fonctions sous forme de code machine.

Consommateur: autre fonction (dont une dans une autre langue, compilée par un autre compilateur ou liée par un autre éditeur de liens).


Pourquoi l'ABI serait-il défini par l'architecture? Pourquoi différents OS sur la même architecture ne pourraient-ils pas définir différents ABI?
Andreas Haferburg

10

Fonctionnalité: ensemble de contrats affectant le compilateur, les rédacteurs d'assemblage, l'éditeur de liens et le système d'exploitation. Les contrats spécifient comment les fonctions sont disposées, où les paramètres sont passés, comment les paramètres sont passés, comment les retours de fonction fonctionnent. Celles-ci sont généralement spécifiques à un tuple (architecture de processeur, système d'exploitation).

Entités existantes: disposition des paramètres, sémantique des fonctions, allocation des registres. Par exemple, les architectures ARM ont de nombreux ABI (APCS, EABI, GNU-EABI, sans parler d'un tas de cas historiques) - en utilisant un ABI mixte, votre code ne fonctionnera tout simplement pas lors d'appels au-delà des frontières.

Consommateur: compilateur, rédacteurs d'assemblage, système d'exploitation, architecture spécifique au processeur.

Qui a besoin de ces détails? Le compilateur, les rédacteurs d'assemblage, les éditeurs de liens qui génèrent du code (ou les exigences d'alignement), le système d'exploitation (gestion des interruptions, interface syscall). Si vous faisiez de la programmation d'assemblage, vous vous conformiez à un ABI!

Le mangling de nom C ++ est un cas spécial - c'est un problème lié à l'éditeur de liens et au linker dynamique - si le mangling de nom n'est pas standardisé, alors la liaison dynamique ne fonctionnera pas. Désormais, le C ++ ABI est appelé juste ainsi, le C ++ ABI. Ce n'est pas un problème au niveau de l'éditeur de liens, mais plutôt un problème de génération de code. Une fois que vous avez un binaire C ++, il n'est pas possible de le rendre compatible avec un autre ABI C ++ (gestion de nom, gestion des exceptions) sans recompilation à partir de la source.

ELF est un format de fichier pour l'utilisation d'un chargeur et d'un éditeur de liens dynamique. ELF est un format de conteneur pour le code binaire et les données, et en tant que tel spécifie l'ABI d'un morceau de code. Je ne considérerais pas ELF comme un ABI au sens strict, car les exécutables PE ne sont pas un ABI.

Tous les ABI sont spécifiques au jeu d'instructions. Un ARM ABI n'a aucun sens sur un processeur MSP430 ou x86_64.

Windows a plusieurs ABI - par exemple, fastcall et stdcall sont deux ABI d'usage courant. Le syscall ABI est à nouveau différent.


9

Permettez-moi au moins de répondre à une partie de votre question. Avec un exemple de la façon dont l'ABI Linux affecte les appels système et pourquoi cela est utile.

Un appel système est un moyen pour un programme de l'espace utilisateur de demander quelque chose au noyau. Il fonctionne en plaçant le code numérique de l'appel et de l'argument dans un certain registre et en déclenchant une interruption. Ensuite, un basculement se produit vers kernelspace et le noyau recherche le code numérique et l'argument, gère la demande, remet le résultat dans un registre et déclenche un basculement vers l'espace utilisateur. Cela est nécessaire par exemple lorsque l'application souhaite allouer de la mémoire ou ouvrir un fichier (syscalls "brk" et "open").

Maintenant, les appels système ont des noms courts "brk", etc. et les opcodes correspondants, ils sont définis dans un fichier d'en-tête spécifique au système. Tant que ces opcodes restent les mêmes, vous pouvez exécuter les mêmes programmes utilisateur compilés avec différents noyaux mis à jour sans avoir à recompiler. Vous avez donc une interface utilisée par les binaires précompilés, d'où ABI.


4

Pour appeler du code dans des bibliothèques partagées ou du code d'appel entre des unités de compilation, le fichier objet doit contenir des étiquettes pour les appels. C ++ modifie les noms des étiquettes de méthode afin d'imposer le masquage des données et de permettre des méthodes surchargées. C'est pourquoi vous ne pouvez pas mélanger des fichiers de différents compilateurs C ++ à moins qu'ils ne prennent explicitement en charge le même ABI.


4

La meilleure façon de faire la différence entre ABI et API est de savoir pourquoi et à quoi sert-elle:

Pour x86-64, il y a généralement un ABI (et pour x86 32 bits, il y en a un autre):

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 le suivent avec quelques légères variations. Et Windows x64 possède son propre ABI:

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

Connaître l'ABI et supposer qu'un autre compilateur le suit également, alors les binaires savent théoriquement comment s'appeler (API de bibliothèques en particulier) et passer des paramètres sur la pile ou par des registres, etc. Ou quels registres seront modifiés lors de l'appel des fonctions, etc. Ces connaissances aideront essentiellement les logiciels à s'intégrer les uns aux autres. Connaissant l'ordre de la disposition des registres / piles, je peux facilement assembler différents logiciels écrits en assemblages sans trop de problème.

Mais les API sont différentes:

Il s'agit d'un nom de fonctions de haut niveau, avec un argument défini, de sorte que si différentes pièces de logiciel sont construites à l'aide de ces API, PEUVENT être capables de s'appeler les unes les autres. Mais une exigence supplémentaire de SAME ABI doit être respectée.

Par exemple, Windows était auparavant compatible avec l'API POSIX:

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

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

Et Linux est également compatible POSIX. Mais les binaires ne peuvent pas être simplement déplacés et exécutés immédiatement. Mais comme ils utilisaient les mêmes NOMS dans l'API compatible POSIX, vous pouvez prendre le même logiciel en C, le recompiler dans les différents OS et le faire fonctionner immédiatement.

Les API visent à faciliter l'intégration des logiciels - étape de pré-compilation. Ainsi, après la compilation, le logiciel peut être totalement différent - si les ABI sont différents.

Les ABI sont destinés à définir l'intégration exacte des logiciels au niveau binaire / assembleur.


La convention d'appel Windows x86-64 n'utilise pas la convention d'appel SysV que tous les autres systèmes d'exploitation x86-64 utilisent. Linux / OS X / FreeBSD partagent tous la même convention d'appel, mais ils ne partagent pas l'ABI complet. L'ABI d'un système d'exploitation comprend des numéros d'appel système. Par exemple, freebsd.org/doc/en_US.ISO8859-1/books/developers-handbook/… dit que SYS_execvec'est 11 sur 32 bits linux, mais 59 sur FreeBSD.
Peter Cordes

merci pour votre commentaire, j'ai modifié mon commentaire pour mieux répondre à la différence entre ABI et API.
Peter Teoh

Il vous manque toujours la différence entre une convention d'appel et un ABI complet (appels système et tout). Vous pouvez exécuter certains binaires FreeBSD sous Linux, car Linux (le noyau) fournit une couche de compatibilité FreeBSD. Même alors, cela est limité aux binaires qui n'essaient pas d'utiliser une partie de FreeBSD ABI que Linux ne fournit pas. (par exemple, tout appel système FreeBSD uniquement). Compatible ABI signifie que vous pouvez exécuter le même binaire sur les deux systèmes, pas seulement qu'ils compileraient de la même manière.
Peter Cordes

"Couche de compatibilité FreeBSD", je n'en ai jamais entendu parler. Pouvez-vous pointer vers le code source du noyau Linux pertinent? Mais l'inverse existe: freebsd.org/doc/en_US.ISO8859-1/books/handbook/linuxemu.html .
Peter Teoh

Ce n'est pas quelque chose que j'utilise. Je pensais que quelque chose comme ça existait, mais peut-être que ça n'existe plus. tldp.org/HOWTO/Linux+FreeBSD-6.html dit que ce n'est pas maintenu et que le howto date de 2000. xD. unix.stackexchange.com/questions/172038/… confirme qu'il a été abandonné et n'a jamais été refait (puisque personne ne le voulait assez pour le faire). personality(2)peut définir PER_BSD. Je pense que je me souviens d'avoir vu personality(PER_LINUX)en stracesortie tout le temps, mais les binaires Linux 64 bits modernes ne le font plus.
Peter Cordes

4

Exemple ABI exécutable minimal de bibliothèque partagée Linux

Dans le contexte des bibliothèques partagées, l'implication la plus importante d '"avoir un ABI stable" est que vous n'avez pas besoin de recompiler vos programmes après les changements de bibliothèque.

Ainsi, par exemple:

  • si vous vendez une bibliothèque partagée, vous évitez à vos utilisateurs l'ennui de recompiler tout ce qui dépend de votre bibliothèque pour chaque nouvelle version

  • si vous vendez un programme source fermé qui dépend d'une bibliothèque partagée présente dans la distribution de l'utilisateur, vous pouvez libérer et tester moins de préconstructions si vous êtes certain qu'ABI est stable sur certaines versions du système d'exploitation cible.

    Ceci est particulièrement important dans le cas de la bibliothèque standard C, à laquelle de nombreux programmes de votre système sont liés.

Maintenant, je veux en fournir un exemple exécutable concret minimal.

principal c

#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

Compile et fonctionne bien avec:

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

Supposons maintenant que pour la v2 de la bibliothèque, nous voulons ajouter un nouveau champ à mylib_mystructappelénew_field .

Si nous avons ajouté le champ avant old_fieldcomme dans:

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

et reconstruit la bibliothèque mais pas main.out , alors l'assertion échoue!

C'est parce que la ligne:

myobject->old_field == 1

avait généré l'assembly qui tente d'accéder au tout premier intde la structure, qui est maintenant new_fieldau lieu de l'attenduold_field .

Par conséquent, ce changement a brisé l'ABI.

Si, cependant, nous ajoutons new_fieldaprès old_field:

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

puis l'ancien assemblage généré accède toujours au premier int de la structure, et le programme fonctionne toujours, car nous avons maintenu l'ABI stable.

Voici une version entièrement automatisée de cet exemple sur GitHub .

Une autre façon de maintenir cet ABI stable aurait été de traiter mylib_mystructcomme une structure opaque et d'accéder uniquement à ses champs via des assistants de méthode. Cela facilite la stabilité de l'ABI, mais entraînerait une surcharge de performances car nous ferions plus d'appels de fonctions.

API vs ABI

Dans l'exemple précédent, il est intéressant de noter que l'ajout de l' new_fieldavant old_fieldne cassait que l'ABI, mais pas l'API.

Ce que cela signifie, c'est que si nous avions recompilé notre main.c programme par rapport à la bibliothèque, cela aurait fonctionné malgré tout.

Nous aurions également cassé l'API si nous avions changé par exemple la signature de la fonction:

mylib_mystruct* mylib_init(int old_field, int new_field);

puisque dans ce cas, main.c arrêterait complètement la compilation.

API sémantique vs API de programmation

Nous pouvons également classer les changements d'API dans un troisième type: les changements sémantiques.

L'API sémantique est généralement une description en langage naturel de ce que l'API est censée faire, généralement incluse dans la documentation de l'API.

Il est donc possible de casser l'API sémantique sans casser la construction du programme lui-même.

Par exemple, si nous avions modifié

myobject->old_field = old_field;

à:

myobject->old_field = old_field + 1;

alors cela n'aurait brisé ni l'API de programmation, ni l'ABI, mais main.cl'API sémantique se briserait.

Il existe deux façons de vérifier par programme l'API de contrat:

  • tester un tas de cas d'angle. Facile à faire, mais vous pourriez toujours en manquer un.
  • vérification formelle . Plus difficile à faire, mais produit une preuve mathématique d'exactitude, unifiant essentiellement la documentation et les tests d'une manière vérifiable "humaine" / machine! Tant qu'il n'y a pas de bug dans votre description formelle bien sûr ;-)

    Ce concept est étroitement lié à la formalisation des mathématiques elles-mêmes: /math/53969/what-does-formal-mean/3297537#3297537

Liste de tout ce qui casse les ABI des bibliothèques partagées C / C ++

TODO: trouvez / créez la liste ultime:

Exemple exécutable Java minimal

Qu'est-ce que la compatibilité binaire en Java?

Testé dans Ubuntu 18.10, GCC 8.2.0.


3

L'ABI doit être cohérent entre l'appelant et l'appelé pour être certain que l'appel réussit. Utilisation de pile, utilisation de registre, pop de pile de fin de routine. Ce sont toutes les parties les plus importantes de l'ABI.


3

Sommaire

Il existe différentes interprétations et opinions fortes sur la couche exacte qui définissent une ABI (interface binaire d'application).

À mon avis, un ABI est une convention subjective de ce qui est considéré comme une plate-forme donnée pour une API spécifique. L'ABI est le "reste" des conventions qui "ne changeront pas" pour une API spécifique ou qui seront traitées par l'environnement d'exécution: exécuteurs, outils, éditeurs de liens, compilateurs, jvm et OS.

Définition d'une interface : ABI, API

Si vous souhaitez utiliser une bibliothèque comme joda-time, vous devez déclarer une dépendance sur joda-time-<major>.<minor>.<patch>.jar. La bibliothèque suit les meilleures pratiques et utilise le versionnage sémantique . Cela définit la compatibilité de l'API à trois niveaux:

  1. Patch - Vous n'avez pas besoin de changer du tout votre code. La bibliothèque corrige juste quelques bugs.
  2. Mineur - Vous n'avez pas besoin de changer votre code depuis les ajouts
  3. Majeur - L'interface (API) a changé et vous devrez peut-être changer votre code.

Afin que vous puissiez utiliser une nouvelle version majeure de la même bibliothèque, de nombreuses autres conventions doivent encore être respectées:

  • Le langage binaire utilisé pour les bibliothèques (dans les cas Java, la version cible JVM qui définit le bytecode Java)
  • Conventions d'appel
  • Conventions JVM
  • Conventions de liaison
  • Conventions d'exécution Toutes celles-ci sont définies et gérées par les outils que nous utilisons.

Exemples

Étude de cas Java

Par exemple, Java a normalisé toutes ces conventions, non pas dans un outil, mais dans une spécification JVM formelle. La spécification a permis à d'autres fournisseurs de fournir un ensemble différent d'outils pouvant produire des bibliothèques compatibles.

Java fournit deux autres études de cas intéressantes pour ABI: les versions Scala et la machine virtuelle Dalvik .

La machine virtuelle Dalvik a cassé l'ABI

La machine virtuelle Dalvik a besoin d'un type de bytecode différent du bytecode Java. Les bibliothèques Dalvik sont obtenues en convertissant le bytecode Java (avec la même API) pour Dalvik. De cette façon, vous pouvez obtenir deux versions de la même API: définies par l'original joda-time-1.7.2.jar. On pourrait m'appeler joda-time-1.7.2.jaretjoda-time-1.7.2-dalvik.jar . Ils utilisent un ABI différent pour les vms Java standard orientés pile: celui d'Oracle, celui d'IBM, Java ouvert ou tout autre; et le deuxième ABI est celui autour de Dalvik.

Les versions successives de Scala sont incompatibles

Scala n'a pas de compatibilité binaire entre les versions mineures de Scala: 2.X. Pour cette raison, la même API "io.reactivex" %% "rxscala"% "0.26.5" a trois versions (à l'avenir plus): pour Scala 2.10, 2.11 et 2.12. Qu'est-ce qui a changé? Je ne sais pas pour l'instant , mais les binaires ne sont pas compatibles. Les dernières versions ajoutent probablement des éléments qui rendent les bibliothèques inutilisables sur les anciennes machines virtuelles, probablement des éléments liés aux conventions de liaison / d'attribution de noms / de paramètres.

Les versions successives de Java sont incompatibles

Java a également des problèmes avec les principales versions de la JVM: 4,5,6,7,8,9. Ils n'offrent qu'une compatibilité descendante. Jvm9 sait comment exécuter du code compilé / ciblé ( -targetoption de javac ) pour toutes les autres versions, tandis que JVM 4 ne sait pas comment exécuter du code ciblé pour JVM 5. Tout cela pendant que vous avez une bibliothèque joda. Cette incompatibilité passe sous le radar grâce à différentes solutions:

  1. Versioning sémantique: lorsque les bibliothèques ciblent une JVM supérieure, elles changent généralement la version principale.
  2. Utilisez JVM 4 comme ABI, et vous êtes en sécurité.
  3. Java 9 ajoute une spécification sur la façon dont vous pouvez inclure le bytecode pour la JVM ciblée spécifique dans la même bibliothèque.

Pourquoi ai-je commencé avec la définition de l'API?

L'API et l'ABI ne sont que des conventions sur la façon dont vous définissez la compatibilité. Les couches inférieures sont génériques en ce qui concerne une pléthore de sémantique de haut niveau. C'est pourquoi il est facile de faire des conventions. Le premier type de conventions sont sur le point d' alignement de la mémoire, l' encodage des octets, les conventions d' appel, petits et grands encodages endian, etc. En plus d'entre eux vous obtenez les conventions exécutables comme les autres décrites, conventions liant, code binaire intermédiaire comme celui utilisé par Java ou LLVM IR utilisé par GCC. Troisièmement, vous obtenez des conventions sur la façon de trouver des bibliothèques, comment les charger (voir Chargeurs de classes Java). Au fur et à mesure que vous progressez dans les concepts, vous avez de nouvelles conventions que vous considérez comme une donnée. C'est pourquoi ils ne sont pas arrivés au versioning sémantique . Ils sont implicites ou effondrés dans leversion. Nous pourrions modifier le versioning sémantique avec <major>-<minor>-<patch>-<platform/ABI>. C'est ce qui se passe en fait déjà: la plate - forme est déjà rpm, dll, jar(bytecode JVM), war(+ jvm serveur web), apk, 2.11(spécifiques à une version Scala) et ainsi de suite. Lorsque vous dites APK, vous parlez déjà d'une partie ABI spécifique de votre API.

L'API peut être portée sur différents ABI

Le niveau supérieur d'une abstraction (les sources écrites par rapport à l'API la plus élevée peuvent être recompilées / portées vers toute autre abstraction de niveau inférieur.

Disons que j'ai quelques sources pour rxscala. Si les outils Scala sont modifiés, je peux les recompiler. Si la JVM change, je pourrais avoir des conversions automatiques de l'ancienne machine vers la nouvelle sans me soucier des concepts de haut niveau. Bien que le portage puisse être difficile, cela aidera tout autre client. Si un nouveau système d'exploitation est créé à l'aide d'un code assembleur totalement différent, un traducteur peut être créé.

API portées dans plusieurs langues

Il existe des API qui sont portées dans plusieurs langues comme les flux réactifs . En général, ils définissent des mappages à des langues / plates-formes spécifiques. Je dirais que l'API est la spécification principale formellement définie en langage humain ou même en langage de programmation spécifique. Tous les autres "mappages" sont en quelque sorte ABI, sinon plus d'API que l'ABI habituel. La même chose se produit avec les interfaces REST.


1

En bref et en philosophie, seules les choses d'un genre peuvent bien s'entendre, et l'ABI pourrait être considéré comme le type de logiciel qui fonctionne ensemble.


1

J'essayais également de comprendre ABI et la réponse de JesperE était très utile.

D'un point de vue très simple, nous pouvons essayer de comprendre ABI en considérant la compatibilité binaire.

KDE wiki définit une bibliothèque comme compatible binaire «si un programme lié dynamiquement à une ancienne version de la bibliothèque continue de fonctionner avec les nouvelles versions de la bibliothèque sans avoir besoin de recompiler.» Pour plus d'informations sur la liaison dynamique, reportez-vous à la section Liaison statique vs liaison dynamique

Maintenant, essayons de regarder uniquement les aspects les plus élémentaires nécessaires à la compatibilité binaire d'une bibliothèque (en supposant qu'il n'y a pas de changement de code source dans la bibliothèque):

  1. Architecture de jeu d'instructions identique / rétrocompatible (instructions du processeur, structure des fichiers de registre, organisation de la pile, types d'accès à la mémoire, ainsi que les tailles, la disposition et l'alignement des types de données de base auxquels le processeur peut accéder directement)
  2. Mêmes conventions d'appel
  3. Convention de gestion du même nom (cela peut être nécessaire si, par exemple, un programme Fortran doit appeler une fonction de bibliothèque C ++).

Bien sûr, il existe de nombreux autres détails, mais c'est surtout ce que l'ABI couvre également.

Plus précisément pour répondre à votre question, de ce qui précède, nous pouvons déduire:

Fonctionnalité ABI: compatibilité binaire

entités existantes: programme / bibliothèques / OS existants

consommateur: bibliothèques, OS

J'espère que cela t'aides!


1

Interface binaire d'application (ABI)

Fonctionnalité:

  • Traduction du modèle du programmeur au type de données de domaine du système sous-jacent, taille, alignement, la convention d'appel, qui contrôle la façon dont les arguments des fonctions sont passés et les valeurs de retour récupérées; les numéros d'appel système et comment une application doit effectuer des appels système vers le système d'exploitation; le schéma de gestion des noms des compilateurs de langage de haut niveau, la propagation des exceptions et la convention d'appel entre les compilateurs sur la même plate-forme, mais ne nécessitent pas de compatibilité entre plates-formes ...

Entités existantes:

  • Blocs logiques qui participent directement à l'exécution du programme: ALU, registres à usage général, registres de mappage mémoire / E / S des E / S, etc ...

consommateur:

  • Editeur de liens, assembleur ...

Ceux qui en ont besoin doivent s'assurer que les chaînes d'outils de construction fonctionnent dans leur ensemble. Si vous écrivez un module en langage assembleur, un autre en Python, et au lieu que votre propre chargeur de démarrage veuille utiliser un système d'exploitation, alors vos modules "application" fonctionnent à travers les frontières "binaires" et nécessitent l'accord d'une telle "interface".

Manipulation des noms C ++ car il peut être nécessaire de lier des fichiers objets de différentes langues de haut niveau dans votre application. Envisagez d'utiliser la bibliothèque standard GCC pour effectuer des appels système vers Windows créés avec Visual C ++.

ELF est une attente possible de l'éditeur de liens à partir d'un fichier objet pour l'interprétation, bien que JVM puisse avoir une autre idée.

Pour une application Windows RT Store, essayez de rechercher ARM ABI si vous souhaitez vraiment faire fonctionner ensemble une chaîne d'outils de construction.


1

Le terme ABI est utilisé pour désigner deux concepts distincts mais liés.

Quand on parle de compilateurs, il fait référence aux règles utilisées pour traduire des constructions de niveau source en constructions binaires. Quelle est la taille des types de données? comment fonctionne la pile? comment passer des paramètres aux fonctions? quels registres doivent être sauvegardés par l'appelant par rapport à l'appelé?

Quand on parle de bibliothèques, il fait référence à l'interface binaire présentée par une bibliothèque compilée. Cette interface est le résultat d'un certain nombre de facteurs, notamment le code source de la bibliothèque, les règles utilisées par le compilateur et, dans certains cas, les définitions extraites d'autres bibliothèques.

Les modifications apportées à une bibliothèque peuvent casser l'ABI sans casser l'API. Prenons par exemple une bibliothèque avec une interface comme.

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

et le programmeur d'application écrit du code comme

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

Le programmeur d'application ne se soucie pas de la taille ou de la disposition de FOO, mais le binaire d'application se retrouve avec une taille codée en dur de foo. Si le programmeur de bibliothèque ajoute un champ supplémentaire à foo et que quelqu'un utilise le nouveau binaire de bibliothèque avec l'ancien binaire d'application, alors la bibliothèque peut faire des accès mémoire hors limites.

OTOH si l'auteur de la bibliothèque avait conçu son API comme.

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

et le programmeur d'application écrit du code comme

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

Ensuite, le binaire de l'application n'a besoin de rien savoir de la structure de FOO, qui peut tous être cachés à l'intérieur de la bibliothèque. Le prix à payer pour cela est que les opérations de tas sont impliquées.


0

ABI- Application Binary Interfaceconcerne une communication de code machine en cours d' exécution entre deux parties de programme binaire comme - application, bibliothèque, OS ... ABIdécrit comment les objets sont enregistrés en mémoire et comment les fonctions sont appelées ( calling convention)

Un bon exemple d'API et d'ABI est l'écosystème iOS avec le langage Swift .

  • Application- Lorsque vous créez une application en utilisant différentes langues. Par exemple, vous pouvez créer une application en utilisant Swiftet Objective-C[Mixage Swift et Objective-C]

  • Application - OS- runtime - Swift runtimeet standard librariesfont partie du système d'exploitation et ne doivent pas être inclus dans chaque bundle (par exemple, application, framework). C'est la même chose que les utilisations d'Objective-C

  • Library- Module Stabilitycase - temps de compilation - vous pourrez importer un framework qui a été construit avec une autre version du compilateur de Swift. Cela signifie qu'il est sécuritaire de créer un binaire de source fermée (pré-build) qui sera consommé par une version différente du compilateur ( .swiftinterfaceutilisé avec .swiftmodule) et vous n'obtiendrez pas

    Module compiled with _ cannot be imported by the _ compiler
    
  • Library- Library Evolutionétui

    1. Temps de compilation - si une dépendance a été modifiée, un client ne doit pas être recompilé.
    2. Runtime - une bibliothèque système ou une infrastructure dynamique peut être remplacée à chaud par une nouvelle.

[API vs ABI]

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.