汇编对sp指针进行修改_iOS arm64汇编

在iOS中,消息发送是汇编

1. 先看一段C的代码,我们可以放到main.m中:

int addFunction(int a, int b) {    int c = a + b;    return c;}

2. 选择真机,Xcode选中Assemble转成汇编代码

6e71f2dd835af96c34a3cd7d2777158b.png

Xcode选中Assemble

这样我们可以看到汇编,注意一定要选择Generic iOS Device,模拟器或真机转成的汇编和本文有差异。

3. _addFunction:

忽略掉.file、.loc、.cfi_startproc,可得到以下相关汇编代码:

     sub    sp, sp, #16             ; =16     str    w0, [sp, #12]      str    w1, [sp, #8]      ldr    w0, [sp, #12]      ldr    w1, [sp, #8]      add    w0, w0, w1     str    w0, [sp, #4]     ldr    w0, [sp, #4]     add    sp, sp, #16             ; =16      ret

为什么是w0,w1呢,不是r0,r1或x0,x1呢?

w0 - w30访问时,访问的是这些寄存器的低32位。

当使用 r0 - r30 访问时,它就是一个64位的数。

3.1 添加注释后的汇编

首先,分配栈所需的所有临时存储空间。栈是一大块函数随时想使用的内存。
ARM中的栈内存是高地址向低地址分布的,意味着你必须从栈指针开始减。
在这里,分配了16个字节长度的内存。
 sub sp, sp, #16             ; =16

8cc38ad0d1a42cf3bfc01c68a893c413.png

