为了账号安全,请及时绑定邮箱和手机立即绑定

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

/ 猿问

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

拉风的咖菲猫 2019-07-25 14:41:51

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

我从未明白ABI是什么。请不要指向维基百科的文章。如果我能理解它,我就不会在这里张贴这么长的帖子。

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

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

接口:它是间“现有实体”层 functionalityconsumer的该功能。接口本身不起作用。它只是调用后面的功能。

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

命令行界面(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?

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


查看完整描述

3 回答

?
www说

理解“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格式的应用程序的操作系统将无法解释二进制文件或运行应用程序。

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

另外,关于你关于C ++名称修改的注释:ABI可以为C ++编译器定义一种“标准化”方式,以便为了兼容性而进行名称修改。也就是说,如果我创建一个库并且您开发了一个使用该库的程序,那么您应该能够使用与我不同的编译器,而不必担心由于不同的名称修改方案而导致的二进制文件不兼容。如果您要定义新的二进制文件格式或编写编译器或链接器,那么这只是有用的。


查看完整回答
反对 回复 2019-07-25
?
慕圣8478803

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

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

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

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

我认为你很难将不同类型的接口挤入一组固定的特性中。例如,界面不一定必须拆分为消费者和生产者。接口只是两个实体交互的约定。

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

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

编辑:一些说明澄清:

  • ABI中的“二进制”不排除使用字符串或文本。如果要链接导出C ++类的DLL,则必须对其中的方法和类型签名进行编码。这就是C ++名称变形的地方。

  • 你从未提供过ABI的原因是绝大多数程序员都不会这样做。ABI由设计平台(即操作系统)的人提供,很少有程序员有权设计广泛使用的ABI。



查看完整回答
反对 回复 2019-07-25
?
不负相思意

如果 - 你实际上根本不需要ABI--

  • 你的程序没有功能,并且 -

  • 您的程序是一个单独运行的可执行程序(即嵌入式系统),它实际上是唯一运行的程序,它不需要与任何其他程序进行通信。

过于简化的摘要:

API: “以下是您可以调用的所有功能。”

ABI: “这是如何调用一个函数。”

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

  • 可以说,ABI最大和最重要的部分是程序调用标准,有时称为“调用约定”。调用约定标准化了“函数”如何转换为汇编代码。

  • ABI还规定了如何表示库中公开函数的名称,以便其他代码可以调用这些库并知道应该传递哪些参数。这被称为“名称重整”。

  • ABI还规定了可以使用哪种类型的数据类型,它们必须如何对齐以及其他低级细节。

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

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

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

  • 首先,编译器插入一些汇编代码来保存当前地址,这样当你的“函数”完成后,CPU就可以跳回到正确的位置并继续执行。

  • 接下来,编译器生成汇编代码以传递参数。

    • 一些调用约定规定应该将参数放在堆栈上(当然按特定顺序)。

    • 其他约定规定,参数应放在特定的寄存器中(当然,取决于它们的数据类型)。

    • 还有其他惯例规定应该使用堆栈和寄存器的特定组合。

  • 当然,如果之前这些寄存器中有任何重要的东西,那么这些值现在会被覆盖并永远丢失,因此一些调用约定可能要求编译器在将参数放入其中之前保存其中的一些寄存器。

  • 现在,编译器插入一条跳转指令,告诉CPU转到之前创建的标签(_MyFunction1:)。此时,您可以将CPU视为“处于”“功能”中。

  • 在函数结束时,编译器会放置一些汇编代码,使CPU在正确的位置写入返回值。调用约定将指示是否应将返回值放入特定寄存器(取决于其类型)或堆栈上。

  • 现在是清理的时候了。调用约定将指示编译器放置清理汇编代码的位置。

    • 一些约定说调用者必须清理堆栈。这意味着在“功能”完成并且CPU跳回到之前的位置之后,下一个要执行的代码应该是一些非常具体的清理代码。

    • 其他约定说清理代码的某些特定部分应该在跳回之前的“函数”的末尾。

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

  • 对于x86或x86-64 CPU(32位环境):

    • CDECL

    • STDCALL

    • FASTCALL

    • VECTORCALL

    • THISCALL

  • 对于x86-64 CPU(64位环境):

    • SYSTEMV

    • MSNATIVE

    • VECTORCALL

  • 对于ARM CPU(32位)

    • AAPCS

  • 对于ARM CPU(64位)

    • AAPCS64

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

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

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


查看完整回答
反对 回复 2019-07-25

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信