O que é uma interface binária de aplicativo (ABI)?


493

Eu nunca entendi claramente o que é uma ABI. Por favor, não me aponte para um artigo da Wikipedia. Se eu pudesse entender, não estaria aqui postando uma postagem tão longa.

Esta é a minha mentalidade sobre diferentes interfaces:

Um controle remoto da TV é uma interface entre o usuário e a TV. É uma entidade existente, mas inútil (não fornece nenhuma funcionalidade) por si só. Toda a funcionalidade de cada um desses botões no controle remoto é implementada no aparelho de televisão.

Interface: é uma camada de "entidade existente" entre functionalitye consumerdessa funcionalidade. Uma interface por si só não faz nada. Apenas invoca a funcionalidade por trás.

Agora, dependendo de quem é o usuário, existem diferentes tipos de interfaces.

Os comandos da CLI (Command Line Interface) são as entidades existentes, o consumidor é o usuário e a funcionalidade está por trás.

functionality: minha funcionalidade de software que resolve algum objetivo para o qual estamos descrevendo essa interface.

existing entities: comandos

consumer: do utilizador

Janela da interface gráfica do usuário (GUI) , botões, etc. são as entidades existentes e, novamente, o consumidor é o usuário e a funcionalidade está por trás.

functionality: minha funcionalidade de software que resolve algum problema para o qual estamos descrevendo essa interface.

existing entities: janela, botões etc.

consumer: do utilizador

As funções da Interface de programação de aplicativos (API) (ou, para ser mais correto), interfaces (na programação baseada em interface) são as entidades existentes, o consumidor aqui é outro programa que não é usuário e, novamente, a funcionalidade está por trás dessa camada.

functionality: minha funcionalidade de software que resolve algum problema para o qual estamos descrevendo essa interface.

existing entities: funções, interfaces (matriz de funções).

consumer: outro programa / aplicativo.

Interface Binária de Aplicativo (ABI) Aqui é onde meu problema começa.

functionality: ???

existing entities: ???

consumer: ???

  • Escrevi software em idiomas diferentes e forneci diferentes tipos de interfaces (CLI, GUI e API), mas não tenho certeza se já forneci alguma ABI.

A Wikipedia diz:

As ABIs abrangem detalhes como

  • tipo de dados, tamanho e alinhamento;
  • a convenção de chamada, que controla como os argumentos das funções são passados ​​e retornam os valores recuperados;
  • os números de chamada do sistema e como um aplicativo deve fazer chamadas do sistema para o sistema operacional;

Outras ABIs padronizam detalhes como

  • o nome C ++ desconfigurado,
  • propagação de exceção e
  • convenção de chamada entre compiladores na mesma plataforma, mas não requer compatibilidade entre plataformas.
  • Quem precisa desses detalhes? Por favor, não diga o sistema operacional. Eu sei programação de montagem. Eu sei como funciona o link e o carregamento. Eu sei exatamente o que acontece por dentro.

  • Por que o nome C ++ mutilado apareceu? Eu pensei que estávamos conversando no nível binário. Por que os idiomas entram?

De qualquer forma, baixei o [PDF] System V Application Binary Interface Edition 4.1 (18-03-2007) para ver exatamente o que ele contém. Bem, a maior parte não fazia sentido.

  • Por que contém dois capítulos (4 e 5) para descrever o formato de arquivo ELF ? De fato, esses são os únicos dois capítulos significativos dessa especificação. O restante dos capítulos é "específico do processador". Enfim, eu acho que é um tópico completamente diferente. Por favor, não diga que as especificações de formato de arquivo ELF são a ABI. Não se qualifica para ser uma interface de acordo com a definição.

  • Eu sei, já que estamos falando em um nível tão baixo, deve ser muito específico. Mas não tenho certeza de como é específica a "arquitetura do conjunto de instruções (ISA)"?

  • Onde posso encontrar a ABI do Microsoft Windows?

Então, essas são as principais consultas que estão me incomodando.


7
Compiladores "Por favor, não diga, SO" precisam conhecer a ABI. Os vinculadores precisam conhecer a ABI. O kernel precisa conhecer a ABI para configurar o programa na RAM para que ele funcione corretamente. Quanto ao C ++, veja abaixo, intencionalmente transforma rótulos em sem sentido por causa de métodos sobrecarregados e privados, e o vinculador e qualquer outro compilador precisam ter nomes compatíveis para trabalhar com ele, ou seja, a mesma ABI.
Justin Smith

8
Eu acho que a pergunta é tão clara; descrevendo exatamente qual é o formato de resposta esperado e ainda não uma resposta satisfatória que possa ser aceita.
Legends2k

3
@ legends2k Minha opinião sobre a questão é que o OP realmente sabe o que é uma ABI, mas não percebe isso. A grande maioria dos programadores nunca projeta ou fornece uma ABI, porque esse é o trabalho dos projetistas de sistemas operacionais / plataformas.
precisa

4
@Espere: Eu concordo com o seu ponto. Mas, provavelmente, o OP quer conhecê-lo claramente, no formato que achar melhor, mesmo que ele não precise fornecer uma ABI.
Legends2k

2
Eu era ignorante. Recentemente, enquanto trabalhava com todas essas coisas. Eu percebi o que é realmente ABI. Sim, eu concordo que meu modelo está com defeito. Não é apropriado ajustar a ABI ao meu modelo. Obrigado @ JasperE. Foi preciso apenas experiência de trabalho para perceber sua resposta.
garras

Respostas:


535

Uma maneira fácil de entender "ABI" é compará-lo com "API".

