ARM64基础 -- 栈帧管理示例
ARM64 架构中的栈帧管理示例:x29
和 x30
的使用
以下是一个完整的 ARM64 函数调用示例,展示了 x29
(帧指针)和 x30
(链接寄存器/返回地址)的使用过程。
1. 函数入口:保存上下文
假设我们有一个简单的函数 foo
,在进入函数时使用以下指令:
foo:
stp x29, x30, [sp, #-16]! // 保存旧的帧指针和返回地址,并更新 SP
mov x29, sp // 设置新的帧指针
初始状态:
- 假设
sp = 0x1000
。 - 寄存器
x29
(帧指针)保存了调用者的栈帧基址,例如x29 = 0x2000
。 - 寄存器
x30
(返回地址)保存了调用者的返回地址,例如x30 = 0x3000
。
执行 stp x29, x30, [sp, #-16]!
后:
sp
减少 16 字节:sp = 0x1000 - 16 = 0x0FF0
。- 保存
x29
和x30
:x29
(值为0x2000
)被存储在内存地址0x0FF0
。x30
(值为0x3000
)被存储在内存地址0x0FF8
。
内存布局:
地址 值
0x0FF0: 0x2000 // 保存的旧帧指针(x29)
0x0FF8: 0x3000 // 保存的返回地址(x30)
sp = 0x0FF0
执行 mov x29, sp
后:
- 更新帧指针:
x29 = sp = 0x0FF0
,此时x29
指向当前函数的栈帧基址。
2. 函数体:执行代码
在函数 foo
内部可以执行各种操作,期间 sp
和 x29
不会改变指向的栈帧。
3. 函数结束:恢复上下文
在函数即将结束时,使用以下指令恢复之前保存的上下文:
ldp x29, x30, [sp], #16 // 恢复旧的帧指针和返回地址,并更新 SP
ret // 返回到调用者
执行 ldp x29, x30, [sp], #16
前:
sp = 0x0FF0
- 内存布局:
地址 值 0x0FF0: 0x2000 // 保存的旧帧指针(x29) 0x0FF8: 0x3000 // 保存的返回地址(x30)
执行 ldp x29, x30, [sp], #16
后:
- 从内存中恢复
x29
和x30
:x29
恢复为0x2000
(旧帧指针)。x30
恢复为0x3000
(返回地址)。
sp
增加 16 字节:sp = 0x0FF0 + 16 = 0x1000
,恢复到调用函数前的值。
内存布局(执行完毕后):
sp = 0x1000
4. 函数返回
最后,执行 ret
指令,跳转到 x30
所保存的返回地址 0x3000
,函数执行结束,程序返回到调用者。
5. 总结
-
函数入口时:
- 使用
stp
指令保存了x29
和x30
,并将sp
减少 16 字节以分配新的栈帧。 sp
从0x1000
变为0x0FF0
。- 栈上存储了
0x2000
(旧的x29
)和0x3000
(旧的x30
)。
- 使用
-
函数结束时:
- 使用
ldp
指令恢复x29
和x30
,并将sp
增加 16 字节以恢复到函数调用前的状态。 sp
恢复到0x1000
。
- 使用
这个过程确保了函数执行时保存和恢复了必要的上下文(帧指针和返回地址),以便函数能够正确返回并保持调用栈的完整性。