664

我从来没有清楚地理解 ABI 是什么。请不要将我指向维基百科的文章。如果我能理解,我就不会在这里发这么长的帖子了。

这是我对不同界面的看法:

电视遥控器是用户和电视之间的接口。它是一个现有的实体,但本身无用(不提供任何功能)。遥控器上每个按钮的所有功能都在电视机中实现。

接口:functionality它是该功能之间的“现有实体”层 consumer。接口本身不做任何事情。它只是调用背后的功能。

现在取决于用户是谁,有不同类型的界面。

命令行界面 (CLI)命令是现有实体,消费者是用户,功能位于后面。

functionality:我的软件功能解决了我们描述此界面的某些目的。

existing entities:命令

consumer:用户

图形用户界面 (GUI)窗口、按钮等是现有的实体,而消费者又是用户,功能落后。

functionality:我的软件功能解决了我们描述这个接口的一些问题。

existing entities:窗口、按钮等。

consumer:用户

应用程序编程接口(API)函数(或者更准确地说)接口(在基于接口的编程中)是现有的实体,这里的消费者是另一个程序而不是用户,功能再次位于这一层后面。

functionality:我的软件功能解决了我们描述这个接口的一些问题。

existing entities:函数,接口(函数数组)。

consumer:另一个程序/应用程序。

应用程序二进制接口 (ABI)这是我的问题开始的地方。

functionality:???

existing entities:???

consumer:???

  • 我用不同的语言编写了软件并提供了不同类型的接口(CLI、GUI 和 API),但我不确定我是否曾经提供过任何 ABI。

维基百科说:

ABI 涵盖详细信息,例如

  • 数据类型、大小和对齐方式;
  • 调用约定,它控制函数的参数如何传递和返回值的检索;
  • 系统调用号以及应用程序应如何对操作系统进行系统调用;

其他 ABI 标准化细节,例如

  • C++ 名称修改,
  • 异常传播,以及
  • 同一平台上编译器之间的调用约定,但不需要跨平台兼容性。
  • 谁需要这些细节?请不要说操作系统。我知道汇编编程。我知道链接和加载是如何工作的。我确切地知道里面发生了什么。

  • 为什么 C++ 名称修饰会出现?我以为我们在二进制级别上讨论。为什么语言会出现?

无论如何,我已经下载了[PDF] System V Application Binary Interface Edition 4.1 (1997-03-18)来看看它到底包含什么。好吧,大部分都没有任何意义。

  • 为什么它包含两章(第 4 章和第 5 章)来描述ELF文件格式?事实上,这是该规范仅有的两个重要章节。其余章节是“特定于处理器的”。无论如何,我认为这是一个完全不同的话题。请不要说ELF文件格式规范就是ABI。根据定义,它不符合成为接口的条件。

  • 我知道,既然我们在这么低的层面上谈论它,它必须非常具体。但我不确定它是如何具体“指令集架构(ISA)”的?

  • 在哪里可以找到 Microsoft Windows 的 ABI?

所以,这些是困扰我的主要问题。

4

17 回答 17

707

理解“ABI”的一种简单方法是将其与“API”进行比较。

您已经熟悉 API 的概念。如果您想使用某个库或您的操作系统的功能,您将针对 API 进行编程。API 由数据类型/结构、常量、函数等组成,您可以在代码中使用它们来访问该外部组件的功能。

ABI 非常相似。将其视为 API 的编译版本(或机器语言级别的 API)。编写源代码时,您可以通过 API 访问库。编译代码后,您的应用程序将通过 ABI 访问库中的二进制数据。ABI 定义了编译后的应用程序将用于访问外部库的结构和方法(就像 API 所做的那样),只是在较低级别上。您的 API 定义了将参数传递给函数的顺序。您的 ABI 定义了如何实现的机制这些参数被传递(寄存器、堆栈等)。您的 API 定义了哪些函数是您的库的一部分。您的 ABI 定义了您的代码如何存储在库文件中,以便任何使用您的库的程序都可以找到所需的函数并执行它。

