829 Stimmen

Was ist ein Application Binary Interface (ABI)?

Ich habe nie genau verstanden, was ein ABI ist. Bitte verweisen Sie mich nicht auf einen Wikipedia-Artikel. Wenn ich es verstehen würde, wäre ich nicht hier und würde einen so langen Beitrag schreiben.

Das ist meine Einstellung zu verschiedenen Schnittstellen:

Eine TV-Fernbedienung ist eine Schnittstelle zwischen dem Benutzer und dem Fernsehgerät. Sie ist eine existierende Einheit, die aber an sich nutzlos ist (sie bietet keine Funktionen). Die gesamte Funktionalität für jede dieser Tasten auf der Fernbedienung ist im Fernsehgerät implementiert.

Schnittstelle: Es handelt sich um eine "existierende Entitätsebene" zwischen der functionality y consumer dieser Funktionalität. Eine Schnittstelle an sich tut nichts. Sie ruft lediglich die dahinter liegende Funktionalität auf.

Je nachdem, wer der Nutzer ist, gibt es verschiedene Arten von Schnittstellen.

Befehlszeilenschnittstelle (CLI) Die Befehle sind die vorhandenen Einheiten, der Verbraucher ist der Nutzer und die Funktionalität liegt dahinter.

functionality: meine Softwarefunktionalität, die einen Zweck erfüllt Zweck, für den wir diese Schnittstelle beschreiben.

existing entities: Befehle

consumer: Benutzer

Grafische Benutzeroberfläche (GUI) Fenster, Schaltflächen, etc. sind die bestehenden Entitäten, und auch hier ist der Verbraucher der Benutzer, und die Funktionalität liegt dahinter.

functionality: meine Softwarefunktionalität, die ein Problem löst, für das wir diese Schnittstelle beschreiben.

existing entities: Fenster, Schaltflächen usw..

consumer: Benutzer

