首页 > 其他分享 >函数调用约定中寄存器的保存问题

函数调用约定中寄存器的保存问题

时间:2024-03-29 09:56:33浏览次数:23  
标签:fp s0 int 约定 sp sw 函数调用 a5 寄存器

函数调用约定中寄存器的保存问题
calling convention

Created: 2024-03-25T17:03+08:00
Published: 2024-03-29T09:50+08:00
Categories: Compiler

目录

一个方便的在线查看汇编网站:Compiler Explorer

函数调用时,caller 和 callee 的寄存器保存问题。

callee 至少能看到:

  1. local variables:自己的局部变量
  2. function arguments:调用自己的函数参数
  3. return address: 要返回到的 caller 的地址

基于 risc-v(32-bits) gcc(trunk),执行过程中:

  1. local variables 由 stack pointer(sp 寄存器)索引
  2. function arguments 由 frame pointer(fp 寄存器,也叫做 s0)索引
  3. caller 的信息,如 return address 和 caller 的 fp 在 frame pointer 下面,sp 指向的栈区高地址

例子

int foo(int p0, int p1, int p2, int p3, int p4, int p5, int p6, int p7, int p8, int p9, int p10) {
    int a = 1;
    int b = 2;
    return p0 + p1 + a;
}

int main() {
    foo(0,1,2,3,4,5,6,7,8,9,10);
    return 0;
}

对应汇编如下:

# call foo(0,1,2,3,4,5,6,7,8,9,10) in main():
li      a5,10
sw      a5,8(sp)
li      a5,9
sw      a5,4(sp)
li      a5,8
sw      a5,0(sp)
li      a7,7
li      a6,6
li      a5,5
li      a4,4
li      a3,3
li      a2,2
li      a1,1
li      a0,0
call    foo(int, int, int, int, int, int, int, int, int, int, int)
  1. 函数参数压栈的顺序是从后往前,10 先被压栈,然后是 9 ……
  2. 把 foo 的参数放到 sp 上,sp[0] 就是 倒数第一个参数,sp[1] 是倒数第二个参数,……

这样 foo 作为 callee,看到的内存和寄存器的场景就是:

  1. 前 8 个参数在寄存器上
  2. sp 指向了函数第 8 个以后的参数

说明现在 callee 的参数是由 sp 索引的

risc-v 32-bits gcc(trunk) 这样处理 fp 和 sp:

  1. local variables 由 stack pointer 索引
  2. function arguments 由 frame pointer 索引
  3. caller 的 ra 等存储在 sp 指向的栈区高地址

callee 要执行 fp=sp,这样就可以用 fp 索引调用参数了,
但是现在 foo 看到的 fp 是前文 main 的函数参数索引,
如果直接执行 fp=sp,原来 fp 的值就被覆盖了,所以要把 fp 保存起来。
这就是 foo 函数开头的逻辑:

  1. 存储 caller 的 ra 和 fp
  2. 修改 fp 和 sp 方便自己调用

foo 最后的逻辑就是把 fp、ra 恢复到 main 调用前的状态

caller-callee

foo(int, int, int, int, int, int, int, int, int, int, int):
        addi    sp,sp,-64   # save a stack for local variables
        sw      ra,60(sp)
        sw      s0,56(sp)
        addi    s0,sp,64    # adjust fp to previous stack pointer to index arguments
        sw      a0,-36(s0)
        sw      a1,-40(s0)
        sw      a2,-44(s0)
        sw      a3,-48(s0)
        sw      a4,-52(s0)
        sw      a5,-56(s0)
        sw      a6,-60(s0)
        sw      a7,-64(s0)
        li      a5,1
        sw      a5,-20(s0)
        li      a5,2
        sw      a5,-24(s0)
        lw      a4,-36(s0)
        lw      a5,-40(s0)
        add     a4,a4,a5
        lw      a5,-20(s0)
        add     a5,a4,a5
        mv      a0,a5       # result in a0
        lw      ra,60(sp)
        lw      s0,56(sp)	# restore register fp for caller
        addi    sp,sp,64    # restore register sp for caller
        jr      ra

caller-save 和 callee-save

callee-save 的意思是说,如果 callee 想要用这个寄存器,那么就要预先保存好里面的值,用完了以后恢复。
比如 fp 和 sp 就是 callee-save,caller 将这两个寄存器交给 callee,callee 为了自己的方便,调整它们的位置,
但是最后返回的时候,fp 和 sp 要像调用 callee 之前一样。