当涉及到使用外部库的应用程序时,ABI 很重要。库中充满了代码和其他资源,但您的程序必须知道如何在库文件中找到它需要的内容。您的 ABI 定义了库的内容如何存储在文件中,您的程序使用 ABI 搜索文件并找到它需要的内容。如果您系统中的所有内容都符合相同的 ABI,那么任何程序都可以使用任何库文件,无论它们是谁创建的。Linux 和 Windows 使用不同的 ABI,因此 Windows 程序不知道如何访问为 Linux 编译的库。

有时,ABI 更改是不可避免的。发生这种情况时,除非重新编译以使用新版本的库,否则使用该库的任何程序都将无法运行。如果 ABI 更改但 API 没有更改,则新旧库版本有时称为“源兼容”。这意味着虽然为一个库版本编译的程序不能与另一个库版本一起使用,但如果重新编译,为一个库版本编写的源代码将适用于另一个库版本。

出于这个原因,开发人员倾向于尝试保持他们的 ABI 稳定(以尽量减少中断)。保持 ABI 稳定意味着不更改函数接口(返回类型和数量、类型和参数顺序)、数据类型或数据结构的定义、定义的常量等。可以添加新的函数和数据类型,但必须保留现有的函数和数据类型相同。例如,如果您的库使用 32 位整数来指示函数的偏移量并且您切换到 64 位整数,那么使用该库的已编译代码将无法正确访问该字段(或任何跟随它的字段) . 访问数据结构成员会在编译期间转换为内存地址和偏移量,如果数据结构发生变化,

ABI 不一定是您将明确提供的东西,除非您正在进行非常低级的系统设计工作。它也不是特定于语言的,因为(例如)C 应用程序和 Pascal 应用程序在编译后可以使用相同的 ABI。

编辑:关于您对 SysV ABI 文档中有关 ELF 文件格式的章节的问题:包含此信息的原因是因为 ELF 格式定义了操作系统和应用程序之间的接口。当您告诉操作系统运行程序时,它希望程序以某种方式格式化,并且(例如)希望二进制文件的第一部分是一个 ELF 标头,其中包含特定内存偏移处的某些信息。这就是应用程序如何将有关自身的重要信息传达给操作系统的方式。如果您以非 ELF 二进制格式(例如 a.out 或 PE)构建程序,则需要 ELF 格式应用程序的操作系统将无法解释二进制文件或运行应用程序。

IIRC,Windows 当前使用Portable Executable(或 PE)格式。该维基百科页面的“外部链接”部分中有链接,其中包含有关 PE 格式的更多信息。

此外,关于您关于 C++ 名称修饰的注释:在库文件中定位函数时,通常按名称查找该函数。C++ 允许您重载函数名称,因此仅名称不足以识别函数。C++ 编译器有自己的内部处理方式,称为名称修饰。ABI 可以定义对函数名称进行编码的标准方式,以便使用不同语言或编译器构建的程序可以找到所需的内容。当您extern "c"在 C++ 程序中使用时,您是在指示编译器使用一种标准化的方式来记录其他软件可以理解的名称。

于 2010-03-16T18:04:28.433 回答
166

如果您了解汇编以及操作系统级别的工作原理,那么您就符合某个 ABI。ABI 管理诸如如何传递参数、放置返回值的位置。对于许多平台,只有一个 ABI 可供选择,在这些情况下,ABI 只是“事情的工作方式”。

但是,ABI 也管理诸如类/对象在 C++ 中的布局方式。如果您希望能够跨模块边界传递对象引用,或者如果您想混合使用不同编译器编译的代码,这是必要的。

此外,如果您有一个可以执行 32 位二进制文​​件的 64 位操作系统,那么您将有不同的 ABI 用于 32 位和 64 位代码。

通常,您链接到同一可执行文件的任何代码都必须符合相同的 ABI。如果要使用不同的 ABI 在代码之间进行通信,则必须使用某种形式的 RPC 或序列化协议。

我认为您过于努力地将不同类型的界面挤入一组固定的特征中。例如,一个接口不一定要分成消费者和生产者。接口只是两个实体交互的约定。

ABI 可以(部分)与 ISA 无关。某些方面(例如调用约定)依赖于 ISA,而其他方面(例如 C++ 类布局)则不依赖。

一个定义良好的 ABI 对于编写编译器的人来说非常重要。如果没有明确定义的 ABI,就不可能生成可互操作的代码。

