arm64入栈出栈_【iOS内功】ARM黑魔法—栈桢的入栈和出栈
栈桢之谜
调用一个子函数,在内存上会入一个新的栈桢。子函数执行完了,当前栈桢会出栈。在运行时,栈桢的出栈和入栈的逻辑是怎么实现的呢?
这是一个很有趣的问题,也是一个重要的知识点,它是排查疑难Crash的必备技能。
ARM64特殊寄存器
栈桢的入栈和出栈依赖于3个特殊寄存器,它们是fp、lr、sp,在ARM汇编里对应的是X29、X30、x31
特殊寄存器作用LR (X30)link register 链接寄存器,保存返回上一层调用函数的地址
FP (X29)Frame point 指向栈底,保存栈桢的地址
SP (x31)Stack point 指向栈顶, 可以用来寻址
PC指向当前执行的代码的地址,我们无法访问PC寄存器
CPSR状态寄存器。不同于编程语言里面的if else.在汇编中就需要根据状态寄存器中的一些状态来控制分支的执行。
案例分析
下面基于一个Demo来分析
void func2() {
}
int func1(int a, int b,int c) {
int x = 7+18;
int y = a + b + c;
func2();
return y;
}
int main(int argc, char * argv[]) {
func1(5,7,9);
}
复制代码
调试汇编代码:
XCode设置Debug->Debug Workflow->Always Show Disassembly,然后真机调试运行Demo,就可以查看到每一个方法的ARM64汇编指令。
main函数汇编代码
OCSimpleTest`main:
0x104a5a008 : sub sp, sp, #0x20 ; =0x20
0x104a5a00c : stp x29, x30, [sp, #0x10]
0x104a5a010 : add x29, sp, #0x10 ; =0x10
0x104a5a014 : stur w0, [x29, #-0x4]
0x104a5a018 : str x1, [sp]
0x104a5a01c : mov w0, #0x5
0x104a5a020 : mov w1, #0x7
0x104a5a024 : mov w2, #0x9
-> 0x104a5a028 : bl 0x104a59fd4 ; func1 at main.m:60
0x104a5a02c : mov w8, #0x0
0x104a5a030 : mov x0, x8
0x104a5a034 : ldp x29, x30, [sp, #0x10]
0x104a5a038 : add sp, sp, #0x20 ; =0x20
0x104a5a03c : ret
复制代码
main函数指令解析
第5行到第7行
mov指令是给寄存器赋值,main函数调用func1时会传递3个参数,因此跳转func1前,要先将3个参数存储到寄存器w0,w1,w2.(w寄存器只占32位,也就是4个字节)
第八行
bl 0x102a41fd8 ; func1 at main.m:60
bl是跳转指令,从main函数跳转到下一个函数func1
函数A汇编代码
OCSimpleTest`func1:
0x10428dfb8 : sub sp, sp, #0x30 ; =0x30
0x10428dfbc : stp x29, x30, [sp, #0x20]
0x10428dfc0 : add x29, sp, #0x20 ; =0x20
0x10428dfc4 : stur w0, [x29, #-0x4]
0x10428dfc8 : stur w1, [x29, #-0x8]
0x10428dfcc : stur w2, [x29, #-0xc]
0x10428dfd0 : mov w8, #0x19
-> 0x10428dfd4 : str w8, [sp, #0x10]
0x10428dfd8 : ldur w8, [x29, #-0x4]
0x10428dfdc : ldur w9, [x29, #-0x8]
0x10428dfe0 : add w8, w8, w9
0x10428dfe4 : ldur w9, [x29, #-0xc]
0x10428dfe8 : add w8, w8, w9
0x10428dfec : str w8, [sp, #0xc]
0x10428dff0 : bl 0x10428dfb4 ; func2 at main.m:58:1
0x10428dff4 : ldr w0, [sp, #0xc]
0x10428dff8 : ldp x29, x30, [sp, #0x20]
0x10428dffc : add sp, sp, #0x30 ; =0x30
0x10428e000 : ret
复制代码
函数A指令解析
第1行
sub sp, sp, #0x30 ; =0x30
sub是减法指令。
SP寄存器的值向低地址偏移48个字节(0x30)。这时候SP已经指向新栈桢的顶部。
第2行
stp x29, x30, [sp, #0x20]
stp是存值指令,存2个值
存储上一个栈桢fp寄存器(x29)和lr寄存器(x30)的值,存储的位置是sp寄存器地址向高地址偏移32个字节(0x20)。
这里存储上一个栈桢fp和lr的值是一个重要的设计,下一个函数执行完,读取这两个值就可以回到原来的逻辑。
偏移的方向和大小(知识点)
因为栈是从高地址向低地址生长,所以入栈时地址偏移都是负向的。ARM64里寄存器是64位,也就是8个字节,这里要存储fp和lr两个寄存器,所以偏移量是16个字节。
复制代码
思考:fp_A和lrA存储时哪个在前面,哪个在后面,为什么?
复制代码
第3行
add x29, sp, #0x20 ; =0x20
add是加法指令。
设置fp(x29)寄存器,将其指向sp寄存器向高地址偏移32个字节的位置(0x20)。
此时函数A的栈桢已经布局完成,fp_A指向栈底,sp_A指向栈顶,占了16个字节。上一个栈桢的fp和lr的指针存储在栈桢A之前,也占了16个字节。
思考:为什么栈桢A的空间只有32个字节?
fp到sp之间的内存,主要用来存储寄存器带过来的入参、函数内的局部变量。
函数A有3个入参,每个入参占4个字节。2个局部变量,每个4字节,总共20字节。内存有字节对齐,所以总共申请了32个字节的空间。
复制代码
思考:如果函数A有10几个入参,入参类型除了int,还有其他的类型,这个时候栈桢的空间会是多少呢?
复制代码
第16行
ldr w0, [sp, #0xc]
ldr是取值指令。
将sp向高地址偏移12个字节(0xc)的值读出来,存储到w0寄存器。sp+0xc存的是“a + b + c”的结果,是函数A要返回的结果y。
第17行
ldp x29, x30, [sp, #0x20]
ldp是取值指令,取2个值
将sp向高地址偏移32个字节的两个值,取出来存储到fp寄存器(x29)和lr寄存器(x30)。
这里和第二行命令是一一对应的,取回main函数的fp和lr
第18行
ret
函数A栈桢出栈,执行lr寄存器指向的指令地址,也就是main函数跳转到fun_A的下一行命令。
小结
main函数调用函数A入栈过程
将传递给函数A的参数,存储到w0开始的寄存器中
保存main函数栈底指针fp和返回地址lr。
对fp和sp指针进行偏移,开辟函数A的栈桢空间
函数A执行完出栈过程
从内存中取出返回值,储存到w0寄存器里
从内存中取出main函数的fp和lr
执行lr的指令
总结
本文介绍了函数入栈和出栈的底层实现,具体使用到哪些的特殊寄存器,如何通过简单的arm指令操作,一步一步完成调用栈的管理。理解这个过程,可以欣赏到Arm设计的精妙之处,也能让我们排查疑难问题时可以俯瞰全局。
为了方便读者理解,前面有一篇文章还介绍了栈的基础概念,栈在内存中的布局,调用栈的内存布局。
【iOS内功】深入解析Crash调用栈的内存布局
参考
文章汇总
iOS内功系列
架构系列
思维系列
arm64入栈出栈_【iOS内功】ARM黑魔法—栈桢的入栈和出栈相关推荐
- 高速的二舍八入三七作五_京承高速收费“二舍八入 三七作五”
(3)望和桥至北七家里程16.74公里,盘算费额8.37元,按"二舍八入"原则,应收10元. 市民马*回声:京承高速公路的出京偏向收费*正,对小车而言,黄港出口和后沙峪出口收费都是 ...
- 栈的输出_程序设计做题笔记:计算表达式(一):栈
题目要求输入一个四则运算表达式(保证合法,不含空格),输出这个表达式的值. 解决这个问题有两种主要的思路,一种是用栈解决,较为符合人在计算四则运算表达式时的行为,从左向右算过去:另一种思路是递归,把原 ...
- python win10 捕获 弹出窗口_[python爬虫] Selenium高级篇之窗口移动、弹出对话框自登录...
在我们使用Selenium Python制作自动爬虫和网页自动测试的时候,通常会遇到弹出新的窗体或对话框的时候,此时你捕获的窗体已经被打断,从而干扰你的爬虫. 那怎么解决这个问题呢? 本篇文章主要记录 ...
- 用python画出叶子_有没有一种方法可以在叶子上画出许多标记?
我正在尝试使用Folium从pandas数据框中读取地理信息. 我的密码是:import folium from folium import plugins import pandas as pd . ...
- 【数据结构入门】栈(Stack)的实现(定义、销毁、入栈、出栈等) | 图解数据结构,超详细哦~
文章目录 (1)前言 1)栈的概念 2)进栈出栈的形式 3)栈的存储结构 (2)栈的实现(顺序栈) 1)栈的定义 2)栈的初始化 3)栈的销毁 4)入栈 5)出栈 6)检测栈是否为空 7)获取栈中有效 ...
- python 栈实现 加减乘除_数据结构与算法(六):基于栈实现简单的四则运算
栈,是一种操作受限的线性表,其操作规则为:后进者先出,先进者后出. 目录 题目需求: 给定一个字符串,形如"1+6*5/3-8",根据其内容,计算结果,此例结果为3. 解题思路: ...
- 初始化栈的代码_数据结构中的栈,你知道多少?
由于栈比较简单,也很容易理解,学过的人都知道一句话就可以描述栈的特性:后进先出.所以这篇文章主要是写如何使用代码来描述栈,当然也是让大家很容易理解的语言.还是先给出这篇文章的大致脉络. 首先,对栈有一 ...
- java 取栈顶元素_《Java实战之内存模型》详解篇
内存是非常重要的系统资源,是硬盘和CPU的中间仓库及桥梁,承载着操作系统和应用程序的实时运行 JVM内存布局规定了Java在运行过程中内存申请.分配.管理的策略,保证了JVM的高效稳定运行 不同的JV ...
- java 栈的变量_深入Java核心:JVM中的栈和局部变量
操作数栈和局部变量区一样,操作数栈也被组织成一个以字长为单位的数组.但和前者不同的是,它不是通过索引来访问的,而是通过入栈和出栈来访问的.可把操作数栈理解为存储计算时,临时数据的存储区域.下面我们通过 ...
最新文章
- Qt: 实现浮点slider和浮点spinbox
- 64位Fedora运行32位C++程序中int精度溢出处理
- java上下文即ServletContext
- CF468B Two Sets
- 申请https协议总结
- collapse用法
- Astar2007初赛第一场的题目2
- 在Ubuntu 20.04部署SONIC testbed(topo 0)
- 在openEuler上搭建LFS
- mac linux 键盘布局,Macbook Pro 推出中文键盘布局
- 程序员多次下载的浏览器竟然是它!
- JAVA集合中常见的List和Map用法
- [强烈推荐]ring0下文件解锁强制删除工具
- 切割钢条问题(rod cutting problem)
- 计算机 手机原理是什么,什么是手机投屏,手机投屏到电脑上的原理
- 【STM32】继电器的使用
- 按键状态机—实现连发
- 物联网终端设备的工作原理和功能讲解
- 微软中国公布2010年度经典MSN签名Top10
- 寒假集训D1.22.12.28
热门文章
- 雷军卸任小米电子董事长!常程等人也退出该公司 网友:为造车放弃家业?
- 万象双环设计+原色双影像极致体验 华为P50标准版9月29日正式开售
- 2021年Q2全球智能手机销量小米升至第二,苹果降至第三
- 苹果iOS 15正式发布!一大波新功能上线,这几个被刷爆了
- CES 2021落下帷幕 未来3年展会时间已公布
- 腾讯微博正式关闭:目前无法登陆 用户可申请个人内容备份
- 支付宝要给全国人民发100亿,7月1日起!
- 华为正式宣布全场景AI计算框架MindSpore开源 降低AI开发门槛
- realme曝光全球首张6400万像素样张 大家品一品?
- 如何才能招到马云这样的人才?海尔张瑞敏这样说...