2、函数调用栈

2021-03-23  本文已影响0人  Jax_YD

注意:我们使用的是ARM64框架,所以要使用真机,而不是模拟器,也不能使用命令行工程。


SP & FP 寄存器

⚠️ 注意:ARM64开始,取消了32位的 LDMSTMPUSHPOP指令,取而代之的是ldr\ldpstr\stp指令。
ARM64里面,对栈的操作是16字节对齐的!!!

另外:ldr & str 的变种 ldp & stp 还可以操作两个寄存器。

下面我们看一个例子:


这里我们可以通过si命令 或者 Ctrl + 单步跳转 来进行单步执行
进入sum函数内部:

下面我们来一条一条的分析一下sum函数的汇编代码:
汇编代码 解释
0x1003b6730 <+0>: sub sp, sp, #0x10 sp是栈顶指针,拉伸栈空间16个字节
0x1003b6734 <+4>: str w0, [sp, #0xc] sp往上加12个字节的位置,存放w0里面的值
0x1004f6258 <+8>: str w1, [sp, #0x8] sp往上加12个字节的位置,存放w1里面的值
0x1004f625c <+12>: ldr w8, [sp, #0xc] sp偏移12个字节位置的值取出来,放入w8
0x1004f6260 <+16>: ldr w9, [sp, #0x8] sp偏移12个字节位置的值取出来,放入w9
0x1004f6264 <+20>: add w0, w8, w9 w8w9 里面的值相加,并赋值给w0
0x1004f6268 <+24>: add sp, sp, #0x10 sp指针向上偏移16个字节。栈平衡,因为最开始的时候拉伸了16个字节
0x1004f626c <+28>: ret 返回,相当于return

这里可能有人会疑惑,为什么开始的时候会操作w0&w1这两个寄存器呢?大家看main函数的混编会看到:

0x1003ee288 <+20>: mov    w0, #0xa
0x1003ee28c <+24>: mov    w1, #0x14

同时,我们在1、汇编初探里面也讲过:通常,CPU会先将内存中的数据存储到通用寄存器中,然后再对通用寄存器中的数据进行运算


bl 和 ret 指令

比如:


bl在执行指令,跳转到sum函数的时候:
1、首先会把下一条指令的地址0x1003ee294存放到lr(x30)寄存器里面,这也是为什么sum执行完毕之后还能回到main函数原因,因为保存了回家的路。
2、接着再跳转进sum函数。

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

汇编代码 解释
0x100dda264 <+0>: stp x29, x30, [sp, #-0x10]! sp偏移16个字节,拉伸栈空间,存储x29 & x30里面的内容。[sp, #-0x10]!相当于-=,就是将sp的地址偏移16个字节再赋值给sp。注意这种简写方式,只适用于正好占满栈空间的情况,因为栈是从栈底开始写入。
0x100dda268 <+4>: mov x29, sp sp里面的值给x29
0x100dda26c <+8>: bl 0x100dda260 跳转至B并将下一条指令地址保存到lr(x30)
0x100dda270 <+12>: ldp x29, x30, [sp], #0x10 从栈里面取出16个字节的值,分别赋值给x29x30(就是开始的时候,存到内存中的值),并且sp向上偏移16个字节。栈平衡
0x100dda274 <+16>: ret 返回

看到这里我们了解到,此时A的回家的路是放在栈里面的

总结:ARM64平台,在函数嵌套调用的时候,需要将x30入栈。也就是说,函数的返回地址会保存在栈里面。


函数的参数和返回值

ARM64中,函数的参数是存放在x0 ~ x7(w0 ~ w7)这8个寄存器里面的。如果超过8个参数,就会入栈。
函数的返回值是放在x0寄存器里面的。
以上两点,我们再上面探索的过程中已经见证过了,这里就不多做赘述(注意看sum函数的汇编代码)。
我们在下一篇文章的时候,具体谈论一下参数和返回值的特殊情况

tips :
在我们日常看法OC代码的时候,函数的参数最好不要超过6个,因为OC的函数自带两个参数(id selfSEL_cmd),如果再多加6个,就会有参数要入栈。这样影响读取速率。


一些调试技巧:


上一篇 下一篇

猜你喜欢

热点阅读