RISC-V的ABI
Ianus Inferus
2022-01-23
学习资料
RISC-V ELF psABI Document,ABI文档,需要从releases中下载。
RISC-V Reference Card,RISC-V汇编快速查找卡。
x64 software conventions,VC x64 ABI文档,用作对比资料。
前言
考虑到实际情况,基于RV64I和LP64D来举例。部分内容不在psABI文档中,通过查看clang编译结果进行补充。
寄存器
参数寄存器a0-a7,临时寄存器t0-t6,被调方保存寄存器s0-s11。s0又作为栈指针寄存器fp。
常数0寄存器zero,返回地址寄存器ra,栈寄存器sp。
浮点参数寄存器fa0-fa7,浮点临时寄存器ft0-ft11,浮点被调方保存寄存器fs0-fs11。
调用函数时,参数和返回值都可以通过寄存器传递,参数寄存器和临时寄存器的值需要调用方保存,被调方保存寄存器需要被调用方保存。
栈
栈从高地址向低地址增长。调用函数中进入函数时栈指针应对齐16字节边界。栈上的第一个参数在栈指针指向的位置(偏移量为0),后续参数在更高的地址上。
比sp低的栈地址在应用程序中不应该使用,因为可能会被操作系统、调试器等覆盖。
High
| ParentLocals |
| ...... |
fp+48 | VarArgument14 |
fp+40 | VarArgument13 |
fp+32 | VarArgument12 |
fp+24 | Argument11 |
fp+16 | Argument10 |
fp+8 | Argument9 |
fp+0 | Argument8 | 进入函数时sp在此处,父函数栈帧,参数均放在父函数的栈帧中
|----------------|
fp-8 | ReturnAddress | 在非叶函数中应在prolog中将ra寄存器保存到此处
fp-16 | FramePointer | 在非叶函数中应在prolog中将fp寄存器保存到此处
fp-24 | SavedRegister0 |
fp-32 | SavedRegister1 |
fp-40 | SavedRegister2 |
fp-48 | Local0 |
fp-56 | Local1 |
fp-64 | Local2 |
fp-72 | Local3 |
| ...... |
| ChildArguments | prolog结束时sp在此处
|----------------|
Low
传参
参数优先放到参数寄存器或浮点参数寄存器,否则放到栈上。
可变长参数函数的额外参数放到局部变量中,与固定参数相邻,这样可变长参数函数的栈指针sp变化和没有额外参数时一样。
参数寄存器默认没有像x64那样的home space,原因可能是参数寄存器较多,比较占用栈空间。如果需要放到栈上,需要从局部变量区分配空间。
参数求值顺序为从左向右。
函数调用指令重定位
我们可以使用以下命令查看.o文件和可执行文件反汇编的结果。
llvm-objdump --syms --reloc -d file.o > file.s
llvm-objdump --syms --reloc -d program > program.s
比较这两种结果,我们会发现.o文件的反汇编中的函数调用和跳转的偏移量均为0,而可执行文件中不为0。
一般函数调用使用auipc+jalr或jal进行相对地址调用。如果需要调用其他编译单元(.o文件)的函数,则在链接前不知道其地址,需要在重定位表中使用一项R_RISCV_CALL以指示链接器进行重定位。
jal的寻址空间比auipc+jalr小,但是指令数少一条。为了降低链接结果中的指令数量,使用了一个被称为linker relaxation的技术。简单的说,就是编译时所有函数调用指令和跳转指令都使用长形式进行,且跳转偏移均设为0,然后链接时再根据实际情况多次迭代减少指令数直到收敛。
示例
将如下程序
int TestVariadicLeaf(int argument0, int argument1, int argument2, int argument3,
int argument4, int argument5, int argument6, int argument7, int argument8,
int argument9, int argument10, int argument11, ...)
{
return argument0 + argument1 + argument2;
}
int identity(int value)
{
return value;
}
void TestVariadic(void)
{
TestVariadicLeaf(identity(0), identity(1), identity(2), identity(3),
identity(4), identity(5), identity(6), identity(7), identity(8),
identity(9), identity(10), identity(11), identity(12), identity(13));
}
使用clang不加优化编译并链接,之后使用llvm-objdump反汇编,结果如下(已加注释)
00000000000145b4 <TestVariadicLeaf>:
#prolog
145b4: 13 01 01 f9 addi sp, sp, -112 #压栈
145b8: 23 34 11 06 sd ra, 104(sp) #保存返回地址
145bc: 23 30 81 06 sd s0, 96(sp) #保存栈指针fp
145c0: 23 3c 91 04 sd s1, 88(sp) #保存被调方保存寄存器,下同
145c4: 23 38 21 05 sd s2, 80(sp)
145c8: 23 34 31 05 sd s3, 72(sp)
145cc: 23 30 41 05 sd s4, 64(sp)
145d0: 23 3c 51 03 sd s5, 56(sp)
145d4: 13 04 01 07 addi s0, sp, 112 #修改栈指针fp
#body
145d8: 83 32 84 01 ld t0, 24(s0) #load argument11
145dc: 03 33 04 01 ld t1, 16(s0) #load argument10
145e0: 83 33 84 00 ld t2, 8(s0) #load argument9
145e4: 03 3e 04 00 ld t3, 0(s0) #load argument8
145e8: 93 8e 08 00 mv t4, a7 #load argument7
145ec: 13 0f 08 00 mv t5, a6 #load argument6
145f0: 93 8f 07 00 mv t6, a5 #load argument5
145f4: 93 04 07 00 mv s1, a4 #load argument4
145f8: 13 89 06 00 mv s2, a3 #load argument3
145fc: 93 09 06 00 mv s3, a2 #load argument2
14600: 13 8a 05 00 mv s4, a1 #load argument1
14604: 93 0a 05 00 mv s5, a0 #load argument0
14608: 23 22 a4 fc sw a0, -60(s0) #save to local,下同,fp+0到fp-56已被prolog中的内容占用
1460c: 23 20 b4 fc sw a1, -64(s0)
14610: 23 2e c4 fa sw a2, -68(s0)
14614: 23 2c d4 fa sw a3, -72(s0)
14618: 23 2a e4 fa sw a4, -76(s0)
1461c: 23 28 f4 fa sw a5, -80(s0)
14620: 23 26 04 fb sw a6, -84(s0)
14624: 23 24 14 fb sw a7, -88(s0)
14628: 23 22 c4 fb sw t3, -92(s0)
1462c: 23 20 74 fa sw t2, -96(s0)
14630: 23 2e 64 f8 sw t1, -100(s0)
14634: 23 2c 54 f8 sw t0, -104(s0)
14638: 03 25 44 fc lw a0, -60(s0) #load local
1463c: 83 25 04 fc lw a1, -64(s0)
14640: 33 05 b5 00 add a0, a0, a1 #argument0 + argument1
14644: 83 25 c4 fb lw a1, -68(s0)
14648: 3b 05 b5 00 addw a0, a0, a1 #(argument0 + argument1) + argument2
#epilog
1464c: 83 3a 81 03 ld s5, 56(sp) #恢复保存的寄存器,下同
14650: 03 3a 01 04 ld s4, 64(sp)
14654: 83 39 81 04 ld s3, 72(sp)
14658: 03 39 01 05 ld s2, 80(sp)
1465c: 83 34 81 05 ld s1, 88(sp)
14660: 03 34 01 06 ld s0, 96(sp)
14664: 83 30 81 06 ld ra, 104(sp)
14668: 13 01 01 07 addi sp, sp, 112 #退栈
1466c: 67 80 00 00 ret
0000000000014670 <identity>:
#prolog
14670: 13 01 01 fe addi sp, sp, -32
14674: 23 3c 11 00 sd ra, 24(sp)
14678: 23 38 81 00 sd s0, 16(sp)
1467c: 13 04 01 02 addi s0, sp, 32
#body
14680: 93 05 05 00 mv a1, a0
14684: 23 26 a4 fe sw a0, -20(s0)
14688: 03 25 c4 fe lw a0, -20(s0)
#epilog
1468c: 03 34 01 01 ld s0, 16(sp)
14690: 83 30 81 01 ld ra, 24(sp)
14694: 13 01 01 02 addi sp, sp, 32
14698: 67 80 00 00 ret
000000000001469c <TestVariadic>:
#prolog
1469c: 13 01 01 f5 addi sp, sp, -176
146a0: 23 34 11 0a sd ra, 168(sp)
146a4: 23 30 81 0a sd s0, 160(sp)
146a8: 13 04 01 0b addi s0, sp, 176
#body
146ac: 13 05 00 00 mv a0, zero
146b0: ef f0 1f fc jal -64 <identity> #identity(0)
146b4: 93 05 10 00 addi a1, zero, 1
146b8: 23 34 a4 fe sd a0, -24(s0) #save identity(0)
146bc: 13 85 05 00 mv a0, a1
146c0: ef f0 1f fb jal -80 <identity> #identity(1)
146c4: 93 05 20 00 addi a1, zero, 2
146c8: 23 30 a4 fe sd a0, -32(s0) #save identity(1),下同
146cc: 13 85 05 00 mv a0, a1
146d0: ef f0 1f fa jal -96 <identity>
146d4: 93 05 30 00 addi a1, zero, 3
146d8: 23 3c a4 fc sd a0, -40(s0)
146dc: 13 85 05 00 mv a0, a1
146e0: ef f0 1f f9 jal -112 <identity>
146e4: 93 05 40 00 addi a1, zero, 4
146e8: 23 38 a4 fc sd a0, -48(s0)
146ec: 13 85 05 00 mv a0, a1
146f0: ef f0 1f f8 jal -128 <identity>
146f4: 93 05 50 00 addi a1, zero, 5
146f8: 23 34 a4 fc sd a0, -56(s0)
146fc: 13 85 05 00 mv a0, a1
14700: ef f0 1f f7 jal -144 <identity>
14704: 93 05 60 00 addi a1, zero, 6
14708: 23 30 a4 fc sd a0, -64(s0)
1470c: 13 85 05 00 mv a0, a1
14710: ef f0 1f f6 jal -160 <identity>
14714: 93 05 70 00 addi a1, zero, 7
14718: 23 3c a4 fa sd a0, -72(s0)
1471c: 13 85 05 00 mv a0, a1
14720: ef f0 1f f5 jal -176 <identity>
14724: 93 05 80 00 addi a1, zero, 8
14728: 23 38 a4 fa sd a0, -80(s0)
1472c: 13 85 05 00 mv a0, a1
14730: ef f0 1f f4 jal -192 <identity> #identity(8)
14734: 93 05 90 00 addi a1, zero, 9
14738: 23 34 a4 fa sd a0, -88(s0) #save identity(8)
1473c: 13 85 05 00 mv a0, a1
14740: ef f0 1f f3 jal -208 <identity>
14744: 93 05 a0 00 addi a1, zero, 10
14748: 23 30 a4 fa sd a0, -96(s0)
1474c: 13 85 05 00 mv a0, a1
14750: ef f0 1f f2 jal -224 <identity>
14754: 93 05 b0 00 addi a1, zero, 11
14758: 23 3c a4 f8 sd a0, -104(s0)
1475c: 13 85 05 00 mv a0, a1
14760: ef f0 1f f1 jal -240 <identity>
14764: 93 05 c0 00 addi a1, zero, 12
14768: 23 38 a4 f8 sd a0, -112(s0)
1476c: 13 85 05 00 mv a0, a1
14770: ef f0 1f f0 jal -256 <identity>
14774: 93 05 d0 00 addi a1, zero, 13
14778: 23 34 a4 f8 sd a0, -120(s0)
1477c: 13 85 05 00 mv a0, a1
14780: ef f0 1f ef jal -272 <identity> #identity(13)
14784: 93 05 01 00 mv a1, sp #a1为额外参数区头部指针
14788: 23 b4 a5 02 sd a0, 40(a1) #调用函数前将argument13压栈
1478c: 03 35 84 f8 ld a0, -120(s0) #从局部变量load argument12
14790: 23 b0 a5 02 sd a0, 32(a1) #调用函数前将argument12压栈
14794: 03 36 04 f9 ld a2, -112(s0)
14798: 23 bc c5 00 sd a2, 24(a1) #调用函数前将argument11压栈
1479c: 83 36 84 f9 ld a3, -104(s0)
147a0: 23 b8 d5 00 sd a3, 16(a1) #argument10
147a4: 03 37 04 fa ld a4, -96(s0)
147a8: 23 b4 e5 00 sd a4, 8(a1) #argument9
147ac: 83 37 84 fa ld a5, -88(s0)
147b0: 23 b0 f5 00 sd a5, 0(a1) #argument8
147b4: 03 35 84 fe ld a0, -24(s0) #调用函数前将argument0放入寄存器,下同
147b8: 83 35 04 fe ld a1, -32(s0)
147bc: 03 36 84 fd ld a2, -40(s0)
147c0: 83 36 04 fd ld a3, -48(s0)
147c4: 03 37 84 fc ld a4, -56(s0)
147c8: 83 37 04 fc ld a5, -64(s0)
147cc: 03 38 84 fb ld a6, -72(s0)
147d0: 83 38 04 fb ld a7, -80(s0)
147d4: ef f0 1f de jal -544 <TestVariadicLeaf> #调用函数
#epilog
147d8: 03 34 01 0a ld s0, 160(sp)
147dc: 83 30 81 0a ld ra, 168(sp)
147e0: 13 01 01 0b addi sp, sp, 176
147e4: 67 80 00 00 ret
修改记录
2022-01-25 修订栈帧结构