Сборка RISC-V - компоновка стека - вызов функции

В настоящее время я работаю с реализацией процессора 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

Из моего понимания: указатель стека перемещен, чтобы освободить место для адреса возврата и параметров. (Здесь может быть особый случай.) Как обрабатываются переданные аргументы, когда они находятся в стеке? Как они получены обратно? В общем, методология мне понятна, но как бы я вручную кодировал этот сегмент, чтобы работать.

Что касается смежной темы, стек должен выглядеть примерно так

| ???                            |
| 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>  |

Но это в значительной степени так. Кто-нибудь может указать, как это устроено, и как эти два списка (вызов функции) связаны друг с другом?

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

То, что вы хотите знать, определяется соглашениями о вызовах RISC-V.

Основные моменты:

Аргументы функции обычно передаются в a0 к a7регистры, а не в стеке. Аргумент передается через стек, только если в стеке нет места.a* регистров осталось.

Некоторые регистры сохраняются для вызывающего абонента, а другие - для вызываемого (см. Таблицу 26.1, Глава 26 Руководство программиста сборки RISC-V в базовой спецификации RISC-V, ратифицировано 08.06.2019). Это означает, что перед вызовом функции вызывающая сторона должна сохранить все сохраненные регистры вызывающей стороны в стек, если она хочет сохранить их содержимое. Точно так же вызываемая функция должна сохранить все сохраненные регистры вызываемого объекта в стек, если она хочет использовать их в своих целях.

Другие вопросы по тегам