编辑:一些需要澄清的注释:

  • ABI 中的“二进制”不排除使用字符串或文本。如果要链接导出 C++ 类的 DLL,则必须在其中的某处对方法和类型签名进行编码。这就是 C++ 名称修饰的用武之地。
  • 您从未提供 ABI 的原因是绝大多数程序员永远不会这样做。ABI 由设计平台(即操作系统)的同一个人提供,很少有程序员有幸设计出广泛使用的 ABI。
于 2010-01-31T09:50:47.260 回答
85

如果—— _

  • 你的程序没有功能,而且——
  • 你的程序是一个单独运行的可执行文件(即嵌入式系统),它实际上是唯一运行的东西,它不需要与其他任何东西对话。

过于简单的总结:

API: “这是你可以调用的所有函数。”

ABI: “这就是调用函数的方式。”

ABI 是编译器和链接器遵守的一组规则,以便编译您的程序,以便正常工作。ABI 涵盖多个主题:

  • 可以说,ABI 最大和最重要的部分是过程调用标准,有时也称为“调用约定”。调用约定标准化了如何将“函数”转换为汇编代码。
  • ABI 还规定了库中公开函数的名称应该如何表示,以便其他代码可以调用这些库并知道应该传递哪些参数。这称为“名称修改”。
  • ABI 还规定了可以使用的数据类型、它们必须如何对齐以及其他低级细节。

深入了解调用约定,我认为它是 ABI 的核心:

机器本身没有“功能”的概念。当您用 c 等高级语言编写函数时,编译器会生成一行汇编代码,例如_MyFunction1:. 这是一个标签,它最终会被汇编器解析成一个地址。这个标签在汇编代码中标记了你的“函数”的“开始”。在高级代码中,当您“调用”该函数时,您真正在做的是使 CPU跳转到该标签的地址并继续在那里执行。

为了准备跳转,编译器必须做一些重要的事情。调用约定就像一个清单,编译器遵循它来完成所有这些工作:

  • 首先,编译器会插入一点汇编代码来保存当前地址,这样当你的“函数”完成后,CPU 就可以跳回正确的地方继续执行。
  • 接下来,编译器生成汇编代码来传递参数。
    • 一些调用约定规定参数应该放在堆栈上(当然以特定的顺序)。
    • 其他约定规定应将参数放在特定的寄存器中(当然取决于它们的数据类型)。
    • 还有其他约定规定应使用堆栈和寄存器的特定组合。
  • 当然,如果之前在这些寄存器中有什么重要的东西,这些值现在会被覆盖并永远丢失,因此一些调用约定可能会要求编译器在将参数放入其中之前保存其中的一些寄存器。
  • 现在编译器插入一条跳转指令,告诉 CPU 转到它之前创建的标签 ( _MyFunction1:)。此时,您可以将 CPU 视为“在”您的“功能”。
  • 在函数结束时,编译器会放入一些汇编代码,使 CPU 将返回值写入正确的位置。调用约定将规定返回值是否应放入特定寄存器(取决于其类型)或堆栈中。
  • 现在是清理的时候了。调用约定将规定编译器放置清理汇编代码的位置。
    • 一些约定说调用者必须清理堆栈。这意味着在“函数”完成并且 CPU 跳回到之前的位置之后,下一个要执行的代码应该是一些非常具体的清理代码。
    • 其他约定说清理代码的某些特定部分应该在跳回之前的“函数”末尾。

有许多不同的 ABI / 调用约定。一些主要的有:

  • 对于 x86 或 x86-64 CPU(32 位环境):
    • CDECL
    • 标准呼叫
    • 快速呼叫
    • 向量呼叫
    • 这个电话
  • 对于 x86-64 CPU(64 位环境):
    • 系统V
    • MSNATIVE
    • 向量呼叫
  • 对于 ARM CPU(32 位)
    • AAPCS
  • 对于 ARM CPU(64 位)
    • AAPCS64

是一个很棒的页面,它实际上显示了为不同 ABI 编译时生成的程序集的差异。

另一件要提的是, ABI不仅与程序的可执行模块相关。链接器也使用它来确保您的程序正确调用库函数。您的计算机上运行着多个共享库,并且只要您的编译器知道它们各自使用什么 ABI,它就可以正确地从它们调用函数而不会破坏堆栈。

