汇编二 — 栈

栏目: 编程语言 · 发布时间: 7年前

内容简介:注意:ARM64开始,取消32位的 LDM,STM,PUSH,POP指令! 取而代之的是ldr\ldp str\stpARM64里面sp 寄存器指向哪里,哪里就是栈,通过 sp 偏移来 读取/存 数据。

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

栈结构

SP和FP寄存器

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

注意:ARM64开始,取消32位的 LDM,STM,PUSH,POP指令! 取而代之的是ldr\ldp str\stp

ARM64里面 对栈(sp)的操作(必须)是16字节对齐的! ! (否则即会崩溃,如:add sp,0x6)

sp 寄存器指向哪里,哪里就是栈,通过 sp 偏移来 读取/存 数据。

关于内存读写指令

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

(栈空间的开辟,设计其实是从上往下走的,从高到低)

汇编二 — 栈

栈空间的开辟

操作系统设计的时候,怎么把内存的堆栈分开呢?

所以做了一个约定,从上往下为栈,从下往上为堆。这样就能更加合理的利用内存空间,为弹性的空间。(Stack Overflow 堆栈溢出)

汇编二 — 栈

堆栈空间划分

str(store register)指令

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

ldr(load register)指令

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

此ldr 和 str 的变种ldp 和 stp 还可以操作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

