preface
This article will cover block types, circular references, and related interview questions, and the next article will verify and analyze these upper-level representations using the underlying source code.
To prepare
- Libclosure source
I. Clang analysis block
Clang analysis of basic variables
Create mMain in main.m:
int mMain(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
return mMain();
}
return NSApplicationMain(argc, argv);
}
int mMain(void) {
int a = 18;
void(^block)(void) = ^{
printf("ssl - %d",a);
};
block();
return 0;
}
Copy the code
To get the main. CPP file, run clang-rewrite-objc main.m -o main. CPP.
int mMain(void) { int a = 18; void(*block)(void) = __mMain_block_impl_0(__mMain_block_func_0, &__mMain_block_desc_0_DATA, a)); block->FuncPtr(block); return 0; } struct __mMain_block_impl_0 { struct __block_impl impl; struct __mMain_block_desc_0* Desc; int a; __mMain_block_impl_0(void *fp, struct __mMain_block_desc_0 *desc, int _a, int flags=0) : a(_a) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code
- in
mMain
Function is called__mMain_block_impl_0
. __mMain_block_impl_0
It’s a structure. There’s oneint a
A member variable has a constructor wherea(_a)
Is thea
A member variable has been assigned.impl.isa = &_NSConcreteStackBlock;
And you can see here is aStack block
And in our understanding, this is supposed to beHeap block
That’s right. We’re going to analyze it.fp
ismMain
In the__mMain_block_func_0
And assigns it toFuncPtr
And the finalblock
The call to is the callFuncPtr
That is__mMain_block_func_0
.
Check the __mMain_block_func_0:
static void __mMain_block_func_0(struct __mMain_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
printf("ssl - %d",a);
}
Copy the code
__cself
is__mMain_block_impl_0
Structure.- Here,
a
And the outsidea
Is not a value, so changes will report errors.
__block modifies the basic variable clang analysis
Modify the code to modify a with __block:
int mMain(void) {
__block int a = 18;
void(^block)(void) = ^{
printf("ssl - %d",a);
};
block();
return 0;
}
Copy the code
CPP: clang-rewrite-objc main.m -o main. CPP: clang-rewrite-objc main.m -o main. CPP
int mMain(void) { __Block_byref_a_0 a = { (void*)0, (__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 18}; Void (*block)(void) = __mMain_block_impl_0(__mMain_block_func_0, &__mMain_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344)); block->FuncPtr(block); return 0; } struct __mMain_block_impl_0 { struct __block_impl impl; struct __mMain_block_desc_0* Desc; __Block_byref_a_0 *a; // by ref __mMain_block_impl_0(void *fp, struct __mMain_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; }}; struct __Block_byref_a_0 { void *__isa; __Block_byref_a_0 *__forwarding; int __flags; int __size; int a; }; static void __mMain_block_func_0(struct __mMain_block_impl_0 *__cself) { __Block_byref_a_0 *a = __cself->a; // bound by ref printf("ssl - %d",(a->__forwarding->a)); }Copy the code
- You can see the variables
a
It’s compiled here__Block_byref_a_0
Structure, when initialized will&a
Assigned to__forwarding
, which assigns its own address to itself__forwarding
. - call
__mMain_block_impl_0
When the constructor of the__forwarding
Assigned to__mMain_block_impl_0
Member variable ofa
. - So call
__mMain_block_func_0
Function,__cself->a
And outside thea
Is pointing to the same memory address, can be modified.
Block assembly analysis signature, copy process
Assembly analysis yields the underlying calls and libraries
Create an NSObject object and print it in the block, using assembly debugging to find the function objc_retainBlock that the block first calls:
Objc_retainBlock symbol breakpoint to get the next function called _Block_copy:
_Block_copy symbol breakpoint to get libsystem_blocks. Dylib:
_Block_copy is available in libsystem_blocks, but the libsystem_blocks library is not open source. You can disassemble the source code and use libclosure 79 as an alternative project.
_Block_copy source code analysis
Enter the _Block_copy function:
// Copy, or bump refcount, of a block. If really copying, call the copy helper if present. void *_Block_copy(const void *arg) { struct Block_layout *aBlock; if (! arg) return NULL; // The following would be better done as a switch statement aBlock = (struct Block_layout *)arg; If (aBlock->flags & BLOCK_NEEDS_FREE) {// latches on high latching_incr_int(&aBlock->flags); return aBlock; Else if (aBlock->flags & BLOCK_IS_GLOBAL) {return aBlock; } else {// Its a stack block. Make a copy. Size_t size = Block_size(aBlock); Struct Block_layout *result = (struct Block_layout *)malloc(size); if (! result) return NULL; // memmove(result, aBlock, size); // bitcopy first #if __has_feature(ptrauth_calls) // Resign the invoke pointer as it uses address authentication. result->invoke = aBlock->invoke; #if __has_feature(ptrauth_signed_block_descriptors) if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) { uintptr_t oldDesc = ptrauth_blend_discriminator( &aBlock->descriptor, _Block_descriptor_ptrauth_discriminator); uintptr_t newDesc = ptrauth_blend_discriminator( &result->descriptor, _Block_descriptor_ptrauth_discriminator); Copy of descriptor Result -> Descriptor = ptrAuth_auth_and_resume (aBlock-> Descriptor, ptrAUTH_key_asda, oldDesc, ptrauth_key_asda, newDesc); } #endif #endif // reset refcount 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 marks MallocBlock result->isa = _NSConcreteMallocBlock; return result; }}Copy the code
block
Is the nature ofBlock_layout
Structure.- The function is passing you
Stack block
Will open up a new oneHeap block
And thenStack block
The associated values are copied to the heap.
Check the Block_layout:
struct Block_layout { void * __ptrauth_objc_isa_pointer isa; // Where to point block, stack/heap/global volatile int32_t flags; // Contains ref count whether it is destructing and whether it has a signature int32_t reserved; BlockInvokeFunction invoke; Struct Block_descriptor_1 *descriptor; // Imported variables};Copy the code
Check flags values:
// Values for Block_layout->flags to describe block objects enum { BLOCK_DEALLOCATING = (0x0001), // Whether the Runtime is destructing BLOCK_REFCOUNT_MASK = (0xfffe), // Runtime reference count mask BLOCK_INLINE_LAYOUT_STRING = (1 << 21), // compiler #if BLOCK_SMALL_DESCRIPTOR_SUPPORTED BLOCK_SMALL_DESCRIPTOR = (1 << 22), // compiler #endif BLOCK_IS_NOESCAPE = (1 << 23), // compiler BLOCK_NEEDS_FREE = (1 << 24), BLOCK_HAS_COPY_DISPOSE = (1 << 25), // Compiler whether copy, BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code BLOCK_IS_GC = (1 << 27), // runtime BLOCK_IS_GLOBAL = (1 << 28), Block BLOCK_USE_STRET = (1 << 29), // Compiler: undefined if! BLOCK_HAS_SIGNATURE BLOCK_HAS_SIGNATURE = (1 << 30), BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // Compiler};Copy the code
Assembler analysis signature, copy process
_Block_copy symbol breakpoint, print block through register:
- And you can see that at this point
block
orStackBlock
.
Assembler follows the flow, goes to the bottom, prints the block again through the register:
- You can see
StackBlock
Turned out to beMallocBlock
.block
The address is different. signature
:v8@? 0
The return value isvoid
, accounting for8
Bytes,@?
Type, from0
Start at position #).invoke
: function caller,copy
,dispose
The following will be analyzed.
Get signature details from NSMethodSignature:
(lldb) po [NSMethodSignature signatureWithObjCTypes:"v8@?0"] <NSMethodSignature: 0xb91f68C7625b0cf7 > Number of arguments = 1 frame size = 224 is struct return? NO // NO return value: -------- -------- -------- -------- type encoding (v) 'v' flags {} modifiers {} frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0} memory {offset = 0, size = 0} argument 0: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- / / parameters starting from 0 position type encoding (@) '@? '// The type is @? Flags {isObject, isBlock}} frame {offset = 0, offset adjust = 0, size = 8 Memory {offset = 0, size = 8} (LLDB) memory {offset = 0, size = 8}Copy the code
Three, Block_layout structure
To viewBlock_layout
And the relateddescriptor
Structure:
struct Block_layout { void * __ptrauth_objc_isa_pointer isa; // Where to point block, stack/heap/global volatile int32_t flags; // Contains ref count whether it is destructing and whether it has a signature int32_t reserved; BlockInvokeFunction invoke; Struct Block_descriptor_1 *descriptor; // Imported variables}; #define BLOCK_DESCRIPTOR_1 1 struct Block_descriptor_1 { uintptr_t reserved; uintptr_t size; }; #define BLOCK_DESCRIPTOR_2 1 struct Block_descriptor_2 { // requires BLOCK_HAS_COPY_DISPOSE BlockCopyFunction copy; BlockDisposeFunction dispose; }; #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
- Here,
Block_descriptor_2
,Block_descriptor_3
It’s optional, it may or may not, and then it goes through themget
Function to analyze.
By searching the query, find_Block_descriptor_2
,_Block_descriptor_3
Function:
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
uint8_t *desc = (uint8_t *)_Block_get_descriptor(aBlock);
desc += sizeof(struct Block_descriptor_1);
return (struct Block_descriptor_2 *)desc;
}
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
uint8_t *desc = (uint8_t *)_Block_get_descriptor(aBlock);
desc += sizeof(struct Block_descriptor_1);
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
desc += sizeof(struct Block_descriptor_2);
}
return (struct Block_descriptor_3 *)desc;
}
Copy the code
Block_descriptor_2
Is through theblock
translationBlock_descriptor_1
The size of the.- If you have
copy
,dispose
The function,block
Translation firstBlock_descriptor_1
Is the size ofBlock_descriptor_2
.Block_descriptor_2
And then I shift it by its sizeBlock_descriptor_3
. - If there is no
copy
,dispose
The function,Block_descriptor_3
byblock
translationBlock_descriptor_1
The magnitude of theta is directly obtained.
Back to assembly debugging, LLDB print validation
0x00000001048f0010
isBlock_descriptor_2
The address of its first32
byte0x00000001048ef3e3
isBlock_descriptor_3
The first member variable ofsignature
, prints out its valuev8@? 0
.
4. The capture variable life cycle of __block
Block prints an __block modified NSObject object inside a block:
int mMain(void) {
__block NSObject *obj = [NSObject new];
void(^block)(void) = ^{
NSLog(@"ssl - %@",obj);
};
block();
return 0;
}
Copy the code
Clang-rewrite-objc main.m -o main. CPP
int mMain(void) { __Block_byref_obj_0 obj = { (void*)0,(__Block_byref_obj_0 *)&obj, 33554432, sizeof(__Block_byref_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, objc_msgSend((id)objc_getClass("NSObject"), sel_registerName("new")) }; void(*block)(void) = __mMain_block_impl_0(__mMain_block_func_0, &__mMain_block_desc_0_DATA, (__Block_byref_obj_0 *)&obj, 570425344)); block->FuncPtr(block); return 0; } struct __mMain_block_impl_0 { struct __block_impl impl; struct __mMain_block_desc_0* Desc; __Block_byref_obj_0 *obj; // by ref __mMain_block_impl_0(void *fp, struct __mMain_block_desc_0 *desc, __Block_byref_obj_0 *_obj, int flags=0) : obj(_obj->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }}; struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; struct __Block_byref_obj_0 { void *__isa; __Block_byref_obj_0 *__forwarding; int __flags; int __size; void (*__Block_byref_id_object_copy)(void*, void*); void (*__Block_byref_id_object_dispose)(void*); NSObject *obj; }; static void __mMain_block_func_0(struct __mMain_block_impl_0 *__cself) { __Block_byref_obj_0 *obj = __cself->obj; // bound by ref NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_rpsdqw797gn7n3l8myk82s5w0000gn_T_main_35fc7e_mi_0,(obj->__forwarding->obj)); }Copy the code
Capture variable Block_byref copy analysis
View the __mMain_block_desc_0 related functions:
static struct __mMain_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __mMain_block_impl_0*, struct __mMain_block_impl_0*);
void (*dispose)(struct __mMain_block_impl_0*);
} __mMain_block_desc_0_DATA = { 0,
sizeof(struct __mMain_block_impl_0),
__mMain_block_copy_0,
__mMain_block_dispose_0};
static void __mMain_block_copy_0(struct __mMain_block_impl_0*dst, struct __mMain_block_impl_0*src) {
_Block_object_assign((void*)&dst->obj, (void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __mMain_block_dispose_0(struct __mMain_block_impl_0*src) {
_Block_object_dispose((void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);
}
Copy the code
copy
anddispose
That’s what we mentioned aboveBlock_descriptor_2
The member variable in, where assigned to__mMain_block_copy_0
Functions and__mMain_block_dispose_0
The function,__mMain_block_copy_0
Call the_Block_object_assign
Function._Block_object_assign
The first argument to&dst->obj
Is in the heap__Block_byref_obj_0
._Block_object_assign
The second argument tosrc->obj
In the stack__Block_byref_obj_0
._Block_object_assign
The third pass to the function is8
That isBLOCK_FIELD_IS_BYREF
Type.enum { // see function implementation for a more complete description of these fields and combinations BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block, ... BLOCK_FIELD_IS_BLOCK = 7, // a block variable BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines. }; Copy the code
Search the _Block_object_assign function in the source code:
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)) {// Common object types case BLOCK_FIELD_IS_OBJECT: /******* id object = ... ; [^{ object; } copy]; ********/ // There is no operation and the system level arc is handling _Block_retain_object_default = fn (arc) _Block_retain_object(object); // The stack block object is assigned to the stack block object *dest = object; break; / / __block modification type case BLOCK_FIELD_IS_BLOCK: / * * * * * * * void ^ (object) (void) =... ; [^{ object; } copy]; ********/ *dest = _Block_copy(object); break; case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK: case BLOCK_FIELD_IS_BYREF: // 8 /******* // copy the onstack __block container to the heap // Note this __weak is old GC-weak/MRC-unretained. // ARC-style __weak is handled by the copy helper directly. __block ... x; __weak __block ... x; [^{ x; } copy]; * * * * * * * * / / / the object here is __Block_byref_obj_0 * dest = _Block_byref_copy (object); break; case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT: case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK: /******* // copy the actual field held in the __block container // Note this is MRC unretained __block only. // ARC retained __block is handled by the copy helper directly. __block id object; __block void (^object)(void); [^{ object; } copy]; ********/ *dest = object; break; case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK: case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK: /******* // copy the actual field held in the __block container // Note this __weak is old GC-weak/MRC-unretained. // ARC-style __weak is handled by the copy helper directly. __weak __block id object; __weak __block void (^object)(void); [^{ object; } copy]; ********/ *dest = object; break; default: break; }}Copy the code
flags
isBLOCK_FIELD_IS_BYREF
Type, so the call is_Block_byref_copy
Function, passed inobject
I already said it’s on the stack__Block_byref_obj_0
.
Enter the _Block_byref_copy:
Static struct Block_byref *_Block_byref_copy(const void *arg) {struct Block_byref * SRC = (struct Block_byref *arg) Block_byref *)arg; If ((SRC ->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) { 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; if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) { // Trust copy helper to copy everything of interest // If more than one field shows up in a byref block this is wrong XXX struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1); struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1); copy2->byref_keep = src2->byref_keep; copy2->byref_destroy = src2->byref_destroy; if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) { struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1); struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1); copy3->layout = src3->layout; } // Capture external variables - memory processing - life cycle save (*src2->byref_keep)(copy, SRC); } else { // Bitwise copy. // This copy includes Block_byref_3, if any. memmove(copy+1, src+1, src->size - sizeof(*src)); } } // already copied to heap else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) { latching_incr_int(&src->forwarding->flags); } return src->forwarding; }Copy the code
- Open up again on the heap
Block_byref
Structure memory will be on the stackBlock_byref
The values are copied over, and the values on the stack and heapforwarding
They all point to this memory space. - Called after the copy operation is complete
byref_keep
Function, what does this function call.
Object copy analysis in Block_byref
Block_byref:
Struct Block_byref {void * __ptrAuth_objc_isa_pointer isa; // 8 struct Block_byref *forwarding; // 8 volatile int32_t flags; // contains ref count//4 uint32_t size; / / 4}; struct Block_byref_2 { // requires BLOCK_BYREF_HAS_COPY_DISPOSE BlockByrefKeepFunction byref_keep; // 8 BlockByrefDestroyFunction byref_destroy; / / 8}; struct Block_byref_3 { // requires BLOCK_BYREF_LAYOUT_EXTENDED const char *layout; };Copy the code
Size: byref_keep: byref_keep: byref_keep
int mMain(void) {
__Block_byref_obj_0 obj = {
(void*)0,(__Block_byref_obj_0 *)&obj,
33554432,
sizeof(__Block_byref_obj_0),
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
objc_msgSend((id)objc_getClass("NSObject"), sel_registerName("new"))
};
void(*block)(void) = __mMain_block_impl_0(__mMain_block_func_0, &__mMain_block_desc_0_DATA, (__Block_byref_obj_0 *)&obj, 570425344));
block->FuncPtr(block);
return 0;
}
struct __Block_byref_obj_0 {
void *__isa;
__Block_byref_obj_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *obj;
};
Copy the code
Byref_keep calls __Block_byref_id_object_copy_131:
// block -> Block_byref -> object
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
Copy the code
- In combination with
__Block_byref_obj_0
The structure andBlock_byref
Structure, shift it down40
Student: 1 byte, so you get 1 byteNSObject *obj
forobj
That’s called_Block_object_assign
Function, passed inflags
is131
.
3
+128
=131
, so whenflags
is131
When do is*dest = object
Operation, which is in the heapobject
And the stackobject
Pointing to the same memory space.
Block copy summary:
block
through_Block_copy
Functions are copied from the stack to the heap.block
Capture variableBlock_byref
Through the_Block_object_assign
Functions are copied from the stack to the heap.Block_byref
In theobject
through_Block_object_assign
Function to make related copies.
The release of
The __Block_byref_id_object_dispose_131 function called on release:
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
Copy the code
Call the _Block_object_dispose function
void _Block_object_dispose(const void *object, const int flags) { switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) { case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK: case BLOCK_FIELD_IS_BYREF: // get rid of the __block data structure held in a Block _Block_byref_release(object); break; case BLOCK_FIELD_IS_BLOCK: _Block_release(object); break; case BLOCK_FIELD_IS_OBJECT: _Block_release_object(object); break; case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT: case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK: case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK: case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK: break; default: break; }}Copy the code