内容简介:注意: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
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》 这本书的介绍吧!