2024年3月24日发(作者:)
arm函数调用中的堆栈变化
在计算机的运行过程中,函数调用是一种常见的操
作。当程序调用一个函数时,需要先将当前的运行状态
(例如当前指令的地址、堆栈指针等)保存在堆栈中,然
后跳转到函数中执行。函数执行完毕后,再从堆栈中恢复
之前的运行状态,继续执行原来的程序。这个过程中,堆
栈扮演了一个非常重要的角色。本文将介绍在ARM架构
中,函数调用时堆栈的变化。
1. 堆栈的基本概念
在程序中,有一片内存区域被用来存放函数的局部变
量和一些临时变量,称为堆栈。堆栈是一个先进后出的数
据结构,即最后存入的数据最先弹出。当程序调用一个函
数时,会在堆栈中分配一段空间来存放函数的参数、局部
变量、返回地址等信息。当函数返回时,这些信息会从堆
栈中弹出,恢复程序之前的状态。
在ARM架构中,堆栈的地址是4字节对齐的,即堆栈
指针(SP)的值必须是4的倍数。这是因为ARM指令集中
的大多数指令都是以4字节为单位的,如果SP不是4字节
对齐的,那么执行指令时会出错。
2. 函数调用时堆栈的变化
当程序调用一个函数时,堆栈的变化可以分为以下几
个步骤:
(1)保存寄存器
在ARM架构中,函数调用过程中一些重要的状态信息
通常保存在寄存器中。为了不影响原程序的运行,需要在
堆栈中保存这些寄存器的值。这些寄存器包括:R0~R3(函
数参数)、R14(LR,返回地址)、R13(SP,堆栈指
针)、R11(FP,帧指针)等。在进入函数之前,需要将这
些寄存器的值压入堆栈中。具体操作为:
``` PUSH {R0-R3, R11, LR} ```
以上指令将R0~R3、R11、LR的值压入堆栈中。其中,
LR保存的是返回地址,R11保存的是帧指针(Frame
Pointer,FP)。FP是一个指针,指向当前函数的栈帧,用
于访问局部变量。堆栈指针SP也需要被保存,但不是在这
里保存,而是在进入函数之前保存。
(2)分配空间
在堆栈中为当前函数分配空间,用于存放参数、局部
变量和其它临时变量。分配的空间的大小由当前函数所需
的局部变量和参数决定。在分配空间之前,要先保存堆栈
指针SP的值。具体操作为:
``` SUB SP, SP, #8 ```
以上指令将SP减去8,表示在堆栈中为当前函数分配
8字节的空间。这个空间可以用来存放函数的参数和临时变
量。注意,这里分配的空间大小必须是4字节的倍数,以
保证堆栈是4字节对齐的。
(3)传递参数
将函数的参数传递给被调用的函数。ARM中的前4个
参数可以直接存放在R0~R3中,超过4个参数的部分需要
压入堆栈中。如果有浮点类型的参数,需要使用寄存器
S0~S15和D0~D7来传递。具体操作为:
``` MOV R0, #1 MOV R1, #2 MOV R2,
#3 MOV R3, #4 ```
以上指令将4个整型参数传递给被调用的函数。如果
有超过4个参数的部分,需要使用STMFD(Store Multiple
with Decrement)指令将多余的参数压入堆栈中。例如:
``` STMFD SP!, {R4-R7} ```
以上指令将R4~R7的值压入堆栈中,并将堆栈指针SP
的值减去16(4个寄存器,每个寄存器4字节),以指向
下一个空闲的位置。
(4)调用函数
跳转到被调用函数的入口地址,并将返回地址(函数
调用后要返回的地址)保存在LR寄存器中。具体操作为:
``` BL subfunction ```
以上指令调用名为subfunction的子函数,并将跳转
前的下一条指令地址保存在LR寄存器中。BL指令实际上是
一个伪指令,它会将当前指令的地址加上8(因为ARM指令
的长度是4字节),然后将计算出的地址保存在LR寄存器
中。返回地址的值等于LR寄存器的值,因此LR寄存器中
的值是调用函数前的指令地址。
(5)执行被调用函数
被调用函数开始执行,并分配自己的局部变量和临时
变量。这些变量存放在被调用函数的栈帧中,由帧指针FP
来访问。堆栈指针SP也会变化,指向当前函数的栈顶。函
数执行过程中,需要访问局部变量和参数,可以使用FP和
SP来计算变量的地址。例如:
``` MOV R4, R0 ADD R4, R4, R1 STR
R4, [FP, #-4] ```
以上指令将函数的前两个参数相加,并将结果存放在
局部变量中。注意,这里使用FP来访问局部变量,使用SP
来访问栈空间中存放的参数。
(6)返回函数
被调用函数执行结束后,需要将结果返回给调用函
数。在ARM中,函数的返回值通常存放在R0中。返回函数
的指令为:
``` MOV R0, #0 POP {R0-R3, R11, PC} ```
以上指令将R0中的值作为函数的返回值,并从堆栈中
弹出寄存器的值。注意,返回地址保存在LR寄存器中,可
以使用POP指令一并弹出,并跳转到返回地址。POP指令的
参数格式和PUSH指令相反,用于弹出堆栈中保存的寄存器
值。
3. 总结
在ARM架构中,函数调用时堆栈的变化涉及到寄存器
的保存、空间的分配、参数的传递、函数的调用和返回,
以及局部变量和临时变量的使用。了解堆栈的变化对于理
解ARM汇编语言和优化函数调用非常有帮助。可以通过调
试器或者汇编代码来观察堆栈的变化,加深对堆栈的理
解。


发布评论