This is the 12th day of my participation in the August Text Challenge.More challenges in August

Block low-level compilation

Let’s write a simple block in main and convert it to a CPP file.

int main(a){
    __block int a = 18
   // __Block_byref_a_0 int a = 18; The two equal
    void(^block)(void) = ^{
        a++;
        printf(" - %d",a);
    };
    
    block();
    return 0;
}
Copy the code
int main(a){
    // int a = 18;
     __Block_byref_a_0 a = {
     (void*)0,
     (__Block_byref_a_0 *)&a,
      0.sizeof(__Block_byref_a_0),
      18};
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}
Copy the code

The underlying block is the __main_block_IMPL_0 constructor.

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

If we look at the structure, it has an argument a in it, and if the block doesn’t capture any external variables, we re-xcrun and see that there is no member variable A in the structure. So the conclusion is that block capture generates the corresponding member variable

One more detail, the default is an NSConcreteStackBlock at compile time, but we learned in the previous section that if an external variable is captured, it should be a heap Block, so there are some operations at run time that turn the stack into a heap.

What is __main_block_func_0, the first argument to __main_block_impl_0?

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; 

        (a->__forwarding->a)++;
        printf("- %d",(a->__forwarding->a));
 }
Copy the code

It looks a little bit like an executing function inside a block, FuncPtr (__block_impl *)block)->FuncPtr ->FuncPtr ->FuncPtr ->FuncPtr ->FuncPtr ->FuncPtr ->FuncPtr

__blockWhat did

After we add __block, the compiler finds an extra one of these constructs

  __Block_byref_a_0 a = {
       (void*)0,
       (__Block_byref_a_0 *)&a,
       0.sizeof(__Block_byref_a_0),
       18
   };
Copy the code

This is written as an initialization of the __Block_byref_a_0 structure

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};
Copy the code

__forwarding = &a refers to the address of A. In the __main_block_IMPL_0 initialization above, A (_a->__forwarding) passes the __forwarding pointer of __Block_byref_a_0 to the member variable a by default, so __Block_byref_a_0 *a = __cself->a; The address of a is the same as the address of the external parameter a, which is why the __block modifier generates the __Block_byref_a_0 structure, which passes the address of the pointer to the block, so it can modify the same memory space.

Block assembler analyzes the process of obtaining a signature copy

Debug assembly mode is enabled and a function is locatedobjc_retainBlock, open libobJC source codeA sign breakpoint in a project_Block_copyLocate thelibsystem_blocks.dylibIn this library, use the real environment to print the register value on retainSo after this function, the block is copied from the stack to the heap.signatureThe signature is block’s signature@?

Block_layout structure

We found the _Block_copy function in libclosure

void *_Block_copy(const void *arg) {
 		/ /... The following is heap and stack but we know that the default for compile time is a stack area
        // Its a stack block. Make a copy. -> heap
        struct Block_layout *result =
            (struct Block_layout *)malloc(aBlock->descriptor->size);
        if(! result)return NULL;
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
        // Resign the invoke pointer as it uses address authentication.
        result->invoke = aBlock->invoke;
#endif
        // reset refCount -- object ISA consortium bit field
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        _Block_call_copy_helper(result, aBlock);
        // ISA is re-marked as a heap area
        result->isa = _NSConcreteMallocBlock;
        returnresult; }}Copy the code

Here we have the block structure Block_layout

struct Block_layout {
    void *isa; / / 8
    volatile int32_t flags; // contains ref count 8
    int32_t reserved; / / 8
    BlockInvokeFunction invoke;
    struct Block_descriptor_1 *descriptor;
    // imported variables
};
Copy the code

Click on the Block_descriptor_1 and you will find no copy or dispose functions as read by the LLDB above, but you will find them in the Block_descriptor_2 structure.

#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
    uintptr_t reserved; / / 8
    uintptr_t size;  / / 8
};

#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
    // requires BLOCK_HAS_COPY_DISPOSE
    BlockCopyFunction copy; / / 8
    BlockDisposeFunction dispose; / / 8
};

#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
    // requires BLOCK_HAS_SIGNATURE
    const char *signature;
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
Copy the code