Você já está familiarizado com o conceito de uma API. Se você deseja usar os recursos de, digamos, alguma biblioteca ou seu sistema operacional, programa contra uma API. A API consiste em tipos / estruturas de dados, constantes, funções etc. que você pode usar no seu código para acessar a funcionalidade desse componente externo.

Uma ABI é muito semelhante. Pense nisso como a versão compilada de uma API (ou como uma API no nível da linguagem de máquina). Ao escrever o código-fonte, você acessa a biblioteca por meio de uma API. Depois que o código é compilado, seu aplicativo acessa os dados binários na biblioteca através da ABI. A ABI define as estruturas e métodos que seu aplicativo compilado usará para acessar a biblioteca externa (exatamente como a API), apenas em um nível inferior. Sua API define a ordem em que você passa argumentos para uma função. Sua ABI define a mecânica de comoesses argumentos são passados ​​(registros, pilha, etc.). Sua API define quais funções fazem parte da sua biblioteca. Sua ABI define como seu código é armazenado dentro do arquivo da biblioteca, para que qualquer programa que utilize sua biblioteca possa localizar e executar a função desejada.

As ABIs são importantes quando se trata de aplicativos que usam bibliotecas externas. As bibliotecas estão cheias de código e outros recursos, mas seu programa precisa saber como localizar o que precisa dentro do arquivo da biblioteca. Sua ABI define como o conteúdo de uma biblioteca é armazenado dentro do arquivo, e seu programa usa a ABI para pesquisar no arquivo e encontrar o que ele precisa. Se tudo no seu sistema estiver em conformidade com a mesma ABI, qualquer programa poderá trabalhar com qualquer arquivo de biblioteca, independentemente de quem os criou. Linux e Windows usam ABIs diferentes, portanto, um programa Windows não saberá como acessar uma biblioteca compilada para Linux.

Às vezes, as alterações ABI são inevitáveis. Quando isso acontece, qualquer programa que use essa biblioteca não funcionará, a menos que seja recompilado para usar a nova versão da biblioteca. Se a ABI for alterada, mas a API não for alterada, as versões antiga e nova da biblioteca serão denominadas "compatíveis com a origem". Isso implica que, embora um programa compilado para uma versão da biblioteca não funcione com o outro, o código-fonte escrito para um funcionará para o outro se for compilado novamente.

Por esse motivo, os desenvolvedores tendem a tentar manter sua ABI estável (para minimizar as interrupções). Manter uma ABI estável significa não alterar as interfaces de função (tipo e número de retorno, tipos e ordem dos argumentos), definições de tipos de dados ou estruturas de dados, constantes definidas, etc. Novas funções e tipos de dados podem ser adicionados, mas os existentes devem permanecer o mesmo. Se, por exemplo, sua biblioteca usar números inteiros de 32 bits para indicar o deslocamento de uma função e você alternar para números inteiros de 64 bits, o código já compilado que usa essa biblioteca não acessará corretamente esse campo (ou qualquer outro) . O acesso aos membros da estrutura de dados é convertido em endereços e compensações de memória durante a compilação e se a estrutura de dados mudar,

Uma ABI não é necessariamente algo que você fornecerá explicitamente, a menos que esteja executando um trabalho de design de sistemas de nível muito baixo. Também não é específico do idioma, já que (por exemplo) um aplicativo C e um aplicativo Pascal podem usar a mesma ABI após serem compilados.

Editar:Em relação à sua pergunta sobre os capítulos sobre o formato de arquivo ELF nos documentos do SysV ABI: A razão pela qual essas informações estão incluídas é porque o formato ELF define a interface entre sistema operacional e aplicativo. Quando você instrui o sistema operacional a executar um programa, ele espera que o programa seja formatado de uma certa maneira e (por exemplo) espera que a primeira seção do binário seja um cabeçalho ELF contendo certas informações com desvios de memória específicos. É assim que o aplicativo comunica informações importantes sobre si mesmo ao sistema operacional. Se você criar um programa em um formato binário que não seja ELF (como a.out ou PE), um SO que espere aplicativos formatados em ELF não poderá interpretar o arquivo binário ou executar o aplicativo.

IIRC, o Windows atualmente usa o formato Portable Executable (ou, PE). Existem links na seção "links externos" dessa página da Wikipedia com mais informações sobre o formato PE.

Além disso, em relação à sua observação sobre a troca de nomes em C ++: Ao localizar uma função em um arquivo de biblioteca, a função geralmente é procurada por nome. O C ++ permite sobrecarregar os nomes das funções, portanto, somente o nome não é suficiente para identificar uma função. Os compiladores C ++ têm suas próprias maneiras de lidar com isso internamente, chamado de manipulação de nomes . Uma ABI pode definir uma maneira padrão de codificar o nome de uma função para que os programas criados com um idioma ou compilador diferente possam localizar o que precisam. Ao usar extern "c"em um programa C ++, você está instruindo o compilador a usar uma maneira padronizada de gravar nomes que seja compreensível por outro software.


2
@ BTA, obrigado pela ótima resposta. A convenção de chamada é um tipo de ABI? Graças
camino

37
Boa resposta. Exceto que isso não é o que é uma ABI. Uma ABI é um conjunto de regras que determina a convenção de chamada e regras para o layout de estruturas. Pascal passa argumentos na pilha na ordem inversa de aplicativos C, para que os compiladores pascal e C NÃO sejam compilados na mesma ABI. Os respectivos padrões para os compiladores C e Pascal implicitamente garantem que este será o caso. Os compiladores C ++ não podem definir uma maneira "padrão" de alterar nomes, pois não há uma maneira padrão. As convenções de manipulação de nomes C ++ não eram compatíveis entre os compiladores C ++ quando havia compiladores C ++ concorrentes no Windows.
Robin Davies