这里,两个参数被存入栈中。这是通过存储寄存指令(str)实现的。
第一个参数是要存储的寄存器
第二个是存储的位置。方括号代表里面值是内存地址。这个方括号指令允许你为一个值指定偏移量,
因此[sp, #12]的意思『在栈指针的地址上加上12字节偏移量』
同样地,str w0, [sp, #12]意味着『存储w0寄存器的值到栈指针地址加上12字节内存的位置』。
 str w0, [sp, #12]    str w1, [sp, #8]

刚被保存到栈的值又被读取到相同的寄存器内。和str指令相反的,ldr指令是从一个内存中加载内容到寄存器。
ldr w0, [sp, #12]意思是『读取出在栈指针地址加上12字节内存的位置的内容,并将内容赋值给寄存器w0』。
如果你好奇为何w0和w1刚被存储又被加载出来,对,它是很奇怪,这两行明明就是多余的嘛!如果编译器允许基本的编译优化,那么这多余的就会被消除。
    ldr w0, [sp, #12]    ldr w1, [sp, #8]


意思是将w0和w1中的内容相加,并将相加的值赋值给r0。
add指令入参可以是两个或者三个,如果是三个,那第一个参数就是存储后两个参数相加的值的寄存器。
所以,这行指令也可以写成:add w0, w0, w1。
add w0, w0, w1

再一次,编译器生成了一些多余的代码:将相加的结果存储起来,又读取到相同的位置。
  str w0, [sp, #4]    ldr w0, [sp, #4]

函数即将终止,因此栈指针放回原来的地方。
函数开始时从sp(栈指针)上减去了12个字节而得到12个字节内存使用。现在它把12个字节还回去。
函数必须保证栈指针操作平衡,否则栈指针可能漂移,最终可能超出了已分配的内存。你应该不希望那样...
  add sp, sp, #16             ; =16    ret

3.2 简单点理解

     sub    sp, sp, #16             ; =16 //栈地址减去16,即分配了16字节内存     str    w0, [sp, #12] //把w0存储到sp栈中,sp指针上加上12字节的偏移量     str    w1, [sp, #8] //把w1存储到sp栈中,sp指针上加上8字节的偏移量     ldr    w0, [sp, #12] //读取栈中12字节偏移量的地址到存储器w0中     ldr    w1, [sp, #8] //读取栈中8字节偏移量的地址到存储器w1中     add    w0, w0, w1 //w0的值加上w1的值存储到w0中     str    w0, [sp, #4]//把w0的值存储到sp栈中     ldr    w0, [sp, #4]//读取sp栈中的值到w0中     add    sp, sp, #16             ; =16 //栈地址加16字节,即回收分配的内存     ret //结束

以上是未优化的汇编代码,有很多重复且没有用的代码。

3.3 Xcode选择release,编译器优化后:

  
  add w0, w1, w0    ret

小结:

  1. iOS中对象内存是分配在堆上的,局部变量或指针都是在栈上的。OC代码都会被机器编译成汇编,不考虑复杂的场景,汇编都是和栈的内存进行打交道。汇编也和CPU的寄存器打交道。

  2. 局部变量的值或地址在栈中,而真正的计算是在寄存器中的,使用时需要分配空间,即sub    sp, sp, #16 ;寄存器可以把值存储到栈中str    w0, [sp, #12];寄存器也可以从栈中取出值,

  3. 即 ldr    w1, [sp, #8];寄存器和寄存器之间也可以相互操作;用完需要释放内存,不然会有内存泄露,即add    sp, sp, #1。

4. arm64寄存器简单介绍

64位处理器有34个寄存器,包括31个通用寄存器、SP、PC、CPSR。

88bc4d10dba28b0f03bbfff702859e31.png

register

x0-x7: 用于子程序调用时的参数传递,x0 还用于返回值传递
x0 - x30 是31个通用整形寄存器。每个寄存器可以存取一个64位大小的数。当使用 r0 - r30 访问时,它就是一个64位的数。当使用 w0 - w30 访问时,访问的是这些寄存器的低32位

查看x0返回值,0x2e=46

6521a84685daf30a64a02e9170f98389.png

register read x0pc:表示当前执行的指令的地址(lldb) register read pc      pc = 0x00000001022c6c50  DataStructureDemo`addFunction + 28 at main.m:14:12lr:链接寄存器,存放着函数的返回地址:这里存放的是fooFunction地址(lldb) register read lr      lr = 0x00000001022c6c74  DataStructureDemo`fooFunction + 24 at main.m:18:95. _fooFunction:在上面的代码中添加下面函数,并调用addFunctionvoid fooFunction() {    int add = addFunction(12, 34);    printf("add = %i", add);}

5.1 添加注释后的汇编

    push    {r7, lr} //r7,lr入栈    mov r7, sp//r7=sp即r7保存了栈顶元素    sub sp, #8//sp减8字节    movs    r0, #12 //r0 = 12    movs    r1, #34//r1 = 34    bl  _addFunction //调用函数addFunction; r0,r1是addFunction两个参数    str r0, [sp, #4]//r0是返回结果,把r0存储到sp中    ldr r1, [sp, #4]//取出sp给r1;这两句等价于r1=r0;    movw    r0, :lower16:(L_.str-(LPC2_0+4))    movt    r0, :upper16:(L_.str-(LPC2_0+4))

 //printf函数的第一个参数是一个字符串,可以搜索L_.str,看到.asciz "add = %i" //前两个指令加载常量的地址,并减去标签的地址(LPC1_0加上4字节)。    add r0, pc     //r0加上pc(程序计数器),这样无论L.str在二进制文件的什么位置都能够准确的存放字符串的位置。     //上面三条指令加载指向所需的字符串的开始地址的指针到r0寄存器。    bl  _printf//执行printf函数,r0是参数,且字符串已拼接好    str r0, [sp]                @ 4-byte Spill//存储r0到栈中    add sp, #8//恢复栈内存    pop {r7, pc}//恢复r7,pc

6.OC函数

前面的写法都是C语言的写法,OC与C还是有一定区别。看下面源码:

- (int)addValue:(int)a toValue:(int)b {    int c = a + b;    return c;}

6.1优化版本汇编

adds    r0, r3, r2bx  lr

r3,r2是参数a和b,为什么不是r0,r1呢?
因为在OC中有两个隐士的参数:id self, SEL _cmd。

6.2 foo函数

- (void)foo {    int add = [self addValue:12 toValue:34];    NSLog(@"add = %i", add);}

转换后的汇编:

 push    {r7, lr}//r7,lr入栈 mov    r7, sp//r7=sp Ltmp10: movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_.2-(LPC4_0+4)) Ltmp11: movs    r2, #12//r2=12 movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_.2-(LPC4_0+4))//查找_cmd movs    r3, #34//r3=34 LPC4_0: add    r1, pc//r1=r1+pc ldr    r1, [r1]//表示加载存储在r1指针内的内容并赋值给r1。用伪代码表示r1=*r1 bl    _objc_msgSend//调用objc_msgsend Ltmp12: mov    r1, r0//r1=ro Ltmp13: movw    r0, :lower16:(L__unnamed_cfstring_-(LPC4_1+4)) movt    r0, :upper16:(L__unnamed_cfstring_-(LPC4_1+4)) //给r0赋值,r0=self; LPC4_1: add    r0, pc//r0=r0+pc bl    _NSLog//调用NSLog


 Ltmp14: pop    {r7, pc}//出栈

movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_.2-(LPC4_0+4))movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_.2-(LPC4_0+4))//查找_cmdadd    r1, pc//r1=r1+pc这三句放一块解读,没什么问题,r1存入当前selector的字符。selector的引用:其实selector就是存储在数据段的字符串。movw    r0, :lower16:(L__unnamed_cfstring_-(LPC4_1+4))movt    r0, :upper16:(L__unnamed_cfstring_-(LPC4_1+4))add    r0, pc//r0=r0+pc这三句与r1的三句类似,r0=self。

3.  总的上面步骤:r0=self,r1=_cmd,r2=12,r3=34;

调用objc_msgSend,调用NSLog。整体流程清晰明了。

总结:
1.至此大概了解了OC的整个汇编的过程.举一反三,看下viewDidLoad方法:

- (void)viewDidLoad {    [super viewDidLoad];    // Do any additional setup after loading the view.}

转成汇编:

    push {r7, lr}    mov r7, sp//只要是方法里调用了别的方法,上面两句少不了。    sub sp, #8//分配1个字节    movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_-(LPC0_0+4))Ltmp1:    movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_-(LPC0_0+4))//r1=cmd    movw    r2, :lower16:(L_OBJC_CLASSLIST_SUP_REFS_$_-(LPC0_1+4))    movt    r2, :upper16:(L_OBJC_CLASSLIST_SUP_REFS_$_-(LPC0_1+4))//r2=super    add r1, pc//r1+=pc    add r2, pc//r2+=pc    ldr r1, [r1]//r1=*r1;表示加载存储在r1指针内的内容并赋值给r1    ldr r2, [r2]//r2=*r2;表示加载存储在r2指针内的内容并赋值给r2    strd    r0, r2, [sp]//str r0, [sp];str r2, [sp + 4]即r0,r2存储到sp中    mov r0, sp//r0=sp    bl  _objc_msgSendSuper2//调用super方法    add sp, #8//恢复栈指针    pop {r7, pc}//恢复r7,pc

如果是汇编,反推到正常的代码逻辑: 

找到bl即找到了调用的方法,再找到r0,r1,r2等参数,知道方法的参数。

71b830963707e36c8eb13dbfea94e3df.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值