Che cos’è un’interfaccia binaria delle applicazioni (ABI)?

Non ho mai capito chiaramente cosa sia un ABI. Per favore non indicarmi un articolo di Wikipedia. Se potessi capirlo, non sarei qui a postare un post così lungo.

Questa è la mia mentalità su diverse interfacce:

Un telecomando TV è un’interfaccia tra l’utente e il televisore. È un’entity framework esistente, ma inutile (non fornisce alcuna funzionalità) di per sé. Tutte le funzionalità per ciascuno di quei pulsanti sul telecomando sono implementate nel televisore.

Interfaccia: è un livello ” quadro esistente” tra la functionality e il consumer di tale funzionalità. Un’interfaccia di per sé non fa nulla. Invoca solo la funzionalità che sta dietro.

Ora, a seconda di chi è l’utente, ci sono diversi tipi di interfacce.

I comandi CLI (Command Line Interface) sono le entity framework esistenti, il consumatore è l’utente e la funzionalità è dietro.

functionality: mia funzionalità software che risolve alcuni scopi a cui stiamo descrivendo questa interfaccia.

existing entities: comandi

consumer: utente

La finestra dell’interfaccia utente grafica (GUI) , i pulsanti, ecc. Sono le quadro esistenti, e ancora una volta il consumatore è l’utente e la funzionalità è dietro.

functionality: mia funzionalità software che risolve alcuni scopi a cui stiamo descrivendo questa interfaccia.

existing entities: finestra, pulsanti ecc.

consumer: utente

Funzioni API (Application Programming Interface) o per essere più corrette, le interfacce (nella programmazione basata sull’interfaccia) sono le quadro esistenti, il consumatore qui è un altro programma non un utente, e ancora una volta la funzionalità sta dietro questo livello.

functionality: mia funzionalità software che risolve alcuni scopi a cui stiamo descrivendo questa interfaccia.

existing entities: funzioni, interfacce (matrice di funzioni).

consumer: un altro programma / applicazione.

Application Binary Interface (ABI) Qui è dove inizia il mio problema.

functionality: ???

existing entities: ???

consumer: ???

  • Ho scritto software in diverse lingue e fornito diversi tipi di interfacce (CLI, GUI e API), ma non sono sicuro, se mai, fornito un ABI.

Wikipedia dice:

Dettagli di copertina di ABI come

  • tipo di dati, dimensioni e allineamento;
  • la convenzione di chiamata, che controlla come vengono passati gli argomenti delle funzioni e recuperati i valori di ritorno;
  • i numeri di chiamata del sistema e come un’applicazione deve effettuare chiamate di sistema al sistema operativo;

Altre ABI standardizzano dettagli come

  • il nome di C ++ che mastica,
  • propagazione delle eccezioni, e
  • chiamare la convenzione tra i compilatori sulla stessa piattaforma, ma non richiede la compatibilità multipiattaforma.
  • Chi ha bisogno di questi dettagli? Per favore non dire il sistema operativo. Conosco la programmazione di assemblaggio. So come funziona il collegamento e il caricamento. So cosa succede esattamente dentro.

  • Perché è entrato in gioco il nome CML? Pensavo che stessimo parlando a livello binario. Perché entrano le lingue?

Comunque, ho scaricato il PDF [PDF] System V Application Binary Interface Edition 4.1 (1997-03-18) per vedere cosa contiene esattamente. Bene, la maggior parte non aveva senso.

  • Perché contiene due capitoli (4 ° e 5 °) per descrivere il formato di file ELF ? In realtà, questi sono gli unici due capitoli significativi di tale specifica. Il resto dei capitoli sono “specifici per processore”. Ad ogni modo, ho pensato che fosse un argomento completamente diverso. Si prega di non dire che le specifiche del formato di file ELF sono l’ABI. Non si qualifica come un’interfaccia secondo la definizione.

  • Lo so, dal momento che stiamo parlando a un livello così basso deve essere molto specifico. Ma non sono sicuro di come sia specifico “instruction set architecture (ISA)”?

  • Dove posso trovare l’ABI di Microsoft Windows?