1
@RobinDavies: Nas plataformas em que os compiladores Pascal chamariam argumentos pop de funções dados por seus chamadores, os compiladores C geralmente definem meios pelos quais um programador pode indicar que funções específicas devem usar, ou se espera que usem, as mesmas convenções de chamada que o Compiladores Pascal, mesmo que os compiladores C usem, por padrão, uma convenção em que as funções chamadas deixam na pilha qualquer coisa colocada por seus chamadores.
Supercat

Posso dizer que os arquivos obj gerados pelo compilador C contêm ABIs?
Mitu Raj

144

Se você conhece montagem e como as coisas funcionam no nível do SO, está em conformidade com uma determinada ABI. A ABI governa coisas como a passagem de parâmetros, onde os valores de retorno são colocados. Para muitas plataformas, há apenas uma ABI para escolher e, nesses casos, a ABI é apenas "como as coisas funcionam".

No entanto, a ABI também governa coisas como o modo como as classes / objetos são dispostos em C ++. Isso é necessário se você deseja passar referências de objeto através dos limites do módulo ou se deseja misturar código compilado com diferentes compiladores.

Além disso, se você tiver um sistema operacional de 64 bits que possa executar binários de 32 bits, terá ABIs diferentes para código de 32 e 64 bits.

Em geral, qualquer código vinculado ao mesmo executável deve estar em conformidade com a mesma ABI. Se você deseja se comunicar entre códigos usando ABIs diferentes, use algum tipo de RPC ou protocolos de serialização.

Eu acho que você está se esforçando demais para espremer diferentes tipos de interfaces em um conjunto fixo de características. Por exemplo, uma interface não precisa necessariamente ser dividida em consumidores e produtores. Uma interface é apenas uma convenção pela qual duas entidades interagem.

As ABIs podem ser (parcialmente) independentes de ISA. Alguns aspectos (como convenções de chamada) dependem do ISA, enquanto outros (como o layout da classe C ++) não.

Uma ABI bem definida é muito importante para as pessoas que escrevem compiladores. Sem uma ABI bem definida, seria impossível gerar código interoperável.

EDIT: Algumas notas para esclarecer:

  • "Binário" na ABI não exclui o uso de cadeias ou texto. Se você deseja vincular uma DLL que exporta uma classe C ++, em algum lugar nela, os métodos e as assinaturas de tipo devem ser codificados. É aí que entra o nome do C ++.
  • A razão pela qual você nunca forneceu uma ABI é que a grande maioria dos programadores nunca fará isso. As ABIs são fornecidas pelas mesmas pessoas que projetam a plataforma (ou seja, sistema operacional), e muito poucos programadores terão o privilégio de projetar uma ABI amplamente usada.

Não estou absolutamente convencido de que meu modelo esteja com defeito. Porque em todos os lugares em que esse modelo de interface é verdadeiro. Então, sim, quero que a ABI também se encaixe nesse modelo, mas não é isso. O importante é que ainda não entendo. Não sei se sou tão burra ou outra coisa, mas isso simplesmente não entra na minha cabeça. Não consigo entender as respostas e o artigo da wiki.
garras

2
@jesperE, "A ABI governa coisas como a forma como os parâmetros são passados, onde os valores de retorno são colocados." está relacionado a "cdecl, stdcall, fastcall, pascal", certo?
camino

3
Sim. O nome próprio é "convenção de chamada", que faz parte da ABI. pt.wikipedia.org/wiki/X86_calling_conventions
JesperE 13/03

4
esta é a resposta correta e precisa sem a verbosidade (bastante barulho )!
Nawaz

Eu recomendo escrever um pouco de montagem. Isso ajudará as pessoas a entender a ABI de maneira mais tangível.
Kunyu Tsai

40

Você realmente não precisa de uma ABI, se--

  • Seu programa não possui funções e--
  • Seu programa é um único executável que está sendo executado sozinho (ou seja, um sistema embutido), onde é literalmente a única coisa em execução e não precisa conversar com mais nada.

Um resumo simplificado:

API: "Aqui estão todas as funções que você pode chamar."

ABI: assim que se chama uma função."

A ABI é um conjunto de regras que os compiladores e vinculadores seguem para compilar seu programa para que funcione corretamente. As ABIs abrangem vários tópicos:

  • Indiscutivelmente, a parte maior e mais importante de uma ABI é o padrão de chamada de procedimento, às vezes conhecido como "convenção de chamada". As convenções de chamada padronizam como "funções" são convertidas em código de montagem.
  • As ABIs também determinam como os nomes das funções expostas nas bibliotecas devem ser representados, para que outro código possa chamar essas bibliotecas e saber quais argumentos devem ser passados. Isso é chamado de "nome desconcertante".
  • As ABIs também determinam que tipo de dados podem ser usados, como eles devem ser alinhados e outros detalhes de baixo nível.

Examinando mais profundamente a convenção de convocação, que considero o núcleo de uma ABI:

A própria máquina não tem conceito de "funções". Quando você escreve uma função em uma linguagem de alto nível como c, o compilador gera uma linha de código de montagem como _MyFunction1:. Este é um rótulo , que acabará sendo resolvido em um endereço pelo assembler. Este rótulo marca o "início" da sua "função" no código de montagem. No código de alto nível, quando você "chama" essa função, o que realmente está fazendo é fazer com que a CPU salte para o endereço desse rótulo e continue executando lá.

