3

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

我发现这个主题很有帮助:关于函数调用堆栈的混淆

但我仍在为函数调用的堆栈布局而苦苦挣扎。请考虑以下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

据我了解:堆栈指针被移动以为返回地址和参数腾出空间。(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>  |

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

4

2 回答 2

2

您想知道的内容由 RISC-V调用约定指定。

要点:

函数参数通常在a0toa7寄存器中传递,而不是在堆栈中。仅当寄存器中没有空间时,才通过堆栈传递参数a*

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

于 2020-02-16T18:00:08.817 回答
1

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

需要澄清的重要步骤是:

   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
于 2015-12-09T15:41:43.307 回答