Quindi, queste sono le domande più importanti che mi infastidiscono.

Un modo semplice per capire “ABI” è confrontarlo con “API”.

Hai già familiarità con il concetto di API. Se si desidera utilizzare le funzionalità di, ad esempio, alcune librerie o il proprio sistema operativo, si utilizzerà un’API. L’API è composta da tipi / strutture di dati, costanti, funzioni, ecc. Che è ansible utilizzare nel codice per accedere alla funzionalità di quel componente esterno.

Un ABI è molto simile. Pensala come la versione compilata di un’API (o come un’API a livello di linguaggio macchina). Quando scrivi codice sorgente, accedi alla libreria tramite un’API. Una volta che il codice è stato compilato, l’applicazione accede ai dati binari nella libreria tramite l’ABI. L’ABI definisce le strutture e i metodi che l’applicazione compilata utilizzerà per accedere alla libreria esterna (proprio come ha fatto l’API), solo a un livello inferiore.

Gli ABI sono importanti quando si tratta di applicazioni che utilizzano librerie esterne. Se un programma è stato creato per utilizzare una particolare libreria e tale libreria è in seguito aggiornata, non si desidera dover ricompilare l’applicazione (e dal punto di vista dell’utente finale, potrebbe non essere disponibile l’origine). Se la libreria aggiornata utilizza lo stesso ABI, non sarà necessario modificare il programma. L’interfaccia alla libreria (a cui interessa davvero tutto il tuo programma) è la stessa anche se il funzionamento interno potrebbe essere cambiato. Due versioni di una libreria che hanno lo stesso ABI sono talvolta chiamate “compatibili con i binari” poiché hanno la stessa interfaccia di basso livello (dovresti essere in grado di sostituire la vecchia versione con quella nuova e non avere grossi problemi).

A volte, le modifiche ABI sono inevitabili. Quando ciò accade, tutti i programmi che usano quella libreria non funzioneranno a meno che non vengano ricompilati per usare la nuova versione della libreria. Se l’ABI cambia ma l’API no, allora la vecchia e la nuova versione della libreria sono talvolta chiamate “compatibili all’origine”. Ciò implica che mentre un programma compilato per una versione di libreria non funziona con l’altro, il codice sorgente scritto per uno funzionerà per l’altro se ricompilato.

Per questo motivo, gli scrittori di biblioteche tendono a cercare di mantenere stabile il loro ABI (per ridurre al minimo le interruzioni). Mantenere una stabile ABI significa non modificare le interfacce di funzione (tipo e numero di ritorno, tipi e ordine degli argomenti), definizioni di tipi di dati o strutture dati, costanti definite, ecc. Nuove funzioni e tipi di dati possono essere aggiunti, ma quelli esistenti devono rimanere lo stesso. Se si espande, ad esempio, un campo della struttura dati a 16 bit in un campo a 32 bit, il codice già compilato che utilizza tale struttura dati non accederà correttamente a quel campo (oa nessuno dei successivi). L’accesso ai membri della struttura dati viene convertito in indirizzi e offset della memoria durante la compilazione e, se la struttura dei dati cambia, questi offset non puntano a ciò che il codice si aspetta che puntino e i risultati sono imprevedibili nel migliore dei casi.

Un ABI non è necessariamente qualcosa che fornirai esplicitamente a meno che non ti aspetti che le persone interagiscano con il tuo codice usando l’assembly. Non è specifico della lingua, poiché (ad esempio) un’applicazione C e un’applicazione Pascal utilizzeranno lo stesso ABI dopo la compilazione.

