我从来没有清楚地理解 ABI 是什么。请不要将我指向维基百科的文章。如果我能理解,我就不会在这里发这么长的帖子了。
这是我对不同界面的看法:
电视遥控器是用户和电视之间的接口。它是一个现有的实体,但本身无用(不提供任何功能)。遥控器上每个按钮的所有功能都在电视机中实现。
接口:functionality它是该功能之间的“现有实体”层 consumer。接口本身不做任何事情。它只是调用背后的功能。 现在取决于用户是谁,有不同类型的界面。 命令行界面 (CLI) 命令是现有实体,消费者是用户,功能位于后面。 functionality:我的软件功能解决了我们描述此界面的某些目的。 existing entities:命令 consumer:用户 图形用户界面 (GUI) 窗口、按钮等是现有的实体,而消费者又是用户,功能落后。 functionality:我的软件功能解决了我们描述这个接口的一些问题。 existing entities:窗口、按钮等。 consumer:用户 应用程序编程接口(API) 函数(或者更准确地说)接口(在基于接口的编程中)是现有的实体,这里的消费者是另一个程序而不是用户,功能再次位于这一层后面。 functionality:我的软件功能解决了我们描述这个接口的一些问题。 existing entities:函数,接口(函数数组)。 consumer:另一个程序/应用程序。 应用程序二进制接口 (ABI) 这是我的问题开始的地方。 functionality:??? existing entities:??? consumer:???
接口:functionality它是该功能之间的“现有实体”层 consumer。接口本身不做任何事情。它只是调用背后的功能。
functionality
consumer
现在取决于用户是谁,有不同类型的界面。
命令行界面 (CLI) 命令是现有实体,消费者是用户,功能位于后面。
functionality:我的软件功能解决了我们描述此界面的某些目的。
functionality:
existing entities:命令
existing entities:
consumer:用户
consumer:
图形用户界面 (GUI) 窗口、按钮等是现有的实体,而消费者又是用户,功能落后。
functionality:我的软件功能解决了我们描述这个接口的一些问题。
existing entities:窗口、按钮等。
应用程序编程接口(API) 函数(或者更准确地说)接口(在基于接口的编程中)是现有的实体,这里的消费者是另一个程序而不是用户,功能再次位于这一层后面。
existing entities:函数,接口(函数数组)。
consumer:另一个程序/应用程序。
应用程序二进制接口 (ABI) 这是我的问题开始的地方。
functionality:???
existing entities:???
consumer:???
维基百科说:
ABI 涵盖详细信息,例如 数据类型、大小和对齐方式; 调用约定,它控制函数的参数如何传递和返回值的检索; 系统调用号以及应用程序应如何对操作系统进行系统调用; 其他 ABI 标准化细节,例如 C++ 名称修改, 异常传播,以及 同一平台上编译器之间的调用约定,但不需要跨平台兼容性。
ABI 涵盖详细信息,例如
其他 ABI 标准化细节,例如
谁需要这些细节?请不要说操作系统。我知道汇编编程。我知道链接和加载是如何工作的。我确切地知道里面发生了什么。
为什么 C++ 名称修饰会出现?我以为我们在二进制级别上讨论。为什么语言会出现?
无论如何,我已经下载了[PDF] System V Application Binary Interface Edition 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 所做的那样),只是在较低级别上。您的 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 程序中使用时,您是在指示编译器使用一种标准化的方式来记录其他软件可以理解的名称。
extern "c"