Em preparação para o salto, o compilador deve fazer um monte de coisas importantes. A convenção de chamada é como uma lista de verificação que o compilador segue para fazer todas essas coisas:

  • Primeiro, o compilador insere um pouco do código de montagem para salvar o endereço atual, para que, quando sua "função" for concluída, a CPU possa voltar ao lugar certo e continuar executando.
  • Em seguida, o compilador gera código de montagem para passar os argumentos.
    • Algumas convenções de chamada determinam que os argumentos devem ser colocados na pilha ( em uma ordem específica, é claro).
    • Outras convenções determinam que os argumentos sejam colocados em registros específicos ( dependendo dos tipos de dados, é claro).
    • Outras convenções ainda determinam que uma combinação específica de pilha e registradores deve ser usada.
  • Obviamente, se havia algo importante nesses registros antes, esses valores agora são substituídos e perdidos para sempre, portanto, algumas convenções de chamada podem exigir que o compilador salve alguns desses registros antes de colocar os argumentos neles.
  • Agora o compilador insere uma instrução de salto dizendo à CPU para ir para o rótulo que criou anteriormente ( _MyFunction1:). Neste ponto, você pode considerar a CPU "na" sua "função".
  • No final da função, o compilador coloca algum código de montagem que fará com que a CPU escreva o valor de retorno no local correto. A convenção de chamada determinará se o valor de retorno deve ser colocado em um registro específico (dependendo do tipo) ou na pilha.
  • Agora é hora de limpar. A convenção de chamada ditará onde o compilador coloca o código do assembly de limpeza.
    • Algumas convenções dizem que o chamador deve limpar a pilha. Isso significa que, depois que a "função" estiver concluída e a CPU voltar para onde estava antes, o próximo código a ser executado deve ser um código de limpeza muito específico.
    • Outras convenções dizem que algumas partes específicas do código de limpeza devem estar no final da "função" antes do salto para trás.

Existem muitas ABIs / convenções de chamada diferentes. Alguns principais são:

  • Para a CPU x86 ou x86-64 (ambiente de 32 bits):
    • CDECL
    • STDCALL
    • FASTCALL
    • VECTORCALL
    • THISCALL
  • Para a CPU x86-64 (ambiente de 64 bits):
    • SYSTEMV
    • MSNATIVE
    • VECTORCALL
  • Para a CPU ARM (32 bits)
    • AAPCS
  • Para a CPU ARM (64 bits)
    • AAPCS64

Aqui está uma ótima página que mostra as diferenças na montagem gerada ao compilar ABIs diferentes.

Outra coisa a mencionar é que uma ABI não é relevante apenas dentro do módulo executável do seu programa. É também usado pelo vinculador para garantir que seu programa chama funções de biblioteca corretamente. Você tem várias bibliotecas compartilhadas em execução no seu computador e, desde que seu compilador saiba qual ABI cada um usa, ele poderá chamar suas funções corretamente, sem explodir a pilha.

Seu compilador entender como chamar funções de biblioteca é extremamente importante. Em uma plataforma hospedada (ou seja, onde o sistema operacional carrega programas), seu programa não pode nem piscar sem fazer uma chamada ao kernel.


19

Uma interface binária de aplicativo (ABI) é semelhante a uma API, mas a função não está acessível ao chamador no nível do código-fonte. Somente uma representação binária está acessível / disponível.

As ABIs podem ser definidas no nível da arquitetura do processador ou no nível do SO. As ABIs são padrões a serem seguidos pela fase gerador de código do compilador. O padrão é fixado pelo sistema operacional ou pelo processador.

Funcionalidade: Defina o mecanismo / padrão para fazer chamadas de função independentes da linguagem de implementação ou de um compilador / vinculador / cadeia de ferramentas específico. Forneça o mecanismo que permite JNI, ou uma interface Python-C, etc.

Entidades existentes: Funções no formato de código de máquina.

Consumidor: Outra função (incluindo uma em outro idioma, compilada por outro compilador ou vinculada por outro vinculador).


Por que o ABI seria definido pela arquitetura? Por que sistemas operacionais diferentes na mesma arquitetura não conseguem definir ABIs diferentes?
Andreas Haferburg

10

Funcionalidade: um conjunto de contratos que afetam o compilador, gravadores de montagem, o vinculador e o sistema operacional. Os contratos especificam como as funções são dispostas, onde os parâmetros são passados, como os parâmetros são passados, como o retorno da função funciona. Geralmente são específicos para uma tupla (arquitetura do processador, sistema operacional).

Entidades existentes: layout de parâmetros, semântica de funções, alocação de registros. Por exemplo, as arquiteturas ARM têm inúmeras ABIs (APCS, EABI, GNU-EABI, não importa muitos casos históricos) - o uso de uma ABI mista resultará em seu código simplesmente não funcionando ao chamar além-fronteiras.

Consumidor: O compilador, gravadores de montagem, sistema operacional, arquitetura específica da CPU.

Quem precisa desses detalhes? O compilador, gravadores de montagem, vinculadores que geram código (ou requisitos de alinhamento), sistema operacional (manipulação de interrupções, interface syscall). Se você fez a programação de montagem, estava em conformidade com uma ABI!

A identificação de nomes C ++ é um caso especial - é um problema centrado no vinculador e no vinculador dinâmico - se a identificação de nomes não for padronizada, a vinculação dinâmica não funcionará. Daí em diante, o C ++ ABI é chamado apenas isso, o C ++ ABI. Não é um problema no nível do vinculador, mas um problema de geração de código. Depois de ter um binário em C ++, não é possível torná-lo compatível com outro C ++ ABI (manipulação de nomes, manipulação de exceções) sem recompilar a partir da fonte.