Modifica: per quanto riguarda la domanda sui capitoli relativi al formato di file ELF nei documenti SysV ABI: Il motivo per cui queste informazioni sono incluse è perché il formato ELF definisce l’interfaccia tra sistema operativo e applicazione. Quando si dice al sistema operativo di eseguire un programma, si aspetta che il programma venga formattato in un certo modo e (ad esempio) si aspetta che la prima sezione del binario sia un’intestazione ELF contenente determinate informazioni su specifici offset di memoria. Questo è il modo in cui l’applicazione comunica importanti informazioni su se stesso al sistema operativo. Se si crea un programma in un formato binario non ELF (come a.out o PE), un sistema operativo che si aspetta applicazioni in formato ELF non sarà in grado di interpretare il file binario o eseguire l’applicazione. Questo è uno dei motivi principali per cui le app di Windows non possono essere eseguite direttamente su una macchina Linux (o viceversa) senza essere ricompilate o eseguite all’interno di un tipo di livello di emulazione che può tradurre da un formato binario a un altro.

IIRC, Windows attualmente utilizza il formato Portable Executable (o, PE). Ci sono collegamenti nella sezione “link esterni” di quella pagina di Wikipedia con ulteriori informazioni sul formato PE.

Inoltre, riguardo alla tua nota sulla manipolazione dei nomi in C ++: l’ABI può definire un modo “standardizzato” per un compilatore C ++ di fare mangling per scopi di compatibilità. Cioè, se creo una libreria e sviluppi un programma che usa la libreria, dovresti essere in grado di usare un compilatore diverso da quello che ho fatto e non dovermi preoccupare che i binari risultanti siano incompatibili a causa di diversi schemi di manomissione dei nomi. Questo è veramente utile solo se stai definendo un nuovo formato di file binario o scrivendo un compilatore o un linker.

Se conosci l’assemblaggio e come funzionano le cose a livello di sistema operativo, ti stai conformando a un determinato ABI. L’ABI governa le cose come vengono passati i parametri, dove vengono posizionati i valori di ritorno. Per molte piattaforms c’è solo un ABI tra cui scegliere, e in questi casi l’ABI è solo “come funzionano le cose”.

Tuttavia, l’ABI governa anche cose come le classi / oggetti sono disposti in C ++. Ciò è necessario se si vuole essere in grado di passare riferimenti object attraverso i limiti del modulo o se si vuole mescolare il codice compilato con diversi compilatori.

Inoltre, se si dispone di un sistema operativo a 64 bit in grado di eseguire file binari a 32 bit, si avranno diversi ABI per codice a 32 e 64 bit.

In generale, qualsiasi codice che colleghi allo stesso eseguibile deve essere conforms allo stesso ABI. Se si desidera comunicare tra codice utilizzando diversi ABI, è necessario utilizzare alcuni tipi di protocolli RPC o di serializzazione.

Penso che tu stia tentando troppo di spremere diversi tipi di interfacce in un set fisso di caratteristiche. Ad esempio, un’interfaccia non deve necessariamente essere suddivisa in consumatori e produttori. Un’interfaccia è solo una convenzione attraverso la quale interagiscono due entity framework.

Le ABI possono essere (parzialmente) ISA-agnostiche. Alcuni aspetti (come le convenzioni di chiamata) dipendono dall’ISA, mentre altri aspetti (come il layout di class C ++) non lo fanno.

Un ABI ben definito è molto importante per le persone che scrivono compilatori. Senza un ABI ben definito, sarebbe imansible generare codice interoperabile.

EDIT: alcune note per chiarire:

  • “Binary” in ABI non esclude l’uso di stringhe o testo. Se si desidera colbind una DLL che esporta una class C ++, da qualche parte in essa devono essere codificati i metodi e i tipi di firma. È qui che entra in gioco la manipolazione dei nomi in C ++.
  • Il motivo per cui non hai mai fornito un ABI è che la stragrande maggioranza dei programmatori non lo farà mai. Gli ABI sono forniti dalle stesse persone che progettano la piattaforma (cioè il sistema operativo) e pochissimi programmatori avranno mai il privilegio di progettare un ABI ampiamente utilizzato.

Un’interfaccia binario dell’applicazione (ABI) è simile a un’API, ma la funzione non è accessibile al chiamante a livello di codice sorgente. Solo una rappresentazione binaria è accessibile / disponibile.

Gli ABI possono essere definiti a livello di architettura del processore o a livello di SO. Gli ABI sono standard che devono essere seguiti dalla fase di generatore di codice del compilatore. Lo standard è fissato dal sistema operativo o dal processore.

