博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
02-汇编初学之汇编角度理解函数
阅读量:6938 次
发布时间:2019-06-27

本文共 3463 字,大约阅读时间需要 11 分钟。

iPhone设备主要使用ARM架构的CPU,iPhone5S之后的CPU架构都是ARM64,因此主要学习ARM64架构的寄存器和与之对应的指令集

1、栈

  • 1.1 了解栈

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

  • 1.2 汇编中对栈的操作

    栈主要是栈顶地址与栈底地址,ARM64使用 sp寄存器 保存栈顶的地址,使用 fp寄存器 保存栈底地址(注意:fp寄存器也称为x29寄存器, 属于通用寄存器, 某些时刻我们利用它保存栈底的地址)

    App整体内存图

    • 栈的操作
      • 栈的拉伸(开辟栈空间)
      • 栈的平衡(回收栈空间)
      • ARM64中栈的操作主要是对sp寄存器的操作, 对 sub sp, #0x10 可以拉伸栈,add sp, #0x10可以平衡栈

    注意: 对sp寄存器进行操作,必须是16字节对齐的!!也就是 add与sub 数是16的倍数,否则报错

    • 栈内存的操作(ARM64下的指令)
      • 栈内存的写入:
        • str:将一个寄存器的数据写入栈内存中
        • stp:将两个寄存器的数据写入栈内存中
      • 栈内存的读取:
        • ldr:从栈内存中读取数据,放入到某个寄存器中
        • ldp: 从占内存中读取数据,分别放入到两个寄存器中
      • 栈内存的读写是从 sp指向地址开始向高内存地址的读写

    举例

    _A:    sub sp, #0x20;    mov x26, 0xffffffff    mov x27, 0xa0a0a0a0    stp x26, x27, [sp];    ldp x27, x26, [sp];    add sp, #0x20;    ret复制代码

    分析

    第一行代码:对栈进行拉伸,用于存储数据

    第四行代码:将寄存器的数据写入到内存中,写入位置可以看下图的分析(写入的时候,x26数据写入到sp~sp+0x10的位置--> 向高地址写入)

    第五行代码:从栈内存中读取数据,放入到寄存器中,但是放的时候和写的时候2个寄存器的位置是相反的,因此,寄存器的数据会被交换(读取的时候,先读取sp~sp+0x10的位置到x27 --> 高地址读取)

    分析图

    xcode内存调试图

