关于api:什么是应用程序二进制接口(ABI)?

What is an application binary interface (ABI)?

我从来都不清楚ABI是什么。请不要给我指维基百科的文章。如果我能理解的话,我不会在这里发表这么长的文章。

这是我对不同接口的看法:

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

Interface: It is an"existing entity" layer between the
functionality and consumer of that functionality. An interface by itself
doesn't do anything. It just invokes the functionality lying behind.

Now depending on who the user is there are different type of interfaces.

Command Line Interface (CLI) commands are the existing entities,
the consumer is the user and functionality lies behind.

functionality: my software functionality which solves some
purpose to which we are describing this interface.

existing entities: commands

consumer: user

Graphical User Interface(GUI) window, buttons, etc. are the existing
entities, and again the consumer is the user and functionality lies behind.

functionality: my software functionality which solves some problem to which we are describing this interface.

existing entities: window, buttons etc..

consumer: user

Application Programming Interface(API) functions (or to be
more correct) interfaces (in interfaced based programming) are the
existing entities, consumer here is another program not a user, and again
functionality lies behind this layer.

functionality: my software functionality which solves some
problem to which we are describing this interface.

existing entities: functions, Interfaces (array of functions).

consumer: another program/application.

Application Binary Interface (ABI) Here is where my problem starts.

functionality: ???

existing entities: ???

consumer: ???

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

维基百科说:

ABIs cover details such as

  • data type, size, and 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;

Other ABIs standardize details such as

  • the C++ name mangling,
  • exception propagation, and
  • calling convention between compilers on the same platform, but do
    not require cross-platform compatibility.
  • 谁需要这些细节?请不要说操作系统。我知道汇编程序。我知道链接和加载是如何工作的。我很清楚里面发生了什么。

  • 为什么C++名字出现了?我以为我们是在讨论二进制的问题。为什么会有语言出现?

无论如何,我已经下载了[pdf]SystemV应用程序二进制接口版本4.1(1997-03-18)来查看它到底包含什么。嗯,大部分都没有意义。

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

  • 我知道,既然我们的谈话水平如此之低,一定非常具体。但我不确定它是如何"指令集体系结构(ISA)"特定的?

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

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


理解"abi"的一个简单方法是将其与"api"进行比较。好的。

您已经熟悉了API的概念。如果您想使用一些库或操作系统的特性,比如说,您将使用一个API。API由数据类型/结构、常量、函数等组成,您可以在代码中使用这些数据类型/结构来访问外部组件的功能。好的。

ABI非常相似。可以将其视为API的编译版本(或机器语言级别的API)。编写源代码时,可以通过API访问库。编译代码后,应用程序将通过ABI访问库中的二进制数据。ABI定义了编译后的应用程序将用于访问外部库(就像API一样)的结构和方法,只在较低的级别上使用。好的。

当涉及到使用外部库的应用程序时,ABI非常重要。如果一个程序是为使用某个特定的库而构建的,并且该库稍后会被更新,那么您不需要重新编译该应用程序(从最终用户的角度来看,您可能没有源代码)。如果更新的库使用相同的ABI,那么您的程序将不需要更改。库的接口(这是您的程序真正关心的)是相同的,即使内部工作可能已经改变。具有相同ABI的库的两个版本有时被称为"二进制兼容",因为它们具有相同的低级接口(您应该能够用新版本替换旧版本,并且没有任何主要问题)。好的。

有时,ABI变化是不可避免的。发生这种情况时,任何使用该库的程序都将无法工作,除非重新编译以使用该库的新版本。如果ABI改变了,但是API没有改变,那么旧的和新的库版本有时被称为"源代码兼容"。这意味着,虽然为一个库版本编译的程序不能与另一个库版本一起工作,但为其中一个库版本编写的源代码如果重新编译就可以为另一个库版本工作。好的。

因此,图书馆作者倾向于保持其ABI的稳定(以尽量减少中断)。保持ABI稳定意味着不更改函数接口(返回类型和数字、类型和参数顺序)、数据类型或数据结构的定义、定义的常量等。可以添加新的函数和数据类型,但现有的函数和数据类型必须保持不变。如果将16位数据结构字段展开为32位字段,则使用该数据结构的已编译代码将无法正确访问该字段(或其后面的任何字段)。在编译期间,访问数据结构成员会转换为内存地址和偏移量,如果数据结构发生变化,那么这些偏移量将不会指向代码希望它们指向的内容,并且结果最多是不可预测的。好的。

ABI不一定是您将显式提供的东西,除非您希望人们使用程序集与您的代码进行交互。它也不特定于语言,因为(例如)C应用程序和Pascal应用程序在编译后将使用相同的ABI。好的。