您的编译器了解如何调用库函数非常重要。在托管平台(即操作系统加载程序的平台)上,如果不进行内核调用,您的程序甚至无法闪烁。

于 2016-12-30T20:40:46.243 回答
21

应用程序二进制接口 (ABI) 类似于 API,但调用者无法在源代码级别访问该函数。只有二​​进制表示是可访问/可用的。

ABI 可以在处理器架构级别或操作系统级别定义。ABI 是编译器的代码生成器阶段要遵循的标准。该标准由操作系统或处理器固定。

功能:定义机制/标准以使函数调用独立于实现语言或特定的编译器/链接器/工具链。提供允许 JNI 的机制,或 Python-C 接口等。

现有实体:机器码形式的函数。

消费者:另一个函数(包括另一种语言的函数,由另一个编译器编译,或由另一个链接器链接)。

于 2010-03-23T06:07:56.487 回答
18

Linux 共享库最小可运行 ABI 示例

在共享库的上下文中,“拥有稳定的 ABI”最重要的含义是在库更改后您不需要重新编译程序。

例如:

  • 如果您正在销售共享库,则可以为用户省去为每个新版本重新编译依赖于您的库的所有内容的烦恼

  • 如果您正在销售依赖于用户发行版中存在的共享库的封闭源代码程序,如果您确定 ABI 在目标操作系统的某些版本中是稳定的,您可以发布和测试更少的预构建。

    这对于 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

编译并运行良好:

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

现在,假设对于库的 v2,我们要向mylib_mystruct被调用添加一个新字段new_field

如果我们之前添加了该字段,old_field如下所示:

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

并重建了库但没有重建main.out,那么断言失败!

这是因为该行:

myobject->old_field == 1

已生成试图访问第一个int结构的程序集,现在new_field不是预期的old_field.

因此,此更改破坏了 ABI。

但是,如果我们在new_field之后添加old_field

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

然后旧生成的程序集仍然访问第一个int结构,并且程序仍然工作,因为我们保持 ABI 稳定。

这是GitHub 上此示例的全自动版本

保持此 ABI 稳定的另一种方法是将其mylib_mystruct视为不透明结构,并且仅通过方法助手访问其字段。这使得保持 ABI 稳定变得更容易,但会产生性能开销,因为我们会进行更多的函数调用。

API 与 ABI

在前面的示例中,有趣的是,添加new_fieldbeforeold_field只会破坏 ABI,但不会破坏 API。

这意味着,如果我们main.c针对库重新编译我们的程序,无论如何它都会工作。

但是,如果我们更改了函数签名,我们也会破坏 API:

mylib_mystruct* mylib_init(int old_field, int new_field);

因为在这种情况下,main.c将完全停止编译。

语义 API 与编程 API

我们还可以将 API 更改分类为第三种类型:语义更改。

语义 API 通常是对 API 应该做什么的自然语言描述,通常包含在 API 文档中。

因此,可以在不破坏程序构建本身的情况下破坏语义 API。

例如,如果我们修改了

myobject->old_field = old_field;

到:

myobject->old_field = old_field + 1;

那么这既不会破坏编程 API,也不会破坏 ABI,但main.c语义 API 会破坏。

有两种以编程方式检查合约 API 的方法:

破坏 C/C++ 共享库 ABI 的所有内容的列表

TODO:查找/创建最终列表:

Java 最小可运行示例

Java 中的二进制兼容性是什么?

在 Ubuntu 18.10、GCC 8.2.0 中测试。

于 2019-03-03T10:13:20.500 回答
12

功能:一组影响编译器、汇编编写器、链接器和操作系统的契约。合约规定了函数的布局方式、参数传递的位置、参数的传递方式、函数返回的工作方式。这些通常特定于(处理器架构,操作系统)元组。

现有实体:参数布局、函数语义、寄存器分配。例如,ARM 架构有许多 ABI(APCS、EABI、GNU-EABI,更不用说一堆历史案例)——使用混合 ABI 将导致您的代码在跨边界调用时根本无法工作。

消费者:编译器、汇编编写者、操作系统、CPU 特定架构。

