OC method
Now that we have seen the basic instructions for assembly in the previous articles, let’s look at how OC assembly is implemented.
Objc_msgSend (id self, SEL _cmd) is called by default with two arguments. Let’s look at a simple assembly implementation of the OC method:
The assembly code for the main function looks like this:
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
Copy the code
So in this code, we see objc_msgSend, we hit a breakpoint here, we read x0 and x1, what are they
Through LLDB debugging, we can see that x0 is Person and x1 is instance SEL, which verifies that the OC method is objc_msgSend at the bottom.
When we see a piece of assembly code, we go to objc_msgSend and read it to x0 and x1 to see which object or class method is being called.
objc_storeStrong
In line 5 from the bottom of the main function code, we see that the objc_storeStrong method is called. This method should only be called on objects that are modified by strong, because the p we define is local and is modified by strong by default
In the figure above, the LLDB reads the values in x0, which holds the pointer address of the p object, and x1=nil.
In the OC source, find objc_storeStrong and plug x0 and x1 into the function
objc_storeStrong(id *location, id obj) { id prev = *location; //prev = *location = p object, obj = nil if (obj == prev) {return; } objc_retain(obj); *location = obj; //p = nil objc_release(prev); //objc_release(p) }Copy the code
So by analysis, what we’re doing here is we’re pointing p to nil, freeing up the heap where the P object is. Since p is a local variable, the function is released when it completes execution.
Block
The underlying Block is also a structure, which is also an object. Most of the time we use blocks as arguments to implement result callbacks, so our main object of study will be the contents of the 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
};
Copy the code
Let’s look at a simple block:
We can see objc_retainBlock at line 10 of the assembly code, when we read the X0 register, we can see that this is a globalBlock, invoke at 0x1005C58C0. Further down, we can see that there is a jump instruction in line 17, read the jump address, Ei!! Invoke is the address of Invoke and tells us that it is the invoke block on line 74 of main. Click on it and see the assembly implementation of 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
Copy the code