Funzionalità: Definire il meccanismo / standard per rendere le chiamate di funzione indipendenti dal linguaggio di implementazione o da uno specifico compilatore / linker / toolchain. Fornire il meccanismo che consente JNI o ​​un’interfaccia Python-C, ecc.

Entità esistenti: funzioni in forma di codice macchina.

Consumatore: un’altra funzione (inclusa una in un’altra lingua, compilata da un altro compilatore o collegata da un altro linker).

In realtà non hai bisogno di un ABI se–

  • Il tuo programma non ha funzioni e–
  • Il tuo programma è un singolo eseguibile che funziona da solo (cioè un sistema embedded) dove è letteralmente l’unica cosa in esecuzione e non ha bisogno di parlare con qualcos’altro.

Un riassunto semplicistico:

API: “Ecco tutte le funzioni che potresti chiamare”.

ABI: “Ecco come chiamare una funzione.”

L’ABI è un insieme di regole a cui i compilatori e i linker aderiscono per compilare il tuo programma in modo che funzioni correttamente. Le ABI coprono più argomenti:

  • Probabilmente la parte più grande e più importante di un ABI è lo standard di chiamata alla procedura noto anche come “convenzione di chiamata”. Le convenzioni di chiamata standardizzano il modo in cui le “funzioni” vengono tradotte in codice assembly.
  • Gli ABI dettano anche il modo in cui i nomi delle funzioni esposte nelle librerie dovrebbero essere rappresentati in modo che un altro codice possa chiamare quelle librerie e sapere quali argomenti dovrebbero essere passati. Questo è chiamato “nome mangling”.
  • Gli ABI stabiliscono anche quale tipo di dati può essere utilizzato, come devono essere allineati e altri dettagli di basso livello.

Dando uno sguardo più approfondito alla convenzione delle chiamate, che considero il nucleo di un ABI:

La macchina stessa non ha concetto di “funzioni”. Quando si scrive una funzione in un linguaggio di alto livello come c, il compilatore genera una riga di codice assembly come _MyFunction1: Questa è un’etichetta , che alla fine verrà risolta in un indirizzo dall’assemblatore. Questa etichetta indica “l’inizio” della “funzione” nel codice assembly. Nel codice di alto livello, quando “chiami” quella funzione, ciò che stai facendo è far sì che la CPU salti all’indirizzo di quella etichetta e continui a eseguirla.