ELF é um formato de arquivo para o uso de um carregador e vinculador dinâmico. ELF é um formato de contêiner para código e dados binários e, como tal, especifica a ABI de um trecho de código. Eu não consideraria o ELF um ABI no sentido estrito, pois os executáveis ​​do PE não são um ABI.

Todas as ABIs são específicas do conjunto de instruções. Uma ARI ABI não fará sentido em um processador MSP430 ou x86_64.

O Windows possui várias ABIs - por exemplo, fastcall e stdcall são duas ABIs de uso comum. O syscall ABI é diferente novamente.


9

Deixe-me pelo menos responder uma parte da sua pergunta. Com um exemplo de como a ABI do Linux afeta as chamadas do sistema e por que isso é útil.

Uma chamada de sistema é uma maneira de um programa do espaço do usuário solicitar algo ao kernelspace. Ele funciona colocando o código numérico da chamada e o argumento em um determinado registro e acionando uma interrupção. Em seguida, ocorre uma troca no kernelspace e o kernel consulta o código numérico e o argumento, manipula a solicitação, coloca o resultado novamente em um registro e aciona a troca no espaço do usuário. Isso é necessário, por exemplo, quando o aplicativo deseja alocar memória ou abrir um arquivo (syscalls "brk" e "open").

Agora, os syscalls têm nomes abreviados "brk" etc., e códigos de operação correspondentes, eles são definidos em um arquivo de cabeçalho específico do sistema. Enquanto esses opcodes permanecerem os mesmos, você poderá executar os mesmos programas compilados do usuário com kernels diferentes e atualizados, sem precisar recompilar. Então você tem uma interface usada por binários pré-compilados, daí a ABI.


4

Para chamar o código em bibliotecas compartilhadas ou entre as unidades de compilação, o arquivo de objeto precisa conter rótulos para as chamadas. O C ++ gerencia os nomes dos rótulos dos métodos para forçar a ocultação de dados e permitir métodos sobrecarregados. É por isso que você não pode misturar arquivos de diferentes compiladores C ++, a menos que eles suportem explicitamente a mesma ABI.


4

A melhor maneira de diferenciar entre ABI e API é saber por que e para que é usada:

Para x86-64, geralmente há uma ABI (e para x86 de 32 bits, há outro conjunto):

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 seguem-no com algumas pequenas variações. E o Windows x64 tem sua própria ABI:

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

Conhecendo a ABI e assumindo que outro compilador a segue também, os binários, teoricamente, sabem como se chamar (API de bibliotecas em particular) e passar parâmetros pela pilha ou por registradores etc. Ou quais registradores serão alterados ao chamar as funções, etc. Essencialmente, esse conhecimento ajudará o software a se integrar. Conhecendo a ordem dos registros / layout da pilha, posso facilmente montar diferentes softwares escritos em montagens sem muito problema.

Mas as API são diferentes:

É um nome de funções de alto nível, com argumento definido, de modo que, se diferentes partes de software constroem usando essa API, PODEM ser capazes de se conectar. Mas um requisito adicional da mesma ABI deve ser respeitado.

Por exemplo, o Windows costumava ser compatível com a API POSIX:

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

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

E o Linux também é compatível com POSIX. Mas os binários não podem ser movidos e executados imediatamente. Mas como eles usaram os mesmos NOMES na API compatível com POSIX, você pode pegar o mesmo software em C, recompilar nos diferentes sistemas operacionais e executá-lo imediatamente.

As APIs visam facilitar a integração do software - estágio de pré-compilação. Então, após a compilação, o software pode parecer totalmente diferente - se as ABI forem diferentes.

A ABI visa definir a integração exata do software no nível binário / de montagem.


A convenção de chamada do Windows x86-64 não usa a convenção de chamada SysV usada por todos os outros sistemas operacionais x86-64. Todos Linux / OS X / FreeBSD compartilham a mesma convenção de chamada, mas não compartilham a ABI completa. A ABI de um sistema operacional inclui números de chamada do sistema. por exemplo, freebsd.org/doc/en_US.ISO8859-1/books/developers-handbook/… diz que SYS_execveé 11 no Linux de 32 bits, mas 59 no FreeBSD.
Peter Cordes

obrigado pelo seu comentário, modifiquei meu comentário para melhor responder à diferença entre ABI e API.
Peter Teoh

Você ainda está perdendo a diferença entre uma convenção de chamada e uma ABI completa (chamadas do sistema e tudo mais). Você pode executar alguns binários do FreeBSD no Linux, porque o Linux (o kernel) fornece uma camada de compatibilidade do FreeBSD. Mesmo assim, isso é limitado a binários que não tentam usar nenhuma parte da ABI do FreeBSD que o Linux não fornece. (por exemplo, qualquer chamada do sistema somente para FreeBSD). Compatível com ABI significa que você pode executar o mesmo binário em ambos os sistemas, não apenas que eles compilariam da mesma forma.
Peter Cordes

"Camada de compatibilidade do FreeBSD", nunca ouvi falar disso. Você pode apontar para o código fonte do kernel linux relevante? Mas o contrário existe: freebsd.org/doc/en_US.ISO8859-1/books/handbook/linuxemu.html .
precisa saber é o seguinte

Não é algo que eu uso. Eu pensei que algo assim existisse, mas talvez não exista mais. O tldp.org/HOWTO/Linux+FreeBSD-6.html diz que não é mantido, e que esse tutorial é de 2000. xD. O unix.stackexchange.com/questions/172038/… confirma que foi abandonado e nunca foi repetido (já que ninguém o desejava o suficiente para fazê-lo). personality(2)pode definir PER_BSD. Eu acho que eu lembro de ter visto personality(PER_LINUX)na stracesaída o tempo todo, mas moderno de 64 bits binários Linux não mais fazer isso.
Peter Cordes

