Ich habe nie klar verstanden, was ein ABI ist. Bitte verweisen Sie mich nicht auf einen Wikipedia-Artikel. Wenn ich es verstehen könnte, wäre ich nicht hier, um einen so langen Beitrag zu veröffentlichen.
Dies ist meine Einstellung zu verschiedenen Schnittstellen:
Eine TV-Fernbedienung ist eine Schnittstelle zwischen dem Benutzer und dem Fernseher. Es ist eine vorhandene Entität, aber für sich genommen nutzlos (bietet keine Funktionalität). Alle Funktionen für jede dieser Tasten auf der Fernbedienung sind im Fernsehgerät implementiert.
Schnittstelle: Es handelt sich um eine "vorhandene Entität" zwischen der
functionality
undconsumer
dieser Funktionalität. Eine Schnittstelle an sich macht nichts. Es ruft nur die dahinter liegende Funktionalität auf.Je nachdem, wer der Benutzer ist, gibt es nun verschiedene Arten von Schnittstellen.
CLI- Befehle (Command Line Interface) sind die vorhandenen Entitäten, der Verbraucher ist der Benutzer und die Funktionalität steckt dahinter.
functionality:
meine Softwarefunktionalität, die einen Zweck löst, für den wir diese Schnittstelle beschreiben.
existing entities:
Befehle
consumer:
BenutzerFenster, Schaltflächen usw. der grafischen Benutzeroberfläche (GUI) sind die vorhandenen Entitäten, und wiederum 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, Knöpfe etc ..
consumer:
BenutzerAPI- Funktionen ( Application Programming Interface) (oder genauer gesagt) Schnittstellen (bei der Schnittstellenprogrammierung) sind die vorhandenen Entitäten, der Verbraucher ist hier ein anderes Programm, kein Benutzer, und wiederum liegt die Funktionalität hinter dieser Schicht.
functionality:
meine Softwarefunktionalität, die ein Problem löst, für das wir diese Schnittstelle beschreiben.
existing entities:
Funktionen, Schnittstellen (Array von Funktionen).
consumer:
ein anderes Programm / eine andere Anwendung.Application Binary Interface (ABI) Hier beginnt mein Problem.
functionality:
???
existing entities:
???
consumer:
???
- Ich habe Software in verschiedenen Sprachen geschrieben und verschiedene Arten von Schnittstellen (CLI, GUI und API) bereitgestellt, bin mir aber nicht sicher, ob ich jemals ein ABI bereitgestellt habe.
ABIs decken Details wie
- Datentyp, Größe und Ausrichtung;
- die aufrufende Konvention, die steuert, wie die Argumente von Funktionen übergeben und Rückgabewerte abgerufen werden;
- die Systemrufnummern und wie eine Anwendung Systemaufrufe an das Betriebssystem tätigen soll;
Andere ABIs standardisieren Details wie
- der C ++ Name Mangling,
- Ausnahmeverbreitung und
- Aufrufkonvention zwischen Compilern auf derselben Plattform, jedoch keine plattformübergreifende Kompatibilität erforderlich.
Wer braucht diese Details? Bitte sagen Sie nicht das Betriebssystem. Ich kenne Assembler-Programmierung. Ich weiß, wie das Verknüpfen und Laden funktioniert. Ich weiß genau, was drinnen passiert.
Warum kam C ++ Name Mangling ins Spiel? Ich dachte, wir reden auf binärer Ebene. Warum kommen Sprachen herein?
Wie auch immer, ich habe die [PDF] System V Application Binary Interface Edition 4.1 (1997-03-18) heruntergeladen, um zu sehen, was genau sie enthält. Nun, das meiste ergab keinen Sinn.
Warum enthält es zwei Kapitel (4. und 5.), um das ELF- Dateiformat zu beschreiben ? Tatsächlich sind dies die einzigen zwei wesentlichen Kapitel dieser Spezifikation. Der Rest der Kapitel ist "prozessorspezifisch". Jedenfalls denke ich, dass es ein ganz anderes Thema ist. Bitte sagen Sie nicht , dass ELF - Dateiformat - Spezifikationen sind die ABI. Es ist keine Schnittstelle gemäß der Definition.
Ich weiß, da wir auf so niedrigem Niveau sprechen, muss es sehr spezifisch sein. Aber ich bin nicht sicher, wie es "Befehlssatzarchitektur (ISA)" spezifisch ist?
Wo finde ich das ABI von Microsoft Windows?
Das sind also die Hauptfragen, die mich nerven.
Antworten:
Eine einfache Möglichkeit, "ABI" zu verstehen, besteht darin, es mit "API" zu vergleichen.
Sie kennen das Konzept einer API bereits. Wenn Sie beispielsweise die Funktionen einer Bibliothek oder Ihres Betriebssystems verwenden möchten, 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. Stellen Sie sich das als kompilierte Version einer API vor (oder als API auf Maschinensprachenebene). Wenn Sie Quellcode schreiben, greifen Sie über eine API auf die Bibliothek zu. Sobald der Code kompiliert ist, greift Ihre Anwendung über das ABI auf die Binärdaten in der Bibliothek zu. Das ABI definiert die Strukturen und Methoden, mit denen Ihre kompilierte Anwendung auf die externe Bibliothek zugreift (genau wie die API), nur auf einer niedrigeren Ebene. Ihre API definiert die Reihenfolge, in der Sie Argumente an eine Funktion übergeben. Ihr ABI definiert die Mechanik, wieDiese Argumente werden übergeben (Register, Stapel usw.). Ihre API definiert, welche Funktionen Teil Ihrer Bibliothek sind. Ihr ABI definiert, wie Ihr Code in der Bibliotheksdatei gespeichert wird, damit 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 mit Code und anderen Ressourcen, aber Ihr Programm muss wissen, wie es die benötigten Informationen in der Bibliotheksdatei findet. Ihr ABI definiert, wie der Inhalt einer Bibliothek in der Datei gespeichert wird, und Ihr Programm verwendet das ABI, um die Datei zu durchsuchen und zu finden, was es benötigt. Wenn alles in Ihrem System dem gleichen ABI entspricht, kann jedes Programm mit jeder Bibliotheksdatei arbeiten, unabhängig davon, wer sie erstellt hat. Linux und Windows verwenden unterschiedliche ABIs, sodass ein Windows-Programm nicht weiß, wie es auf eine für Linux kompilierte Bibliothek zugreifen soll.
Manchmal sind ABI-Änderungen unvermeidlich. In diesem Fall funktionieren alle Programme, die diese Bibliothek verwenden, nur, wenn sie für die Verwendung der neuen Version der Bibliothek neu kompiliert wurden. Wenn sich der ABI ändert, die API jedoch nicht, werden die alten und neuen Bibliotheksversionen manchmal als "quellkompatibel" bezeichnet. Dies bedeutet, dass ein für eine Bibliotheksversion kompiliertes Programm zwar nicht mit der anderen funktioniert, der für eine geschriebene Quellcode jedoch für die andere funktioniert, wenn er neu kompiliert wird.
Aus diesem Grund neigen Entwickler dazu, ihren ABI stabil zu halten (um Störungen zu minimieren). Um einen ABI stabil zu halten, müssen die Funktionsschnittstellen (Rückgabetyp und -nummer, Typen und Reihenfolge der Argumente), Definitionen von Datentypen oder Datenstrukturen, definierte Konstanten usw. nicht geändert werden. Neue Funktionen und Datentypen können hinzugefügt werden, vorhandene müssen jedoch erhalten bleiben das Gleiche. Wenn Ihre Bibliothek beispielsweise 32-Bit-Ganzzahlen verwendet, um den Offset einer Funktion anzugeben, und Sie zu 64-Bit-Ganzzahlen wechseln, greift bereits kompilierter Code, der diese Bibliothek verwendet, nicht korrekt auf dieses Feld (oder einen darauf folgenden) zu . Der Zugriff auf Datenstrukturelemente wird während der Kompilierung in Speicheradressen und Offsets konvertiert. Wenn sich die Datenstruktur ändert,
Ein ABI ist nicht unbedingt etwas, das Sie explizit bereitstellen, es sei denn, Sie führen sehr einfache Systemdesignarbeiten durch. Es ist auch nicht sprachspezifisch, da (zum Beispiel) eine C-Anwendung und eine Pascal-Anwendung nach dem Kompilieren denselben ABI verwenden können.
Bearbeiten:Zu Ihrer Frage zu den Kapiteln zum ELF-Dateiformat in den SysV ABI-Dokumenten: Diese Informationen sind enthalten, weil das ELF-Format die Schnittstelle zwischen Betriebssystem und Anwendung definiert. Wenn Sie das Betriebssystem anweisen, ein Programm auszuführen, erwartet es, dass das Programm auf eine bestimmte Weise formatiert wird, und erwartet beispielsweise, dass der erste Abschnitt der Binärdatei ein ELF-Header ist, der bestimmte Informationen bei bestimmten Speicherversätzen enthält. Auf diese Weise übermittelt die Anwendung wichtige Informationen über sich selbst an das Betriebssystem. 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.
IIRC, Windows verwendet derzeit das Format Portable Executable (oder PE). Es gibt Links im Abschnitt "Externe Links" dieser Wikipedia-Seite mit weiteren Informationen zum PE-Format.
In Bezug auf Ihren Hinweis zur C ++ - Namensverknüpfung: Wenn Sie eine Funktion in einer Bibliotheksdatei suchen, wird die Funktion normalerweise nach Namen gesucht. In C ++ können Sie Funktionsnamen überladen, sodass der Name allein nicht ausreicht, um eine Funktion zu identifizieren. C ++ - Compiler haben ihre eigenen Möglichkeiten, intern damit umzugehen, die so genannte Name Mangling . Ein ABI kann eine Standardmethode zum Codieren des Namens einer Funktion definieren, damit Programme, die mit einer anderen Sprache oder einem anderen Compiler erstellt wurden, das finden, was sie benötigen. Wenn Sie
extern "c"
in einem C ++ - Programm verwenden, weisen Sie den Compiler an, eine standardisierte Methode zum Aufzeichnen von Namen zu verwenden, die für andere Software verständlich ist.quelle
Wenn Sie sich mit Assembly und der Funktionsweise auf Betriebssystemebene auskennen, entsprechen Sie einem bestimmten ABI. Der ABI regelt beispielsweise, wie Parameter übergeben werden und wo Rückgabewerte platziert werden. Für viele Plattformen steht nur ein ABI zur Auswahl, und in diesen Fällen ist der ABI nur "wie die Dinge funktionieren".
Das ABI regelt jedoch auch Dinge wie das Layout von Klassen / Objekten in C ++. Dies ist erforderlich, wenn Sie Objektreferenzen über Modulgrenzen hinweg übergeben möchten oder wenn Sie mit verschiedenen Compilern kompilierten Code mischen möchten.
Wenn Sie ein 64-Bit-Betriebssystem haben, das 32-Bit-Binärdateien ausführen kann, haben Sie verschiedene ABIs für 32- und 64-Bit-Code.
Im Allgemeinen muss jeder Code, den Sie mit derselben ausführbaren Datei verknüpfen, demselben ABI entsprechen. Wenn Sie mit verschiedenen ABIs zwischen Code kommunizieren möchten, müssen Sie eine Form von RPC- oder Serialisierungsprotokollen verwenden.
Ich denke, Sie sind zu sehr bemüht, verschiedene Arten von Schnittstellen in einen festen Satz von Merkmalen zu integrieren. Beispielsweise muss eine Schnittstelle nicht unbedingt in Verbraucher und Hersteller aufgeteilt werden. Eine Schnittstelle ist nur eine Konvention, nach der zwei Entitäten interagieren.
ABIs können (teilweise) ISA-agnostisch sein. Einige Aspekte (z. B. Aufrufkonventionen) hängen von der ISA ab, andere (z. B. das Layout der C ++ - Klasse) nicht.
Ein gut definierter ABI ist sehr wichtig für Leute, die Compiler schreiben. Ohne einen genau definierten ABI wäre es unmöglich, interoperablen Code zu generieren.
EDIT: Einige Anmerkungen zur Verdeutlichung:
quelle
Sie brauchen eigentlich überhaupt keinen ABI, wenn ...
Eine vereinfachte Zusammenfassung:
Das ABI besteht aus Regeln, die Compiler und Linker einhalten, um Ihr Programm so zu kompilieren, dass es ordnungsgemäß funktioniert. ABIs decken mehrere Themen ab:
Ein genauerer Blick auf die Calling Convention, die ich als Kern eines ABI betrachte:
Die Maschine selbst hat kein Konzept von "Funktionen". Wenn Sie eine Funktion in einer höheren Sprache wie c schreiben, generiert der Compiler eine Zeile mit Assembler-Code wie
_MyFunction1:
. Dies ist eine Bezeichnung , die vom Assembler schließlich in eine Adresse aufgelöst wird. Dieses Etikett markiert den "Start" Ihrer "Funktion" im Assembler-Code. Wenn Sie im High-Level-Code diese Funktion "aufrufen", veranlasst die CPU tatsächlich , zur Adresse dieses Labels zu springen und dort weiter auszuführen.In Vorbereitung auf den Sprung muss der Compiler eine Reihe wichtiger Dinge tun. Die Aufrufkonvention ist wie eine Checkliste, der der Compiler folgt, um all diese Dinge zu tun:
_MyFunction1:
). An diesem Punkt können Sie die CPU als "in" Ihrer "Funktion" betrachten.Es gibt viele verschiedene ABIs / Aufrufkonventionen. Einige der wichtigsten sind:
Hier ist eine großartige Seite, die tatsächlich die Unterschiede in der Assembly zeigt, die beim Kompilieren für verschiedene ABIs generiert wurden.
Eine andere Sache zu erwähnen ist, dass ein ABI nicht nur innerhalb des ausführbaren Moduls Ihres Programms relevant ist . Es wird auch vom Linker verwendet, um sicherzustellen, dass Ihr Programm die Bibliotheksfunktionen korrekt aufruft. Auf Ihrem Computer werden mehrere gemeinsam genutzte Bibliotheken ausgeführt. Solange Ihr Compiler weiß, welche ABI er jeweils verwendet, kann er Funktionen von ihnen ordnungsgemäß aufrufen, ohne den Stapel in die Luft zu jagen.
Es ist äußerst wichtig, dass Ihr Compiler versteht, wie Bibliotheksfunktionen aufgerufen werden . Auf einer gehosteten Plattform (dh einer Plattform, auf der ein Betriebssystem Programme lädt) kann Ihr Programm nicht einmal blinken, ohne einen Kernelaufruf zu tätigen.
quelle
Eine Application Binary Interface (ABI) ähnelt einer API, aber die Funktion ist für den Aufrufer auf Quellcodeebene nicht 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 ABIs sind Standards, denen die Codegeneratorphase des Compilers folgen muss. Der Standard wird entweder vom Betriebssystem oder vom Prozessor festgelegt.
Funktionalität: Definieren Sie den Mechanismus / Standard, 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.
Bestehende Entitäten: Funktionen in Maschinencodeform.
Consumer: Eine andere Funktion (einschließlich einer in einer anderen Sprache, die von einem anderen Compiler kompiliert oder von einem anderen Linker verknüpft wurde).
quelle
Funktionalität: Eine Reihe von Verträgen, die sich auf den Compiler, Assembly Writer, den Linker und das Betriebssystem auswirken. Die Verträge legen fest, wie Funktionen angeordnet sind, wo Parameter übergeben werden, wie Parameter übergeben werden, wie Funktionsrückgaben funktionieren. Diese sind im Allgemeinen spezifisch für ein Tupel (Prozessorarchitektur, Betriebssystem).
Bestehende Entitäten: Parameterlayout, Funktionssemantik, Registerzuordnung. Beispielsweise verfügt die ARM-Architektur über zahlreiche ABIs (APCS, EABI, GNU-EABI, unabhängig von einer Reihe historischer Fälle). Die Verwendung eines gemischten ABI führt dazu, dass Ihr Code beim Aufrufen über Grenzen hinweg einfach nicht funktioniert.
Consumer: Der Compiler, Assembly Writer, Betriebssystem, CPU-spezifische Architektur.
Wer braucht diese Details? Der Compiler, Assembly Writer, Linker, die Code generieren (oder Alignment-Anforderungen), Betriebssystem (Interrupt-Behandlung, Syscall-Schnittstelle). Wenn Sie Baugruppenprogrammierung durchgeführt haben, haben Sie sich an einen ABI angepasst!
C ++ - Namensverknüpfung ist ein Sonderfall - es ist ein auf Linker und dynamische Linker zentriertes Problem - wenn die Namensverknüpfung nicht standardisiert ist, funktioniert die dynamische Verknüpfung nicht. Von nun an heißt das C ++ ABI genau das, das C ++ ABI. Es handelt sich nicht um ein Problem auf Linker-Ebene, sondern um ein Problem bei der Codegenerierung. Sobald Sie eine C ++ - Binärdatei haben, ist es nicht möglich, sie mit einem anderen C ++ - ABI (Name Mangling, Ausnahmebehandlung) kompatibel zu machen, ohne sie aus dem Quellcode neu zu kompilieren.
ELF ist ein Dateiformat für die Verwendung eines Loaders und eines dynamischen Linkers. ELF ist ein Containerformat für Binärcode und Daten und gibt als solches den ABI eines Codeteils an. Ich würde ELF nicht als ABI im engeren Sinne betrachten, da ausführbare PE-Dateien kein ABI sind.
Alle ABIs sind befehlssatzspezifisch. Ein ARM-ABI ist auf einem MSP430- oder x86_64-Prozessor nicht sinnvoll.
Windows verfügt über mehrere ABIs. Fastcall und stdcall sind beispielsweise zwei häufig verwendete ABIs. Der Syscall ABI ist wieder anders.
quelle
Lassen Sie mich zumindest einen Teil Ihrer Frage beantworten. Mit einem Beispiel, wie sich das Linux-ABI auf die Systemaufrufe auswirkt und warum dies nützlich ist.
Ein Systemaufruf ist eine Möglichkeit für ein Userspace-Programm, den Kernelspace um etwas zu bitten. Es funktioniert, indem der numerische Code für den Aufruf und das Argument in ein bestimmtes Register gestellt und ein Interrupt ausgelöst wird. Dann erfolgt ein Wechsel zum Kernelspace und der Kernel sucht nach dem numerischen Code und dem Argument, verarbeitet die Anforderung, legt das Ergebnis wieder in einem Register ab und löst einen Wechsel zurück zum Userspace aus. Dies ist beispielsweise erforderlich, wenn die Anwendung Speicher zuweisen oder eine Datei öffnen möchte (syscalls "brk" und "open").
Jetzt haben die Systemaufrufe Kurznamen "brk" usw. und entsprechende Opcodes, die in einer systemspezifischen Header-Datei definiert sind. Solange diese Opcodes gleich bleiben, können Sie dieselben kompilierten Userland-Programme mit verschiedenen aktualisierten Kerneln ausführen, ohne sie neu kompilieren zu müssen. Sie haben also eine Schnittstelle, die von vorkompilierten Binärdateien verwendet wird, daher ABI.
quelle
Um Code in gemeinsam genutzten Bibliotheken oder Code zwischen Kompilierungseinheiten aufzurufen, muss die Objektdatei Beschriftungen für die Aufrufe enthalten. C ++ entstellt die Namen von Methodenbezeichnungen, um das Ausblenden von Daten zu erzwingen und überladene Methoden zuzulassen. Aus diesem Grund können Sie keine Dateien von verschiedenen C ++ - Compilern mischen, es sei denn, sie unterstützen explizit dasselbe ABI.
quelle
Der beste Weg, um zwischen ABI und API zu unterscheiden, besteht darin, zu wissen, warum und wofür es verwendet wird:
Für x86-64 gibt es im Allgemeinen einen ABI (und für x86 32-Bit gibt es einen anderen Satz):
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 folgen mit einigen geringfügigen Abweichungen. Und Windows x64 hat einen eigenen ABI:
http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64/
Wenn Sie den ABI kennen und davon ausgehen, dass auch ein anderer Compiler ihm folgt, wissen die Binärdateien theoretisch, wie sie sich gegenseitig aufrufen (insbesondere die Bibliotheks-API) und Parameter über den Stapel oder über Register usw. übergeben. Oder welche Register beim Aufrufen der Funktionen usw. Geändert werden Im Wesentlichen hilft dieses Wissen der Software, sich miteinander zu integrieren. Wenn ich die Reihenfolge der Register / des Stapellayouts kenne, kann ich problemlos verschiedene Software, die in Baugruppen geschrieben ist, problemlos zusammensetzen.
Aber API sind anders:
Es handelt sich um Funktionsnamen auf hoher Ebene, für die ein Argument definiert ist. Wenn verschiedene Softwareteile mithilfe dieser API erstellt werden, können sie möglicherweise ineinander aufrufen. Eine zusätzliche Anforderung von SAME ABI muss jedoch eingehalten werden.
Beispielsweise war Windows früher POSIX-API-kompatibel:
https://en.wikipedia.org/wiki/Windows_Services_for_UNIX
https://en.wikipedia.org/wiki/POSIX
Und Linux ist auch POSIX-kompatibel. Die Binärdateien können jedoch nicht einfach verschoben und sofort ausgeführt werden. Da sie jedoch dieselben NAMEN in der POSIX-kompatiblen API verwendet haben, können Sie dieselbe Software in C verwenden, sie in den verschiedenen Betriebssystemen neu kompilieren und sofort zum Laufen bringen.
APIs sollen die Integration von Software erleichtern - Vorkompilierungsphase. Nach der Kompilierung kann die Software also völlig anders aussehen - wenn die ABI unterschiedlich sind.
ABI sollen die genaue Integration von Software auf Binär- / Assembly-Ebene definieren.
quelle
SYS_execve
11 unter 32-Bit-Linux ist, aber 59 unter FreeBSD.personality(2)
einstellen kannPER_BSD
. Ich denke, ich erinnere mich, dass ich die ganze Zeitpersonality(PER_LINUX)
in derstrace
Ausgabe gesehen habe, aber moderne 64-Bit-Linux-Binärdateien tun das nicht mehr.Beispiel für eine minimal ausführbare Linux-gemeinsam genutzte Linux-Bibliothek
Im Zusammenhang mit gemeinsam genutzten Bibliotheken besteht die wichtigste Auswirkung eines "stabilen ABI" darin, dass Sie Ihre Programme nach dem Ändern der Bibliothek nicht neu kompilieren müssen.
Also zum Beispiel:
Wenn Sie eine gemeinsam genutzte Bibliothek verkaufen, ersparen Sie Ihren Benutzern 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 in der Distribution des Benutzers abhängt, können Sie weniger Prebuilts veröffentlichen und testen, wenn Sie sicher sind, dass ABI für bestimmte Versionen des Zielbetriebssystems stabil ist.
Dies ist besonders wichtig im Fall der C-Standardbibliothek, mit der viele, viele Programme in Ihrem System verknüpft sind.
Jetzt möchte ich ein minimales konkretes lauffähiges Beispiel dafür liefern.
Haupt c
mylib.c
mylib.h
Kompiliert und läuft gut mit:
Angenommen, wir möchten für Version 2 der Bibliothek ein neues Feld zum
mylib_mystruct
Aufruf hinzufügennew_field
.Wenn wir das Feld zuvor
old_field
wie folgt hinzugefügt haben :und die Bibliothek neu aufgebaut, aber nicht
main.out
, dann schlägt die Behauptung fehl!Dies liegt daran, dass die Zeile:
hatte eine Assembly generiert, die versucht, auf die allererste
int
Struktur zuzugreifen , die jetztnew_field
anstelle der erwarteten istold_field
.Daher hat diese Änderung den ABI gebrochen.
Wenn wir jedoch
new_field
nachher hinzufügenold_field
:Dann greift die alte generierte Assembly immer noch auf die erste
int
Struktur zu, und das Programm funktioniert weiterhin, da wir den ABI stabil gehalten haben.Hier ist eine vollautomatische Version dieses Beispiels auf GitHub .
Eine andere Möglichkeit, diesen ABI stabil zu halten, wäre gewesen, ihn
mylib_mystruct
als undurchsichtige Struktur zu behandeln und nur über Methodenhelfer auf seine Felder zuzugreifen. Dies macht es einfacher, den ABI stabil zu halten, würde jedoch einen Leistungsaufwand verursachen, da wir mehr Funktionsaufrufe ausführen würden.API gegen ABI
Im vorherigen Beispiel ist es interessant , dass die beachten Zugabe
new_field
vorold_field
, nur das ABI brach, aber nicht die API.Dies bedeutet, dass wenn wir unser
main.c
Programm gegen die Bibliothek neu kompiliert hätten, es trotzdem funktioniert hätte.Wir hätten die API jedoch auch beschädigt, wenn wir zum Beispiel die Funktionssignatur geändert hätten:
da würde in diesem Fall
main.c
das Kompilieren ganz aufhören.Semantische API vs Programmier-API
Wir können API-Änderungen auch in einen dritten Typ einteilen: semantische Änderungen.
Die semantische API ist normalerweise eine natürliche Beschreibung dessen, was die API tun soll, normalerweise in der API-Dokumentation enthalten.
Es ist daher möglich, die semantische API zu unterbrechen, ohne den Programmerstellung selbst zu unterbrechen.
Zum Beispiel, wenn wir geändert hätten
zu:
dann hätte dies weder die Programmier-API noch ABI beschädigt, aber
main.c
die semantische API würde beschädigt.Es gibt zwei Möglichkeiten, die Vertrags-API programmgesteuert zu überprüfen:
formale Überprüfung . Schwieriger zu machen, aber einen mathematischen Korrektheitsnachweis zu erbringen, der Dokumentation und Tests auf eine "menschlich" / maschinenprüfbare Weise vereinheitlicht! Solange deine formale Beschreibung natürlich keinen Fehler enthält ;-)
Dieses Konzept steht in engem Zusammenhang mit der Formalisierung der Mathematik selbst: /math/53969/what-does-formal-mean/3297537#3297537
Liste aller Elemente, die C / C ++ - ABIs für gemeinsam genutzte Bibliotheken beschädigen
TODO: Finde / erstelle die ultimative Liste:
Java minimal lauffähiges Beispiel
Was ist Binärkompatibilität in Java?
Getestet in Ubuntu 18.10, GCC 8.2.0.
quelle
Der ABI muss zwischen Anrufer und Angerufenen konsistent sein, um sicherzustellen, dass der Anruf erfolgreich ist. Stack-Verwendung, Register-Verwendung, Stack-Pop am Ende der Routine. All dies sind die wichtigsten Teile des ABI.
quelle
Zusammenfassung
Es gibt verschiedene Interpretationen und starke Meinungen über die genaue Schicht, die eine ABI (Application Binary Interface) definieren.
Meiner Ansicht nach ist ein ABI eine subjektive Konvention dessen, was als gegeben / Plattform für eine bestimmte API angesehen wird. Der ABI ist der "Rest" von Konventionen, die sich für eine bestimmte API "nicht ändern" oder die von der Laufzeitumgebung behandelt werden: Executoren, Tools, Linker, Compiler, JVM und Betriebssystem.
Definieren einer Schnittstelle : ABI, API
Wenn Sie eine Bibliothek wie joda-time verwenden möchten, müssen Sie eine Abhängigkeit von deklarieren
joda-time-<major>.<minor>.<patch>.jar
. Die Bibliothek folgt Best Practices und verwendet die semantische Versionierung . Dies definiert die API-Kompatibilität auf drei Ebenen:Damit Sie eine neue Hauptversion derselben Bibliothek verwenden können, müssen noch viele andere Konventionen beachtet werden:
Beispiele
Java-Fallstudie
Beispielsweise standardisierte Java alle diese Konventionen nicht in einem Tool, sondern in einer formalen JVM-Spezifikation. Die Spezifikation ermöglichte es anderen Anbietern, andere Tools bereitzustellen, mit denen kompatible Bibliotheken ausgegeben werden können.
Java bietet zwei weitere interessante Fallstudien für ABI: Scala-Versionen und Dalvik Virtual Machine.
Die virtuelle Maschine von Dalvik hat den ABI gebrochen
Die Dalvik-VM benötigt einen anderen Bytecode als den Java-Bytecode. Die Dalvik-Bibliotheken werden durch Konvertieren des Java-Bytecodes (mit derselben API) für Dalvik erhalten. Auf diese Weise können Sie zwei Versionen derselben API erhalten: definiert durch das Original
joda-time-1.7.2.jar
. Wir könnten mich anrufenjoda-time-1.7.2.jar
undjoda-time-1.7.2-dalvik.jar
. Sie verwenden eine andere ABI für die stapelorientierten Standard-Java-VMs: die von Oracle, die von IBM, Open Java oder eine andere; und der zweite ABI ist der um Dalvik.Aufeinanderfolgende Scala-Versionen sind nicht kompatibel
Scala hat keine binäre Kompatibilität zwischen kleineren Scala-Versionen: 2.X. Aus diesem Grund hat dieselbe API "io.reactivex" %% "rxscala"% "0.26.5" drei Versionen (in Zukunft mehr): für Scala 2.10, 2.11 und 2.12. Was ist geändert? Ich weiß es noch nicht , aber die Binärdateien sind nicht kompatibel. Wahrscheinlich fügen die neuesten Versionen Dinge hinzu, die die Bibliotheken auf den alten virtuellen Maschinen unbrauchbar machen, wahrscheinlich Dinge, die mit Verknüpfungs- / Namens- / Parameterkonventionen zusammenhängen.
Aufeinanderfolgende Java-Versionen sind nicht kompatibel
Java hat auch Probleme mit den Hauptversionen der JVM: 4,5,6,7,8,9. Sie bieten nur Abwärtskompatibilität. Jvm9 kann kompilierten / zielgerichteten Code (
-target
Option von javac ) für alle anderen Versionen ausführen, während JVM 4 nicht weiß, wie Code ausgeführt wird, der auf JVM 5 ausgerichtet ist. All dies, während Sie über eine joda-Bibliothek verfügen. Diese Inkompatibilität fliegt dank verschiedener Lösungen unter dem Radar:Warum habe ich mit der API-Definition begonnen?
API und ABI sind nur Konventionen zur Definition der Kompatibilität. Die unteren Schichten sind generisch in Bezug auf eine Vielzahl von Semantiken auf hoher Ebene. Deshalb ist es einfach, einige Konventionen zu treffen. Die erste Art von Konventionen betrifft Speicherausrichtung, Bytecodierung, Aufrufkonventionen, Big- und Little-Endian-Codierungen usw. Darüber hinaus erhalten Sie die ausführbaren Konventionen wie die anderen beschriebenen, Verknüpfungskonventionen, Zwischenbytecode wie den von Java oder LLVM IR von GCC verwendet. Drittens erhalten Sie Konventionen zum Auffinden und Laden von Bibliotheken (siehe Java-Klassenladeprogramme). Wenn Sie in Konzepten immer höher gehen, haben Sie neue Konventionen, die Sie als gegeben betrachten. Deshalb haben sie es nicht bis zur semantischen Versionierung geschafft .Ausführung. Wir könnten die semantische Versionierung mit ändern
<major>-<minor>-<patch>-<platform/ABI>
. Das ist , was tatsächlich geschieht bereits: Plattform ist bereits einrpm
,dll
,jar
(JVM Bytecode),war
(JVM + Web - Server),apk
,2.11
(spezifische Scala - Version) und so weiter. Wenn Sie APK sagen, sprechen Sie bereits über einen bestimmten ABI-Teil Ihrer API.Die API kann auf verschiedene ABI portiert werden
Die oberste Ebene einer Abstraktion (die für die höchste API geschriebenen Quellen können neu kompiliert / auf jede andere untergeordnete Abstraktion portiert werden.
Nehmen wir an, ich habe einige Quellen für rxscala. Wenn die Scala-Tools geändert werden, kann ich sie neu kompilieren. Wenn sich die JVM ändert, könnte ich automatisch von der alten auf die neue Maschine konvertieren, ohne mich um die Konzepte auf hoher Ebene zu kümmern. Während die Portierung schwierig sein kann, hilft dies jedem anderen Client. Wenn ein neues Betriebssystem mit einem völlig anderen Assembler-Code erstellt wird, kann ein Übersetzer erstellt werden.
Sprachübergreifend portierte APIs
Es gibt APIs, die in mehreren Sprachen portiert sind, z. B. reaktive Streams . Im Allgemeinen definieren sie Zuordnungen zu bestimmten Sprachen / Plattformen. Ich würde argumentieren, dass die API die Master-Spezifikation ist, die formal in der menschlichen Sprache oder sogar in einer bestimmten Programmiersprache definiert ist. Alle anderen "Zuordnungen" sind gewissermaßen ABI, ansonsten mehr API als das übliche ABI. Gleiches gilt für die REST-Schnittstellen.
quelle
Kurz gesagt und in der Philosophie können nur Dinge einer Art gut miteinander auskommen, und der ABI könnte als der angesehen werden Art von Software angesehen werden, die zusammenarbeiten.
quelle
Ich habe auch versucht, ABI zu verstehen, und JesperEs Antwort war sehr hilfreich.
Aus einer sehr einfachen Perspektive können wir versuchen, ABI zu verstehen, indem wir die Binärkompatibilität berücksichtigen.
Das KDE-Wiki definiert eine Bibliothek als binär kompatibel, "wenn ein Programm, das dynamisch mit einer früheren Version der Bibliothek verknüpft ist, weiterhin mit neueren Versionen der Bibliothek ausgeführt wird, ohne dass eine Neukompilierung erforderlich ist." Weitere Informationen zur dynamischen Verknüpfung finden Sie unter Statische Verknüpfung und dynamische Verknüpfung
Lassen Sie uns nun versuchen, nur die grundlegendsten Aspekte zu betrachten, die für eine Binärkompatibilität einer Bibliothek erforderlich sind (vorausgesetzt, es gibt keine Änderungen am Quellcode der Bibliothek):
Sicher, es gibt viele andere Details, aber dies ist meistens das, was der ABI auch abdeckt.
Um Ihre Frage genauer zu beantworten, können wir aus dem oben Gesagten Folgendes ableiten:
Hoffe das hilft!
quelle
Application Binary Interface (ABI)
Funktionalität:
Bestehende Einheiten:
Verbraucher:
Diese werden von jedem benötigt, der sicherstellen muss, dass Build-Tool-Ketten als Ganzes funktionieren. Wenn Sie ein Modul in Assemblersprache und ein anderes in Python schreiben und anstelle Ihres eigenen Bootloaders ein Betriebssystem verwenden möchten, arbeiten Ihre "Anwendungs" -Module über "binäre" Grenzen hinweg und erfordern die Zustimmung einer solchen "Schnittstelle".
Mangeln von C ++ - Namen, da möglicherweise Objektdateien aus verschiedenen Hochsprachen in Ihrer Anwendung verknüpft werden müssen. Erwägen Sie die Verwendung der GCC-Standardbibliothek für Systemaufrufe an Windows, die mit Visual C ++ erstellt wurden.
ELF ist eine mögliche Erwartung des Linkers an eine Objektdatei zur Interpretation, obwohl JVM möglicherweise eine andere Idee hat.
Versuchen Sie für eine Windows RT Store-App, nach ARM ABI zu suchen, wenn Sie wirklich möchten, dass einige Build-Toolketten zusammenarbeiten.
quelle
Der Begriff ABI bezieht sich auf zwei unterschiedliche, aber verwandte Konzepte.
Wenn es um Compiler geht, bezieht es sich auf die Regeln, die zum Übersetzen von Konstrukten auf Quellenebene in binäre Konstrukte verwendet werden. Wie groß sind die Datentypen? Wie funktioniert der Stack? Wie übergebe ich Parameter an Funktionen? Welche Register sollte der Anrufer gegenüber dem Angerufenen speichern?
Wenn es um Bibliotheken geht, bezieht es sich auf die binäre Schnittstelle, die von einer kompilierten Bibliothek dargestellt wird. Diese Schnittstelle ist das Ergebnis einer Reihe von Faktoren, einschließlich des Quellcodes der Bibliothek, der vom Compiler verwendeten Regeln und in einigen Fällen Definitionen, die aus anderen Bibliotheken übernommen wurden.
Änderungen an einer Bibliothek können den ABI beschädigen, ohne die API zu beschädigen. Stellen Sie sich zum Beispiel eine Bibliothek mit einer Schnittstelle wie vor.
und der Anwendungsprogrammierer schreibt Code wie
Der Anwendungsprogrammierer kümmert sich nicht um die Größe oder das Layout von FOO, aber die Anwendungsbinärdatei erhält eine fest codierte Größe von foo. Wenn der Bibliotheksprogrammierer foo ein zusätzliches Feld hinzufügt und jemand die neue Bibliotheksbinärdatei mit der alten Anwendungsbinärdatei verwendet, kann die Bibliothek Speicherzugriffe außerhalb der Grenzen durchführen.
OTOH, wenn der Bibliotheksautor seine API wie folgt gestaltet hat.
und der Anwendungsprogrammierer schreibt Code wie
Dann muss die Anwendungsbinärdatei nichts über die Struktur von FOO wissen, die alle in der Bibliothek versteckt sein können. Der Preis, den Sie dafür zahlen, ist jedoch, dass Heap-Operationen beteiligt sind.
quelle
ABI
-Application Binary Interface
ist über eine Kommunikationsmaschinencode in der Laufzeit zwischen zwei binären Programmteilen wie - Anwendung, einer Bibliothek, OS ... beschreibt , wie Objekte im Speicher gespeichert werden und wie die Funktionen genannt werden (ABI
calling convention
)Ein gutes Beispiel für API und ABI ist das iOS-Ökosystem mit Swift-Sprache .
Application
- Wenn Sie eine Anwendung in verschiedenen Sprachen erstellen. Zum Beispiel können Sie eine Anwendung mitSwift
und [Mischen von Swift und Objective-C] erstellen.Objective-C
Application - OS
- Laufzeit -Swift runtime
undstandard libraries
sind Teile des Betriebssystems und sollten nicht in jedem Bundle enthalten sein (z. B. App, Framework). Es ist dasselbe wie das, was Objective-C verwendetLibrary
-Module Stability
case - compile time - Sie können ein Framework importieren, das mit einer anderen Version von Swifts Compiler erstellt wurde. Dies bedeutet, dass es sicher ist, eine Closed-Source-Binärdatei (Pre-Build) zu erstellen, die von einer anderen Version des Compilers verwendet wird (.swiftinterface
mit der verwendet wird.swiftmodule
), und die Sie nicht erhaltenLibrary
-Library Evolution
Fall[API vs ABI]
quelle