( [sp,#0x10] 代表左边的 sp 加上右边的 0x10,若是减的话就是[sp,#-0x10])

register write x0 0xffffffff

register write x1 0xffffffff

修改 xo 和 x1 寄存器的值

读取完毕以后:

add sp, sp, #0x20 复位保持栈的平衡,防止栈空间的浪费,或者溢出。

bl和ret指令

bl标号

(用于返回继续执行)
# + 数字:立即数,代表一个常数。

bl 与 8086 汇编中的 jump 类似。

.s 代表汇编文件

.text 代码段

.gloal 定义全局变量。

64 位的寄存器 8 个字节

.text
.global _A,_B

_A:
    mov x0,#0xaaaaaaaa
    bl _B
    mov x0,#0xcccccc
    ret

_B:
    mov x0,#0xbbbb
    ret

(在进入 A 函数之前,lr 保存了下一步执行的地址,bl _B 之后,lr 则保存为下一条指令的地址,覆盖掉之前保存的值,进入 B 函数执行完,再次跳回 A 函数,陷入死循环。)

每一个函数都会开辟一个栈空间

指令优化

stp x29, x30 ,[sp, #-0x10]! 加 “!” 的作用相当于:

sub sp, sp,#0x10; stp x29, x30, 【sp】

将 sp -10 赋值给 sp;

在 ARM 64 汇编中,x29 没有什么作用,x30 保存返回的地址。

stp x29, x30 ,[sp, #-0x10]! // 将 x29, x30 寄存器中的值放到栈里面
mov x29, sp
bl 0x2345463  // 函数调用
ldp   x29, x30, [sp], #10  // 完毕以后,在从栈里面读出来放到 x29, x30 中,保护x29 , x30 这两个寄存器(现场保护)
ret

str/ldr 操作一个寄存器,stp/ ldp 管理两个寄存器

优化后的代码:

.text
.global _A,_B

_A:
    mov x0,#0xaaaaaaaa
    str x30,[sp,#-0x10]!
    bl _B
    mov x0,#0xcccccc
    ldr x30,[sp],#0x10  // 相当于: ldr x30, [sp]; add sp, sp, #0x10;
    ret

_B:
    mov x0,#0xbbbb
    ret

ret

  • 默认使用lr(x30)寄存器的值,通过底层指令提示CPU此处作为下条指令地址! 就是跳转到 x30 寄存器所保存的地址

ARM64平台的特色指令,它面向硬件做了优化处理的

x30寄存器

x30寄存器存放的是函数的返回地址.当ret指令执行时刻,会寻找x30寄存器保存的地址值!

注意:在函数嵌套调用的时候.需要讲x30入栈!

函数的参数和返回值

ARM64下, 函数的参数 (通常)是存放在X0到X7(W0到W7)这8个寄存器里面的.如果超过8个参数,就会入栈.

int sum2(int a, int b, int c, int d, int e, int f, int g, int h, int i) {
    return a+b+c+d+e+f+g+h+I;
}

sum2(1,2,3,4,5,6,7,8,9);

001-函数的参数和返回值`main:
    0x10486a8d0 <+0>:  sub    sp, sp, #0x30             ; =0x30 
    0x10486a8d4 <+4>:  stp    x29, x30, [sp, #0x20]
    0x10486a8d8 <+8>:  add    x29, sp, #0x20            ; =0x20 
    0x10486a8dc <+12>: orr    w8, wzr, #0x1
    0x10486a8e0 <+16>: orr    w9, wzr, #0x2
    0x10486a8e4 <+20>: orr    w2, wzr, #0x3
    0x10486a8e8 <+24>: orr    w3, wzr, #0x4
    0x10486a8ec <+28>: mov    w4, #0x5
    0x10486a8f0 <+32>: orr    w5, wzr, #0x6
    0x10486a8f4 <+36>: orr    w6, wzr, #0x7
    0x10486a8f8 <+40>: orr    w7, wzr, #0x8
    0x10486a8fc <+44>: mov    w10, #0x9
    0x10486a900 <+48>: stur   w0, [x29, #-0x4]
    0x10486a904 <+52>: str    x1, [sp, #0x10]
->  0x10486a908 <+56>: mov    x0, x8
    0x10486a90c <+60>: mov    x1, x9
    0x10486a910 <+64>: str    w10, [sp]
    0x10486a914 <+68>: bl     0x10486a858               ; sum2 at main.m:16
    0x10486a918 <+72>: mov    w8, #0x0
    0x10486a91c <+76>: str    w0, [sp, #0xc]
    0x10486a920 <+80>: mov    x0, x8
    0x10486a924 <+84>: ldp    x29, x30, [sp, #0x20]
    0x10486a928 <+88>: add    sp, sp, #0x30             ; =0x30 
    0x10486a92c <+92>: ret
汇编二 — 栈

函数调用之前栈的变化

注: 汇编代码开始一定都是x29 ,x30 都入栈,为了保护函数的返回地址。

stur 是一个无符号的变种指令,可以把它当做 str .

stur w0, [x29, #-0x4] —— 减的时候为 stur

str x1, [sp, #0x10] —— 加的时候为 str.

【sp】 【】代表把 sp 的值取出来,当做一个内存地址,相当于取地址,代表 sp 指针所指向的区域。

拉伸空间是往低地址拉伸,读写是往高地址。

汇编二 — 栈

image.png

汇编二 — 栈

各寄存器值

汇编二 — 栈

函数调用过程

函数的返回值(通常)是放在X0 寄存器里面的,. 这是编译器决定的,如果返回结构体即放不下了,不能以 x0 作为返回值了

0x1009e28d8 <+0>:  sub    sp, sp, #0x10             ; =0x10 // sp 减 16 字节,向低地址
    0x1009e28dc <+4>:  str    w0, [sp, #0xc]  // 内部数据可以随意放
    0x1009e28e0 <+8>:  str    w1, [sp, #0x8]
->  0x1009e28e4 <+12>: ldr    w0, [sp, #0xc]
    0x1009e28e8 <+16>: ldr    w1, [sp, #0x8]
    0x1009e28ec <+20>: add    w0, w0, w1  // 两个参数相加放入w0, 即为返回值
    0x1009e28f0 <+24>: add    sp, sp, #0x10             ; =0x10 // sp 加 16 字节,向高地址
    0x1009e28f4 <+28>: ret

sum (10, 20);

简化版 替代上面的汇编代码

add x0, x0, x1

ret

注:如果一个函数内部不再调用其它的函数,我们称之为 叶子函数

我们知道了 x0---x7 是存放参数的, OC 方法的调用,

[self viewDidLoad] ;

即为 msgSend(self, @selector(viewDidLoad));

那么 x0 即为 self ,x1 即为调用的方法地址。

函数的局部变量

函数的局部变量放在栈里面!

int sum(int a, int b) {
    return a + b;
}
或
int sum(int a, int b) {
    int c = 10;
    int d = 11;
    return a+b+c+d+;
}

栈拉伸空间时,至少拉伸 16 个字节,即 0x10 最低。

上面的函数,就只拉伸一个字节。

sub sp, sp, #0x10 ; =0x10

如果为:

int sum(int a, int b) {
    int c = 10;
    int d = 11;
    int e = 12;
    return a+b+c+d+e;
}

则拉伸两个字节

汇编二 — 栈

image.png

如果内部调用函数,即函数嵌套调用,则会拉伸0x30,保存 x29, x30 寄存器,16个字节。

int sum(int a, int b) {
    int c = 10;
    int d = 11;
    printf("%d",d);
    return a+b+c+d;
}
汇编二 — 栈

image.png

adrp : 基地址 + 偏移地址 获取一个全局变量。

函数嵌套调用2

int func(int a, int b) {
    int c = sum(10, 11);
    int e = sum(12, 13);
    return c+e;
}
或
int func(int a, int b) {
    int c = sum(a, b);
    int e = sum(a, b);
    return e;
}
汇编二 — 栈

image.png

汇编二 — 栈

汇编-代码比对


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Developing Large Web Applications

Developing Large Web Applications

Kyle Loudon / Yahoo Press / 2010-3-15 / USD 34.99

As web applications grow, so do the challenges. These applications need to live up to demanding performance requirements, and be reliable around the clock every day of the year. And they need to withs......一起来看看 《Developing Large Web Applications》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试