In preparazione per il salto, il compilatore deve fare un sacco di cose importanti. La convenzione di chiamata è come una lista di controllo che il compilatore segue per fare tutto questo:

  • Innanzitutto, il compilatore inserisce un po ‘di codice assembly per salvare l’indirizzo corrente, in modo che quando la “funzione” viene eseguita, la CPU possa tornare al posto giusto e continuare l’esecuzione.
  • Successivamente, il compilatore genera codice assembly per passare gli argomenti.
    • Alcune convenzioni di chiamata dettano che gli argomenti dovrebbero essere messi in pila ( in un particolare ordine, ovviamente).
    • Altre convenzioni stabiliscono che gli argomenti dovrebbero essere inseriti in registri particolari (a seconda dei tipi di dati, naturalmente).
    • Altre convenzioni impongono che venga utilizzata una combinazione specifica di stack e registri.
  • Naturalmente, se prima c’era qualcosa di importante in quei registri, quei valori sono ora sovrascritti e persi per sempre, quindi alcune convenzioni di chiamata potrebbero dettare che il compilatore dovrebbe salvare alcuni di quei registri prima di inserire gli argomenti in essi.
  • Ora il compilatore inserisce un’istruzione di salto che indica alla CPU di andare a quella etichetta che ha creato in precedenza ( _MyFunction1: . A questo punto, puoi considerare la CPU come “in” la tua “funzione”.
  • Alla fine della funzione, il compilatore inserisce un codice assembly che farà sì che la CPU scriva il valore restituito nella posizione corretta. La convenzione di chiamata stabilirà se il valore di ritorno debba essere inserito in un particolare registro (a seconda del suo tipo) o in pila.
  • Ora è il momento di ripulire. La convenzione di chiamata imporrà dove il compilatore inserisce il codice del cleanup assembly.
    • Alcune convenzioni dicono che il chiamante deve pulire lo stack. Ciò significa che dopo che la “funzione” è stata eseguita e che la CPU è tornata alla posizione precedente, il codice successivo da eseguire dovrebbe essere un codice di pulizia molto specifico.
    • Altre convenzioni dicono che alcune parti particolari del codice di pulizia dovrebbero trovarsi alla fine della “funzione” prima del salto indietro.

Vi sono molte diverse ABI / convenzioni di chiamata. Alcuni dei principali sono:

  • Per la CPU x86 o x86-64 (ambiente a 32 bit):
    • CDECL
    • STDCALL
    • fastcall
    • VECTORCALL
    • thiscall
  • Per la CPU x86-64 (ambiente a 64 bit):
    • SystemV
    • MSNATIVE
    • VECTORCALL
  • Per la CPU ARM (32 bit)
    • AAPCS
  • Per la CPU ARM (64 bit)
    • AAPCS64

Ecco una grande pagina che mostra in realtà le differenze nell’assemblaggio generato durante la compilazione per diversi ABI.

Un’altra cosa da ricordare è che un ABI non è rilevante solo all’interno del modulo eseguibile del programma. Viene anche utilizzato dal linker per assicurarsi che il programma chiami correttamente le funzioni della libreria. Hai più librerie condivise in esecuzione sul tuo computer, e fintanto che il compilatore sa che cosa usano ABI, può richiamare le funzioni da loro correttamente senza far esplodere lo stack.

Il tuo compilatore capisce come chiamare le funzioni della libreria è estremamente importante. Su una piattaforma ospitata (cioè, in cui un sistema operativo carica programmi), il tuo programma non può nemmeno lampeggiare senza effettuare una chiamata al kernel.

Funzionalità: una serie di contratti che riguardano il compilatore, i writer di assembly, il linker e il sistema operativo. I contratti specificano come sono disposte le funzioni, dove vengono passati i parametri, come vengono passati i parametri, come funziona la funzione restituisce. Questi sono generalmente specifici per una tupla (architettura del processore, sistema operativo).

Entità esistenti: layout dei parametri, semantica delle funzioni, allocazione dei registri. Ad esempio, le architetture ARM hanno numerosi ABI (APCS, EABI, GNU-EABI, non importa un mucchio di casi storici) – l’utilizzo di un ABI misto farà sì che il codice semplicemente non funzioni quando si chiama oltre i limiti.

Consumatore: compilatore, scrittori assembly, sistema operativo, architettura specifica della CPU.

Chi ha bisogno di questi dettagli? Il compilatore, gli assembly writer, i linker che generano codice (o requisiti di allineamento), il sistema operativo (gestione degli interrupt, interfaccia syscall). Se hai fatto la programmazione di assemblaggio, eri conforms ad un ABI!

Il mangling del nome in C ++ è un caso speciale – è un linker e un problema centrato sul linker dinamico – se il mangling del nome non è standardizzato, il collegamento dinamico non funzionerà. D’ora in poi, l’ABI C ++ è chiamato solo questo, l’ABI C ++. Non si tratta di un problema a livello di linker, ma di un problema di generazione di codice. Una volta che hai un binario C ++, non è ansible renderlo compatibile con un altro C ++ ABI (manomissione di nomi, gestione delle eccezioni) senza ricompilare dall’origine.

ELF è un formato di file per l’uso di un loader e di un linker dinamico. ELF è un formato contenitore per codice binario e dati, e come tale specifica l’ABI di un pezzo di codice. Non considererei ELF un ABI in senso stretto, in quanto gli eseguibili PE non sono un ABI.

Tutte le ABI sono istruzioni specifiche. Un ABI ARM non ha senso su un processore MSP430 o x86_64.

Windows ha diversi ABI – ad esempio, fastcall e stdcall sono due ABI di uso comune. L’ABI syscall è di nuovo diverso.

Lasciatemi almeno rispondere a una parte della tua domanda. Con un esempio di come l’ABI di Linux influenza i systemcall e perché ciò sia utile.

Una systemcall è un modo per un programma userspace di chiedere al kernelspace qualcosa. Funziona inserendo il codice numerico per la chiamata e l’argomento in un determinato registro e triggersndo un interrupt. Più di un interruttore si verifica in kernelspace e il kernel cerca il codice numerico e l’argomento, gestisce la richiesta, reinserisce il risultato in un registro e triggers un passaggio allo spazio utente. Questo è necessario ad esempio quando l’applicazione vuole allocare memoria o aprire un file (syscalls “brk” e “open”).

Ora le syscalls hanno i nomi brevi “brk”, ecc. E gli opcode corrispondenti, questi sono definiti in un file di intestazione specifico del sistema. Finché questi opcode rimangono gli stessi, è ansible eseguire gli stessi programmi compilati per utenti con diversi kernel aggiornati senza doverli ricompilare. Quindi hai un’interfaccia usata da binarys precompilati, quindi ABI.

Il modo migliore per distinguere tra ABI e API è sapere perché e a cosa serve:

Per x86-64 c’è generalmente un ABI (e per x86 a 32 bit c’è un altro set):

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

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

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

Linux + FreeBSD + MacOSX lo seguono con alcune lievi variazioni. E Windows x64 ha il suo ABI:

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

Conoscendo l’ABI e assumendo che anche l’altro compilatore lo segua, allora i binari teoricamente sanno come chiamarsi l’un l’altro (in particolare API di librerie) e passare i parametri sopra allo stack o tramite registri, ecc. O quali registri verranno modificati al momento della chiamata delle funzioni ecc. Essenzialmente queste conoscenze aiuteranno il software ad integrarsi l’un l’altro. Conoscendo l’ordine dei registri / layout di stack, riesco facilmente a mettere insieme diversi software scritti negli assiemi insieme senza molti problemi.

Ma le API sono diverse:

Si tratta di nomi di funzioni di alto livello, con argomenti definiti, tali che se pezzi di software diversi vengono creati utilizzando queste API, POTREBBERO essere in grado di chiamarsi l’un l’altro. Ma un requisito aggiuntivo di SAME ABI deve essere rispettato.

Ad esempio, Windows era conforms all’API POSIX:

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

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

Anche Linux è conforms a POSIX. Ma i binari non possono essere semplicemente spostati ed eseguiti immediatamente. Ma poiché hanno usato lo stesso NAMES nell’API POSIX compatibile, puoi prendere lo stesso software in C, ricompilarlo nel diverso sistema operativo e farlo funzionare immediatamente.

Le API hanno lo scopo di facilitare l’integrazione del software – fase di pre-compilazione. Quindi, dopo la compilazione, il software può sembrare completamente diverso – se l’ABI è diverso.

Gli ABI sono pensati per definire l’esatta integrazione del software a livello di binario / assemblaggio.

Per chiamare il codice nelle librerie condivise o chiamare il codice tra le unità di compilazione, il file object deve contenere etichette per le chiamate. C ++ manipola i nomi delle etichette dei metodi per rafforzare l’occultamento dei dati e consentire i metodi sovraccaricati. Questo è il motivo per cui non è ansible combinare file da diversi compilatori C ++ a meno che non supportino esplicitamente lo stesso ABI.

Sommario

Ci sono varie interpretazioni e opinioni forti sul livello esatto che definisce un ABI (interfaccia binaria dell’applicazione).

A mio avviso, un ABI è una convenzione soggettiva di ciò che è considerato una data piattaforma per una specifica API. L’ABI è il “resto” delle convenzioni che “non cambierà” per una specifica API o che sarà affrontata dall’ambiente di runtime: esecutori, strumenti, linker, compilatori, jvm e OS.

Definire un’interfaccia: ABI, API

Se vuoi usare una libreria come joda-time devi dichiarare una dipendenza su joda-time-...jar . La libreria segue le migliori pratiche e usa il Versioning semantico . Questo definisce la compatibilità API a tre livelli:

  1. Patch: non è necessario modificare tutto il codice. La libreria risolve solo alcuni bug.
  2. Minore: non è necessario modificare il codice dopo le aggiunte
  3. Maggiore: l’interfaccia (API) viene modificata e potrebbe essere necessario modificare il codice.

Per poter utilizzare una nuova versione principale della stessa libreria, molte altre convenzioni devono ancora essere rispettate:

  • Il linguaggio binario utilizzato per le librerie (nei casi Java la versione di destinazione JVM che definisce il bytecode Java)
  • Chiamare convenzioni
  • Convenzioni JVM
  • Convenzioni di collegamento
  • Convenzioni di runtime Tutte queste sono definite e gestite dagli strumenti che usiamo.

Esempi

Case study Java

Ad esempio, Java ha standardizzato tutte queste convenzioni, non in uno strumento, ma in una specifica JVM formale. La specifica consentiva ad altri fornitori di fornire un diverso set di strumenti in grado di generare librerie compatibili.

Java fornisce altri due casi di studio interessanti per ABI: versioni Scala e macchina virtuale Dalvik .

La macchina virtuale Dalvik ha rotto l’ABI

La VM Dalvik ha bisogno di un diverso tipo di bytecode rispetto al bytecode Java. Le librerie Dalvik si ottengono convertendo il bytecode Java (con la stessa API) per Dalvik. In questo modo è ansible ottenere due versioni della stessa API: definite dall’originale joda-time-1.7.2.jar . Potremmo chiamarmi joda-time-1.7.2.jar e joda-time-1.7.2-dalvik.jar . Usano un ABI diverso per lo standard Java vms stack-oriented: quello di Oracle, quello di IBM, Java aperto o altro; e il secondo ABI è quello intorno a Dalvik.

Le versioni successive di Scala sono incompatibili

Scala non ha compatibilità binaria tra le versioni minori di Scala: 2.X. Per questo motivo la stessa API “io.reactivex” %% “rxscala”% “0.26.5” ha tre versioni (in futuro più): per Scala 2.10, 2.11 e 2.12. Cosa è cambiato? Non lo so per ora , ma i binari non sono compatibili. Probabilmente le ultime versioni aggiungono cose che rendono le librerie inutilizzabili sulle vecchie macchine virtuali, probabilmente cose legate alle convenzioni di linking / naming / parameter.

Le versioni successive di Java sono incompatibili

Java ha anche problemi con le principali versioni della JVM: 4,5,6,7,8,9. Offrono solo la compatibilità con le versioni precedenti. Jvm9 sa come eseguire il codice compilato / mirato (l’opzione -target di javac) per tutte le altre versioni, mentre JVM 4 non sa come eseguire il codice destinato a JVM 5. Tutto questo mentre si ha una libreria joda. Questa incompatibilità vola sotto il radar grazie a diverse soluzioni:

  1. Versioning semantico: quando le librerie prendono di mira JVM più elevato, di solito cambiano la versione principale.
  2. Usa JVM 4 come ABI e sei al sicuro.
  3. Java 9 aggiunge una specifica su come è ansible includere bytecode per JVM mirata specifica nella stessa libreria.

Perché ho iniziato con la definizione dell’API?

API e ABI sono solo convenzioni su come si definisce la compatibilità. The lower layers are generic in respect of a plethora of high level semantics. That’s why it’s easy to make some conventions. The first kind of conventions are about memory alignment, byte encoding, calling conventions, big and little endian encodings, etc. On top of them you get the executable conventions like others described, linking conventions, intermediate byte code like the one used by Java or LLVM IR used by GCC. Third you get conventions on how to find libraries, how to load them (see Java classloaders). As you go higher and higher in concepts you have new conventions that you consider as a given. That’s why they didn’t made it to the semantic versioning . They are implicit or collapsed in the major version. We could amend semantic versioning with --- . This is what is actually happening already: platform is already a rpm , dll , jar (JVM bytecode), war (jvm+web server), apk , 2.11 (specific Scala version) and so on. When you say APK you already talk about a specific ABI part of your API.

API can be ported to different ABI

The top level of an abstraction (the sources written against the highest API can be recompiled/ported to any other lower level abstraction.

Let’s say I have some sources for rxscala. If the Scala tools are changed I can recompile them to that. If the JVM changes I could have automatic conversions from the old machine to the new one without bothering with the high level concepts. While porting might be difficult will help any other client. If a new operating system is created using a totally different assembler code a translator can be created.

APIs ported across languages

There are APIs that are ported in multiple languages like reactive streams . In general they define mappings to specific languages/platforms. I would argue that the API is the master specification formally defined in human language or even a specific programming language. All the other “mappings” are ABI in a sense, else more API than the usual ABI. The same is happening with the REST interfaces.

The ABI needs to be consistent between caller and callee to be certain that the call succeeds. Stack use, register use, end-of-routine stack pop. All these are the most important parts of the ABI.

In short and in philosophy, only things of a kind can get along well, and the ABI could be seen as the kind of which software stuff work together.

I was also trying to understand ABI and JesperE’s answer was very helpful.

From a very simple perspective, we may try to understand ABI by considering binary compatibility.

KDE wiki defines a library as binary compatible “if a program linked dynamically to a formsr version of the library continues running with newer versions of the library without the need to recompile.” For more on dynamic linking, refer Static linking vs dynamic linking

Now, let’s try to look at just the most basic aspects needed for a library to be binary compatibility (assuming there are no source code changes to the library):

  1. Same/backward compatible instruction set architecture (processor instructions, register file structure, stack organization, memory access types, along with sizes, layout, and alignment of basic data types the processor can directly access)
  2. Same calling conventions
  3. Same name mangling convention (this might be needed if say a Fortran program needs to call some C++ library function).

Sure, there are many other details but this is mostly what the ABI also covers.

More specifically to answer your question, from the above, we can deduce:

ABI functionality: binary compatibility

existing entities: existing program/libraries/OS

consumer: libraries, OS

Spero che questo ti aiuti!

Application binary interface (ABI)

Functionality:

  • Translation from the programmer’s model to the underlying system’s domain data type, size, alignment, the calling convention, which controls how functions’ arguments are passed and return values retrieved; the system call numbers and how an application should make system calls to the operating system; the high-level language compilers’ name mangling scheme, exception propagation, and calling convention between compilers on the same platform, but do not require cross-platform compatibility…

Existing entities:

  • Logical blocks that directly participate in program’s execution: ALU, general purpose registers, registers for memory/ I/O mapping of I/O, etc…

consumer:

  • Language processors linker, assembler…

These are needed by whoever has to ensure that build tool-chains work as a whole. If you write one module in assembly language, another in Python, and instead of your own boot-loader want to use an operating system, then your “application” modules are working across “binary” boundaries and require agreement of such “interface”.

C++ name mangling because object files from different high-level languages might be required to be linked in your application. Consider using GCC standard library making system calls to Windows built with Visual C++.

ELF is one possible expectation of the linker from an object file for interpretation, though JVM might have some other idea.

For a Windows RT Store app, try searching for ARM ABI if you really wish to make some build tool-chain work together.

The term ABI is used to refer to two distinct but related concepts.

When talking about compilers it refers to the rules used to translate from source-level constructs to binary constructs. How big are the data types? how does the stack work? how do I pass parameters to functions? which registers should be saved by the caller vs the callee?

When talking about libraries it refers to the binary interface presented by a compiled library. This interface is the result of a number of factors including the source code of the library, the rules used by the compiler and in some cases definitions picked up from other libraries.

Changes to a library can break the ABI without breaking the API. Consider for example a library with an interface like.

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

and the application programmer writes code like

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

The application programmer doesn’t care about the size or layout of FOO, but the application binary ends up with a hardcoded size of foo. If the library programmer adds an extra field to foo and someone uses the new library binary with the old application binary then the library may make out of bounds memory accesses.

OTOH if the library author had designed their API like.

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

and the application programmer writes code like

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

Then the application binary does not need to know anything about the structure of FOO, that can all be hidden inside the library. The price you pay for that though is that heap operations are involved.