谁需要这些细节?编译器、汇编编写器、执行代码生成(或对齐要求)的链接器、操作系统(中断处理、系统调用接口)。如果您进行汇编编程,那么您就符合 ABI!

C++ 名称修饰是一种特殊情况 - 它是一个以链接器和动态链接器为中心的问题 - 如果名称修饰未标准化,则动态链接将不起作用。此后,C++ ABI 就被称为 C++ ABI。这不是链接器级别的问题,而是代码生成问题。一旦你有了一个 C++ 二进制文件,就不可能在不从源代码重新编译的情况下使其与另一个 C++ ABI(名称修改、异常处理)兼容。

ELF 是一种供加载器和动态链接器使用的文件格式。ELF 是二进制代码和数据的容器格式,因此指定了一段代码的 ABI。我不会认为 ELF 是严格意义上的 ABI,因为 PE 可执行文件不是 ABI。

所有 ABI 都是特定于指令集的。ARM ABI 在 MSP430 或 x86_64 处理器上没有意义。

Windows 有几个 ABI - 例如,fastcall 和 stdcall 是两个常用的 ABI。系统调用 ABI 再次不同。

于 2010-03-23T06:26:25.150 回答
10

让我至少回答你问题的一部分。举例说明 Linux ABI 如何影响系统调用,以及它为何有用。

系统调用是用户空间程序向内核空间请求某些东西的一种方式。它通过将调用的数字代码和参数放在某个寄存器中并触发中断来工作。然后内核空间发生切换,内核查找数字代码和参数,处理请求,将结果放回寄存器并触发切换回用户空间。例如,当应用程序想要分配内存或打开文件(系统调用“brk”和“open”)时,这是需要的。

现在系统调用有短名称“brk”等和相应的操作码,这些在系统特定的头文件中定义。只要这些操作码保持不变,您就可以使用不同的更新内核运行相同的已编译用户态程序,而无需重新编译。所以你有一个预编译二进制文件使用的接口,因此是 ABI。

于 2010-01-31T09:50:18.747 回答
8

概括

对于定义 ABI(应用程序二进制接口)的确切层,有各种解释和强烈的意见。

在我看来,ABI 是对特定 API 的给定/平台的主观约定。ABI 是“不会改变”特定 API 或将由运行时环境处理的约定的“其余部分”:执行程序、工具、链接器、编译器、jvm 和操作系统。

定义接口:ABI、API

如果你想使用像 joda-time 这样的库,你必须声明对joda-time-<major>.<minor>.<patch>.jar. 该库遵循最佳实践并使用语义版本控制。这定义了三个级别的 API 兼容性:

  1. 补丁 - 您根本不需要更改您的代码。该库只是修复了一些错误。
  2. 次要 - 您不需要更改代码,因为添加了一些东西(尊重开放封闭原则)
  3. 主要 - 接口 (API) 已更改,您可能需要更改代码。

为了让您使用同一库的新主要版本,许多其他约定仍需遵守:

  • 用于库的二进制语言(在 Java 情况下是定义 Java 字节码的 JVM 目标版本)
  • 调用约定
  • JVM 约定
  • 链接约定
  • 运行时约定所有这些都是由我们使用的工具定义和管理的。

例子

Java 案例研究

例如,Java 标准化了所有这些约定,而不是在工具中,而是在正式的 JVM 规范中。该规范允许其他供应商提供一组不同的工具来输出兼容的库。

Java 为 ABI 提供了另外两个有趣的案例研究:Scala 版本和Dalvik虚拟机。

Dalvik 虚拟机破解 ABI

Dalvik VM 需要与 Java 字节码不同类型的字节码。Dalvik 库是通过为 Dalvik 转换 Java 字节码(具有相同的 API)而获得的。通过这种方式,您可以获得相同 API 的两个版本:由原始joda-time-1.7.2.jar. 我们可以称它为joda-time-1.7.2.jarand joda-time-1.7.2-dalvik.jar。他们为面向堆栈的标准 Java 虚拟机使用不同的 ABI:Oracle 的、IBM 的、开放 Java 或任何其他;第二个 ABI 是围绕 Dalvik 的。

Scala 后续版本不兼容