Global search for get methods to find Block_descriptor_2, I’m going to shift Block_descriptor_1 the size of the memory space so I get Block_descriptor_2 and I’m going to shift another Block_descriptor_2 to get Block_descriptor_3 where the 2’s and the 3’s are optional

static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
    if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
    uint8_t *desc = (uint8_t *)aBlock->descriptor;
    desc += sizeof(struct Block_descriptor_1);
    return (struct Block_descriptor_2 *)desc;
}
Copy the code

So the block printed on the console above has a signature and copy descriptor, so Block_descriptor_2 and Block_descriptor_3. We can check x0’s memory address to verify this

The first 0x00000001F7d94580 is the ISA for Block_layout, which is translated by two 8 bytes to get 0x000000019e4ACA38, the address of invoke, The memory for Block_descriptor_1 is block and shift 8 bytes to 0x00000001FD099a18 to check the current memory space.

The address 0x000000019E49e308 in the blue box is the address of dispose, and the address 0x000000019E49e310 in the red box is the address of Dispose. The first line is the memory space for Block_descriptor_1 (16 bytes), the second line is the memory space for Block_descriptor_2 (16 bytes), and the third line begins with the memory space for Block_descriptor_3. So we’ll take that const char star, and we’ll get the block’s signature as well

Block captures external variables

Go back to our previous CPP file and find the Block_descriptor_1 and Block_descriptor_2 structures

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = {
    0.sizeof(struct __main_block_impl_0),
    __main_block_copy_0,
    __main_block_dispose_0
    
};
Copy the code

So the Block_descriptor_2 ‘ ‘copy = __main_block_copy_0

static void __main_block_copy_0(struct __main_block_impl_0*dst, 
                                struct __main_block_impl_0*src) 
{_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/); }Copy the code

Use the _Block_object_assign function to call the actual Block_descriptor_2 copy. Search for this function in libclosure’s source code. Found block annotation BLOCK_FIELD_IS_OBJECT for the type of externally captured variable

BLOCK_FIELD_IS_BYREF=8 in the __main_block_COPY_0 block above because it is an int variable decorated with __block.

void _Block_object_assign(void *destArg, const void *object, const int flags) {
    const void **dest = (const void **)destArg;
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_OBJECT: // oc Object type
        _Block_retain_object(object);
        *dest = object;
        break;

      case BLOCK_FIELD_IS_BLOCK: // Block value copy
        *dest = _Block_copy(object);
        break;
    
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        *dest = _Block_byref_copy(object);
        break;
		/ /...
      default:
        break; }}Copy the code

BLOCK_FIELD_IS_OBJECT: a common object type _Block_retain_object_default = fn Is assigned to system-level ARC operations by default. BLOCK_FIELD_IS_BLOCK: copy the value BLOCK_FIELD_IS_BYREF: enter the _Block_byref_copy code

// src points to stack
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
copy->isa = NULL;
// byref value 4 is logical refcount of 2: one for caller, one for stack
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy;  // patch stack to point to heap copy
copy->size = src->size;
(*src2->byref_keep)(copy, src);
Copy the code

(*src2->byref_keep)(copy, SRC) rewrites the object inside the structure. The address of the copy is the same as the address of the captured variable, which is why __block objects are pointer copies. Summary: If a variable is decorated with __block, the block first copies it from the stack to the heap. Then block captures the variable Block_byref and copies it, and Block_byref copies the variable for the object inside it. This is also the principle of __block three-layer copy.

Block flag logo

  • Bit 1, release flag, – commonBLOCKNEEDSFREEDo the bit and the operation, along with Flags, to indicate that the block can be freed.
  • Low 16 bits, stores the value of the reference count; Is an optional parameter
  • Bit 24, which indicates whether the lower 16 is valid, is used by the program to increase or decrease the value of the reference count bit.
  • (25) Whether a copy helper function is available;
  • 26th, whether it has a block destructor;
  • 27th, indicating whether there is garbage collection; //0SX
  • Bit 28, indicating whether the block is global;
  • At number 30, withBLOCK_USE_STRETIn contrast, determine whether the current block has a signature. Used for dynamic invocation at Runtime.