ARMV8 - A64 - 函数调用,内存栈操作

说明

  • 看了下ARM平台上C语言函数调用的反汇编代码,理清楚了其中的内存栈汇编操作,特整理下。
  • 本文环境基于:ARMv8-a架构A53核soc,aarch64状态。

预先了解的知识点

内存栈

相关寄存器

  1. FP:Frame Pointer(栈帧指针),指向当前栈帧的顶部,在A53平台是使用通用寄存器x29保存。
  2. SP:Stack Pointer(栈顶指针),保存当前栈顶地址,在A53平台是一个特殊寄存器,不同异常等级是不同的寄存器,
  3. LR:Link Register(链接寄存器),保存子函数运行结束后的返回地址(跳转指令的下一条指令地址),在A53平台是使用通用寄存器x30充当,详细使用请看bl和ret指令说明。
  • 问题:初次了解,不好理解和区分FP和SP的作用和角色,SP是全局唯一的保存栈顶地址的寄存器,而FP是保存单个函数的栈帧基址,调用新函数,入栈操作结束后,需要将SP的值赋值给FP,类似于:SP是全局变量,而x29是局部变量,虽然大部分时刻两个寄存器值是一样的。

相关汇编指令

实例

  • C源码(a.c)

    #include <stdio.h>

    int test1()
    {
    return test(1, 2);
    }

    int test(int a, int b)
    {
    return a+b;
    }

    int main()
    {
    test1();
    return 0;
    }

  • 对应的汇编代码(aarch64-linux-gnu-gcc -S a.c)

    复制代码
      .arch armv8-a
      .file   "a.c"
      .text
      .align  2
      .global test1
      .type   test1, %function

    test1:
    stp x29, x30, [sp, -16]!
    add x29, sp, 0
    mov w1, 2
    mov w0, 1
    bl test
    ldp x29, x30, [sp], 16
    ret
    .size test1, .-test1
    .align 2
    .global test
    .type test, %function
    test:
    sub sp, sp, #16
    str w0, [sp, 12]
    str w1, [sp, 8]
    ldr w1, [sp, 12]
    ldr w0, [sp, 8]
    add w0, w1, w0
    add sp, sp, 16
    ret
    .size test, .-test
    .align 2
    .global main
    .type main, %function
    main:
    stp x29, x30, [sp, -16]!
    add x29, sp, 0
    bl test1
    mov w0, 0
    ldp x29, x30, [sp], 16
    ret
    .size main, .-main
    .ident "GCC: (Linaro GCC 6.3-2017.05) 6.3.1 20170404"
    .section .note.GNU-stack,"",@progbits

说明

  • 从汇编代码可以看出存在两种不同实现,如下:
  1. 函数调用栈中间函数(test1)
  2. 函数调用栈末端函数(test)

中间函数

复制代码
test1:
    stp x29, x30, [sp, -16]! //将栈空间扩大16字节(更改sp寄存器值),再将x29,x30的数据(遗传自父函数)保存到栈顶 
    add x29, sp, 0 //将栈顶地址(sp)即此函数的栈帧基址保存到x29,
    ...  //函数操作(省略)
    bl  test //跳转到test函数执行
    ldp x29, x30, [sp], 16 //将栈顶数据load到x29,x30中,再缩小栈空间16字节(即将sp恢复到父函数的栈顶)
    ret //返回父函数

末端函数

复制代码
test:
    sub sp, sp, #16 //将sp保存的数据减小16字节,即将栈空间扩大16字节
    ... //函数操作(省略)
    add sp, sp, 16 //将sp保存的数据增加16字节,即将栈空间缩小16字节
    ret

问题

  1. 为什么中间函数和末端函数实现不同,中间函数需要将x29,x30保存到栈内存中,最后再从栈内存中load到x29,x30中。
  • 是因为中间函数(test1)bl指令调用末端函数(test)时,会覆盖掉x30的数据(原本保存的是父函数main,跳转test1的下一条指令),覆盖后中间函数(test1)的ret指令就跳不回main函数了,因此需要先将x30的数据保存到栈上,从子函数跳转回来后,需要将x29,x30的数据从栈上恢复。
  • x29是栈帧指针,保存是当前函数的frame pointer,是约定俗成,因此需要保存和恢复,但是也不是必须,例如:test函数中就没有使用x29。

注意项

  1. sp 必须16Byte 对齐,扩大和缩小都必须是16字节的倍数。
相关推荐
亿道电子Emdoor1 小时前
【Arm】如何高效实现2兆字节对齐
arm开发·单片机·mcu
姓刘的哦4 小时前
STM32的启动流分析
linux·运维·arm开发
zlinear数据采集卡6 小时前
FRAM铁电存储器原理深度解析:D223校准参数的“永久保险箱“
arm开发·人工智能·stm32·单片机·fpga开发·开源
喜欢吃燃面1 天前
Linux网络编程深度解析:从Socket API到自定义协议设计
linux·网络·arm开发
rcms152702692181 天前
DS200ADCIH1ADB 输入 / 输出板
arm开发
又见情义1 天前
RK3568 Android 13 移植 EC20-CE 4G 模块全记录
android·arm开发·驱动开发
Black_Rock_br2 天前
ARM服务器固件FVP仿真开发流程深度解析实战指南
arm开发·人工智能·嵌入式硬件·硬件工程
又见情义2 天前
RK3568 Android 13驱动适配-WiFi&BT模块(RTL8822CS)
android·arm开发·驱动开发
DevHub2 天前
STM32 USB HID 教程:TinyUSB 实现键盘设备,从描述符到枚举
arm开发·stm32·单片机·嵌入式硬件·mcu·计算机外设
蓝速科技2 天前
蓝速鸿蒙信创终端长效流畅性深度评测
arm开发·华为·harmonyos