Anwendungsprogrammierschnittstelle (API) Funktionen (oder zu sein Schnittstellen (in der schnittstellenbasierten Programmierung) sind die bestehenden Entitäten, der Verbraucher ist hier ein anderes Programm, kein Benutzer, und wieder Funktionalität liegt hinter dieser Schicht.

functionality: meine Softwarefunktionalität, die einige Probleme löst Problem löst, für das wir diese Schnittstelle beschreiben.

existing entities: Funktionen, Schnittstellen (Array von Funktionen).

consumer: ein anderes Programm/eine andere Anwendung.

Anwendungsbinäre Schnittstelle (ABI) Genau hier beginnt mein Problem.

functionality: ???

existing entities: ???

consumer: ???

  • Ich habe Software in verschiedenen Sprachen geschrieben und verschiedene Arten von Schnittstellen bereitgestellt (CLI, GUI und API), aber ich bin nicht sicher, ob ich jemals eine ABI bereitgestellt habe.

Wikipedia sagt:

ABIs umfassen Details wie

  • Datentyp, Größe und Ausrichtung;
  • die Aufrufkonvention, die steuert, wie die Argumente der Funktionen übergeben und Rückgabewerte abgerufen werden;
  • die Nummern der Systemaufrufe und wie eine Anwendung Systemaufrufe tätigen sollte an das Betriebssystem richten soll;

Andere ABIs standardisieren Details wie

  • die Verstümmelung von C++-Namen,
  • die Ausbreitung von Ausnahmen, und
  • Aufrufkonvention zwischen Compilern auf der gleichen Plattform, aber nicht keine plattformübergreifende Kompatibilität erfordern.
  • Wer braucht diese Details? Bitte sagen Sie nicht das Betriebssystem. Ich kenne Assembler-Programmierung. Ich weiß, wie Binden und Laden funktioniert. Ich weiß genau, was darin passiert.

  • Warum wurde die C++-Namensvermischung eingeführt? Ich dachte, wir sprechen auf der binären Ebene. Warum kommen Sprachen ins Spiel?

Wie auch immer, ich habe die [PDF] System V Anwendung Binäre Schnittstelle Ausgabe 4.1 (1997-03-18) um zu sehen, was genau er enthält. Nun, das meiste ergab keinen Sinn.

  • Warum enthält sie zwei Kapitel (4. und 5.), die die ELF Dateiformat? Tatsächlich sind dies die einzigen beiden wichtigen Kapitel dieser Spezifikation. Der Rest der Kapitel ist "prozessorspezifisch". Wie auch immer, ich denke, das ist ein völlig anderes Thema. Bitte sagen Sie nicht, dass die Spezifikationen des ELF-Dateiformats sind die ABI. Es qualifiziert sich nicht als ein Schnittstelle gemäß der Definition.

  • Ich weiß, da wir auf einer so niedrigen Ebene sprechen, muss es sehr spezifisch sein. Aber ich bin mir nicht sicher, wie spezifisch die "Befehlssatzarchitektur (ISA)" ist.

  • Wo kann ich das ABI von Microsoft Windows finden?

Das sind also die wichtigsten Fragen, die mich beschäftigen.

10 Stimmen

"Bitte sagen Sie nicht OS" Compiler müssen die ABI kennen. Linker müssen die ABI kennen. Der Kernel muss die ABI kennen, um das Programm im RAM einrichten zu können, damit es ordnungsgemäß läuft. Was C++ betrifft, siehe unten, so werden Bezeichnungen absichtlich in Kauderwelsch umgewandelt, weil Überladungen und private Methoden vorhanden sind, und der Linker und jeder andere Compiler müssen über eine kompatible Namensumwandlung verfügen, um damit arbeiten zu können, mit anderen Worten, über dieselbe ABI.

1 Stimmen

"Sie können eine x86-Datei nicht auf einem ARM-Prozessor ausführen, Punkt. Die auf einem x86-System verwendete ABI kann also nicht dieselbe sein, die auf einem ARM-System verwendet wird.

20 Stimmen

Ich denke, die Frage ist so klar; sie beschreibt genau, welches Antwortformat erwartet wird, und dennoch gibt es keine einzige zufriedenstellende Antwort, die akzeptiert werden kann.

925voto

bta Punkte 41611

Eine einfache Möglichkeit, "ABI" zu verstehen, ist der Vergleich mit "API".

Sie sind bereits mit dem Konzept einer API vertraut. Wenn Sie die Funktionen z. B. einer Bibliothek oder Ihres Betriebssystems nutzen wollen, programmieren Sie gegen eine API. Die API besteht aus Datentypen/Strukturen, Konstanten, Funktionen usw., die Sie in Ihrem Code verwenden können, um auf die Funktionen dieser externen Komponente zuzugreifen.

Ein ABI ist sehr ähnlich. Betrachten Sie sie als die kompilierte Version einer API (oder als eine API auf der Ebene der Maschinensprache). Wenn Sie Quellcode schreiben, greifen Sie über eine API auf die Bibliothek zu. Sobald der Code kompiliert ist, greift Ihre Anwendung über die ABI auf die Binärdaten in der Bibliothek zu. Die ABI definiert die Strukturen und Methoden, die Ihre kompilierte Anwendung für den Zugriff auf die externe Bibliothek verwendet (genau wie die API), nur auf einer niedrigeren Ebene. Ihre API definiert die Reihenfolge, in der Sie Argumente an eine Funktion übergeben. Ihre ABI definiert die Mechanismen von cómo diese Argumente übergeben werden (Register, Stack, etc.). Ihre API definiert, welche Funktionen Teil Ihrer Bibliothek sind. Ihre ABI legt fest, wie Ihr Code in der Bibliotheksdatei gespeichert wird, so dass jedes Programm, das Ihre Bibliothek verwendet, die gewünschte Funktion finden und ausführen kann.

ABIs sind wichtig, wenn es um Anwendungen geht, die externe Bibliotheken verwenden. Bibliotheken sind voll von Code und anderen Ressourcen, aber Ihr Programm muss wissen, wie es das, was es braucht, innerhalb der Bibliotheksdatei finden kann. Ihre ABI legt fest, wie der Inhalt einer Bibliothek in der Datei gespeichert wird, und Ihr Programm verwendet die ABI, um die Datei zu durchsuchen und das zu finden, was es braucht. Wenn alles in Ihrem System mit der gleichen ABI übereinstimmt, kann jedes Programm mit jeder Bibliotheksdatei arbeiten, unabhängig davon, wer sie erstellt hat. Linux und Windows verwenden unterschiedliche ABIs, so dass ein Windows-Programm nicht weiß, wie es auf eine für Linux kompilierte Bibliothek zugreifen kann.

Manchmal sind ABI-Änderungen unvermeidlich. Wenn dies geschieht, funktionieren alle Programme, die diese Bibliothek verwenden, nicht, es sei denn, sie werden neu kompiliert, um die neue Version der Bibliothek zu verwenden. Wenn sich die ABI ändert, aber nicht die API, dann werden die alte und die neue Bibliotheksversion manchmal als "quellkompatibel" bezeichnet. Dies bedeutet, dass ein Programm, das für eine Bibliotheksversion kompiliert wurde, mit der anderen nicht funktioniert, während der für die eine Version geschriebene Quellcode mit der anderen funktioniert, wenn er neu kompiliert wird.

Aus diesem Grund versuchen die Entwickler, ihre ABI stabil zu halten (um Störungen zu minimieren). Eine ABI stabil zu halten bedeutet, Funktionsschnittstellen (Rückgabetyp und -anzahl, Typen und Reihenfolge der Argumente), Definitionen von Datentypen oder Datenstrukturen, definierte Konstanten usw. nicht zu ändern. Neue Funktionen und Datentypen können hinzugefügt werden, aber die bestehenden müssen gleich bleiben. Wenn Ihre Bibliothek beispielsweise 32-Bit-Ganzzahlen zur Angabe des Offsets einer Funktion verwendet und Sie zu 64-Bit-Ganzzahlen wechseln, kann bereits kompilierter Code, der diese Bibliothek verwendet, nicht mehr korrekt auf dieses Feld (oder ein nachfolgendes) zugreifen. Der Zugriff auf Datenstrukturelemente wird während der Kompilierung in Speicheradressen und Offsets umgewandelt, und wenn sich die Datenstruktur ändert, dann zeigen diese Offsets nicht auf das, was der Code erwartet, und die Ergebnisse sind bestenfalls unvorhersehbar.

Eine ABI ist nicht unbedingt etwas, das Sie explizit zur Verfügung stellen, es sei denn, Sie arbeiten an einem sehr einfachen Systemdesign. Sie ist auch nicht sprachspezifisch, da (zum Beispiel) eine C-Anwendung und eine Pascal-Anwendung die gleiche ABI verwenden können, nachdem sie kompiliert wurden.

Editar: Bezüglich Ihrer Frage zu den Kapiteln über das ELF-Dateiformat in den SysV ABI-Dokumenten: Der Grund, warum diese Informationen enthalten sind, ist, dass das ELF-Format die Schnittstelle zwischen Betriebssystem und Anwendung definiert. Wenn Sie dem Betriebssystem sagen, dass es ein Programm ausführen soll, erwartet es, dass das Programm auf eine bestimmte Art und Weise formatiert ist, und (zum Beispiel) erwartet es, dass der erste Abschnitt der Binärdatei ein ELF-Header ist, der bestimmte Informationen an bestimmten Speicherabständen enthält. Auf diese Weise teilt die Anwendung dem Betriebssystem wichtige Informationen über sich selbst mit. Wenn Sie ein Programm in einem nicht-ELF-Binärformat (z. B. a.out oder PE) erstellen, kann ein Betriebssystem, das ELF-formatierte Anwendungen erwartet, die Binärdatei nicht interpretieren oder die Anwendung nicht ausführen. Dies ist einer der Hauptgründe, warum Windows-Anwendungen nicht direkt auf einem Linux-Rechner (oder umgekehrt) ausgeführt werden können, ohne dass sie entweder neu kompiliert oder innerhalb einer Emulationsschicht ausgeführt werden, die ein Binärformat in ein anderes übersetzen kann.

IIRC, Windows verwendet derzeit die Portable ausführbare Datei (oder, PE) Format. Im Abschnitt "Externe Links" dieser Wikipedia-Seite finden Sie Links mit weiteren Informationen über das PE-Format.

Zu Ihrer Anmerkung über die Verstümmelung von C++-Namen: Beim Auffinden einer Funktion in einer Bibliotheksdatei wird die Funktion normalerweise anhand ihres Namens gesucht. C++ erlaubt die Überladung von Funktionsnamen, so dass der Name allein nicht ausreicht, um eine Funktion zu identifizieren. C++-Compiler haben ihre eigenen Methoden, um damit intern umzugehen, genannt Namensverstümmelung . Eine ABI kann eine Standardkodierung für den Namen einer Funktion definieren, so dass Programme, die mit einer anderen Sprache oder einem anderen Compiler erstellt wurden, die benötigten Funktionen finden können. Wenn Sie extern "c" in einem C++-Programm weisen Sie den Compiler an, eine standardisierte Art der Aufzeichnung von Namen zu verwenden, die für andere Software verständlich ist.

7 Stimmen

@bta, danke für die tolle Antwort. Ist die Aufrufkonvention eine Art ABI? Danke

86 Stimmen

Gute Antwort. Aber das ist nicht das, was eine ABI ist. Eine ABI ist ein Satz von Regeln, der die Aufrufkonvention und die Regeln für die Anordnung von Strukturen festlegt. Pascal übergibt Argumente auf dem Stack in der umgekehrten Reihenfolge wie C-Anwendungen, daher kompilieren Pascal- und C-Compiler NICHT mit der gleichen ABI. Die jeweiligen Standards für C- und Pascal-Compiler stellen implizit sicher, dass dies der Fall sein wird. C++-Compiler können keinen "Standard"-Weg zur Namensumwandlung definieren, da es keinen Standardweg gibt. Die C++-Namensumwandlungskonventionen waren zwischen den C++-Compilern nicht kompatibel, als es konkurrierende C++-Compiler unter Windows gab.

2 Stimmen

186voto

JesperE Punkte 61161

Wenn Sie sich mit Assembler auskennen und wissen, wie die Dinge auf OS-Ebene funktionieren, sind Sie konform mit einer bestimmten ABI. Die ABI regelt z. B., wie Parameter übergeben werden und wo Rückgabewerte platziert werden. Für viele Plattformen gibt es nur eine ABI, aus der man wählen kann, und in diesen Fällen ist die ABI einfach "wie die Dinge funktionieren".

Die ABI regelt jedoch auch Dinge wie die Anordnung von Klassen/Objekten in C++. Dies ist notwendig, wenn Sie in der Lage sein wollen, Objektreferenzen über Modulgrenzen hinweg zu übergeben, oder wenn Sie mit verschiedenen Compilern kompilierten Code mischen wollen.

Auch wenn Sie ein 64-Bit-Betriebssystem haben, das 32-Bit-Binärdateien ausführen kann, haben Sie unterschiedliche ABIs für 32- und 64-Bit-Code.

Im Allgemeinen muss jeder Code, den Sie in dieselbe ausführbare Datei linken, mit derselben ABI übereinstimmen. Wenn Sie zwischen Code mit unterschiedlichen ABIs kommunizieren möchten, müssen Sie eine Form von RPC- oder Serialisierungsprotokollen verwenden.

Meiner Meinung nach versuchen Sie zu sehr, verschiedene Arten von Schnittstellen in einen festen Satz von Merkmalen zu zwängen. Zum Beispiel muss eine Schnittstelle nicht unbedingt in Konsumenten und Produzenten unterteilt werden. Eine Schnittstelle ist nur eine Konvention, durch die zwei Entitäten interagieren.

ABIs können (teilweise) ISA-agnostisch sein. Einige Aspekte (z. B. die Aufrufkonventionen) hängen von der ISA ab, während andere Aspekte (z. B. das C++-Klassenlayout) dies nicht tun.

Eine gut definierte ABI ist sehr wichtig für Leute, die Compiler schreiben. Ohne eine gut definierte ABI wäre es unmöglich, interoperablen Code zu erzeugen.

EDIT: Einige Anmerkungen zur Klarstellung:

  • "Binär" in der ABI schließt die Verwendung von Strings oder Text nicht aus. Wenn Sie eine DLL verlinken wollen, die eine C++-Klasse exportiert, müssen irgendwo darin die Methoden und Typsignaturen kodiert sein. Hier kommt die C++-Namensvermischung ins Spiel.
  • Der Grund, warum Sie nie eine ABI zur Verfügung gestellt haben, ist, dass die große Mehrheit der Programmierer dies nie tun wird. ABIs werden von denselben Leuten bereitgestellt, die auch die Plattform (d.h. das Betriebssystem) entwerfen, und nur sehr wenige Programmierer werden jemals das Privileg haben, eine weit verbreitete ABI zu entwerfen.

0 Stimmen

Ich bin keineswegs überzeugt, dass meine Vorlage fehlerhaft ist. Denn überall, wo diese Vorlage für die Schnittstelle gilt, trifft sie zu. Also, ja, ich will, ich erwarte, dass ABI auch in diese Schablone passt, aber das ist es nicht. Das WICHTIGSTE ist, dass ich es immer noch nicht verstehe. Ich weiß nicht, ob ich so dumm bin oder etwas anderes, aber es geht einfach nicht in meinen Kopf. Ich bin nicht in der Lage, die Antworten und den Wiki-Artikel zu realisieren.

2 Stimmen

@jesperE, "Die ABI regelt Dinge wie die Übergabe von Parametern und die Platzierung von Rückgabewerten. " bezieht sich auf "cdecl,stdcall,fastcall,pascal" richtig?

3 Stimmen

Ja. Die richtige Bezeichnung ist "Calling Convention", die Teil der ABI ist. de.wikipedia.org/wiki/X86_Aufrufe_Konventionen

111voto

Lakey Punkte 1788

Sie haben tatsächlich nicht überhaupt ein ABI benötigen, wenn

  • Ihr Programm hat keine Funktionen, und
  • Ihr Programm ist eine einzelne ausführbare Datei, die allein läuft (d.h. ein eingebettetes System), wo es buchstäblich das Einzige ist, was läuft, und es muss mit nichts anderem kommunizieren.

Eine stark vereinfachte Zusammenfassung:

API: "Hier sind alle Funktionen, die Sie aufrufen können."

ABI: "Das ist cómo um eine Funktion aufzurufen."

Die ABI ist eine Reihe von Regeln, an die sich Compiler und Linker halten müssen, um Ihr Programm so zu kompilieren, dass es ordnungsgemäß funktioniert. ABIs decken mehrere Themen ab:

  • Der wohl größte und wichtigste Teil einer ABI ist die Standardprozeduraufruf manchmal auch als "Einberufungskonvention" bezeichnet. Aufrufkonventionen standardisieren, wie "Funktionen" in Assemblercode übersetzt werden.
  • ABIs diktieren auch die Art und Weise, wie die Namen von exponierten Funktionen in Bibliotheken dargestellt werden, so dass anderer Code diese Bibliotheken aufrufen kann und weiß, welche Argumente übergeben werden sollten. Dies wird als "Namensmangling" bezeichnet.
  • ABIs schreiben auch vor, welche Datentypen verwendet werden können, wie sie ausgerichtet werden müssen und andere Details auf niedriger Ebene.

Ein genauerer Blick auf die Aufrufkonvention, die ich als das Herzstück einer ABI betrachte:

Die Maschine selbst hat kein Konzept von "Funktionen". Wenn Sie eine Funktion in einer Hochsprache wie c schreiben, erzeugt der Compiler eine Zeile Assemblercode wie _MyFunction1: . Dies ist eine Etikett , die schließlich vom Assembler in eine Adresse aufgelöst wird. Dieses Label markiert den "Start" Ihrer "Funktion" im Assemblercode. Wenn Sie im High-Level-Code diese Funktion "aufrufen", veranlassen Sie die CPU in Wirklichkeit dazu springen an die Adresse dieses Etiketts und setzen die Ausführung dort fort.

In Vorbereitung auf den Sprung muss der Compiler eine Reihe wichtiger Aufgaben erledigen. Die Aufrufkonvention ist wie eine Checkliste, der der Compiler folgt, um all diese Dinge zu tun:

  • Zunächst fügt der Compiler ein wenig Assemblercode ein, um die aktuelle Adresse zu speichern, so dass die CPU nach Beendigung Ihrer "Funktion" an die richtige Stelle zurückspringen und die Ausführung fortsetzen kann.
  • Anschließend generiert der Compiler Assembler-Code zur Übergabe der Argumente.
    • Einige Aufrufkonventionen schreiben vor, dass die Argumente auf den Stack gelegt werden ( in einer bestimmten Reihenfolge natürlich).
    • Andere Konventionen schreiben vor, dass die Argumente in bestimmten Registern abgelegt werden ( abhängig von ihren Datentypen natürlich).
    • Wieder andere Konventionen schreiben vor, dass eine bestimmte Kombination von Stack und Registern verwendet werden sollte.
  • Wenn sich vorher etwas Wichtiges in diesen Registern befand, werden diese Werte natürlich überschrieben und gehen für immer verloren, so dass einige Aufrufkonventionen vorschreiben können, dass der Compiler einige dieser Register speichern sollte, bevor er die Argumente darin ablegt.
  • Jetzt fügt der Compiler eine Sprunganweisung ein, die die CPU anweist, zu dem zuvor erstellten Label zu springen ( _MyFunction1: ). Zu diesem Zeitpunkt können Sie die CPU als "in" Ihrer "Funktion" befindlich betrachten.
  • Am Ende der Funktion fügt der Compiler einen Assemblercode ein, der die CPU veranlasst, den Rückgabewert an die richtige Stelle zu schreiben. Die Aufrufkonvention gibt vor, ob der Rückgabewert in ein bestimmtes Register (je nach Typ) oder auf den Stack geschrieben werden soll.
  • Jetzt ist es an der Zeit, aufzuräumen. Die Aufrufkonvention gibt vor, wo der Compiler den Aufräum-Assemblercode platziert.
    • Einige Konventionen besagen, dass der Aufrufer den Stack aufräumen muss. Das bedeutet, dass nach Beendigung der "Funktion" und dem Zurückspringen der CPU an die Stelle, an der sie vorher war, der nächste auszuführende Code ein ganz bestimmter Aufräumcode sein sollte.
    • Andere Konventionen besagen, dass einige bestimmte Teile des Bereinigungscodes am Ende der "Funktion" stehen sollten avant den Sprung zurück.

Es gibt viele verschiedene ABIs/Aufrufkonventionen. Einige der wichtigsten sind:

  • Für die x86- oder x86-64-CPU (32-Bit-Umgebung):
    • CDECL
    • STDCALL
    • FASTCALL
    • VECTORCALL
    • THISCALL
  • Für die x86-64-CPU (64-Bit-Umgebung):
    • SYSTEMV
    • MSNATIVE
    • VECTORCALL
  • Für die ARM-CPU (32-Bit)
    • AAPCS
  • Für die ARM-CPU (64-Bit)
    • AAPCS64

こちら ist eine großartige Seite, die die Unterschiede in der Assembly zeigt, die beim Kompilieren für verschiedene ABIs erzeugt wird.

Zu erwähnen ist auch, dass eine ABI nicht nur relevant ist dentro de das ausführbare Modul Ihres Programms. Es ist également wird vom Linker verwendet, um sicherzustellen, dass Ihr Programm Bibliotheksfunktionen korrekt aufruft. Sie haben mehrere gemeinsam genutzte Bibliotheken auf Ihrem Computer laufen, und solange Ihr Compiler weiß, welche ABI sie jeweils verwenden, kann er Funktionen aus ihnen korrekt aufrufen, ohne den Stack zu sprengen.

Ihr Compiler versteht, wie man Bibliotheksfunktionen aufruft extrem wichtig. Auf einer gehosteten Plattform (d. h. einer Plattform, auf der ein Betriebssystem Programme lädt) kann Ihr Programm nicht einmal blinken, ohne einen Kernel-Aufruf zu tätigen.

27voto

Linux shared library minimal lauffähiges ABI Beispiel

Im Zusammenhang mit gemeinsam genutzten Bibliotheken bedeutet "eine stabile ABI haben" vor allem, dass Sie Ihre Programme nicht neu kompilieren müssen, wenn sich die Bibliothek ändert.

So zum Beispiel:

  • Wenn Sie eine gemeinsam genutzte Bibliothek verkaufen, ersparen Sie Ihren Nutzern den Ärger, bei jeder neuen Version alles neu zu kompilieren, was von Ihrer Bibliothek abhängt.

  • Wenn Sie ein Closed-Source-Programm verkaufen, das von einer gemeinsam genutzten Bibliothek abhängt, die in der Distribution des Benutzers vorhanden ist, können Sie weniger Vorabversionen veröffentlichen und testen, wenn Sie sicher sind, dass die ABI in bestimmten Versionen des Zielbetriebssystems stabil ist.

    Dies ist besonders wichtig im Fall der C-Standardbibliothek, auf die viele Programme in Ihrem System verweisen.

Nun möchte ich ein minimales, konkretes und lauffähiges Beispiel dafür liefern.

main.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

Kompiliert und läuft gut mit:

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

Nehmen wir nun an, dass wir für die Version 2 der Bibliothek ein neues Feld hinzufügen wollen, das mylib_mystruct genannt. new_field .

Wenn wir das Feld vorher hinzugefügt haben old_field wie in:

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

und die Bibliothek wiederaufgebaut, aber nicht main.out dann schlägt die Behauptung fehl!

Das liegt daran, dass die Linie:

myobject->old_field == 1

generierte Baugruppe, die versucht, auf die allererste int der Struktur, die jetzt new_field statt der erwarteten old_field .

Daher hat diese Änderung die ABI zerstört.

Wenn wir jedoch hinzufügen new_field nach old_field :

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

dann greift die alte generierte Baugruppe immer noch auf die erste int der Struktur, und das Programm funktioniert immer noch, weil wir die ABI stabil gehalten haben.

Hier ist ein vollständig automatisierte Version dieses Beispiels auf GitHub .

Eine andere Möglichkeit, diesen ABI stabil zu halten, wäre die Behandlung mylib_mystruct als undurchsichtige Struktur und greifen nur über Methodenhelfer auf ihre Felder zu. Dies macht es einfacher, die ABI stabil zu halten, aber würde einen Leistungs-Overhead verursachen, da wir mehr Funktionsaufrufe machen würden.

API vs. ABI

Im vorangegangenen Beispiel ist es interessant zu sehen, dass das Hinzufügen der new_field avant old_field nur die ABI, nicht aber die API zerstört.

Das bedeutet, dass wir, wenn wir unsere Daten neu kompiliert hätten main.c Programm gegen die Bibliothek, hätte es trotzdem funktioniert.

Wir hätten aber auch die API gebrochen, wenn wir zum Beispiel die Funktionssignatur geändert hätten:

mylib_mystruct* mylib_init(int old_field, int new_field);

denn in diesem Fall, main.c würde die Kompilierung ganz einstellen.

Semantische API vs. Programmier-API

Wir können API-Änderungen auch in eine dritte Art einordnen: semantische Änderungen.

Bei der semantischen API handelt es sich in der Regel um eine Beschreibung der API in natürlicher Sprache, die in der Regel in der API-Dokumentation enthalten ist.

Es ist daher möglich, die semantische API zu brechen, ohne den Programmaufbau selbst zu zerstören.

Wenn wir zum Beispiel Folgendes geändert hätten

myobject->old_field = old_field;

zu:

myobject->old_field = old_field + 1;

dann hätte dies weder die Programmier-API noch die ABI gebrochen, sondern main.c würde die semantische API zusammenbrechen.

Es gibt zwei Möglichkeiten, die Vertrags-API programmatisch zu überprüfen:

  • Testen Sie eine Reihe von Eckfällen. Das ist leicht zu machen, aber man kann immer einen übersehen.
  • formale Verifikation . Das ist zwar schwieriger, führt aber zu einem mathematischen Korrektheitsnachweis, der im Wesentlichen Dokumentation und Tests in einer "menschlich" / maschinell überprüfbaren Weise vereinigt! Solange Ihre formale Beschreibung keinen Fehler enthält, versteht sich ;-)

    Dieses Konzept ist eng mit der Formalisierung der Mathematik selbst verbunden: https://math.stackexchange.com/questions/53969/what-does-formal-mean/3297537#3297537