所以,callee-save 的寄存器在函数调用前后保持不变。

caller-callee

标签:fp,s0,int,约定,sp,sw,函数调用,a5,寄存器
From: https://www.cnblogs.com/ticlab/p/18103119

相关文章

  • [Blazor] 学习随笔——呈现约定
    中文版从父组件应用一组已更新的参数之后。为级联参数应用已更新的值之后。通知事件并调用其自己的某个事件处理程序之后。在调用其自己的StateHasChanged方法后英文版Afterapplyinganupdatedsetofparametersfromaparentcomponent.Afterapplyinganupdate......
  • UDS诊断协议一起学习——5应用层协议-5.4服务描述约定
    5.4服务描述约定5.4.1服务描述    上回书咱们说到哪儿了我也给忘了,详情大家往前去翻一翻,这回书咱们接着上回书继续说,咱们继续介绍应用层服务的相关知识。    协议中此部分内容是约定俗称的,不做多余赘述,接下来主要是介绍A_PDU的相关内容,A_PDU:应用层,协议数......
  • [C#] .NET8增加了Arm架构的多寄存器的查表函数(VectorTableLookup/VectorTableLookupEx
    作者:zyl910发现.NET8增加了Arm架构的多寄存器的查表函数(VectorTableLookup/VectorTableLookupExtension),这给编写SIMD向量化算法带来了方便。一、指令说明在学习Arm的AdvSimd(Neon)指令集时,发现它的Lookup(查表)功能,类似X86的Sse系列指令集中的字节Shuffle(换位。如_mm_shuffle_epi......
  • Day 15(操作符)赋值+单目+关系+逻辑+条件+逗号表达式+下标引用+函数调用
    1.赋值操作符:=   复合赋值符:+=         -=       *=       /=     &=      |=     ^=       %=    >>=    <<=eg: a=a+2→a+=2  a=a>>1→a>>=1连续赋值:a=b=c(从右向左运行)(不推荐此方法)2......
  • C++ static函数调用问题
    静态成员变量虽然在类中,但它并不是随对象的建立而分配空间的,也不是随对象的撤销而释放(一般的成员在对象建立时会分配空间,在对象撤销时会释放)。静态成员变量是在程序编译时分配空间,而在程序结束时释放空间。静态成员的定义和声明要加个关键static。静态成员可以通过双冒号来使用......
  • Vertx实战之如何追踪异步函数调用
    Vertx实战之如何追踪异步函数调用穹柏关注IP属地:上海0.1922021.06.1016:29:51字数2,257阅读1,043背景日常开发中我们经常需要处理各种系统问题,而这些系统问题通常由一些非预期的因素引起(比如非预期的输入,内存不够,网络波动等)。此时就需要知道本次系统问题影响了谁......
  • 【ARM 嵌入式 C 入门及渐进11 -- 确保数据写入寄存器】
    文章目录背景1.内存障碍2.对齐访问3.缓存一致性4.写缓冲区背景在ARM架构中,要确保通过write函数写入的数据真正地被写入到寄存器中,需要考虑几个方面:内存障碍(MemoryBarrier):使用内存障碍指令来确保之前的所有内存操作完成后再执行后续的指令。对齐访问:确保......
  • 19笔试真题:看程序写结果,含有内嵌对象的类的构造函数调用次序
    看程序写结果,含有内嵌对象的类的构造函数调用次序#include<iostream>usingnamespacestd;classStudent1{public:Student1(){cout<<"Student1+"<<endl;}~Student1(){cout<<"-Student1"<<endl;}};classStudent......
  • ARMv8 寄存器
    本文主要介绍Armv8/v9指令集架构中常用部分,详细的还是要看Armarchitecturereferencemanual.ARMv8架构ARMv8架构支持3种指令集:T32,A32,A64ARMv8架构有两种执行状态:AArch32,AArch64一个App可以混合使用T32和A32,但是不能混合使用A32和A64.Registers......
  • C++函数调用优化
    C++函数调用优化施磊老师网课笔记截图1、用临时对象拷贝构造一个新对象的时候,编译器会对其优化,直接用生成临时对象的方法构造新对象;......