iOS底层-汇编探索(三)

423 阅读3分钟

前言

    上一篇文章我们已经探索过了CPU与寄存器的一些原理和内在联系。我们知道任何高级语言方法的执行,他们的底层都是调用函数方法。但有没有想过函数的底层是怎么实现的呢?函数的调用一定涉及到了函数的调用栈,什么又是函数调用栈道呢?

栈和堆

    什么是是在内存中一种具有特殊访问方式的存储空间。遵循后进先出( Last In Out Firt)LIFO。栈的开口对iOS系统来说来说是高地址往低地址走。局部变量参数等这些在代码中写死的东西是放在栈空间。

  • SP和FP寄存器 sp寄存器:在任意时刻会保存我们栈顶的地址。
    fp寄存器:也称为x29寄存器属于通用寄存器,但是在某些时刻我们利用它保存栈底的地址!
    sp保存栈顶(开口),fp保存栈低。栈空间的开辟是通过栈的拉升sub获取。
sub    sp, sp, #0x40             ; 
// 拉伸0x40(64字节)空间
stp    x29, x30, [sp, #0x30]     ;
// x29\x30 寄存器入栈保护
add    x29, sp, #0x30            ;
// x29指向栈帧的底部
... 
ldp    x29, x30, [sp, #0x30]     ;
// 恢复x29/x30 寄存器的值
add    sp, sp, #0x40             ; 
// 栈平衡
ret

    从上面汇编代码可以看到栈空间操作,以及栈平衡。那什么是栈平衡呢?栈是往低地址走,通过sub减可以将地址开辟出来,通过add指令加可以将栈地址恢复这个过程称为栈平衡。

  • 内存指令     strldr都是操作栈空间指令。stp指令是同时写入两个数据。当写数据时不能直接往sp寄存器写入数据,需要先拉伸栈空间。读/写 数据是都是往高地址读/写。str(store register)指令:将数据从寄存器中读出来,存到内存中。ldr(load register)指令:将数据从内存中读出来,存到寄存器中。ldrstr 的变种ldpstp还可以操作2个寄存器。
sub    sp, sp, #0x20    ;
// 拉伸栈空间32个字节
stp    x0, x1, [sp, #0x10] ;
// sp往上加16个字节,存放x0 和 x1
ldp    x1, x0, [sp, #0x10] ;
// 将sp偏移16个字节的值取出来,放入x1 和 x0

    上述汇编代码的用途其实是使用32个字节空间作为这段程序的栈空间,然后利用栈将x0x1的值进行交换。[ ...]寻址 ,把左边的值存到右边地址里面去。
sp寄存器就是一个指针一个标记,sp操作必须16字节对齐,做8字节操作会崩溃。寄存的值进行交换其实内存地址没有变化。
bl指令:将下一条指令的地址放入lr(x30)寄存器,转到标号处执行指令。
ret指令:默认使用lr(x30)寄存器的值,通过底层指令提示CPU此处作为下条指令地址。ARM64平台的特色指令,它面向硬件做了优化处理的。x30寄存器:x30寄存器存放的是函数的返回地址。当ret指令执行时刻,会寻找x30寄存器保存的地址值。在函数嵌套调用的时候,需要讲x30入栈。

  • 函数的参数和返回值     ARM64下,函数的参数是存放在x0x7(w0w7)这8个寄存器里面的。如果超过8个参数,就会入栈。函数的返回值是放在x0寄存器里面的。为了提高效率,oc方法参数最好不要超过6个,函数不要超过8个。定义一些数组、结构体来提高效率。系统汇编是由llvm转换的。