4

Exemplo de ABI mínimo executável da biblioteca compartilhada Linux

No contexto de bibliotecas compartilhadas, a implicação mais importante de "ter uma ABI estável" é que você não precisa recompilar seus programas após a alteração da biblioteca.

Então, por exemplo:

  • se você estiver vendendo uma biblioteca compartilhada, poupe aos usuários o incômodo de recompilar tudo o que depende da sua biblioteca para cada nova versão

  • se você estiver vendendo um programa de código-fonte fechado que depende de uma biblioteca compartilhada presente na distribuição do usuário, poderá liberar e testar menos pré-compilações se tiver certeza de que a ABI é estável em determinadas versões do sistema operacional de destino.

    Isso é especialmente importante no caso da biblioteca padrão C, à qual muitos programas do seu sistema se vinculam.

Agora, quero fornecer um exemplo executável concreto mínimo disso.

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

Compila e executa bem com:

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

Agora, suponha que, para a v2 da biblioteca, desejemos adicionar um novo campo ao mylib_mystructchamado new_field.

Se adicionamos o campo antes, old_fieldcomo em:

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

e reconstruiu a biblioteca, mas não main.out, a declaração falha!

Isso ocorre porque a linha:

myobject->old_field == 1

gerou um assembly que está tentando acessar o primeiro intda estrutura, que agora está em new_fieldvez do esperado old_field.

Portanto, essa mudança quebrou a ABI.

Se, no entanto, adicionarmos new_fielddepois old_field:

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

então o antigo assembly gerado ainda acessa o primeiro intda estrutura e o programa ainda funciona, porque mantivemos a ABI estável.

Aqui está uma versão totalmente automatizada deste exemplo no GitHub .

Outra maneira de manter essa ABI estável seria tratá-la mylib_mystructcomo uma estrutura opaca e acessar apenas seus campos por meio de auxiliares de método. Isso facilita a manutenção da ABI estável, mas acarreta uma sobrecarga de desempenho, pois faríamos mais chamadas de função.

API vs ABI

No exemplo anterior, é interessante notar que a adição do new_fieldanterior old_fieldquebrou apenas a ABI, mas não a API.

O que isso significa é que, se tivéssemos recompilado nosso main.cprograma contra a biblioteca, ele teria funcionado independentemente.

No entanto, também teríamos quebrado a API se tivéssemos alterado, por exemplo, a assinatura da função:

mylib_mystruct* mylib_init(int old_field, int new_field);

pois nesse caso, main.cpararia de compilar completamente.

API semântica vs API de programação

Também podemos classificar as alterações da API em um terceiro tipo: alterações semânticas.

A API semântica, geralmente é uma descrição da linguagem natural do que a API deve fazer, geralmente incluída na documentação da API.

Portanto, é possível quebrar a API semântica sem interromper a criação do programa.

Por exemplo, se tivéssemos modificado

myobject->old_field = old_field;

para:

myobject->old_field = old_field + 1;

então isso não teria quebrado nem a API de programação nem a ABI, mas main.ca API semântica seria interrompida.

Há duas maneiras de verificar programaticamente a API do contrato:

  • teste vários casos de canto. Fácil de fazer, mas você sempre pode perder uma.
  • verificação formal . Mais difícil de fazer, mas produz provas matemáticas de correção, unificando essencialmente a documentação e os testes de maneira "humana" / verificável por máquina! Contanto que não exista um bug em sua descrição formal, é claro ;-)

    Esse conceito está intimamente relacionado à formalização da própria matemática: /math/53969/what-does-formal-mean/3297537#3297537

Lista de tudo o que quebra as ABIs da biblioteca compartilhada C / C ++

TODO: encontre / crie a lista definitiva:

Exemplo de Java mínimo executável

O que é compatibilidade binária em Java?

Testado no Ubuntu 18.10, GCC 8.2.0.


3

A ABI precisa ser consistente entre o chamador e o chamado para garantir que a chamada seja bem-sucedida. Uso de pilha, uso de registro, pop de pilha de fim de rotina. Todas essas são as partes mais importantes da ABI.


3

Sumário

Existem várias interpretações e opiniões fortes da camada exata que define uma ABI (interface binária do aplicativo).

Na minha opinião, uma ABI é uma convenção subjetiva do que é considerado um dado / plataforma para uma API específica. A ABI é o "resto" de convenções que "não serão alteradas" para uma API específica ou que serão tratadas pelo ambiente de tempo de execução: executores, ferramentas, vinculadores, compiladores, jvm e SO.

Definindo uma interface : ABI, API

Se você deseja usar uma biblioteca como joda-time, deve declarar uma dependência joda-time-<major>.<minor>.<patch>.jar. A biblioteca segue as práticas recomendadas e usa o Semantic Versioning . Isso define a compatibilidade da API em três níveis:

  1. Patch - Você não precisa alterar todo o seu código. A biblioteca apenas corrige alguns erros.
  2. Menor - Você não precisa alterar seu código, pois as adições
  3. Principal - A interface (API) foi alterada e pode ser necessário alterar seu código.

Para você usar uma nova versão principal da mesma biblioteca, muitas outras convenções ainda devem ser respeitadas:

  • A linguagem binária usada para as bibliotecas (nos casos Java, a versão de destino da JVM que define o bytecode Java)
  • Convenções de chamada
  • Convenções da JVM
  • Convenções de vinculação
  • Convenções de tempo de execução Todos esses são definidos e gerenciados pelas ferramentas que usamos.

