Ianus Inferus/地狱门神

Exoptatus infera advenisti.

View on GitHub Go Back

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 修订栈帧结构

Go Back