RISC-V汇编-堆栈布局-函数调用

RISC-V assembly - stack layout - function call

目前我正在使用 RISC-V 处理器实现。我需要 运行 部分手工制作的汇编代码。 (最后会有动态代码注入。)为此,我必须了解RISC-V汇编中函数调用的基础知识。

我发现这个主题很有帮助:confusion about function call stack

但我仍在为函数调用的堆栈布局而苦苦挣扎。请考虑以下 C 代码:

void some_func(int a, int b, int* c){
   int cnt = a;
   for(;cnt > 0;cnt--){
      *c += b;
   }
}

void main(){
   int a = 5;
   int b = 6;
   int c = 0;

   some_func(a,b,&c);
}

此程序通过一系列加法实现基本乘法。派生的汇编代码 (riscv64-unknown-elf-gcc -nostartfiles mul.c -o mul && riscv64-unknown-elf-objdump -D mul) 如下所示:

0000000000010000 <some_func>:
   10000:   fd010113            addi    sp,sp,-48
   10004:   02813423            sd  s0,40(sp)
   10008:   03010413            addi    s0,sp,48
   1000c:   fca42e23            sw  a0,-36(s0)
   10010:   fcb42c23            sw  a1,-40(s0)
   10014:   fcc43823            sd  a2,-48(s0)
   10018:   fdc42783            lw  a5,-36(s0)
   1001c:   fef42623            sw  a5,-20(s0)
   10020:   0280006f            j   10048 <some_func+0x48>
   10024:   fd043783            ld  a5,-48(s0)
   10028:   0007a703            lw  a4,0(a5)
   1002c:   fd842783            lw  a5,-40(s0)
   10030:   00f7073b            addw    a4,a4,a5
   10034:   fd043783            ld  a5,-48(s0)
   10038:   00e7a023            sw  a4,0(a5)
   1003c:   fec42783            lw  a5,-20(s0)
   10040:   fff7879b            addiw   a5,a5,-1
   10044:   fef42623            sw  a5,-20(s0)
   10048:   fec42783            lw  a5,-20(s0)
   1004c:   fcf04ce3            bgtz    a5,10024 <some_func+0x24>
   10050:   00000013            nop
   10054:   02813403            ld  s0,40(sp)
   10058:   03010113            addi    sp,sp,48
   1005c:   00008067            ret

0000000000010060 <main>:
   10060:   fe010113            addi    sp,sp,-32
   10064:   00113c23            sd  ra,24(sp)
   10068:   00813823            sd  s0,16(sp)
   1006c:   02010413            addi    s0,sp,32
   10070:   00500793            li  a5,5
   10074:   fef42623            sw  a5,-20(s0)
   10078:   00600793            li  a5,6
   1007c:   fef42423            sw  a5,-24(s0)
   10080:   fe042223            sw  zero,-28(s0)
   10084:   fe440793            addi    a5,s0,-28
   10088:   00078613            mv  a2,a5
   1008c:   fe842583            lw  a1,-24(s0)
   10090:   fec42503            lw  a0,-20(s0)
   10094:   f6dff0ef            jal 10000 <some_func>
   10098:   00000013            nop
   1009c:   01813083            ld  ra,24(sp)
   100a0:   01013403            ld  s0,16(sp)
   100a4:   02010113            addi    sp,sp,32
   100a8:   00008067            ret

需要说明的重要步骤是:(some_func(int,int,int))

   10060:   fe010113            addi    sp,sp,-32
   10064:   00113c23            sd  ra,24(sp)
   10068:   00813823            sd  s0,16(sp)
   1006c:   02010413            addi    s0,sp,32

和:(main())

   10000:   fd010113            addi    sp,sp,-48
   10004:   02813423            sd  s0,40(sp)
   10008:   03010413            addi    s0,sp,48

根据我的理解:堆栈指针被移动到 space 用于 return-地址和参数。 (main 可能是这里的特例。)传递的参数位于堆栈时如何处理?他们如何取回?总的来说,方法论对我来说很清楚,但我要如何手动编写这个片段才能工作。

关于相关主题,堆栈应该有点像

| ???                            |
| params for some_func() <???>   |
| ra of some_func()              |
| locals of main()       <int c> |
| locals of main()       <int b> |
| locals of main()       <int a> |
| params for main()      <None>  |

但仅此而已。谁能指出这是如何安排的,以及这两个列表(函数调用)如何相互关联?

前几个参数(类型允许)在寄存器中传递,因此它们甚至不会出现在堆栈中。除此之外,不清楚你真正想知道什么。如果您确实获得了堆栈上的一些参数,即使在您调整堆栈指针后它们仍会保留在那里,因此您仍然可以相对于调整后的堆栈指针或帧指针(这里显然是 $s0)来寻址它们。

The important steps, that need clarification are:

   10060:   fe010113            addi    sp,sp,-32  # allocate space
   10064:   00113c23            sd  ra,24(sp)      # save $ra
   10068:   00813823            sd  s0,16(sp)      # save $s0
   1006c:   02010413            addi    s0,sp,32   # set up $s0 as frame pointer

RISC-V 规定了你想知道的calling conventions

要点:

函数参数通常在 a0a7 寄存器中传递,而不是在堆栈中传递。如果 a* 寄存器中没有剩余空间,则仅通过堆栈传递参数。

一些寄存器是调用者保存的,而另一些是被调用者保存的(参见 Table 26.1,第 26 章 RISC-V 汇编程序员手册,在 RISC-V base specification,2019-06-08 批准)。这意味着在调用函数之前,调用者必须将所有调用者保存的寄存器保存到堆栈中,如果它想保留它们的内容。同样,如果被调用函数想要将它们用于自己的目的,则必须将所有被调用者保存的寄存器保存到堆栈中。