abi-introduction
ABI概念
应用二进制接口(英语:application binary interface,缩写为ABI)是指两程序模块间的接口;通常其中一个程序模块是函数库或操作系统所提供的服务,而另一边的模块则是用户所执行的程序。一个ABI定义了机器代码如何访问数据结构与二进制代码,此处所定义的接口相当底层并且相依于硬件。
硬件相关的ABI组成部分
数据表示与内存布局
基本数据类型的大小和对齐
明确规定基本数据类型(如 int, long, short, char, float, double, 指针)在内存中占用多少字节以及在内存地址上的对齐要求(例如,int 必须从4的倍数的地址开始存放)。
复合数据类型的内存布局
定义struct、union、数组、类在内存中的布局方式。
成员顺序:结构体/类成员在内存中的排列顺序。
成员偏移量:每个成员相对于结构体/类起始地址的偏移量。
填充字节:编译器为了满足对齐要求而在成员之间或结构体末尾插入的额外字节。
位域:如何打包和访问位域。
字节序
多字节数据类型在内存中是大端序还是小端序。
数据模型
定义不同种类指针的大小和语义。
浮点数格式
通常遵循IEEE 754标准,但ABI需要明确指定使用的格式(如单精度、双精度)及其在寄存器/内存中的表示。
LoongArch ABI 约定C语言采用LP64/ILP32数据模型。特别强调char默认为有符号
调用约定
调用约定是指函数调用时,如何传递参数、返回值以及如何处理堆栈等细节的约定。它定义了函数调用的方式,包括参数的传递顺序、寄存器的使用、堆栈的管理等。
参数传递
规定函数调用时参数如何传递给被调用函数
- 传递位置:哪些参数通过寄存器传递?哪些通过栈传递?具体使用哪些寄存器?
- 传递顺序:参数是按从左到右还是从右到左的顺序压栈/放入寄存器?
- 栈帧管理:谁负责在函数调用后清理栈上的参数?调用者还是被调用者?(cdecl, stdcall, fastcall, thiscall等的主要区别之一)。
- 寄存器使用约定:哪些寄存器是调用者保存的,哪些是被调用者保存的。被调用函数必须在使用前保存并在返回前恢复Callee-saved寄存器的值。
- 聚合类型参数:结构体、类对象等作为参数或返回值时如何传递?(通过栈?通过寄存器?通过隐藏指针?)
- 浮点参数:使用通用寄存器还是专用的浮点寄存器传递?
返回值传递
规定函数如何返回结果
- 基本类型通常通过特定寄存器返回(如EAX/RAX用于整数,XMM0用于浮点数)。
- 大的结构体或类对象如何返回?(通常通过调用者预留空间并传递一个隐藏的指针参数,返回值写入该空间)。
栈帧结构
函数调用栈帧的布局
- 返回地址存储的位置。
- 保存的调用者/被调用者寄存器的位置。
- 局部变量和临时存储的位置。
- 栈指针和帧指针(如果使用)的管理。
重定位机制
重定位是链接器和加载器修改程序二进制代码的过程,用于解决模块间符号引用(如函数/变量地址)的运行时绑定问题,确保程序在链接时或加载时能正确寻址。其规则由硬件指令集和软件工具链共同定义。
符号地址的计算规则和写入位置
- 地址计算语义:如何根据符号地址、基址、加数计算目标值
- 指令编码约束:目标值如何拆解到指令的特定比特位(受限于指令格式)
- 对齐要求:地址修正是否需要对齐(如跳转目标4字节对齐)
异常处理
- 规定异常如何被抛出、传播、捕获和栈展开。
- 定义了异常处理信息,如哪些栈帧需要清理、catch块的位置信息在目标文件中的存储格式(例如DWARF中的.eh_frame段或Windows的Structured Exception Handling)
- 栈展开过程中如何调用对象的析构函数。
系统调用接口
系统调用号分配和参数传递寄存器
硬件不相关的ABI组成部分
目标文件格式
指定了编译器生成的目标文件(如ELF、PE)和链接器生成的可执行文件/共享库所使用的二进制文件格式
LoongArch ABI 约定使用ELF格式,规定使用32/64位,体系结构ID为258,并规定了e_flags字段的使用,用于表示ABI类型。
动态链接和共享库
- 共享库加载:程序启动时或运行时如何定位和加载共享库
- 符号解析:运行时如何解析共享库中导出符号的地址(延迟绑定 vs 加载时绑定)
- 过程链接表与全局偏移表:ABI定义其结构和行为
- 位置无关代码:PIC和PIE代码的生成规则,使得共享库代码可以被加载到任意内存地址运行
- 库版本控制:如何管理共享库的不同版本和兼容性(如Linux的soname机制)
LoongArch ABI 约定了linux下Glibc标准动态链接器的路径