OC方法
通过前面几篇文章,我们已经了解了汇编的基础指令,今天来看一下OC的汇编是怎么实现的。
我们都知道OC方法在底层是消息发送,而且默认带有两个参数,调用的是objc_msgSend(id self, SEL _cmd),那么我们就看一个简单的OC方法的汇编实现:
这段main函数的汇编代码如下:
ZZZ`main:
0x102e798b0 <+0>: sub sp, sp, #0x30 ; =0x30
0x102e798b4 <+4>: stp x29, x30, [sp, #0x20]
0x102e798b8 <+8>: add x29, sp, #0x20 ; =0x20
0x102e798bc <+12>: stur wzr, [x29, #-0x4]
0x102e798c0 <+16>: stur w0, [x29, #-0x8]
0x102e798c4 <+20>: str x1, [sp, #0x10]
0x102e798c8 <+24>: adrp x8, 4
0x102e798cc <+28>: add x8, x8, #0xc40 ; =0xc40
-> 0x102e798d0 <+32>: ldr x0, [x8]
0x102e798d4 <+36>: adrp x8, 4
0x102e798d8 <+40>: add x8, x8, #0xc00 ; =0xc00
0x102e798dc <+44>: ldr x1, [x8]
0x102e798e0 <+48>: bl 0x102e79c84 ; symbol stub for: objc_msgSend
0x102e798e4 <+52>: mov x29, x29
0x102e798e8 <+56>: bl 0x102e79cc0 ; symbol stub for: objc_retainAutoreleasedReturnValue
0x102e798ec <+60>: add x8, sp, #0x8 ; =0x8
0x102e798f0 <+64>: str x0, [sp, #0x8]
0x102e798f4 <+68>: stur wzr, [x29, #-0x4]
0x102e798f8 <+72>: mov x0, x8
0x102e798fc <+76>: mov x8, #0x0
0x102e79900 <+80>: mov x1, x8
0x102e79904 <+84>: bl 0x102e79cd8 ; symbol stub for: objc_storeStrong
0x102e79908 <+88>: ldur w0, [x29, #-0x4]
0x102e7990c <+92>: ldp x29, x30, [sp, #0x20]
0x102e79910 <+96>: add sp, sp, #0x30 ; =0x30
0x102e79914 <+100>: ret
复制代码
在这段代码里,我们看到了objc_msgSend,在此处打一个断点,读出此时的x0和x1,看看是什么
通过lldb调试,我们看到此时的x0是Person这个类,x1是instance这个SEL,验证了OC方法在底层就是objc_msgSend。
当我们看到一段汇编代码,找到objc_msgSend,读出此时到x0和x1就能知道正在调用的是哪个对象或者类的什么方法。
objc_storeStrong
在main函数代码的倒数第5行,我们看到调用了objc_storeStrong方法。这个方法应该是被strong修饰的对象才会调用,因为我们定义的p是局部变量,默认也是strong修饰的
在上图中,通过lldb读取出了x0和x1中的值,x0中保存着p对象的指针地址,x1=nil。
在OC的源码中,找到objc_storeStrong,把x0和x1带入到函数中
objc_storeStrong(id *location, id obj)
{
id prev = *location; //prev = *location = p对象 , obj = nil
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj; //p = nil
objc_release(prev); //objc_release(p)
}
复制代码
通过分析,这里其实是把p指向nil,p对象所在的堆空间释放。因为p是一个局部变量,函数执行完成就会被释放。
Block
Block的底层也是一个结构体,也是一个对象。我们在使用block的时候,大多数就是作为参数,来实现结果回调,那么我们主要的研究对象就是block中的内容,也就是invoke。
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
BlockInvokeFunction invoke;
struct Block_descriptor_1 *descriptor;
// imported variables
};
复制代码
下面我们看一个简单的block:
可以看到汇编代码的第10行objc_retainBlock,此时我们读x0寄存器,可以看到这个一个globalBlock,invoke在0x1005c58c0这里。继续向下执行,我们可以看到17行有一个跳转指令,读出跳转的地址,Ei!! 正是invoke的地址,并且告诉我们是main函数74行那个block的invoke,点击进去,就看到了invoke的汇编实现。
ZZZ`__main_block_invoke:
-> 0x1005c58c0 <+0>: sub sp, sp, #0x20 ; =0x20
0x1005c58c4 <+4>: stp x29, x30, [sp, #0x10]
0x1005c58c8 <+8>: add x29, sp, #0x10 ; =0x10
0x1005c58cc <+12>: str x0, [sp, #0x8]
0x1005c58d0 <+16>: str x0, [sp]
0x1005c58d4 <+20>: adrp x0, 3
0x1005c58d8 <+24>: add x0, x0, #0x2f8 ; =0x2f8
0x1005c58dc <+28>: bl 0x1005c5bf8 ; symbol stub for: NSLog
0x1005c58e0 <+32>: ldp x29, x30, [sp, #0x10]
0x1005c58e4 <+36>: add sp, sp, #0x20 ; =0x20
0x1005c58e8 <+40>: ret
复制代码