2、函数

  • 2.1 函数的调用与返回

    • bl指令,用于跳转,会执行下面2步

      • 保存当前bl指令的下一条指令的地址到lr(x30)寄存器,用于ret返回
      • 将pc寄存器的值更改为需要跳转的指令地址,实现程序控制
    • ret指令

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

    举例

    void funcB(void) {    return;}void funcA(void) {    funcB();    return;}int main(int argc, char * argv[]) {    funcA();}复制代码

    Xcode汇编代码

    主要分析 funcA 函数的调用与返回

    main中

    第7行 bl 0x1000d07b0, 跳转到funcA函数入口地址,执行funcA函数,同时会将下条指令的地址0x1000d07dc保存到lr寄存器,函数返回后,需要执行这条指令

    第9行 [sp, #0x10]只是在sp的地址上进行偏移,不会改变sp的值

    funcA中

    第2行 先开辟funcA函数的栈,[sp, #-0x10]! 感叹号表示先运算修改寄存器的值,然后在使用寄存器的值, []中括号表示这是一个内存地址,也要对内存地址进行读写操作, 整个指令的意思是,将x29(32位ARM下用于保存fp栈底地址) x30(lr 栈顶地址)写入到funcA函数栈内存中。why?bl跳转到funcA中后,lr寄存器保存了funcA返回后需要执行的下一条指令的地址,直接点就是函数返回后需要执行的下一条语句的位置。为什么要写入函数栈??首先lr寄存器的值会经常修改(bl执行后就会修改), 其次函数栈才能保证当前数据不会被别人给覆盖掉

    第5行 先使用sp的值,然后在修改(sp偏移0x10, 平衡函数栈)。整个指令的意思就是,从函数栈中取出保存的fp lr的值,并还原回去,之后在进行函数栈内存的回收
    第6行 ret指令从lr中获取到函数返回后要执行的下一条指令的地址,退出函数

    经过分析,我们可以总结:函数的调用的时候,会开辟栈空间,并将函数的lr寄存器值存储起来,通俗点说 保护回家的路

  • 2.2 函数的参数、局部变量与返回值

    • 参数的传递:(例如main调用sum函数)
      • 在函数调用的时候,CPU 通常 会将传给被调用函数的参数放在x0-x7 这8个寄存器中, 被调用的函数从寄存器中读取
      • 如果超出8个,放入函数栈中(哪个函数的栈??main中,然后sum从栈中读取出来)
      • 如果一个参数在寄存器中放不下,这个暂时不知道如何处理的。。
    • 函数局部变量:哪个函数内部的局部变量就放在哪个函数的栈中
    • 函数的返回值:当被调用函数要返回前,会将返回值放在x0寄存器中(调试的时候,可以使用lldb命令register write x0 0x1111 改变x0寄存器值,看看函数返回值是否改变)

    举例

    int sum(int aa, int bb, int cc, int dd, int ee, int ff, int gg, int hh, int ii) {    int temp = 20;    return aa+bb+cc+dd+ff+gg+hh+ii+temp;}int main(int argc, char * argv[]) {    sum(1, 2, 3, 4, 5, 6, 7, 8, 9);    return 0;}复制代码

    Xcode汇编代码(请使用真机)

    main函数

    main函数汇编代码分析

    2~3行:拉伸栈,写入栈 保护x29, x30(lr)寄存器,用于函数返回 5~12行 16~17行:先用x2~x9保存前8个参数,然后转移到x0~x7

    13行 x10 存储第9个参数
    18行 将x10写入sp栈,也就是当前函数的栈
    21行 将x0写入栈,也就是将sum函数的返回值先保存到main函数栈中

    sum函数
    sum函数续

    sum函数汇编代码分析

    2行:拉伸栈, 为什么没有像main一样做lr的保护??因为sum是一个叶子函数,也就是sum不再调用其他函数了,lr不会被更改

    3行、27行:从main函数栈内存中读取第9个参数。因为进来的时候sp更改了,所以取的时候要加回去 5~12行:将x0~x7参数写入栈 4行、13行: 生成局部变量,并入栈
    14~26行:读取x0~x7参数并计算和,和放在x9中 27~28行:读取第9个参数并计算 29~30行:读取局部变量temp并计算 最后的计算结果存储在w0中,也就是x0

    栈内存分析图

    注意:sp 的拉伸是16的倍数,虽然有浪费

  • 2.3 函数的总结

    • 函数的调用

      • bl 指令,跳转到函数入口地址处执行函数
      • bl 跳转的同时,还会将下一条指令的地址保存到lr(x30)寄存器中,用于函数返回
    • 函数栈

      • 进入函数后要做的第一件事,就是开辟函数的栈空间,sp向低地址偏移(一般 sub sp, sp, #0x20 类似的汇编代码)
      • 函数栈主要用于保存 lr寄存器、函数参数、函数局部变量(static局部变量不是保存在函数栈中)
      • 栈内存的读写:str/stp ldr/ldp,读写是基于 sp 向高地址进行
    • 函数参数传递

      • 少于等于8个参数:x0~x7寄存器中
      • 超过8个:存储在上一个函数的栈中,使用时从上个函数栈内存中读取
    • 函数局部变量

      • 局部变量存储在栈内存中
      • static修饰的局部变量存储在app的全局数据区
    • 函数返回

      • 首先,平衡函数栈 add sp, sp,#0x20
      • 如果不是叶子函数,lr寄存器的值不是当前函数的返回地址,需要从栈中读取保存好的lr寄存器的值,还原回来
      • ret返回(根据当前lr寄存器存储的指令地址,放入pc寄存器,cpu跳转执行)

补充1:递归函数的调用和函数A调用函数B同样理解,不会因为函数名相同而有什么特殊

补充2:多线程环境下,切换线程时,操作系统切换之前会对寄存器数据进行保护,而函数的栈内存却不会,因此多线程资源抢夺的时候需要我们自己对函数栈内存进行保护(锁或者其他)。

转载于:https://juejin.im/post/5ad884e45188252e9d090e80

你可能感兴趣的文章
Java并发基础03. 传统线程互斥技术—synchronized
查看>>
数据工厂模式,多种数据库选择 web.config 写法及相关操作
查看>>
ios面试题
查看>>
圣杯布局、双飞翼布局与flex布局实现
查看>>
控制条
查看>>
23种设计模式(一) 单例模式
查看>>
移动自动化相关名词解释
查看>>
testAPI
查看>>
delphi 7里怎么隐藏PageControl控件的tabsheet标签
查看>>
【自然语言处理篇】--以NLTK为基础讲解自然语⾔处理的原理和基础知识
查看>>
201771010126 王燕《面向对象程序设计(java)》第二周学习总结
查看>>
Git commit 信息标准和丢弃必须要的commit
查看>>
am335x Qt SocketCAN Demo hacking
查看>>
uva 10806
查看>>
Logback日志配置的简单使用
查看>>
html5-边框属性
查看>>
数据结构之最短路径(1) [迪杰斯特拉算法]
查看>>
日记 2016年8月9日(周二)
查看>>
java 协变返回
查看>>
php生成唯一的串
查看>>