什么是栈?
堆栈严格来说应该叫做栈,栈(Stack)是限定仅在一端进行插入或删除操作的线性表。因此,对栈来说,可以进行插入或删除操作的一端端称为栈顶(top),相应地,另一端称为栈底(bottom)。由于堆栈只允许在一端进行操作,因而按照后进先出(LIFO-Last In First Out)的原理运作。
会变化的栈顶
从栈顶的定义来看,栈顶的位置是可变的。空栈时,栈顶和栈底重合;满栈时,栈顶离栈底最远。ARM为堆栈提供了硬件支持,它使用一个专门的寄存器(堆栈指针)指向堆栈的栈顶。
两种存储器堆栈
递增堆栈:向上生长:向高地址方向生长
递减堆栈:向下生长:向低地址方向生长
ARM堆栈的生长方向
虽然ARM处理器核对于两种生长方式的堆栈均支持,但ADS的C语言编译器仅支持一种方式,即从上往下长,并且必须是满递减堆栈。所以STMFD等指令用的最多。
寄存器
寄存器是用来存储CPU在计算过程中临时数据的, arm64有32个通用寄存器(这里不对浮点数/向量寄存器等做说明):
x0-x31, 这些寄存器可以直接在汇编代码里面使用, 也是最经常被使用到的寄存器.
- SP寄存器, Stack Pointer, 指向栈低的指针. 它是一个隐含的寄存器, 可以在内存操作指令中通过x31寄存器来访问.
- PC寄存器, Program Counter, 记录当前执行的代码的地址. 它是一个隐含的寄存器, 无法被直接访问, 只能被特定的指令隐含访问.
- LR寄存器, Link Register, 指向返回地址, 即return时回到的地址. 它就是x30, 可以随意访问读写, 意味着程序可以随意改变方法的返回地址.
- FP寄存器, Frame Pointer, 指向上一次方法调用的frame的最高位地址, frame位于栈上. 它就是x29, 可以随意访问读写.
FP是指向frame的最高位地址, frame位于栈上, 那frame是什么呢?
frame其实就是一个按照方法调用顺序, 从栈的高地址向低地址依次存放的一组数据, 用于存放上一次方法调用的关键信息. 数据可以参考下图:
寻址方式
|
|
常用指令
b 跳转到地址(无返回), 不会改变LR寄存器的值
bl 跳转到地址(有返回), 会改变LR寄存器的值为返回地址
ldr/ldur 地址对应的内容加载到寄存器
str/stur 寄存器内容存储到内存地址
ldp/stp 取/存一对数据(2个)
cbz/cbnz 为零跳转到地址/不为零跳转到
add 加法运算
mov 寄存器之间内容移动
ldp/stp 从栈取/存数据
adrp, 用来定位数据段中的数据用, 因为aslr会导致代码及数据的地址随机化, 用adrp来根据pc做辅助定位
案例解析
main函数汇编解析
- 设置Debug->Debug Workflow->Always Show Disassembly
- 打断点到callMe函数。PS:callMe调用别的方法才会被保存frame
|
|
这段代码编译结果如下
|
|
|
|
|
|
sp的正负偏移
sp存取的时候有的偏移量是正数, 有的是负数, 这有什么区别呢?
在stack里面, sp指针之下(负数偏移量)的数据是不保证安全的, 可能被覆盖, 而sp指针之上(正数偏移量)的数据是安全的. 放到负数偏移量一般都是临时存一下数据, 需要被整个方法用到的数据一般放到sp的正数偏移位置.
git地址
参考
FP寄存器及frame pointer介绍: http://blog.chinaunix.net/uid-25871104-id-2938389.html
RealView 编译工具 《汇编器指南》
Hopper Disassembler