series
- IOS assembler introductory tutorial (I) ARM64 assembler basics
- Embedding assembly code in Xcode projects
- IOS assembler introduction (3) Assembler Section and data access
- IOS assembly tutorial (four) based on LLDB dynamic debugging quickly analyze the implementation of system functions
preface
In Objc, a Block is a special object whose instance is not a regular object structure, but a Block_layout structure. In the declaration, the structure of Block will be directly stored on the stack in the form of value types, and then it will be copied to the heap and become a special object. Learning the underlying principle of Block on the one hand, we can master the storage and transmission mode of complex value types. On the other hand, it can quickly locate and analyze related logic when encountering Block in reverse analysis.
The structure of the Block
The structure of blocks can be found in the Runtime open-source objC4-706, which is in block-private.h:
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
void (*invoke)(void*,...). ;struct Block_descriptor_1 *descriptor;
// imported variables
};
Copy the code
Compare the normal OC object objc_object structure:
struct objc_object {
private:
isa_t isa; // union contains Class
// ivar instances
}
Copy the code
A Block is followed by a list of captured variables, while a regular object is followed by a list of ivar instances.
Assembler representation of a Block
Let’s use a simple example to analyze the generated assembly code:
// block.m
#import <Foundation/Foundation.h>
typedef int (^CommonBlock)(void);
CommonBlock simpleBlockOnStack() {
int a = 1, b = 2, c = 3, d = 4, e = 5;
int (^theBlock)(void) = ^int {
return a + b + c + d + e;
};
return theBlock;
}
void invokeStackBlock() {
CommonBlock block = simpleBlockOnStack();
block();
}
int main(int argc, char *argv[]) {
invokeStackBlock();
return 0;
}
Copy the code
Generate a.out with clang:
clang -arch arm64 -isysroot `xcrun --sdk iphoneos --show-sdk-path` block.m -framework Foundation -fobjc-arc
Copy the code
Pull a.out into IDA or Hopper for disassembly, and analyze the process of Block creation, transfer, and invocation with the combination of simpleBlockOnStack and invokeStackBlock symbols.
Note that the value of var_XY is -0xxy and the value of var_s0 is 0.
Block creation process
Here is the disassembly result of the _simpleBlockOnStack symbol:
__text:0000000100007D9C SUB SP.SP.#0x70
__text:0000000100007DA0 STP X29, X30, [SP.#0x60+var_s0]
__text:0000000100007DA4 ADD X29, SP.#0x60
__text:0000000100007DA8 MOV W8, # 1
__text:0000000100007DAC STUR W8, [X29,#var_4]
__text:0000000100007DB0 MOV W8, # 2
__text:0000000100007DB4 STUR W8, [X29,#var_8]
__text:0000000100007DB8 MOV W8, # 3
__text:0000000100007DBC STUR W8, [X29,#var_C]
__text:0000000100007DC0 MOV W8, # 4
__text:0000000100007DC4 STUR W8, [X29,#var_10]
__text:0000000100007DC8 MOV W8, # 5
__text:0000000100007DCC STUR W8, [X29,#var_14]
__text:0000000100007DD0 ADRP X9, #__NSConcreteStackBlock_ptr@PAGE
__text:0000000100007DD4 LDR X9, [X9,#__NSConcreteStackBlock_ptr@PAGEOFF]
__text:0000000100007DD8 STR X9, [SP.#0x60+var_58]
__text:0000000100007DDC MOV W8, #0xC0000000
__text:0000000100007DE0 STR W8, [SP.#0x60+var_50]
__text:0000000100007DE4 MOV W8, # 0
__text:0000000100007DE8 STR W8, [SP.#0x60+var_4C]
__text:0000000100007DEC ADRP X9, #___simpleBlockOnStack_block_invoke@PAGE
__text:0000000100007DF0 ADD X9, X9, #___simpleBlockOnStack_block_invoke@PAGEOFF
__text:0000000100007DF4 STR X9, [SP.#0x60+var_48]
__text:0000000100007DF8 ADRP X9, #___block_descriptor_52_e5_i8__0l@PAGE
__text:0000000100007DFC ADD X9, X9, #___block_descriptor_52_e5_i8__0l@PAGEOFF
__text:0000000100007E00 STR X9, [SP.#0x60+var_40]
__text:0000000100007E04 LDUR W8, [X29,#var_4]
__text:0000000100007E08 STR W8, [SP.#0x60+var_38]
__text:0000000100007E0C LDUR W8, [X29,#var_8]
__text:0000000100007E10 STR W8, [SP.#0x60+var_34]
__text:0000000100007E14 LDUR W8, [X29,#var_C]
__text:0000000100007E18 STR W8, [SP.#0x60+var_30]
__text:0000000100007E1C LDUR W8, [X29,#var_10]
__text:0000000100007E20 STR W8, [SP.#0x60+var_2C]
__text:0000000100007E24 LDUR W8, [X29,#var_14]
__text:0000000100007E28 STR W8, [SP.#0x60+var_28]
__text:0000000100007E2C ADD X0, SP.#0x60+var_58
__text:0000000100007E30 BL _objc_retainBlock
__text:0000000100007E34 STUR X0, [X29,#var_20]
__text:0000000100007E38 LDUR X0, [X29,#var_20]
__text:0000000100007E3C BL _objc_retainBlock
__text:0000000100007E40 SUB X9, X29, #-var_20
__text:0000000100007E44 MOV X30, # 0
__text:0000000100007E48 STR X0, [SP.#0x60+var_60]
__text:0000000100007E4C MOV X0, X9
__text:0000000100007E50 MOV X1, X30
__text:0000000100007E54 BL _objc_storeStrong
__text:0000000100007E58 LDR X0, [SP.#0x60+var_60]
__text:0000000100007E5C LDP X29, X30, [SP.#0x60+var_s0]
__text:0000000100007E60 ADD SP.SP.#0x70
__text:0000000100007E64 B _objc_autoreleaseReturnValue
Copy the code
SimpleBlockOnStack (simpleBlockOnStack, simpleBlockOnStack, simpleBlockOnStack, simpleBlockOnStack, simpleBlockOnStack, simpleBlockOnStack, simpleBlockOnStack, simpleBlockOnStack, simpleBlockOnStack, simpleBlockOnStack, simpleBlockOnStack, simpleBlockOnStack, simpleBlockOnStack, simpleBlockOnStack, simpleBlockOnStack Variables A-E are stored in the -0x14 to -0x24 region of the stack, respectively.
Block ISA
__NSConcreteStackBlock_ptr is a pointer to __NSConcreteStackBlock. NSConcreteStackBlock is the ISA data of a Block.
__text:0000000100007DD0 ADRP X9, #__NSConcreteStackBlock_ptr@PAGE
__text:0000000100007DD4 LDR X9, [X9,#__NSConcreteStackBlock_ptr@PAGEOFF]
__text:0000000100007DD8 STR X9, [SP.#0x60+var_58]
Copy the code
It is stored in the -0x68 region of the stack (IDA, var_XY = -0xxY, SP pointing to -0x70, -0x70 + 0x60 + (-0x58) = -0x68).
Flags & Reserved
The Wn register is the lower 32 bits of Xn, that is, a Word = 4B, which is exactly the length of an int. They are stored in the -0x60 and -0x5c areas of the stack, respectively.
__text:0000000100007DDC MOV W8, #0xC0000000
__text:0000000100007DE0 STR W8, [SP.#0x60+var_50]
__text:0000000100007DE4 MOV W8, # 0
__text:0000000100007DE8 STR W8, [SP.#0x60+var_4C]
Copy the code
Block Invoker
Block Invoker is a function pointer to the Block’s logic, which is stored in the -0x58 region of the stack.
__text:0000000100007DEC ADRP X9, #___simpleBlockOnStack_block_invoke@PAGE
__text:0000000100007DF0 ADD X9, X9, #___simpleBlockOnStack_block_invoke@PAGEOFF
__text:0000000100007DF4 STR X9, [SP.#0x60+var_48]
Copy the code
Block Descriptor
Next is the storage logic of the Block Descriptor, which has the structure:
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
Copy the code
Uintptr_t is the alias of an unsigned long:
typedef unsigned long uintptr_t;
Copy the code
This is a 16B structure in AArch64. Note that its pointer is stored in memory, that is, 8B. Its storage logic is defined at 7DF8-7e00 and it is stored at -0x50 on the stack.
__text:0000000100007DF8 ADRP X9, #___block_descriptor_52_e5_i8__0l@PAGE
__text:0000000100007DFC ADD X9, X9, #___block_descriptor_52_e5_i8__0l@PAGEOFF
__text:0000000100007E00 STR X9, [SP.#0x60+var_40]
Copy the code
Imported Variables
The 7e04-7e28 region is the variable storage logic captured by the Block. Since __block is not declared, these values are simply static copies.
__text:0000000100007E04 LDUR W8, [X29,#var_4]
__text:0000000100007E08 STR W8, [SP.#0x60+var_38]
__text:0000000100007E0C LDUR W8, [X29,#var_8]
__text:0000000100007E10 STR W8, [SP.#0x60+var_34]
__text:0000000100007E14 LDUR W8, [X29,#var_C]
__text:0000000100007E18 STR W8, [SP.#0x60+var_30]
__text:0000000100007E1C LDUR W8, [X29,#var_10]
__text:0000000100007E20 STR W8, [SP.#0x60+var_2C]
__text:0000000100007E24 LDUR W8, [X29,#var_14]
__text:0000000100007E28 STR W8, [SP.#0x60+var_28]
Copy the code
This logic copies variables from -0x14 ~ -0x24 region on the stack to -0x48 ~ -0x38 region respectively. Combined with the above analysis, this is to copy local variables A-E to the variable capture region of the Block.
Stack Layout
With the above analysis, we can draw the memory Layout of the Block on the stack, where the light blue area is the entire contents of the Block Layout.
Block transfer
In the MRC era, blocks on the stack are not automatically copied to the heap, which means that blocks are accessed directly from -0x68 to -0x34. In this case, if other function calls are involved before the Block is called, In ARC, the Block is copied to the heap immediately after it is created. This code is in the 7E2C ~ 7E48 range:
__text:0000000100007E2C ADD X0, SP.#0x60+var_58
__text:0000000100007E30 BL _objc_retainBlock
__text:0000000100007E34 STUR X0, [X29,#var_20]
__text:0000000100007E38 LDUR X0, [X29,#var_20]
__text:0000000100007E3C BL _objc_retainBlock
__text:0000000100007E40 SUB X9, X29, #-var_20
__text:0000000100007E44 MOV X30, # 0
__text:0000000100007E48 STR X0, [SP.#0x60+var_60]
Copy the code
X0 = -0x70 + 0x60-0x58 = -0x68 = &block_ISA; then objc_retainBlock is called with ISA address as argument:
BLOCK_EXPORT void *_Block_copy(const void *aBlock)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
id objc_retainBlock(id x) {
return (id)_Block_copy(x);
}
Copy the code
BuiltIn’s function _Block_copy is actually called to copy the entire Block to the heap and return the Block ISA address of the heap, stored in the -0x70 section of the stack, as the return value of the function.
To sum up, Block Layout is actually passed the address of the Block ISA. The value of the address of the Block ISA can obtain the complete Block Layout data.
Block the call
Caller analysis
InvokeStackBlock () invokeStackBlock ();
__text:0000000100007EA4 SUB SP.SP.#0x30
__text:0000000100007EA8 STP X29, X30, [SP.#0x20+var_s0]
__text:0000000100007EAC ADD X29, SP.#0x20
__text:0000000100007EB0 BL _simpleBlockOnStack
__text:0000000100007EB4 MOV X29, X29
__text:0000000100007EB8 BL _objc_retainAutoreleasedReturnValue
__text:0000000100007EBC STUR X0, [X29,#var_8]
__text:0000000100007EC0 LDUR X0, [X29,#var_8]
__text:0000000100007EC4 MOV X30, X0
__text:0000000100007EC8 LDR X0, [X0,#0x10]
__text:0000000100007ECC STR X0, [SP.#0x20+var_10]
__text:0000000100007ED0 MOV X0, X30
__text:0000000100007ED4 LDR X30, [SP.#0x20+var_10]
__text:0000000100007ED8 BLR X30
__text:0000000100007EDC SUB X30, X29, #-var_8
__text:0000000100007EE0 STR W0, [SP.#0x20+var_14]
__text:0000000100007EE4 MOV X0, X30
__text:0000000100007EE8 MOV X30, # 0
__text:0000000100007EEC MOV X1, X30
__text:0000000100007EF0 BL _objc_storeStrong
__text:0000000100007EF4 LDP X29, X30, [SP.#0x20+var_s0]
__text:0000000100007EF8 ADD SP.SP.#0x30
__text:0000000100007EFC RET
Copy the code
Focusing on the 7EB0 ~ 7ED8 fields, this is how the simpleBlockOnStack function returns a Block and calls it:
__text:0000000100007EB0 BL _simpleBlockOnStack
__text:0000000100007EB4 MOV X29, X29
__text:0000000100007EB8 BL _objc_retainAutoreleasedReturnValue
__text:0000000100007EBC STUR X0, [X29,#var_8]
__text:0000000100007EC0 LDUR X0, [X29,#var_8]
__text:0000000100007EC4 MOV X30, X0
__text:0000000100007EC8 LDR X0, [X0,#0x10]
__text:0000000100007ECC STR X0, [SP.#0x20+var_10]
__text:0000000100007ED0 MOV X0, X30
__text:0000000100007ED4 LDR X30, [SP.#0x20+var_10]
__text:0000000100007ED8 BLR X30
Copy the code
SimpleBlockOnStack (X0 = ISA + 0x10); simpleBlockOnStack (X0 = ISA + 0x10); It is then assigned to X30 as a BLR parameter to implement the call to Block Invoker (X0 = ISA). So the Block Invoker takes the address of the Block ISA, which is to be able to retrieve Block information in the implementation, such as captured variables.
The Callee analysis
Let’s examine the implementation of ___simpleBlockOnStack_block_invoke, which points to the following code:
__text:0000000100007E68 SUB SP.SP.#0x10
__text:0000000100007E6C STR X0, [SP.#0x10+var_8]
__text:0000000100007E70 MOV X8, X0
__text:0000000100007E74 STR X8, [SP.#0x10+var_10]
__text:0000000100007E78 LDR W9, [X0,#0x20]
__text:0000000100007E7C LDR W10, [X0,#0x24]
__text:0000000100007E80 ADD W9, W9, W10
__text:0000000100007E84 LDR W10, [X0,#0x28]
__text:0000000100007E88 ADD W9, W9, W10
__text:0000000100007E8C LDR W10, [X0,#0x2C]
__text:0000000100007E90 ADD W9, W9, W10
__text:0000000100007E94 LDR W10, [X0,#0x30]
__text:0000000100007E98 ADD W0, W9, W10
__text:0000000100007E9C ADD SP.SP.#0x10
__text:0000000100007EA0 RET
Copy the code
X0 = &block_isa (X0 + 0x20, X0 + 0x24, X0 + 0x24, X0 + 0x24, X0 + 0x24) 0x20 and 0x24 are the addresses of a and B, respectively. The implementation of Block Invoker is basically clear: pass the Block ISA to get the Block information, and the other logic is the same as the general function.
Have a reference Block
However, there is still a problem here. If the Block itself has parameters, how can ISA be passed in? Let’s do an experiment and add two more functions to the first code:
typedef int (^CommonBlockWithParams)(int);
CommonBlockWithParams simpleBlockWithParamsOnStack(a) {
int a = 1, b = 2, c = 3, d = 4, e = 5;
int (^theBlock)(int) = ^int (int f) {
return a + b + c + d + e + f;
};
return theBlock;
}
void invokeStackBlockWithParams(a) {
CommonBlockWithParams block = simpleBlockWithParamsOnStack();
block(100);
}
Copy the code
Then analyze the implementation of invokeStackBlockWithParams remains from fragments from call simpleBlockWithParamsOnStack get Block to call.
__text:0000000100007E98 BL _simpleBlockWithParamsOnStack
__text:0000000100007E9C MOV X29, X29
__text:0000000100007EA0 BL _objc_retainAutoreleasedReturnValue
__text:0000000100007EA4 STUR X0, [X29,#var_8]
__text:0000000100007EA8 LDUR X0, [X29,#var_8]
__text:0000000100007EAC MOV X30, X0
__text:0000000100007EB0 LDR X0, [X0,#0x10]
__text:0000000100007EB4 STR X0, [SP.#0x20+var_10]
__text:0000000100007EB8 MOV X0, X30
__text:0000000100007EBC MOV W1, #0x64
__text:0000000100007EC0 LDR X30, [SP.#0x20+var_10]
__text:0000000100007EC4 BLR X30
Copy the code
Block ISA is passed X0, and Block ISA is passed X0, and Block ISA is passed X0. The function starts with X1.
Dynamic capture of blocks
By default, blocks capture members in Copy form, which makes it impossible to change the value of the original variable in Block Invoker. To do so, you need to modify the variable with __block and Copy it to the heap, which is more complicated and will be covered in the next article.
conclusion
A Block is a special object. If you compare it to a normal OC object, it has no SEL and has the same structure as objc_Object. This is similar to the ISA + ivar list of ordinary OC objects. When a Block is called, the Block’s fixed input parameter X0 = Block ISA, This is similar to the OC method’s fixed input arguments X0 = self, X1 = SEL, and the Block function’s arguments are stored sequentially from X1.