series

  1. IOS assembler introductory tutorial (I) ARM64 assembler basics
  2. Embedding assembly code in Xcode projects
  3. IOS assembler introduction (3) Assembler Section and data access
  4. 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.