Exemplos

Estudo de caso Java

Por exemplo, Java padronizou todas essas convenções, não em uma ferramenta, mas em uma especificação formal da JVM. A especificação permitiu que outros fornecedores fornecessem um conjunto diferente de ferramentas que podem gerar bibliotecas compatíveis.

O Java fornece outros dois estudos de caso interessantes para a ABI: versões Scala e máquina virtual Dalvik .

Máquina virtual Dalvik quebrou a ABI

A VM do Dalvik precisa de um tipo de bytecode diferente do bytecode Java. As bibliotecas Dalvik são obtidas convertendo o bytecode Java (com a mesma API) para Dalvik. Dessa forma, você pode obter duas versões da mesma API: definidas pelo original joda-time-1.7.2.jar. Nós poderíamos me ligar joda-time-1.7.2.jare joda-time-1.7.2-dalvik.jar. Eles usam um ABI diferente para os Java vms padrão orientados a pilha: o Oracle, o IBM, o Java aberto ou qualquer outro; e o segundo ABI é o de Dalvik.

As versões sucessivas do Scala são incompatíveis

O Scala não tem compatibilidade binária entre versões secundárias do Scala: 2.X. Por esse motivo, a mesma API "io.reactivex" %% "rxscala"% "0.26.5" possui três versões (mais no futuro): para Scala 2.10, 2.11 e 2.12. O que mudou? Por enquanto não sei , mas os binários não são compatíveis. Provavelmente, as versões mais recentes adicionam coisas que tornam as bibliotecas inutilizáveis ​​nas máquinas virtuais antigas, provavelmente coisas relacionadas às convenções de vinculação / nomeação / parâmetro.

Os releases sucessivos Java são incompatíveis

O Java também tem problemas com os principais releases da JVM: 4,5,6,7,8,9. Eles oferecem apenas compatibilidade com versões anteriores. A Jvm9 sabe como executar o código compilado / direcionado ( -targetopção do javac ) para todas as outras versões, enquanto a JVM 4 não sabe como executar o código direcionado para a JVM 5. Tudo isso enquanto você tem uma biblioteca joda. Essa incompatibilidade voa abaixo do radar graças a diferentes soluções:

  1. Versão semântica: quando as bibliotecas têm como alvo uma JVM mais alta, elas geralmente alteram a versão principal.
  2. Use a JVM 4 como a ABI e você estará seguro.
  3. O Java 9 inclui uma especificação sobre como você pode incluir o bytecode para JVM direcionada específica na mesma biblioteca.

Por que comecei com a definição da API?

API e ABI são apenas convenções sobre como você define compatibilidade. As camadas inferiores são genéricas em relação a uma infinidade de semânticas de alto nível. É por isso que é fácil fazer algumas convenções. O primeiro tipo de convenções são sobre alinhamento de memória, codificação de bytes, convenções de chamada, codificações big e little endian, etc. Além disso, você obtém convenções executáveis ​​como as descritas, convenções de vinculação, código de byte intermediário , como o usado por Java ou LLVM IR usado pelo GCC. Terceiro, você obtém convenções sobre como encontrar bibliotecas, como carregá-las (consulte Java Classloaders). À medida que você avança cada vez mais nos conceitos, você tem novas convenções que considera um dado. É por isso que eles não chegaram ao versionamento semântico .versão. Poderíamos alterar o versionamento semântico com <major>-<minor>-<patch>-<platform/ABI>. Isto é o que é, na verdade, já está acontecendo: plataforma já é um rpm, dll, jar(JVM bytecode), war(JVM + servidor web), apk, 2.11(específico versão Scala) e assim por diante. Quando você diz APK, você já fala sobre uma parte específica da ABI da sua API.

A API pode ser portada para diferentes ABI

