Skip to content

LA32R汇编编程简介

函数调用约定

ILP32 ABI基本整型调用规范

我们这里只介绍LA32R最常用的ILP32 ABI中的函数调用约定。其实读者了解其中第1、2、10条就能满足大多数场景下的汇编开发需求。

  1. 基本整型调用规范提供了8个参数寄存器$a0 ~ $a7用于参数传递,前两个参数寄存器$a0 ~ $a1也用于返回值。
  2. 若一个标量位宽至多32位,则它在单个参数寄存器中传递,若没有可用的寄存器,则在堆栈中传递;若一个标量宽度超过32位,不超过64位,则可以在一对参数寄存器中传递,低32位在小编号寄存器中,高32位在大编号寄存器中;若没有可用的参数寄存器,则在堆栈上传递标量;若只有一个寄存器可用,则低32位在寄存器中传递,高32位在堆栈中传递。若一个标量宽度大于64位,则通过引用传递,并在参数列表中用地址替换。传递到堆栈上的标量会对齐到类型对齐(Type Alignment)和32中的较大者,但不会超过堆栈对齐。当整型参数传入寄存器或堆栈时,小于32位的整型标量根据其类型的符号扩展至32位。当浮点型参数传入寄存器或堆栈时,比32位窄的浮点类型将被扩展为32位。
  3. 若一个聚合体的的位宽不超过32位,则这个聚合体可以在寄存器中传递,并且这个聚合体在寄存器中的字段布局同它在内存中的字段布局保持一致;若没有可用的寄存器,则在堆栈上传递聚合体;若一个聚合体的位宽超过32位,不超过64位,则可以在一对寄存器中传递,若只有一个寄存器可用,则聚合体的前半部分在寄存器中传递,后半部分在堆栈中传递;若没有可用的寄存器,则在堆栈上传递聚合体。由于填充(padding)而未使用的位,以及从聚合体的末尾至下一个对齐位置之间的位,都是未定义的;若一个聚合体位宽大于64位,则它通过引用传递,并在参数列表中被替换为地址。传递到堆栈上的聚合体会对齐到类型对齐(type alignment)和32中的较大者,但不会超过堆栈对齐。
  4. 对于空的结构体或联合体(unions)参数或返回值,C编译器会认为它们是非标准扩展并忽略; C++编译器则不是这样,C++编译器要求它们必须是分配了大小的类型(sized types)。
  5. 位域(bitfields)以小端顺序排列。跨越其整型类型的对齐边界的位域将从下一个对齐边界开始。例如:

    • struct {int x:10; int y:12;}是一个32位类型,x 为 9-0 位,y 为 21-10 位,31-22 位未定义。

    • struct {short x:10; short y:12;}是一个 32 位类型,x 为 9-0 位,y 为 27-16 位,31-28 位和 15-10位未定义。

  6. 通过引用传递的实参可以由被调用方修改。

  7. 浮点实数的传递方式与相同大小的聚合体相同,浮点型复数的传递方式与包含两个浮点实数的结构体相同。
  8. 在基本整型调用规范中,可变参数的传递方式与命名参数相同,但有一个例外。64位对齐的可变参数和至多64位大小的可变参数通过一对对齐的寄存器传递(例如:寄存器对中的第一个寄存器为偶数),如果没有可用的寄存器,则在堆栈上传递。当可变参数在堆栈上被传递后,所有之后的参数也将在堆栈上被传递(例如,最后一个参数寄存器可能由于对齐寄存器对规则而未被使用)。
  9. 返回值的传递方式与第一个同类型命名参数(named value)的传递方式相同。如果这样的实参是通过引用传递的,则调用者为返回值分配内存,并将地址作为隐式的第一个参数传递。
  10. 堆栈向下增长(朝向更低的地址),堆栈指针应该对齐到一个16字节的边界上作为函数入口。在堆栈上传递的第一个实参位于函数入口的堆栈指针偏移量为零的地方;后面的参数存储在更高的地址中。
  11. 在标准 ABI 中,堆栈指针在整个函数执行过程中必须保持对齐。非标准 ABI 代码必须在调用标准 ABI 过程之前重新调整堆栈指针。操作系统在调用信号处理程序之前必须重新调整堆栈指针;因此,POSIX 信号处理程序不需要重新调整堆栈指针。在服务中断的系统中使用被中断对象的堆栈,如果连接到任何使用非标准堆栈对齐规则的代码,中断服务例程必须重新调整堆栈指针,但如果所有代码都遵循标准 ABI ,则不需要重新调整堆栈指针。
  12. 函数所依赖的数据必须位于函数栈帧范围之内。
  13. 被调用函数应该负责保证寄存器$s0 ~ $s8的值在返回时和调用入口处一致。

上面的规范描述提到的“堆栈对齐”的概念就是其中的第10条。

上面的规范描述还提到了“类型对齐”。举例来说,标量int型的类型对齐意味着它的访存地址必须是4的倍数。下面是所有标量类型的对齐情况:

标量类型 大小(字节) 对齐(字节)
unsinged/signed char 1 1
unsinged/signed short 2 2
unsinged/signed int 4 4
unsinged/signed long 4 4
unsinged/signed long long 8 8
pointer 4 4
float 4 4
double 8 8
long double 16 16

系统调用约定

系统调用是操作系统内核为用户态程序实现的子程序。类似于一般的函数调用,系统调用也需要进行参数传递。LA32R中,系统调用的参数传递规则如下:

  1. 系统调用号存放在$a7寄存器中。
  2. 寄存器$a0 ~ $a6用来传递参数。
  3. 返回值存放在$a0$a1寄存器中。
  4. 系统调用保存$s0 ~ $s8寄存器的值,不保证保持参数寄存器($a0 ~ $a7)和暂存寄存器($t0 ~ $t8)的值。

上面提到的系统调用号,在Linux/LA32R系统中的定义可以从内核源码的include/uapi/asm-generic/unistd.h文件中获得。