Liste von allem, was C / C++ shared library ABIs bricht

TODO: die ultimative Liste finden / erstellen:

Minimal lauffähiges Java-Beispiel

Was bedeutet Binärkompatibilität in Java?

Getestet unter Ubuntu 18.10, GCC 8.2.0.

0 Stimmen

Was bedeutet cc='gcc...' bedeuten? cc sollte dasselbe sein wie gcc in den meisten Linux-Systemen, aber ich verstehe nicht, was ='gcc some_flags...' Teil bedeutet.

1 Stimmen

@starriet definiert die Bash-Shell-Variable cc a gcc ... . Dann verwende ich gcc mit diesen Flags mehrmals. Es geht nur darum, die Flags nicht zu wiederholen.

23voto

alvin Punkte 1166

Eine binäre Anwendungsschnittstelle (ABI) ähnelt einer API, aber die Funktion ist für den Aufrufer nicht auf Quellcodeebene zugänglich. Nur eine binäre Darstellung ist zugänglich/verfügbar.

ABIs können auf der Ebene der Prozessorarchitektur oder auf der Ebene des Betriebssystems definiert werden. Die ABI sind Standards, die von der Code-Generierungsphase des Compilers befolgt werden müssen. Der Standard wird entweder durch das Betriebssystem oder durch den Prozessor festgelegt.

Funktionsweise: Definition des Mechanismus/Standards, um Funktionsaufrufe unabhängig von der Implementierungssprache oder einem bestimmten Compiler/Linker/Toolchain zu machen. Stellen Sie den Mechanismus bereit, der JNI oder eine Python-C-Schnittstelle usw. ermöglicht.

Vorhandene Entitäten: Funktionen in Form von Maschinencode.

Verbraucher: Eine andere Funktion (auch eine in einer anderen Sprache, die von einem anderen Compiler kompiliert oder von einem anderen Linker gelinkt wurde).

1 Stimmen

Warum sollte die ABI durch die Architektur definiert werden? Warum sollten verschiedene Betriebssysteme auf der gleichen Architektur nicht in der Lage sein, unterschiedliche ABI zu definieren?

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X