O nível superior de uma abstração (as fontes gravadas na API mais alta podem ser recompiladas / portadas para qualquer outra abstração de nível inferior.

Digamos que eu tenha algumas fontes para o rxscala. Se as ferramentas Scala forem alteradas, posso recompilá-las para isso. Se a JVM mudar, eu poderia ter conversões automáticas da máquina antiga para a nova sem me preocupar com os conceitos de alto nível. Embora a transferência seja difícil, ajudará qualquer outro cliente. Se um novo sistema operacional for criado usando um código de montador totalmente diferente, um tradutor poderá ser criado.

APIs portadas em vários idiomas

Existem APIs portadas em vários idiomas, como fluxos reativos . Em geral, eles definem mapeamentos para idiomas / plataformas específicos. Eu argumentaria que a API é a especificação principal formalmente definida em linguagem humana ou mesmo em uma linguagem de programação específica. Todos os outros "mapeamentos" são ABI em certo sentido, mais uma API do que a ABI usual. O mesmo está acontecendo com as interfaces REST.


1

Em resumo e em filosofia, apenas coisas desse tipo podem se dar bem, e a ABI pode ser vista como o tipo de material de software que funciona em conjunto.


1

Eu também estava tentando entender a resposta da ABI e JesperE foi muito útil.

De uma perspectiva muito simples, podemos tentar entender a ABI considerando a compatibilidade binária.

O wiki do KDE define uma biblioteca como compatível com binários "se um programa vinculado dinamicamente a uma versão anterior da biblioteca continuar sendo executado com versões mais recentes da biblioteca sem a necessidade de recompilar". Para obter mais informações sobre links dinâmicos, consulte Link estático vs link dinâmico

Agora, vamos tentar examinar apenas os aspectos mais básicos necessários para uma biblioteca ser compatibilidade binária (supondo que não haja alterações no código fonte da biblioteca):

  1. Arquitetura do mesmo conjunto de instruções compatível com versões anteriores (instruções do processador, estrutura do arquivo de registro, organização da pilha, tipos de acesso à memória, além de tamanhos, layout e alinhamento dos tipos de dados básicos que o processador pode acessar diretamente)
  2. As mesmas convenções de chamada
  3. Convenção de manipulação de mesmo nome (isso pode ser necessário se, por exemplo, um programa Fortran precisar chamar alguma função da biblioteca C ++).

Claro, existem muitos outros detalhes, mas isso é principalmente o que a ABI também cobre.

Mais especificamente, para responder à sua pergunta, pelo exposto, podemos deduzir:

Funcionalidade ABI: compatibilidade binária

entidades existentes: programa / bibliotecas / sistema operacional existente

consumidor: bibliotecas, SO

Espero que isto ajude!


1

Interface binária de aplicativo (ABI)

Funcionalidade:

  • Tradução do modelo do programador para o tipo de dados, tamanho, alinhamento, convenção de chamada do domínio do sistema subjacente, que controla como os argumentos das funções são transmitidos e os valores retornados recuperados; os números de chamada do sistema e como um aplicativo deve fazer chamadas do sistema para o sistema operacional; o esquema de manipulação de nomes dos compiladores de linguagem de alto nível, propagação de exceção e convenção de chamada entre compiladores na mesma plataforma, mas não exigem compatibilidade entre plataformas ...

Entidades existentes:

  • Blocos lógicos que participam diretamente da execução do programa: ALU, registradores de uso geral, registradores para mapeamento de E / S de memória / E / S, etc ...

consumidor:

  • Processadores de linguagem linker, assembler ...

Eles são necessários para quem precisa garantir que as cadeias de ferramentas de construção funcionem como um todo. Se você escreve um módulo em linguagem assembly, outro em Python, e em vez de seu próprio gerenciador de inicialização deseja usar um sistema operacional, os módulos "aplicativos" estão funcionando além dos limites "binários" e exigem o consentimento dessa "interface".

Manipulação de nome do C ++ porque pode ser necessário vincular arquivos de objeto de diferentes idiomas de alto nível no seu aplicativo. Considere usar a biblioteca padrão do GCC para fazer chamadas do sistema para o Windows criadas com o Visual C ++.

ELF é uma expectativa possível do vinculador de um arquivo de objeto para interpretação, embora a JVM possa ter alguma outra idéia.

Para um aplicativo da Windows RT Store, tente procurar pelo ARM ABI se você realmente deseja que algumas cadeias de ferramentas de compilação funcionem juntas.


1

O termo ABI é usado para se referir a dois conceitos distintos, mas relacionados.

Ao falar sobre compiladores, refere-se às regras usadas para converter de construções no nível de origem em construções binárias. Qual o tamanho dos tipos de dados? como funciona a pilha? como passo parâmetros para funções? quais registros devem ser salvos pelo chamador vs o chamado?

Ao falar sobre bibliotecas, refere-se à interface binária apresentada por uma biblioteca compilada. Essa interface é o resultado de vários fatores, incluindo o código fonte da biblioteca, as regras usadas pelo compilador e, em alguns casos, as definições obtidas de outras bibliotecas.

Alterações em uma biblioteca podem interromper a ABI sem interromper a API. Considere, por exemplo, uma biblioteca com uma interface semelhante.

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

e o programador de aplicativos escreve código como

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

O programador de aplicativos não se importa com o tamanho ou o layout do FOO, mas o binário do aplicativo acaba com um tamanho de foo codificado. Se o programador da biblioteca adicionar um campo extra a foo e alguém usar o novo binário da biblioteca com o binário antigo do aplicativo, a biblioteca poderá acessar a memória fora dos limites.

OTOH se o autor da biblioteca tiver projetado sua API como.

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

e o programador de aplicativos escreve código como

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

Então o binário do aplicativo não precisa saber nada sobre a estrutura do FOO, que pode estar oculta dentro da biblioteca. O preço que você paga por isso é que as operações de heap estão envolvidas.


0

ABI- Application Binary Interfacetrata-se de uma comunicação de código de máquina em tempo de execução entre duas partes binárias do programa, como - aplicativo, biblioteca, SO ... ABIdescreve como os objetos são salvos na memória e como as funções são chamadas ( calling convention)

Um bom exemplo de API e ABI é o ecossistema iOS com linguagem Swift .

  • Application- Quando você cria um aplicativo usando idiomas diferentes. Por exemplo, você pode criar aplicativos usando Swifte Objective-C[Mixing Swift and Objective-C]

  • Application - OS- tempo de execução - Swift runtimee standard librariessão partes do sistema operacional e não devem ser incluídos em cada pacote (por exemplo, aplicativo, estrutura). É o mesmo que usa o Objective-C

  • Library- Module Stabilitycase - tempo de compilação - você poderá importar uma estrutura que foi criada com outra versão do compilador do Swift. Isso significa que é seguro criar um binário de código fechado (pré-compilação) que será consumido por uma versão diferente do compilador ( .swiftinterfaceusada com .swiftmodule) e você não obterá

    Module compiled with _ cannot be imported by the _ compiler
    
  • Library- Library Evolutioncaso

    1. Tempo de compilação - se uma dependência foi alterada, um cliente não precisa ser recompilado.
    2. Tempo de execução - uma biblioteca do sistema ou uma estrutura dinâmica pode ser trocada a quente por uma nova.

[API vs ABI]

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.