Je n'ai jamais clairement compris ce qu'est un ABI. S'il vous plaît ne m'indiquez pas un article de Wikipedia. Si je pouvais le comprendre, je ne serais pas là en train de poster un si long post.
Ceci est mon état d'esprit à propos des différentes interfaces:
Une télécommande de téléviseur est une interface entre l'utilisateur et le téléviseur. C'est une entité existante, mais inutile (ne fournit aucune fonctionnalité) en soi. 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" située entre les
functionality
etconsumer
de cette fonctionnalité. Une interface en soi ne fait rien. Il appelle simplement la fonctionnalité qui se trouve derrière.Maintenant, en fonction de l'utilisateur, il existe différents types d'interfaces.
Interface de ligne de commande (CLI) Les commandes sont les entités existantes, le consommateur est l'utilisateur et la fonctionnalité est derrière.
functionality:
ma fonctionnalité logicielle qui résout un problème pour lequel nous décrivons cette interface.
existing entities:
commandes
consumer:
utilisateurInterface graphique (GUI) fenêtre, boutons, etc. sont les entités existantes. Là encore, le consommateur est l'utilisateur et la fonctionnalité est derrière.
functionality:
ma fonctionnalité logicielle qui résout un problème pour lequel nous décrivons cette interface.
existing entities:
fenêtre, boutons, etc.
consumer:
utilisateurInterface de programmation d'application (API) Les fonctions (ou pour être plus correctes) les interfaces (dans la programmation basée sur l'interface) sont les entités existantes, le consommateur est ici un autre programme non un utilisateur, et encore une fonctionnalité se cache derrière cette couche.
functionality:
ma fonctionnalité logicielle qui résout un problème pour lequel nous décrivons cette interface.
existing entities:
fonctions, Interfaces (tableau de fonctions).
consumer:
un autre programme/application.Interface binaire d'application (ABI) Voici où mon problème commence.
functionality:
???
existing entities:
???
consumer:
???
Les ABI couvrent des détails tels que
- type de données, taille et alignement;
- la convention d'appel, qui contrôle la manière dont les arguments des fonctions sont transmis et les valeurs renvoyées sont extraites;
- les numéros d'appel système et la manière dont une application doit effectuer des appels système vers le système d'exploitation;
D'autres ABI standardisent des détails tels que
- le nom C++ se déformant,
- propagation d'exception, et
- convention d'appel entre les compilateurs sur la même plate-forme, mais n'exigent pas de compatibilité multiplate-forme.
Qui a besoin de ces détails? S'il vous plaît ne dites pas l'OS. Je connais la programmation de l'Assemblée. Je sais comment lier et charger fonctionne. Je sais exactement ce qui se passe à l'intérieur.
Pourquoi le nom C++ a-t-il été modifié? Je pensais que nous parlions au niveau binaire. Pourquoi les langues entrent-elles?
Quoi qu'il en soit, j'ai téléchargé le [PDF] interface binaire d'application System V Edition 4.1 (1997-03-18) pour voir ce qu'il contient exactement. Eh bien, la plupart de cela n'avait aucun sens.
Pourquoi contient-il deux chapitres (4ème et 5ème) décrivant le format de fichier ELF ? En fait, ce sont les deux seuls chapitres significatifs 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. Ne dites pas que les spécifications de format de fichier ELF sont l’ABI. Il n'est pas qualifié pour être une interface selon la définition.
Je sais, puisque nous parlons à un niveau aussi bas, il doit être très spécifique. Mais je ne suis pas sûr de savoir comment est-il spécifique à "architecture de jeu d'instructions (ISA)"?
Où puis-je trouver l'ABI de Microsoft Windows?
Donc, ce sont les principales requêtes qui me dérangent.
Un moyen simple de comprendre "ABI" consiste à le comparer à "API".
Vous connaissez déjà le concept d'une API. Si vous souhaitez utiliser les fonctionnalités d'une bibliothèque ou de votre système d'exploitation, par exemple, vous utiliserez une API. L'API se compose de types de données/structures, 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 (tout comme l’API), mais à un niveau inférieur.
Les ABI sont importantes pour les applications utilisant des bibliothèques externes. Si un programme est conçu pour utiliser une bibliothèque particulière et que cette bibliothèque est mise à jour ultérieurement, vous ne souhaitez pas avoir à recompiler cette application (et du point de vue de l'utilisateur final, il se peut que vous n'ayez pas la source). Si la bibliothèque mise à jour utilise le même ABI, votre programme n'aura pas besoin de changer. L’interface avec la bibliothèque (qui intéresse tout votre programme) est la même, même si le fonctionnement interne a peut-être changé. Deux versions d'une bibliothèque qui ont le même ABI sont parfois appelées "compatibles binaires" car elles ont la même interface de bas niveau (vous devriez pouvoir remplacer l'ancienne version par la nouvelle sans avoir de problèmes majeurs).
Parfois, les changements ABI sont inévitables. Lorsque cela se produit, tous les programmes utilisant cette bibliothèque ne fonctionneront que s'ils sont recompilés pour utiliser la nouvelle version de la bibliothèque. Si l'ABI change mais que l'API ne change pas, les anciennes et les nouvelles versions de la bibliothèque sont parfois appelées "source compatible". 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 rédacteurs de bibliothèques ont tendance à essayer de maintenir leur ABI stable (pour minimiser les perturbations). Garder une ABI stable signifie ne pas changer les interfaces des fonctions (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 vous développez, par exemple, un champ de structure de données de 16 bits en un champ de 32 bits, le code déjà compilé qui utilise cette structure de données n'accédera pas correctement à ce champ (ni à aucun de ses suivants). L'accès aux membres de la structure de données est converti en adresses de mémoire et en décalages lors de la compilation et si la structure de données change, ces décalages ne pointeront pas sur ce que le code attend d'eux et les résultats sont au mieux imprévisibles.
Une ABI n’est pas nécessairement quelque chose que vous fournirez explicitement, sauf si vous attendez des personnes qu’elles se connectent à votre code avec Assembly. Il n'est pas non plus spécifique à la langue, car (par exemple) une application C et une application Pascal utiliseront la même ABI après leur compilation.
Edit: En ce qui concerne votre question sur les chapitres concernant le format de fichier ELF dans la documentation ABI SysV: la raison pour laquelle ces informations sont incluses est le fait que le format ELF définit la interface entre le système d'exploitation et l'application. Lorsque vous indiquez 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, à la première section du fichier binaire comme un en-tête ELF contenant certaines informations à des décalages de mémoire spécifiques. C’est ainsi que l’application communique au système d’exploitation des informations importantes la concernant. 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 sera pas en mesure d'interpréter le fichier binaire ou de l'exécuter. C'est l'une des principales raisons pour lesquelles les applications Windows ne peuvent pas être exécutées directement sur une machine Linux (ou inversement) sans être recompilées ou exécutées à l'intérieur d'un type de couche d'émulation pouvant être converti d'un format binaire à un autre.
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 la modification de nom C++: L’ABI peut définir un moyen "normalisé" permettant à un compilateur C++ de gérer la modification de nom à des fins de compatibilité. C'est-à-dire que si je crée une bibliothèque et que vous développez un programme utilisant cette bibliothèque, vous devriez pouvoir utiliser un compilateur différent de celui que j'ai utilisé et ne pas avoir à vous soucier de l'incompatibilité des fichiers binaires résultants en raison de différents modèles de gestion des noms. Ceci n’est vraiment utile que si vous définissez un nouveau format de fichier binaire ou écrivez un compilateur ou un éditeur de liens.
Si vous connaissez Assembly et comment les choses fonctionnent au niveau du système d'exploitation, vous vous conformez à un certain ABI. L’ABI régit des choses telles que la manière 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 ce cas l'ABI est simplement "comment ça marche".
Cependant, les ABI régissent également des choses telles que la manière dont les classes/objets sont disposés en C++. Cela 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 capable d'exécuter des fichiers binaires 32 bits, vous aurez différents ABI pour le code 32 et 64 bits.
En général, tout code que vous associez au même exécutable doit être conforme au même ABI. Si vous souhaitez communiquer entre codes à l'aide de différentes ABI, vous devez utiliser une forme de protocole RPC ou de protocole de sérialisation.
Je pense que vous essayez trop de combiner différents types d’interfaces en un ensemble de caractéristiques fixe. Par exemple, une interface ne doit pas nécessairement être divisée en consommateurs et producteurs. Une interface est simplement une convention par laquelle deux entités interagissent.
Les ABI peuvent être (partiellement) ISA-agnostiques. Certains aspects (tels que les conventions d'appel) dépendent de l'ISA, d'autres non (tels que la disposition des classes C++) ne le sont pas.
Un ABI bien défini est très important pour ceux qui écrivent des compilateurs. Sans une ABI bien définie, il serait impossible de générer du code interopérable.
EDIT: Quelques notes pour clarifier:
En fait n'avez pas besoin d'un ABI du tout si--
Un résumé trop simplifié:
API: "Voici toutes les fonctions que vous pouvez appeler."
ABI: "Ceci est comment pour appeler une fonction. "
L'ABI est un ensemble de règles que les compilateurs et les éditeurs de liens respectent afin de compiler votre programme afin que cela fonctionne correctement. Les ABI couvrent plusieurs sujets:
En examinant plus en profondeur la convention d’appel, que je considère comme le cœur d’une ABI:
La machine elle-même n'a pas de concept de "fonctions". Lorsque vous écrivez une fonction dans un langage de haut niveau tel que c, le compilateur génère une ligne de code Assembly telle que _MyFunction1:
. C'est une étiquette , qui finira par être résolue en 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 êtes en train de faire est de forcer le processeur à sauter à l'adresse de cette étiquette et à continuer à y exécuter.
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 suivie par le compilateur pour faire tout ce travail:
_MyFunction1:
). À ce stade, vous pouvez considérer que la CPU est "dans" votre "fonction".Il existe de nombreuses ABI/conventions d'appel différentes. Certains principaux sont:
Here est une excellente page qui montre les différences entre les assemblys générés lors de la compilation pour différentes ABI.
Une autre chose à mentionner est qu'un ABI n'est pas seulement pertinent dans le module exécutable de votre programme. est également utilisé par l'éditeur de liens pour s'assurer que la bibliothèque d'appels de programmes fonctionne correctement. Vous avez plusieurs bibliothèques partagées en cours d'exécution sur votre ordinateur et tant que votre compilateur sait quel ABI ils utilisent, il peut appeler des fonctions à partir de celles-ci correctement sans faire exploser la pile.
Votre compilateur qui comprend comment appeler des fonctions de bibliothèque est extrêmement extrêmement important. Sur une plate-forme hébergée (c'est-à-dire sur laquelle un système d'exploitation charge des programmes), votre programme ne peut même pas clignoter sans passer un appel du noyau.
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éfinies au niveau de l'architecture du processeur ou 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 le système d'exploitation, soit par le processeur.
Fonctionnalité: Définissez le mécanisme/standard 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 permet JNI, ou une interface Python-C, etc.
Entités existantes: Fonctions sous forme de code machine.
Consommateur: une autre fonction (dont une dans un autre langage, compilée par un autre compilateur ou liée par un autre éditeur de liens).
Fonctionnalité: ensemble de contrats qui concernent le compilateur, les rédacteurs d'Assembly, 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 nombreuses ABI (APCS, EABI, GNU-EABI, sans parler de plusieurs cas historiques). .
Consommateur: Le compilateur, les rédacteurs d'assemblage, le système d'exploitation, l'architecture spécifique du processeur.
Qui a besoin de ces détails? Le compilateur, les rédacteurs d'Assembly, les éditeurs de liens chargés de la génération de code (ou des exigences d'alignement), du système d'exploitation (traitement des interruptions, interface syscall). Si vous faisiez de la programmation d'assemblage, vous vous conformiez à un ABI!
La gestion de noms C++ est un cas particulier - c’est un problème lié à l’éditeur de liens et à l’éditeur de liens dynamiques - si la gestion de noms n’est pas normalisée, la liaison dynamique ne fonctionnera pas. Désormais, l'ABI C++ s'appelle justement cela, l'ABI C++. Ce n'est pas un problème de niveau de l'éditeur de liens, mais 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++ (changement de nom, gestion des exceptions) sans recompiler à partir du 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 et les données binaires et, en tant que tel, spécifie l’ABI d’un morceau de code. Je ne considérerais pas ELF comme une ABI au sens strict, car les exécutables PE ne sont pas une ABI.
Toutes les ABI sont spécifiques à un jeu d'instructions. Un ABI ARM n'aura aucun sens sur un processeur MSP430 ou x86_64.
Windows possède plusieurs ABI - par exemple, fastcall et stdcall sont deux ABI à usage courant. Le syscall ABI est encore différent.
Laissez-moi au moins répondre à une partie de votre question. Avec un exemple de la manière dont l’ABI Linux affecte les appels système, et pourquoi c’est utile.
Un appel système est un moyen pour un programme utilisateur de demander quelque chose au kernelspace. Cela fonctionne en mettant le code numérique pour l'appel et l'argument dans un certain registre et en déclenchant une interruption. Puis un commutateur se produit dans l'espace noyau et le noyau recherche le code numérique et l'argument, gère la demande, place le résultat dans un registre et déclenche un changement dans l'espace utilisateur. Ceci est nécessaire par exemple lorsque l'application souhaite allouer de la mémoire ou ouvrir un fichier (appels système "brk" et "open").
Maintenant, les appels système ont des noms abrégés "brk", etc. et les opcodes correspondants, ceux-ci sont définis dans un fichier d'en-tête spécifique au système. Tant que ces codes d'opération restent les mêmes, vous pouvez exécuter les mêmes programmes utilisateur compilés avec différents noyaux mis à jour sans devoir recompiler. Vous avez donc une interface utilisée par les binaires précompilés, d’où ABI.
La meilleure façon de différencier ABI et API est de savoir pourquoi et à quoi sert-il:
Pour x86-64, il y a généralement un ABI (et pour x86 32 bits, il y a un autre ensemble):
http://www.x86-64.org/documentation/abi.pdf
http://people.freebsd.org/~obrien/AMD64-elf-abi.pdf
Linux + FreeBSD + MacOSX suivent avec quelques légères variations. Et Windows x64 a son propre ABI:
http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64/
Connaissant l'ABI et supposant qu'un autre compilateur le suit également, les binaires savent théoriquement comment s'appeler (API des 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 des registres/la structure de la pile, je peux facilement rassembler différents logiciels écrits dans des assemblages sans trop de problèmes.
Mais les API sont différentes:
Il s'agit d'un nom de fonction de haut niveau, avec un argument défini, tel que, si différents logiciels construisent à l'aide de cette API, PEUT être en mesure de s'appeler les uns les autres. Mais une exigence supplémentaire de SAME ABI doit être respectée.
Par exemple, Windows était auparavant conforme à 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 fichiers 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 utiliser le même logiciel en C, le recompiler dans un système d'exploitation différent et le faire fonctionner immédiatement.
Les API ont pour but de faciliter l'intégration du logiciel - phase de pré-compilation. Donc, après la compilation, le logiciel peut avoir un aspect totalement différent - si les ABI sont différentes.
Les ABI sont destinés à définir une intégration exacte du logiciel au niveau binaire/Assemblée.
Pour appeler du code dans des bibliothèques partagées ou un code entre des unités de compilation, le fichier objet doit contenir des étiquettes pour les appels. C++ modifie les noms des étiquettes de méthodes afin d’imposer le masquage des données et de permettre les méthodes surchargées. C'est pourquoi vous ne pouvez pas mélanger des fichiers provenant de différents compilateurs C++ à moins qu'ils ne prennent explicitement en charge le même ABI.
Il existe différentes interprétations et opinions fortes de la couche exacte qui définissent une ABI (interface binaire d'application).
À mon avis, une ABI est une convention subjective de ce qui est considéré comme une plateforme/donnée pour une API spécifique. L'ABI est le "reste" de conventions qui "ne changeront pas" pour une API spécifique ou qui seront traitées par l'environnement d'exécution: exécuteurs, outils, lieurs, compilateurs, jvm et système d'exploitation.
Si vous souhaitez utiliser une bibliothèque telle que 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 Versioning Semantic . Ceci définit la compatibilité de l'API à trois niveaux:
Pour que vous puissiez utiliser une nouvelle version majeure de la même bibliothèque, beaucoup d'autres conventions doivent encore être respectées:
Par exemple, Java a normalisé toutes ces conventions, non pas dans un outil, mais dans une spécification JVM formelle. La spécification permettait à d'autres fournisseurs de fournir un ensemble différent d'outils pouvant générer des bibliothèques compatibles.
Java fournit deux autres études de cas intéressantes pour ABI: Scala versions et Dalvik machine virtuelle.
Dalvik VM nécessite un type de bytecode différent de celui de Java. Les bibliothèques Dalvik sont obtenues en convertissant le bytecode Java (avec la même API) pour Dalvik. De cette manière, vous pouvez obtenir deux versions de la même API: définies par le joda-time-1.7.2.jar
original. Nous pourrions m'appeler joda-time-1.7.2.jar
et joda-time-1.7.2-dalvik.jar
. Ils utilisent un ABI différent pour le standard orienté pile Java vms: celui d’Oracle, celui d’IBM, open Java ou tout autre; et le deuxième ABI est celui autour de Dalvik.
Scala n’a pas de compatibilité binaire entre les versions mineures Scala: 2.X. Pour cette raison, la même API "io.reactivex" %% "rxscala"% "0.26.5" a trois versions (plus dans le futur): pour Scala 2.10, 2.11 et 2.12. Qu'est-ce qui a changé? Je ne sais pas pour l'instant , mais les fichiers 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/nommage/paramètres.
Java a également des problèmes avec les versions majeures de la machine virtuelle Java: 4,5,6,7,8,9. Ils offrent uniquement une compatibilité ascendante. Jvm9 sait comment exécuter le code compilé/ciblé (option -target
de javac) pour toutes les autres versions, alors que JVM 4 ne sait pas comment exécuter du code ciblé pour JVM 5. Tout cela avec une seule bibliothèque Joda. Cette incompatibilité passe sous le radar grâce à différentes solutions:
API et ABI ne sont que des conventions sur la définition de la compatibilité. Les couches inférieures sont génériques en ce qui concerne une pléthore de sémantiques de haut niveau. C'est pourquoi il est facile de faire des conventions. Le premier type de conventions concerne l’alignement de la mémoire, le codage d’octets, les conventions d’appel, les codages grand et petit endian, etc. En outre, vous obtenez les conventions exécutables comme celles décrites, les conventions de liaison, code d'octet intermédiaire comme celui utilisé par Java ou LLVM IR utilisé par GCC. Troisièmement, vous obtenez des conventions sur la recherche de bibliothèques, leur chargement (voir Java chargeurs de classes). En allant de plus en plus haut dans les concepts, vous avez de nouvelles conventions que vous considérez comme une donnée. C'est pourquoi ils ne sont pas parvenus au versioning sémantique . Ils sont implicites ou réduits dans la version majeure . Nous pourrions modifier le versioning sémantique avec <major>-<minor>-<patch>-<platform/ABI>
. C'est déjà ce qui se passe déjà: la plateforme est déjà une rpm
, dll
, jar
(bytecode JVM), war
(jvm + serveur Web), apk
, 2.11
(version spécifique de Scala) et ainsi de suite. Lorsque vous dites APK, vous parlez déjà d'une partie ABI spécifique de votre API.
Le niveau supérieur d'une abstraction (les sources écrites avec 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 en conséquence. Si la machine virtuelle Java 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éé.
Il existe des API portées dans plusieurs langues telles que flux réactifs . En général, ils définissent des correspondances avec des langages/plates-formes spécifiques. Je dirais que l'API est la spécification principale définie formellement en langage humain ou même un langage de programmation spécifique. Tous les autres "mappages" sont en fait ABI, sinon plus d'API que les ABI habituels. La même chose se passe avec les interfaces REST.
exemple ABI exécutable minimal de la bibliothèque partagée Linux
Dans le contexte des bibliothèques partagées, l'implication la plus importante de "disposer d'une ABI stable" est qu'il n'est pas nécessaire de recompiler vos programmes après les modifications de la bibliothèque.
Donc par exemple:
si vous vendez une bibliothèque partagée, vous éviterez à vos utilisateurs 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 publier et tester moins de pré-logiciels si vous êtes certain que ABI est stable dans 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 donner un exemple concret minimal exécutable de cela.
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 voulions ajouter un nouveau champ à mylib_mystruct
appelé new_field
.
Si nous avons ajouté le champ avant old_field
comme dans:
typedef struct {
int new_field;
int old_field;
} mylib_mystruct;
et reconstruit la bibliothèque mais pas main.out
, l'assertion échoue!
C'est parce que la ligne:
myobject->old_field == 1
avait généré Assembly qui essayait d'accéder à la toute première int
de la structure, qui est maintenant new_field
au lieu du old_field
attendu.
Par conséquent, ce changement a brisé le ABI.
Toutefois, si nous ajoutons new_field
après old_field
:
typedef struct {
int old_field;
int new_field;
} mylib_mystruct;
alors l'ancien assemblage généré accède toujours à la première int
de la structure, et le programme fonctionne toujours, car nous avons maintenu la stabilité de l'ABI.
Voici un version entièrement automatisée de cet exemple sur GitHub .
Une autre façon de garder cette ABI stable aurait été de traiter mylib_mystruct
comme une structure opaque , et d'accéder uniquement à ses champs via des aides à la méthode. Cela facilite la stabilité de l’ABI, mais entraîne un surcoût en termes de performances car nous appelons davantage de fonctions.
API vs ABI
Dans l'exemple précédent, il est intéressant de noter que l'ajout de new_field
avant old_field
ne fait que briser l'ABI, mais pas l'API.
Cela signifie que si nous avions recompilé notre programme main.c
contre la bibliothèque, il aurait fonctionné de manière indépendante.
Cependant, 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
cesserait de compiler.
API sémantique vs API de programmation
Nous pouvons également classer les modifications de l'API dans un troisième type: les modifications 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 le 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 cassé ni l'API de programmation, ni l'ABI, mais main.c
l'API sémantique se briserait.
Il existe deux manières de vérifier par programme l'API du contrat:
vérification formelle . Plus difficile à faire, mais produit une preuve mathématique de correction, unifiant essentiellement la documentation et les tests de manière "humaine"/machine vérifiable! Tant qu'il n'y a pas de bogue dans votre description formelle, bien sûr ;-)
Ce concept est étroitement lié à la formalisation de la mathématique elle-même: https://math.stackexchange.com/questions/53969/what-does-formal-mean/3297537#3297537
Liste de tout ce qui casse les ABI de bibliothèques partagées C/C++
TODO: trouver/créer la liste ultime:
Exemple minimal exécutable en Java
Qu'est-ce que la compatibilité binaire en Java?
Testé sous Ubuntu 18.10, GCC 8.2.0.
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.
Le wiki KDE 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 les liens dynamiques, reportez-vous à Lien statique vs. liens dynamiques
Essayons maintenant d’examiner les aspects les plus fondamentaux nécessaires à la compatibilité binaire d’une bibliothèque (en supposant qu’il n’y ait aucune modification de code source dans la bibliothèque):
Bien sûr, il y a beaucoup d'autres détails, mais c'est surtout ce que couvre l'ABI.
Plus précisément, pour répondre à votre question, on peut déduire de ce qui précède:
Fonctionnalité ABI: compatibilité binaire
entités existantes: programme/bibliothèques/systèmes d'exploitation existants
consommateur: bibliothèques, OS
J'espère que cela t'aides!
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 convertir des constructions de niveau source en constructions binaires. Quelle est la taille des types de données? comment fonctionne la pile? Comment puis-je 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, cela fait référence à l'interface binaire présentée par une bibliothèque compilée. Cette interface résulte 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. Considérons 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 de l'application se termine par une taille codée en dur de foo. Si le programmeur de la bibliothèque ajoute un champ supplémentaire à foo et que quelqu'un utilise le nouveau fichier binaire de la bibliothèque avec l'ancien fichier binaire de l'application, la bibliothèque peut effectuer des accès mémoire en dehors des limites.
OTOH si l'auteur de la bibliothèque avait conçu leur 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 fichier binaire de l’application n’a pas besoin de connaître la structure de FOO, tout cela pouvant être caché à l’intérieur de la bibliothèque. Le prix que vous payez pour cela est cependant que les opérations de tas sont impliquées.
L'ABI doit être cohérent entre l'appelant et l'appelé pour être certain du succès de l'appel. Utilisation de pile, utilisation de registre, pop de pile de fin de routine. Ce sont toutes les parties les plus importantes de l’ABI.
Interface binaire d'application (ABI)
Fonctionnalité:
entités existantes:
consommateur:
Celles-ci sont nécessaires à quiconque doit s'assurer que les chaînes d'outils de construction fonctionnent dans leur ensemble. Si vous écrivez un module en langage Assembly, un autre en Python et si, au lieu de votre propre chargeur de démarrage, vous souhaitez utiliser un système d'exploitation, vos modules "application" fonctionnent au-delà des limites "binaires" et nécessitent l'accord de cette "interface".
Nom C++ modifié, car il peut être nécessaire de lier des fichiers objets de différents langages de haut niveau à votre application. Pensez à utiliser la bibliothèque standard GCC pour passer des appels système à Windows construits avec Visual C++.
ELF est l'une des attentes possibles de l'éditeur de liens à partir d'un fichier objet pour 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 que certaines chaînes d'outils de construction fonctionnent ensemble.
En bref et en philosophie, seules les choses d'un type peuvent bien s'entendre, et l'ABI pourrait être considéré comme le type du logiciel qui fonctionne ensemble.