编辑:关于您关于SYSV ABI文档中ELF文件格式章节的问题:之所以包含此信息,是因为ELF格式定义了操作系统和应用程序之间的接口。当您告诉操作系统运行一个程序时,它期望程序以某种方式格式化,并且(例如)期望二进制文件的第一部分是一个ELF头段,其中包含特定内存偏移量的某些信息。这就是应用程序如何向操作系统传递有关自身的重要信息。如果以非elf二进制格式(如a.out或pe)构建程序,那么期望elf格式应用程序的操作系统将无法解释二进制文件或运行该应用程序。这就是为什么Windows应用程序不能直接在Linux机器上运行(反之亦然)的一个重要原因,而不需要重新编译或在某种类型的仿真层中运行,这种仿真层可以从一种二进制格式转换为另一种二进制格式。好的。

iirc,Windows当前使用可移植可执行文件(或,pe)格式。在维基百科页面的"外部链接"部分有一些链接,其中包含关于PE格式的更多信息。好的。

另外,关于C++名称的注释:ABI可以为C++编译器定义一个"标准化"的方式来实现兼容性。也就是说,如果我创建了一个库,并且您开发了一个使用该库的程序,那么您应该能够使用与我不同的编译器,并且不必担心由于不同的名称管理方案而导致的二进制文件不兼容。只有在定义新的二进制文件格式或编写编译器或链接器时,才真正使用这种方法。好的。好啊。


如果你知道汇编和操作系统级的工作原理,你就符合某个ABI。ABI控制参数的传递方式、返回值的位置等。对于许多平台来说,只有一个ABI可供选择,在这些情况下,ABI只是"工作原理"。

但是,ABI还管理着C++中如何设置类/对象的事情。如果希望能够跨模块边界传递对象引用,或者希望混合使用不同编译器编译的代码,那么这是必需的。

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

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

我认为你太难将不同类型的接口压缩成一组固定的特性。例如,一个接口不一定要拆分为消费者和生产者。接口只是两个实体相互作用的约定。

ABI可以(部分)是不可知论者。一些方面(如调用约定)依赖于ISA,而其他方面(如C++类布局)不依赖于ISA。

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

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

  • ABI中的"二进制"不排除使用字符串或文本。如果要链接导出C++类的DLL,则在其中的某个地方,必须对方法和类型签名进行编码。这就是C++名字的缩写进来的地方。
  • 您从未提供ABI的原因是绝大多数程序员永远不会这样做。ABI由设计平台(即操作系统)的同一个人提供,很少有程序员有权设计一个广泛使用的ABI。


你其实根本不需要ABI如果--好的。

  • 您的程序没有功能,并且--
  • 您的程序是一个单独运行的可执行文件(即嵌入式系统),它实际上是唯一运行的程序,不需要与其他任何程序通信。

过于简单的总结:好的。

API:"Here are all the functions you may call."

Ok.

ABI:"This is how to call a function."

Ok.

ABI是编译器和链接器为了编译程序而遵循的一组规则,以便能够正常工作。ABIS涵盖多个主题:好的。

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

更深入地看一下呼叫约定,我认为它是ABI的核心:好的。

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

为了准备跳跃,编译器必须做一些重要的事情。调用约定类似于编译器执行所有这些操作时遵循的清单:好的。

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

有许多不同的ABI/呼叫约定。主要有:好的。

  • 对于x86或x86-64 CPU(32位环境):
    • CDDEL
    • STDCALL
    • 快速呼叫
    • 向量调用
    • 这个电话
  • 对于x86-64 CPU(64位环境):
    • 系统V
    • 杂音的
    • 向量调用
  • 用于ARM CPU(32位)
    • APCCS
  • 用于ARM CPU(64位)
    • AAPCS64

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

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

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


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

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

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

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

使用者:另一个函数(包括另一种语言的函数、由另一个编译器编译的函数或由另一个链接器链接的函数)。


功能:影响编译器、程序集编写器、链接器和操作系统的一组协定。契约指定了函数的布局、传递参数的位置、传递参数的方式、函数返回工作的方式。这些通常是特定于(处理器体系结构、操作系统)元组的。

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

使用者:编译器、程序集编写器、操作系统、特定于CPU的体系结构。

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

C++Name是一种特殊情况——它是链接器和动态链接器中心的问题——如果名称不规范,那么动态链接就不起作用了。从此以后,C++ ABI就被称为C++的ABI。它不是链接器级别的问题,而是代码生成问题。一旦你有一个C++二进制文件,就不可能使它与另一个C++ abi(名字处理,异常处理)兼容,而不需要从源重新编译。

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

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

