iOS-基础的汇编指令(一)

442 阅读5分钟

基础指令

通过一个简单的函数来解释指令:

D12AE7C5-7170-4DC8-88D6-47791D054117.png

9434B6D7-983F-43D6-9B14-2DE8451080EA.png

sub    sp, sp, #0x10             ; =0x10 //减 sp=sp-0x10,开辟0x10空间的函数调用栈
mov    w8, #0xa    //mov移动,相当于赋值w8=0xa
str    w8, [sp, #0xc] //str将数据从寄存器中读出来,存到内存中.把w8中的值0xa读出来,放到[sp #0xc]这块内存中
mov    w8, #0x5 //又给w8赋值w8=0x5。
str    w8, [sp, #0x8] //把w8中的值0x5读出来,放到[sp #0x8]这块内存中
ldr    w8, [sp, #0xc] //将数据从内存中读出来,存到寄存器中。w8=[sp #0xc]这块内存中的数据,也就是我们刚才存的0xa,w8=0xa
ldr    w9, [sp, #0x8] //w9=0x5。此ldr 和 str 的变种ldp 和 stp 还可以操作2个寄存器.
add    w8, w8, w9  //加 w8=w8+w9=0xa+0x5=0xf
str    w8, [sp, #0x4] //把w8的值存到[sp, #0x4]内存中
add    sp, sp, #0x10             ; =0x10 //函数调用结束,恢复栈平衡 sp=sp+0x10
ret //返回

函数调用栈

每一个函数的执行,都需要开辟一块栈空间,当函数执行完毕时,释放掉栈空间。注意:ARM64里面 对栈的操作是16字节对齐的! 函数嵌套使用时,会将x29(sp)、x30(lr)寄存器入栈保护。

下面是常见的函数调用开辟和恢复的栈空间

sub    sp, sp, #0x40             ; 拉伸0x4064字节)空间
stp    x29, x30, [sp, #0x30]     ;x29\x30 寄存器入栈保护
add    x29, sp, #0x30            ; x29指向栈帧的底部
... 
ldp    x29, x30, [sp, #0x30]     ;恢复x29/x30 寄存器的值
add    sp, sp, #0x40             ; 栈平衡
ret

bl和ret指令

bl:跳转指令,把下面的指令地址放到lr(x30)寄存器中,跳转到目标位置执行

ret:默认使用lr(x30)寄存器的值作为下一条指令的地址,所以一般函数都会把lr保存到当前的函数栈中,返回时需要用到。

函数的参数和返回值

F4AF864E-21BB-4620-8A85-DFE97DA1EE4A.png

ZZZ`main:
    0x100959924 <+0>:   sub    sp, sp, #0x40             ; 
    0x100959928 <+4>:   stp    x29, x30, [sp, #0x30]
    0x10095992c <+8>:   add    x29, sp, #0x30            ; =0x30 
    0x100959930 <+12>:  stur   wzr, [x29, #-0x4]
    0x100959934 <+16>:  stur   w0, [x29, #-0x8]
    0x100959938 <+20>:  stur   x1, [x29, #-0x10]
    0x10095993c <+24>:  mov    w0, #0xa   //w0=0xa
    0x100959940 <+28>:  mov    w1, #0x19  //w1=0x19
->  0x100959944 <+32>:  bl     0x1009598f4               ; sum at main.m:11,跳转到0x1009598f4处执行,lr=0x100959948
    0x100959948 <+36>:  stur   w0, [x29, #-0x14]
    0x10095994c <+40>:  ldur   w0, [x29, #-0x8]
ZZZ`sum:
->  0x1009598f4 <+0>:  sub    sp, sp, #0x10             ; =0x10 
    0x1009598f8 <+4>:  str    w0, [sp, #0xc]     //w0的值0xa保存到内存[sp, #0xc]
    0x1009598fc <+8>:  str    w1, [sp, #0x8]     //w1的值0x19保存到内存[sp, #0x8]
    0x100959900 <+12>: mov    w8, #0x5    //w8=0x5
    0x100959904 <+16>: str    w8, [sp, #0x4]    //w8的值0x5保存到内存[sp, #0x4]
    0x100959908 <+20>: ldr    w8, [sp, #0xc]  //w8取出[sp, #0xc]上的值w8=0xa
    0x10095990c <+24>: ldr    w9, [sp, #0x8]   //w9=0x19
    0x100959910 <+28>: add    w8, w8, w9    //w8=w8+w9=0xa+0x19=0x1d
    0x100959914 <+32>: ldr    w9, [sp, #0x4] //w9取出[sp, #0x4]上的值w9=0x5
    0x100959918 <+36>: add    w0, w8, w9  //w0=w8+w9=0x1d+0x5
    0x10095991c <+40>: add    sp, sp, #0x10             ; =0x10 
    0x100959920 <+44>: ret   

从上面的汇编代码中可以看出:ARM64下,函数的参数是存放在X0到X7(W0到W7)这8个寄存器里面的.如果超过8个参数,就会入栈,存放在上一个函数的栈顶。我们平时的OC方法中,也要尽量少与6个参数,如果超过6个,最好使用集合。

函数的返回值是放在X0寄存器里面的。函数的局部变量放在栈里面!

状态寄存器 CPSR

CPU内部的寄存器中,有一种特殊的寄存器CPSR,其他寄存器是用来存放数据的,而CPSR寄存器是按位起作用的,也就是说,它的每一位都有专门的含义,记录特定的信息.注:CPSR寄存器是32位的。最高的4位中,记录着逻辑运算的结果。

31位 N 符号标志位:结果为负N=1,非负N=0
30位 Z 零标志位:结果为零Z=1,非零Z=0
29位 C 进位标志位:无符号数运算,假设一个更高位
                 加法运算,更高位=1,溢出
                 减法运算,更高位=0,溢出
28位 V 溢出标志位:有符号数运算的时候:
                 正数 + 正数 为负数 溢出
                 负数 + 负数 为正数 溢出
                 正数 + 负数 不可能溢出