Scala 在次要 Scala 版本之间没有二进制兼容性: 2.X 。出于这个原因,相同的 API "io.reactivex" %% "rxscala" % "0.26.5" 有三个版本(将来会更多):Scala 2.10、2.11 和 2.12。有什么改变?我现在不知道,但二进制文件不兼容。可能最新版本添加了使库在旧虚拟机上无法使用的内容,可能与链接/命名/参数约定有关。

Java 后续版本不兼容

Java 在 JVM 的主要版本中也存在问题:4、5、6、7、8、9。它们仅提供向后兼容性。Jvm9 知道如何为所有其他版本运行编译/目标代码(javac 的-target选项),而 JVM 4 不知道如何运行针对 JVM 5 的代码。所有这些都在您拥有一个 joda-library 时。由于不同的解决方案,这种不兼容性在雷达上飞速发展:

  1. 语义版本控制:当库以更高的 JVM 为目标时,它们通常会更改主要版本。
  2. 使用 JVM 4 作为 ABI,您是安全的。
  3. Java 9 添加了关于如何在同一库中包含特定目标 JVM 的字节码的规范。

为什么我从 API 定义开始?

API 和 ABI 只是关于如何定义兼容性的约定。就过多的高级语义而言,较低层是通用的。这就是为什么很容易做出一些约定的原因。第一种约定是关于内存对齐、字节编码、调用约定、大端和小端编码等。在它们之上,您可以获得类似其他描述的可执行约定、链接约定、中间字节码(如 Java 或GCC 使用的 LLVM IR。第三,您将获得关于如何查找库、如何加载它们的约定(请参阅 Java 类加载器)。随着你在概念上越来越高,你会有新的约定,你认为这些约定是给定的。这就是他们没有进入语义版本控制的原因。版本。我们可以使用<major>-<minor>-<patch>-<platform/ABI>. 这实际上已经发生了:平台已经是rpm, dll, jar(JVM 字节码), war(jvm+web server), apk, 2.11(特定的 Scala 版本) 等等。当您说 APK 时,您已经在谈论 API 的特定 ABI 部分。

API可以移植到不同的ABI

抽象的顶层(针对最高 API 编写的源代码可以重新编译/移植到任何其他较低级别的抽象。

假设我有一些 rxscala 的资源。如果更改了 Scala 工具,我可以重新编译它们。如果 JVM 发生变化,我可以自动从旧机器转换到新机器,而无需考虑高级概念。虽然移植可能很困难,但对任何其他客户都有帮助。如果使用完全不同的汇编代码创建一个新的操作系统,则可以创建一个翻译器。

跨语言移植的 API

有些 API 以多种语言移植,例如反应式流。一般来说,它们定义到特定语言/平台的映射。我认为 API 是用人类语言或什至特定编程语言正式定义的主要规范。从某种意义上说,所有其他“映射”都是 ABI,或者比通常的 ABI 更多的 API。REST 接口也是如此。

于 2017-04-22T15:39:50.920 回答
5

为了调用共享库中的代码,或者在编译单元之间调用代码,目标文件需要包含调用的标签。C++ 修改方法标签的名称以强制数据隐藏并允许重载方法。这就是为什么您不能混合来自不同 C++ 编译器的文件,除非它们明确支持相同的 ABI。

于 2010-02-01T03:48:23.553 回答
5

区分 ABI 和 API 的最佳方法是了解其用途和用途:

对于 x86-64,通常有一个 ABI(对于 x86 32 位,还有另一组):

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 紧随其后,有一些细微的变化。Windows x64 有自己的 ABI:

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

了解 ABI 并假设其他编译器也遵循它,那么二进制文件理论上知道如何相互调用(尤其是库 API)并通过堆栈或寄存器等传递参数。或者在调用函数等时将更改哪些寄存器. 从本质上讲,这些知识将帮助软件相互集成。知道寄存器/堆栈布局的顺序,我可以轻松地将用程序集编写的不同软件拼凑在一起,而不会出现太大问题。

但 API 不同:

它是一种高级函数名称,定义了参数,因此如果使用这些 API 构建不同的软件部分,则可以相互调用。但必须遵守 SAME ABI 的附加要求。

例如,Windows 曾经是 POSIX API 兼容的:

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

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

Linux 也符合 POSIX。但是二进制文件不能只是移动并立即运行。但是因为他们在 POSIX 兼容的 API 中使用了相同的 NAMES,所以您可以在 C 中使用相同的软件,在不同的操作系统中重新编译它,然后立即让它运行。

API 旨在简化软件的集成 - 预编译阶段。因此,编译后的软件可能看起来完全不同——如果 ABI 不同的话。

ABI 旨在定义二进制/汇编级别的软件的精确集成。

于 2014-02-25T04:01:05.180 回答
4

ABI 一词用于指代两个不同但相关的概念。

在谈论编译器时,它指的是用于从源级构造转换为二进制构造的规则。数据类型有多大?堆栈是如何工作的?如何将参数传递给函数?调用者和被调用者应该保存哪些寄存器?

在谈论库时,它指的是编译库提供的二进制接口。这个接口是许多因素的结果,包括库的源代码、编译器使用的规则以及在某些情况下从其他库中提取的定义。

对库的更改可以在不破坏 API 的情况下破坏 ABI。例如,考虑一个具有类似接口的库。

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

并且应用程序程序员编写类似的代码

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

应用程序程序员并不关心 FOO 的大小或布局,但应用程序二进制文件最终会得到硬编码的 foo 大小。如果库程序员向 foo 添加了一个额外的字段,并且有人将新的库二进制文件与旧的应用程序二进制文件一起使用,那么该库可能会越界访问内存。

OTOH,如果图书馆作者已经设计了他们的 API。

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

并且应用程序程序员编写类似的代码

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

然后应用程序二进制文件不需要知道任何关于 FOO 的结构,这些都可以隐藏在库中。但是,您为此付出的代价是涉及堆操作。

于 2018-05-22T13:50:18.757 回答
3

ABI 需要在调用者和被调用者之间保持一致,以确保调用成功。堆栈使用、寄存器使用、程序结束堆栈弹出。所有这些都是 ABI 中最重要的部分。

于 2010-01-31T09:42:07.377 回答
2

应用程序二进制接口 (ABI)

功能:

  • 从程序员的模型到底层系统的域数据类型、大小、对齐方式、调用约定的转换,它控制函数的参数传递和返回值的检索方式;系统调用号以及应用程序应如何对操作系统进行系统调用;高级语言编译器的名称修改方案、异常传播和同一平台上编译器之间的调用约定,但不需要跨平台兼容性......

现有实体:

  • 直接参与程序执行的逻辑块:ALU、通用寄存器、内存寄存器/ I/O的I/O映射等...

消费者:

  • 语言处理器链接器、汇编器...

任何必须确保构建工具链作为一个整体工作的人都需要这些。如果你用汇编语言编写一个模块,用 Python 编写另一个模块,而不是你自己的引导加载程序想要使用操作系统,那么你的“应用程序”模块正在跨越“二进制”边界工作,并且需要这种“接口”的协议。

C++ 名称修改,因为可能需要在您的应用程序中链接来自不同高级语言的目标文件。考虑使用 GCC 标准库对使用 Visual C++ 构建的 Windows 进行系统调用。

ELF 是从目标文件解释链接器的一种可能期望,尽管 JVM 可能有其他想法。

对于 Windows RT Store 应用程序,如果您真的希望使某些构建工具链协同工作,请尝试搜索 ARM ABI。

于 2013-04-10T05:47:55.127 回答
2

应用程序二进制接口(ABI)

ABI-Application Binary Interface是关于运行时两个二进制部分ABI之间的机器代码通信,例如 - 应用程序、库、操作系统calling convention......

API 和 ABI 的一个很好的例子是带有 Swift 语言的 iOS 生态系统

  • Application layer- 当您使用不同的语言创建应用程序时。例如,您可以使用 [Mixing Swift 和 Objective-C] 创建Swift应用Objective-C程序

  • Application - OS layer- 运行时 -是操作系统的一部分Swift runtimestandard libraries它们不应包含在每个捆绑包中(例如应用程序、框架)。和 Objective-C 使用的一样

  • Library layer-Module Stability案例 -编译时间- 您将能够导入使用另一个版本的 Swift 编译器构建的框架。这意味着创建一个封闭源代码(预构建)二进制文件是安全的,它将被不同版本的编译器.swiftinterface使用(与.swiftmodule[About]一起使用)并且你不会得到

    Module compiled with _ cannot be imported by the _ compiler
    //or
    Compiled module was created by a newer version of the compiler
    
  • Library layer-Library Evolution案例

    1. 编译时间 - 如果依赖项发生了更改,则不必重新编译客户端。
    2. 运行时 - 系统库或动态框架可以被新的热交换。

[API vs ABI]
[Swift 模块和库稳定性]

于 2019-12-10T15:52:02.540 回答
1

简而言之,在哲学上,只有一种东西才能相处得很好,而 ABI 可以被看作是软件东西协同工作的那种。

于 2010-02-26T15:50:29.423 回答
1

我也试图了解 ABI,JesperE 的回答非常有帮助。

从一个非常简单的角度来看,我们可以尝试通过考虑二进制兼容性来理解 ABI。

KDE wiki 将库定义为二进制兼容的“如果动态链接到该库的旧版本的程序继续使用该库的较新版本运行而无需重新编译。” 有关动态链接的更多信息,请参阅静态链接与动态链接

现在,让我们试着看看一个库需要二进制兼容性的最基本方面(假设库没有源代码更改):

  1. 相同/向后兼容的指令集架构(处理器指令、寄存器文件结构、堆栈组织、内存访问类型,以及处理器可以直接访问的基本数据类型的大小、布局和对齐方式)
  2. 相同的调用约定
  3. 同名修改约定(如果说 Fortran 程序需要调用某些 C++ 库函数,则可能需要这样做)。

当然,还有许多其他细节,但这主要是 ABI 还涵盖的内容。

更具体地回答你的问题,从上面我们可以推断:

ABI 功能:二进制兼容性

现有实体:现有程序/库/OS

消费者:图书馆、操作系统

希望这可以帮助!

于 2017-06-21T17:08:05.247 回答
1

A. 简单地说,ABI 与 API 的共同点是它是一个接口。可重用程序公开了一个稳定的接口 (API),可用于在另一个程序中重用该程序。

B. 但是,ABI 是为某些特定语言的某些特定处理器平台发布的接口。所有希望针对同一语言的平台的编译器供应商都必须确保不仅以可重定位目标代码形式的编译代码符合能够相互链接和交叉链接的接口,而且可执行文件也符合它完全可以在平台上运行。因此,ABI 是比典型的函数 API 更广泛的规范/标准集。它可能包括一些由编译器强制语言用户使用的 API 对象。编译器供应商必须在其发行版中包含对相同的支持。不用说,平台供应商是为其平台发布 ABI 的合法机构。

C.平台供应商对 ABI 的定义是:

“1. 可执行文件必须遵守的规范才能在特定的执行环境中执行。例如,用于 Arm 架构的 Linux ABI。

  1. 独立生成的可重定位文件必须符合规范的一个特定方面,以便静态链接和可执行。例如,用于 Arm 架构的 C++ ABI、用于 Arm 架构的运行时 ABI、用于 Arm 架构的 C 库 ABI。”

D、例如。一个联盟也发布了基于 Itanium 架构的 C++ 通用 ABI。平台供应商自己的 C++ ABI 在多大程度上符合它完全取决于平台供应商。

E. 作为另一个例子。用于 Arm 架构的 C++ ABI 在这里

F. 话虽如此,在底层,处理器架构的 ABI 将确保一个可重用程序和另一个重用它的程序之间的 API 适用于该处理器架构。

G. 这将我们带到面向服务的组件(例如基于 SOAP 的 Web 服务)。它们也需要在基于 SOAP 的 Web 服务和客户端程序(可以是应用程序、前端或其他 Web 服务)之间存在 API,以便客户端程序重用 Web 服务。API 是根据标准化协议进行描述的类似于 WSDL(接口描述)和 SOAP(消息格式),并且是语言中立和平台中立的。它不针对任何特定的处理器平台,因此它不像 ABI 那样“二进制”。在任何一种平台类型上并以任何语言编写的客户端程序都可以远程重用以任何其他语言编写并托管在完全不同的处理器平台上的 Web 服务。这是因为 WSDL 和 SOAP 都是基于文本 (XML) 的协议。对于 RESTful Web 服务,

于 2021-04-11T20:42:19.917 回答