Windows有几个ABI—例如,fastcall和stdcall是两个常用的ABI。系统调用ABI又不同了。


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

系统调用是用户空间程序向内核空间请求某些内容的一种方法。它的工作原理是将调用和参数的数字代码放入某个寄存器并触发一个中断。KernelSpace没有切换,内核查找数字代码和参数,处理请求,将结果放回寄存器,并触发返回到用户空间的切换。例如,当应用程序想要分配内存或打开一个文件(系统调用"brk"和"open")时,就需要这样做。

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


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

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

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

https://developer.apple.com/library/mac/documentation/developertools/conceptive/lowlevelabi/140-x86-64_函数_调用_约定/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构建不同的软件部件,则可以相互调用。但必须遵守相同ABI的附加要求。

例如,Windows以前是符合POSIX API的:

https://en.wikipedia.org/wiki/windows_-services_for_-unix

https://en.wikipedia.org/wiki/posix网站

Linux也兼容POSIX。但是二进制文件不能被直接移动和运行。但是,由于它们在符合POSIX的API中使用了相同的名称,所以您可以在C中使用相同的软件,在不同的操作系统中重新编译它,并立即运行它。

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

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


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


总结

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

在我看来,ABI是一种主观的约定,它规定了特定API的给定/平台。ABI是特定API的"不会更改"或运行时环境(执行器、工具、链接器、编译器、JVM和OS)将要处理的约定的"其余部分"。好的。定义接口:abi、api

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

  • 补丁——你根本不需要修改你的代码。图书馆只是修复了一些错误。
  • 次要-由于添加了
  • Major-接口(API)已更改,您可能需要更改代码。
  • 为了让您使用同一个库的新的主要版本,仍然需要遵守许多其他约定:好的。

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

    实例Java案例研究

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

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

    Dalvik VM需要不同类型的字节码,而不是Java字节码。Dalvik库是通过将Java字节码(同一API)转换为Dalvik来获得的。这样,您可以获得同一API的两个版本:由原始joda-time-1.7.2.jar定义。我们可以叫我joda-time-1.7.2.jarjoda-time-1.7.2-dalvik.jar。它们使用不同的ABI,用于面向堆栈的标准Java VMS: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图书馆的时候,所有这些。由于不同的解决方案,这种不相容性会引起雷达的轰鸣:好的。

  • 语义版本化:当库针对更高的JVM时,它们通常会更改主版本。
  • 使用JVM4作为ABI,您就安全了。
  • Java 9添加了一个规范,说明如何在同一个库中为特定目标的JVM添加字节码。
  • 为什么我要从API定义开始?

    API和ABI只是关于如何定义兼容性的约定。较低的层对于大量的高级语义来说是通用的。这就是为什么很容易制定一些约定。第一种约定是关于内存对齐、字节编码、调用约定、大字节和小字节编码等。在上面,您可以得到像其他描述的可执行约定、链接约定、中间字节代码,如由GCC使用的Java或LLVM IR所使用的。第三,在如何查找库、如何加载库(参见Java类加载器)方面,您有一些约定。当你在概念上越来越高的时候,你会有一些你认为是给定的新约定。这就是为什么他们没有进行语义版本控制的原因。它们在主版本中是隐式的或折叠的。我们可以用---修改语义版本。这就是实际发生的事情:平台已经是一个rpmdlljar(jvm字节码)、war(jvm+web服务器)、apk2.11(特定scala版本)等等。当你说apk时,你已经在谈论api中的特定abi部分了。好的。API可以移植到不同的ABI

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

    假设我有一些RXScala的来源。如果scala工具被更改了,我可以重新编译它们。如果JVM发生了变化,我可以在不干扰高级概念的情况下,从旧机器自动转换为新机器。虽然移植可能很困难,但它可以帮助任何其他客户。如果使用完全不同的汇编程序代码创建新的操作系统,则可以创建转换器。好的。跨语言移植的API

    有一些API以多种语言(如反应流)进行移植。一般来说,它们定义到特定语言/平台的映射。我认为API是用人类语言甚至特定编程语言正式定义的主规范。从某种意义上讲,所有其他的"映射"都是ABI,而不是通常的ABI。其余接口也会发生同样的情况。好的。好啊。


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

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

    例如:

    • 如果您销售的是共享库,那么您就可以为用户省去为每个新版本重新编译依赖于库的所有内容的麻烦。

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

      在C标准库的情况下,这一点特别重要,因为系统中的许多程序都链接到C标准库。

    现在,我想提供一个最小的具体可运行的例子。

    主C

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #include
    #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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #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;
    }

    蛋氨酸

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #ifndef MYLIB_H
    #define MYLIB_H

    typedef struct {
        int old_field;
    } mylib_mystruct;

    mylib_mystruct* mylib_init(int old_field);

    #endif

    编译并运行良好:

    1
    2
    3
    4
    5
    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之前添加字段,如:

    1
    2
    3
    4
    typedef struct {
        int new_field;
        int old_field;
    } mylib_mystruct;

    重建了图书馆,但没有重建main.out,那么断言就失败了!

    这是因为行:

    1
    myobject->old_field == 1

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

    因此,这一变化打破了ABI。

    但是,如果在old_field之后加上new_field

    1
    2
    3
    4
    typedef struct {
        int old_field;
        int new_field;
    } mylib_mystruct;

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

    这里是这个例子在GitHub上的全自动版本。

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

    API与ABI

    在前面的例子中,有趣的是在old_field之前添加new_field只会破坏abi,而不是api。

    这意味着,如果我们针对图书馆重新编译了我们的main.c程序,它将无论如何都能工作。

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

    1
    mylib_mystruct* mylib_init(int old_field, int new_field);

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

    语义API vs编程API vs ABI

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

    例如,如果我们修改了

    1
    myobject->old_field = old_field;

    到:

    1
    myobject->old_field = old_field + 1;

    这样一来,既不会破坏API也不会破坏ABI,但main.c仍然会破坏!

    这是因为我们改变了功能应该做什么的"人类描述",而不是程序上明显的方面。

    我刚刚有了哲学上的见解,从某种意义上说,软件的正式验证将更多的"语义API"移动到一个更"可编程验证API"中。

    打破C/C++共享库AbIS的一切清单

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

    • https://github.com/lvc/abi-compliance-checker自动检查工具
    • http://Cual.KD.org/Primes/Bialay-AdvuliTysIsResessig与%C%2B%2BKDE C++ ABI指南
    • https://plan99.net/~mike/writing-shared-libraries.html网站

    Java最小可运行实例

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

    在Ubuntu 18.10,GCC 8.2.0中测试。


    术语abi用于指两个不同但相关的概念。

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

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

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

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

    应用程序程序员编写的代码

    1
    2
    3
    4
    5
    6
    7
    int dostuffwithfoo(int bar) {
      FOO foo;
      initfoo(&foo);
      int result = usefoo(&foo,bar)
      cleanupfoo(&foo);
      return result;
    }

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

    如果图书馆的作者设计了他们的API。

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

    应用程序程序员编写的代码

    1
    2
    3
    4
    5
    6
    7
    int dostuffwithfoo(int bar) {
      FOO * foo;
      foo = newfoo();
      int result = usefoo(&foo,bar)
      deletefoo(&foo);
      return result;
    }

    那么应用程序二进制文件就不需要了解foo的结构,所有这些都可以隐藏在库中。不过,您为此付出的代价是涉及到堆操作。


    我也试着理解阿比和杰斯佩尔的回答是非常有帮助的。

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

    kde wiki将库定义为二进制兼容"如果动态链接到库的前一个版本的程序继续运行,而不需要重新编译。"有关动态链接的详细信息,请参阅静态链接与动态链接

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

  • 相同/向后兼容的指令集架构(处理器指令、寄存器文件结构、堆栈组织、内存访问类型,以及处理器可以直接访问的基本数据类型的大小、布局和对齐方式)
  • 相同的调用约定
  • 同名的MangLink约定(如果FORTRAN程序需要调用一些C++库函数,这可能是需要的)。
  • 当然,还有许多其他细节,但这主要是ABI也涵盖的内容。

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

    ABI functionality: binary compatibility

    existing entities: existing program/libraries/OS

    consumer: libraries, OS

    希望这有帮助!


    应用二进制接口(ABI)

    功能:

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

    现有实体:

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

    消费者:

    • 语言处理器链接器,汇编程序…

    任何人都需要这些来确保构建工具链作为一个整体工作。如果您用汇编语言编写一个模块,用Python编写另一个模块,而不是自己的引导加载程序想要使用操作系统,那么您的"应用程序"模块将跨越"二进制"边界工作,并且需要此类"接口"的同意。

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

    ELF是对象文件中链接器用于解释的一个可能期望,尽管JVM可能有其他的想法。

    对于WindowsRT商店应用程序,如果您真的想让一些构建工具链协同工作,请尝试搜索arm abi。


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


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