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汇编语言和优化函数调用非常有帮助。可以通过调

试器或者汇编代码来观察堆栈的变化,加深对堆栈的理

解。