PUSH
目录
什么是 PUSH?
PUSH 指令用于将数据保存到栈内存中。栈是一种"后进先出"(LIFO)的内存区域,主要用于函数调用时保存现场,同时也可能是要开辟空间给后面用。
作用
- 保存上层函数现场,本层函数执行完后恢复
- 开辟空间给后面用
基本语法
PUSH {寄存器列表}
工作原理
栈的生长方向
在ARM架构中,栈通常是向下生长的: - 栈指针(SP) 指向栈顶 - PUSH时,SP减小,数据存入栈中 - POP时,SP增加,数据从栈中恢复
执行过程
PUSH {R0, R1, LR}
实际执行步骤:
1. SP = SP - 12(为3个寄存器分配空间,每个4字节,因为ARM架构为32位)
1. 将LR存入 [SP+8]
2. 将R1存入 [SP+4]
3. 将R0存入 [SP+0]
示例分析
示例1:基本用法
0x08002F34 B503 .. PUSH {R0, R1, LR}
执行前:
SP = 0x20001000
栈内存:未知
执行后:
SP = 0x20000FF4 (SP减少了12字节)
栈内存:
0x20000FF4: R0的值
0x20000FF8: R1的值
0x20000FFC: LR的值
示例2:函数开头的典型用法
0x08002F00 B570 p. PUSH {R4-R6, LR}
- 保存R4、R5、R6和LR四个寄存器
- 为每个寄存器分配4字节,共16字节栈空间
- SP减少16
示例3:保存多个寄存器
0x08002F10 E92D4FF0 -O.. PUSH {R4-R11, LR}
- 保存R4到R11共8个寄存器,加上LR共9个
- 分配36字节栈空间(9×4)
- SP减少36
PUSH 的作用
1. 函数调用保存现场
my_function:
PUSH {R4-R6, LR} ; 保存要用到的寄存器和返回地址
; ... 函数体 ...
POP {R4-R6, PC} ; 恢复寄存器并返回
2. 临时保存寄存器值
当寄存器不够用时,把不常用的寄存器暂时保存到栈上。
3. 中断处理
发生中断时,自动保存关键寄存器到栈中。
与 POP 指令配对使用
PUSH 和 POP 必须成对使用,确保栈平衡:
; 正确的用法
PUSH {R0, R1, LR} ; 进入时保存
; ... 一些操作 ...
POP {R0, R1, PC} ; 退出时恢复,用PC替代LR实现返回
; 错误的用法会导致栈崩溃!
PUSH {R0, R1, LR}
; ... 忘记POP ...
; 结果:栈指针错乱,程序崩溃
代码示例
完整的函数模板
my_function:
PUSH {R4, R5, LR} ; 1. 保存寄存器
SUB SP, SP, #8 ; 2. 为局部变量分配空间
; 函数体代码
MOV R4, R0 ; 使用保存的寄存器
MOV R5, #100
ADD R0, R4, R5
ADD SP, SP, #8 ; 3. 释放局部变量空间
POP {R4, R5, PC} ; 4. 恢复寄存器并返回
重要注意事项
- 栈对齐:SP必须保持4字节或8字节对齐
- 寄存器顺序:PUSH/POP的寄存器列表会自动按编号排序
- LR处理:PUSH保存LR,POP时通常用PC来同时恢复和返回
- 栈平衡:PUSH和POP必须数量匹配,否则栈会损坏
总结
PUSH 是ARM汇编中最重要的指令之一,它:
- 保存寄存器到栈内存
- 为函数调用建立工作环境
- 保护现场不被破坏
- 必须与POP配对使用