ABI 是什么 ?

371 阅读3分钟

应用二进制接口 (ABI)两个二进制程序模块之间的底层接口,通常存在于:

  • 已编译的应用程序代码
  • 操作系统硬件共享库 之间。

它定义了数据结构函数调用系统调用 在二进制层面如何表示和访问,从而使得分开编译的代码可以在运行时协同工作


✅ 为什么需要 ABI?

在以下情况下需要 ABI:

  1. 程序的不同部分是分开编译的
    • 例如,使用预编译库(如 .so, .dll, .a, .dylib)。
  2. 跨语言或跨编译器集成
    • 例如,从 Rust 调用 C 代码,或 Dart 通过 FFI 调用原生代码。
  3. 与操作系统或硬件交互
    • 系统调用遵循 ABI 规则,因此您的应用程序可以与内核或硬件通信。
  4. 确保可移植性和兼容性
    • 更改库的 ABI(例如函数签名的布局)可能会破坏依赖的应用程序。

⚙️ ABI 具体规定了什么?

它定义了以下技术细节:

方面描述
调用约定 (Calling convention)函数如何被调用:参数如何传递(在寄存器/栈中)、谁清理栈、返回值如何传递。
数据类型布局 (Data types layout)结构体 (structs)、联合体 (unions)、枚举 (enums) 的内存对齐 (alignment)、填充 (padding) 和大小 (size)。
名称修饰 (Name mangling)函数名在二进制文件中如何编码(C++ 使用修饰,C 语言不使用)。
寄存器使用 (Register usage)哪些寄存器用于传递参数、返回值,以及哪些寄存器必须被保留(preserved)。
栈布局 (Stack layout)局部变量和返回地址如何放置。

📌 示例 1:从 Rust 调用 C 函数

假设您有这样一个 C 函数:

// lib.c
int add(int a, int b) {
    return a + b;
}

如果您将其编译成共享库 (libadd.so),并从 Rust 调用它:

// main.rs
extern "C" {
    fn add(a: i32, b: i32) -> i32;
}

fn main() {
    unsafe {
        println!("3 + 4 = {}", add(3, 4));
    }
}

🔸 这里的 "C" 指定了 C ABI,因此 Rust 知道如何正确调用 add —— 使用相同的调用约定、栈布局等。

如果没有 ABI,Rust 和 C 可能以不同方式表示整数、使用不同的调用约定、或将值放在不同的寄存器中——这将导致程序崩溃或数据损坏。


📌 示例 2:结构体布局不匹配

在 C 语言中:

struct A {
    int x;
    char y;
};

在内存中,它可能看起来像这样:

字段字节说明
x0-3int (4 字节)
y4char (1 字节)
---填充(padding)---5–7用于对齐 (alignment)

如果另一种语言(例如 Python 的 ctypes 或 Go 的 FFI)在不知道填充规则的情况下尝试访问此结构体,它将错误地读取内存。ABI 定义了这种布局。


📌 示例 3:Flutter/Dart 通过 FFI 调用原生 C++

当 Dart 调用一个原生 C 函数时:

// native.c
double multiply(double a, double b) {
    return a * b;
}

Dart 端使用 FFI:

final multiply = nativeLib
  .lookupFunction<Double Function(Double, Double),
                   double Function(double, double)>('multiply');

Dart 使用 C ABI 来确保其调用函数的方式以及参数/结果的传递方式与 C 函数匹配。


📌 实际后果:ABI 破坏

如果您用 g++ 9.0 构建了一个 C++ 库,后来又用 g++ 13.0 构建,默认的 C++ ABI 可能已经改变,这可能导致:

  • 段错误 (segmentation faults)
  • 未定义行为 (undefined behavior)
  • 链接器错误 (linker errors)

这就是为什么库维护者要承诺跨版本的 ABI 稳定性


🧠 总结

概念解释
ABI组件之间的二进制级契约 (Binary-level contract)。
为何重要支持跨模块、跨语言、跨编译器的协作。
定义内容函数调用、内存布局、系统调用、寄存器使用方式等。
没有它会怎样来自不同来源的编译代码将无法协同工作。