今天在写DragonOS的进程切换代码的时候,对于ra寄存器的设置与保存有点疑惑,于是写这篇文章来分析一下。

探索过程

我编写了这样的一个C程序:

#include <stdio.h>

void b() {
  printf("b\n");
}

void a() {
  printf("a\n");
  b();
}

int main() {
  a();

  return 0;
}

接着使用以下命令编译:

riscv64-linux-musl-gcc -static -o a a.c

接着反汇编:

riscv64-linux-musl-objdump -D a > a.txt

于是得到了以下片段:


0000000000010216 <b>:
   10216:	1141                	addi	sp,sp,-16
   10218:	e406                	sd	ra,8(sp)
   1021a:	e022                	sd	s0,0(sp)
   1021c:	0800                	addi	s0,sp,16
   1021e:	67c5                	lui	a5,0x11
   10220:	23878513          	addi	a0,a5,568 # 11238 <__errno_location+0xa>
   10224:	228000ef          	jal	ra,1044c <puts>
   10228:	0001                	nop
   1022a:	60a2                	ld	ra,8(sp)
   1022c:	6402                	ld	s0,0(sp)
   1022e:	0141                	addi	sp,sp,16
   10230:	8082                	ret

0000000000010232 <a>:
   10232:	1141                	addi	sp,sp,-16
   10234:	e406                	sd	ra,8(sp)
   10236:	e022                	sd	s0,0(sp)
   10238:	0800                	addi	s0,sp,16
   1023a:	67c5                	lui	a5,0x11
   1023c:	24078513          	addi	a0,a5,576 # 11240 <__errno_location+0x12>
   10240:	20c000ef          	jal	ra,1044c <puts>
   10244:	fd3ff0ef          	jal	ra,10216 <b>
   10248:	0001                	nop
   1024a:	60a2                	ld	ra,8(sp)
   1024c:	6402                	ld	s0,0(sp)
   1024e:	0141                	addi	sp,sp,16
   10250:	8082                	ret

0000000000010252 <main>:
   10252:	1141                	addi	sp,sp,-16
   10254:	e406                	sd	ra,8(sp)
   10256:	e022                	sd	s0,0(sp)
   10258:	0800                	addi	s0,sp,16
   1025a:	fd9ff0ef          	jal	ra,10232 <a>
   1025e:	4781                	li	a5,0
   10260:	853e                	mv	a0,a5
   10262:	60a2                	ld	ra,8(sp)
   10264:	6402                	ld	s0,0(sp)
   10266:	0141                	addi	sp,sp,16
   10268:	8082                	ret

分析

可以看到:

  • main函数使用jal指令,跳转到函数a,并且设置了ra寄存器(这是jal指令的操作)。
  • 接着在函数a()内,首先把ra寄存器的值存储到栈上,然后开始执行其他操作。
  • 在函数a()将要返回的时候,从栈上取出ra的值,并设置到ra寄存器内,于是ret指令就能返回到main函数了。

因此,riscv是“调用者设置ra,被调用者保存ra到栈上”。

转载请注明来源:https://longjin666.cn/?p=1852(在新窗口中打开)

欢迎关注我的公众号“灯珑”,让我们一起了解更多的事物~

你也可能喜欢

发表评论