汇编(三) — 栈(spfp寄存器)

  • 栈:是一种具有特殊的访问方式的存储空间(后进先出, Last In Out Firt,LIFO)

Stack 是由内存区域的结束地址开始,从高位(地址)向低位(地址)分配。比如,内存区域的结束地址是0x8000,第一帧假定是16字节,那么下一次分配的地址就会从0x7FF0开始;第二帧假定需要64字节,那么地址就会移动到0x7FB0。

**注:**用户主动请求而划分出来的内存区域,叫做 Heap(堆)。它由起始地址开始,从低位(地址)向高位(地址)增长。Heap 的一个重要特点就是不会自动消失,必须手动释放,或者由垃圾回收机制来回收。所谓的堆栈溢出是指堆栈开辟的空间碰到了一起,重叠,说明空间不够了。

SP和FP寄存器

  • sp寄存器在任意时刻会保存我们栈顶的地址.
  • fp寄存器也称为x29寄存器属于通用寄存器,但是在某些时刻我们利用它保存栈底的地址!()

注意:ARM64开始,取消32位的 LDM,STM,PUSH,POP指令! 取而代之的是ldr\ldp str\stp ARM64里面 对栈的操作是16字节对齐的!!

注:以前push和pop操作的时候需要对寄存器做保护,也就是sp。做压栈的操作sp会挪动,之后需要恢复的时候sp是需要保护的,通过fp保护。现在push和pop取消了fp就显得比较鸡肋,但是要兼容32位,这个寄存器还保留着。

关于内存读写指令

注意:读/写 数据是都是往高地址读/写

str(store register)指令

将数据从寄存器中读出来,存到内存中。

ldr(load register)指令

将数据从内存中读出来,存到寄存器中

此ldr 和 str 的变种ldp(pair) 和 stp(pair) 还可以操作2个寄存器。

堆栈操作练习

使用32个字节空间作为这段程序的栈空间,然后利用栈将x0和x1的值进行交换。

sub sp, sp, #0x20 ;拉伸栈空间32个字节

stp x0, x1, [sp, #0x10] ;sp往上加16个字节,存放x0 和 x1

ldp x1, x0, [sp, #0x10] ;将sp偏移16个字节的值取出来,放入x1 和 x0

**注:**sub是减, sub sp, sp, #0x20 是将ap向底地址挪动32个字节,然后赋值给sp。str是将寄存器里面的值放到内存中,但是str只能操作一个寄存器,stp可以操作两个, stp x0, x1, [sp, #0x10] 是将x0和x1放在 [sp, #0x10][sp, #0x10] 是一块内存区域,左边操作数加上右边操作数, [sp, #-0x10] 就是左边操作数减去右边操作数。

我们来写一下

然后A()出打断点,并进入A方法。 我们看到在执行完
sub sp, sp, #0x20 后sp是
0x000000016d57b8e0

,然后查看一下

我们来看一下内存地址,输入 0x000000016d57b8e0 ,看到内存开辟了16个字节的空间,当前sp是在6129432800处,前面讲到,写内存是往高地址写,所以 stp x0, x1, [sp, #0x10] 是往高地址写。

现在我们往x0和x1中写入0xffffffff,再ni。

register write x0 0xffffffff
register write x1 0xffffffff
复制代码

因为 stp x0, x1, [sp, #0x10] 所以是从sp加16个字节出开始存数据。

注: add sp, sp, #0x20 是为了栈平衡,如果不这么做,栈会一直往低地址走,最终内存不够用,但是当函数调用完局部变量是不需要了,所以要回收栈空间。