iOS底层原理、Block源码探索:调试libclosure源代码

741 阅读5分钟

官方源码

下载地址:opensource.apple.com/tarballs/li…
下载源码(选择当前最新版本):libclosure-79.tar.gz

本文demo

可以编译调试的libclosure-79,下载地址:github.com/ProBobo/YLB…

编译环境

Xcode14或者Xcode13都可以正常编译
依赖文件:assumes.h、base_private.h、_simple.h、CrashReporterClient.h

assumes.h
使用Google可以搜索到assumes.h文件在Libc-1244.50.9项目中
opensource.apple.com/source/Libc…

base_private.h
使用Google可以搜索到base_private.h文件在libplatform-125中
opensource.apple.com/source/libp…

_simple.h
使用Google可以搜索到_simple.h文件在libplatform-125中
opensource.apple.com/source/libp…

CrashReporterClient.h
使用Google可以搜索到CrashReporterClient.h文件在Libc-997.1.1中
因为CrashReporterClient.h文件中没有进行LIBC_NO_LIBCRASHREPORTERCLIENT宏定义
所以在Xcode中设置Preprocessor Macros为LIBC_NO_LIBCRASHREPORTERCLIENT
(多个target可以通过设置Preprocessor Macros宏定义来区分)
opensource.apple.com/source/Libc…

platform/string.h
使用Google搜索#define _platform_memmove可以查找到libplatform-125项目
因为#include <string.h>文件中已经包含memmove方法
所以无需导入#include <platform/string.h> opensource.apple.com/source/libp…

如何获取依赖文件

使用Google搜索对应文件可以查找到文件所在的对应地址,根据 opensource.apple.com/source 的地址可以找到对应的 opensource.apple.com/tarballs 下载地址

以libplatform项目为例:

1、source地址对应的内容图

source.png

2、tarballs地址对应的内容图

image.png

3、对比两个地址的历史版本可以发现tarballs地址中的项目比source地址中的项目少。
所以当我们发现libplatform-125没有对应的下载地址时,而base_private.h文件又在libplatform-125项目中,这时我们需要自己在本地文件夹通过终端输入touch base_private.h创建对应的文件,然后把代码复制到base_private.h文件当中。

开始编译

1、下载项目并解压,等到libclosure-79项目文件。

libclosure.png

2、打开Blocks.xcodeproj工程

image.png

3、新建target,名称可以自己随意写,我这里使用YLBBlock。

image.png

4、创建Command Line Tool类型的target,工程中会自动创建main.m文件

image.png

5、把编译目标选为YLBBlock

image.png

6、点击运行YLBBlock,因为这时还没有建立依赖关系,所以YLBBlock可以正常运行。

image.png

7、YLBBlock中导入blocks源码
选择YLBBlock->Build Phases->Dependencies->Blocks,将Blocks作为依赖的target。
点击add按钮,然后添加Blocks

image.png

image.png

8、这时试着运行YLBBlock,看工程是否会报错。

image.png

工程报错:'platform/string.h' file not found
查找发现platform/string.h文件的引用在runtime.cpp文件中

image.png

9、导入string.h文件

因为Xcode已经包含string.h文件,所以使用#include <string.h>代替#include <platform/string.h>

image.png

10、这时试着运行YLBBlock,看工程是否会报错。发现报错'os/assumes.h' file not found

因为Xcode中已经没有assumes.h文件,所以通过Google搜索得到assumes.h文件在Libc-1244.50.9项目中,下载Libc-1244.50.9,方法在“如何获取依赖文件”中。

11、为了能将assumes.h文件导入工程,我们还需要对Xcode进行设置。
11.1、在libclosure-79文件夹中新建include文件夹。
11.2、因为runtime.cpp文件的target是Blocks工程,所以要对Blocks进行设置。 找到Blocks->Build Settings->Header Search Paths,添加$(SRCROOT)/include。

image.png

12、将下载的os文件夹(里面包含assumes.h文件)放入include文件夹中。

image.png

13、点击运行YLBBlock,发现报错'_simple.h' file not found,此时使用Google搜索,通过“如何获取依赖文件”中的方法得到_simple.h文件

注意:需要通过终端创建

image.png

14、点击运行YLBBlock,发现报错'os/base_private.h' file not found此时使用Google搜索,通过“如何获取依赖文件”中的方法得到base_private.h'文件。

注意:由os/base_private.h可以知道base_private.h文件的路径。即base_private.h在os文件夹中。

注意:需要通过终端创建

image.png

15、点击运行YLBBlock,报错:Use of undeclared identifier '_platform_memmove',定位到代码#define memmove _platform_memmove,使用Google搜索#define _platform_memmove可以查找_platform_memmove在string.h文件中,又因为string中已经包含memmove方法,所以把#define memmove _platform_memmove注释掉,即//#define memmove _platform_memmove

修改后的runtime.cpp

/*
 * runtime.cpp
 * libclosure
 *
 * Copyright (c) 2008-2010 Apple Inc. All rights reserved.
 *
 * @APPLE_LLVM_LICENSE_HEADER@
 */


#include "Block_private.h"
//#include <platform/string.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <os/assumes.h>
#ifndef os_assumes
#define os_assumes(_x) os_assumes(_x)
#endif
#ifndef os_assert
#define os_assert(_x) os_assert(_x)
#endif

//#define memmove _platform_memmove

#if TARGET_OS_WIN32
#define _CRT_SECURE_NO_WARNINGS 1
#include <windows.h>
static __inline bool OSAtomicCompareAndSwapLong(long oldl, long newl, long volatile *dst) 
{ 
    // fixme barrier is overkill -- see objc-os.h
    long original = InterlockedCompareExchange(dst, newl, oldl);
    return (original == oldl);
}

static __inline bool OSAtomicCompareAndSwapInt(int oldi, int newi, int volatile *dst) 
{ 
    // fixme barrier is overkill -- see objc-os.h
    int original = InterlockedCompareExchange(dst, newi, oldi);
    return (original == oldi);
}
#else
#define OSAtomicCompareAndSwapLong(_Old, _New, _Ptr) __sync_bool_compare_and_swap(_Ptr, _Old, _New)
#define OSAtomicCompareAndSwapInt(_Old, _New, _Ptr) __sync_bool_compare_and_swap(_Ptr, _Old, _New)
#endif


/*******************************************************************************
Internal Utilities
********************************************************************************/

static int32_t latching_incr_int(volatile int32_t *where) {
    while (1) {
        int32_t old_value = *where;
        if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
            return BLOCK_REFCOUNT_MASK;
        }
        if (OSAtomicCompareAndSwapInt(old_value, old_value+2, where)) {
            return old_value+2;
        }
    }
}

static bool latching_incr_int_not_deallocating(volatile int32_t *where) {
    while (1) {
        int32_t old_value = *where;
        if (old_value & BLOCK_DEALLOCATING) {
            // if deallocating we can't do this
            return false;
        }
        if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
            // if latched, we're leaking this block, and we succeed
            return true;
        }
        if (OSAtomicCompareAndSwapInt(old_value, old_value+2, where)) {
            // otherwise, we must store a new retained value without the deallocating bit set
            return true;
        }
    }
}


// return should_deallocate?
static bool latching_decr_int_should_deallocate(volatile int32_t *where) {
    while (1) {
        int32_t old_value = *where;
        if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
            return false; // latched high
        }
        if ((old_value & BLOCK_REFCOUNT_MASK) == 0) {
            return false;   // underflow, latch low
        }
        int32_t new_value = old_value - 2;
        bool result = false;
        if ((old_value & (BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING)) == 2) {
            new_value = old_value - 1;
            result = true;
        }
        if (OSAtomicCompareAndSwapInt(old_value, new_value, where)) {
            return result;
        }
    }
}


/**************************************************************************
Framework callback functions and their default implementations.
***************************************************************************/
#if !TARGET_OS_WIN32
#pragma mark Framework Callback Routines
#endif

static void _Block_retain_object_default(const void *ptr __unused) { }

static void _Block_release_object_default(const void *ptr __unused) { }

static void _Block_destructInstance_default(const void *aBlock __unused) {}

static void (*_Block_retain_object)(const void *ptr) = _Block_retain_object_default;
static void (*_Block_release_object)(const void *ptr) = _Block_release_object_default;
static void (*_Block_destructInstance) (const void *aBlock) = _Block_destructInstance_default;


/**************************************************************************
Callback registration from ObjC runtime and CoreFoundation
***************************************************************************/

void _Block_use_RR2(const Block_callbacks_RR *callbacks) {
    _Block_retain_object = callbacks->retain;
    _Block_release_object = callbacks->release;
    _Block_destructInstance = callbacks->destructInstance;
}

/****************************************************************************
Accessors for block descriptor fields
*****************************************************************************/

template <class T>
static T *unwrap_relative_pointer(int32_t &offset)
{
    if (offset == 0)
        return nullptr;

    uintptr_t base = (uintptr_t)&offset;
    uintptr_t extendedOffset = (uintptr_t)(intptr_t)offset;
    uintptr_t pointer = base + extendedOffset;
    return (T *)pointer;
}

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;
}

static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
{
    if (auto *pFn = _Block_get_copy_function(aBlock))
        pFn(result, aBlock);
}

static void _Block_call_dispose_helper(struct Block_layout *aBlock)
{
    if (auto *pFn = _Block_get_dispose_function(aBlock))
        pFn(aBlock);
}

/*******************************************************************************
Internal Support routines for copying
********************************************************************************/

#if !TARGET_OS_WIN32
#pragma mark Copy/Release support
#endif

// 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);

            result->descriptor =
                    ptrauth_auth_and_resign(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);
        // Set isa last so memory analysis tools see a fully-initialized object.
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}


// Runtime entry points for maintaining the sharing knowledge of byref data blocks.

// A closure has been copied and its fixup routine is asking us to fix up the reference to the shared byref data
// Closures that aren't copied must still work, so everyone always accesses variables after dereferencing the forwarding ptr.
// We ask if the byref pointer that we know about has already been copied to the heap, and if so, increment and return it.
// Otherwise we need to copy it and update the stack forwarding pointer
static struct Block_byref *_Block_byref_copy(const void *arg) {
    struct Block_byref *src = (struct Block_byref *)arg;

    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // 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;

        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;
            }

            (*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;
}

static void _Block_byref_release(const void *arg) {
    struct Block_byref *byref = (struct Block_byref *)arg;

    // dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
    byref = byref->forwarding;
    
    if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
        int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
        os_assert(refcount);
        if (latching_decr_int_should_deallocate(&byref->flags)) {
            if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
                struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
                (*byref2->byref_destroy)(byref);
            }
            free(byref);
        }
    }
}


/************************************************************
 *
 * API supporting SPI
 * _Block_copy, _Block_release, and (old) _Block_destroy
 *
 ***********************************************************/

#if !TARGET_OS_WIN32
#pragma mark SPI/API
#endif


// API entry point to release a copied Block
void _Block_release(const void *arg) {
    struct Block_layout *aBlock = (struct Block_layout *)arg;
    if (!aBlock) return;
    if (aBlock->flags & BLOCK_IS_GLOBAL) return;
    if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return;

    if (latching_decr_int_should_deallocate(&aBlock->flags)) {
        _Block_call_dispose_helper(aBlock);
        _Block_destructInstance(aBlock);
        free(aBlock);
    }
}

bool _Block_tryRetain(const void *arg) {
    struct Block_layout *aBlock = (struct Block_layout *)arg;
    return latching_incr_int_not_deallocating(&aBlock->flags);
}

bool _Block_isDeallocating(const void *arg) {
    struct Block_layout *aBlock = (struct Block_layout *)arg;
    return (aBlock->flags & BLOCK_DEALLOCATING) != 0;
}


/************************************************************
 *
 * SPI used by other layers
 *
 ***********************************************************/

size_t Block_size(void *aBlock) {
    auto *layout = (Block_layout *)aBlock;
    void *desc = _Block_get_descriptor(layout);
#if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
    if (layout->flags & BLOCK_SMALL_DESCRIPTOR)
        return ((Block_descriptor_small *)desc)->size;
#endif
    return ((Block_descriptor_1 *)desc)->size;
}

bool _Block_use_stret(void *aBlock) {
    struct Block_layout *layout = (struct Block_layout *)aBlock;

    int requiredFlags = BLOCK_HAS_SIGNATURE | BLOCK_USE_STRET;
    return (layout->flags & requiredFlags) == requiredFlags;
}

// Checks for a valid signature, not merely the BLOCK_HAS_SIGNATURE bit.
bool _Block_has_signature(void *aBlock) {
    return _Block_signature(aBlock) ? true : false;
}

const char * _Block_signature(void *aBlock)
{
    struct Block_layout *layout = (struct Block_layout *)aBlock;
    if (!(layout->flags & BLOCK_HAS_SIGNATURE))
        return nullptr;

#if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
    if (layout->flags & BLOCK_SMALL_DESCRIPTOR) {
        auto *bds = (Block_descriptor_small *)_Block_get_descriptor(layout);
        return unwrap_relative_pointer<const char>(bds->signature);
    }
#endif

    struct Block_descriptor_3 *desc3 = _Block_descriptor_3(layout);
    return desc3->signature;
}

const char * _Block_layout(void *aBlock)
{
    // Don't return extended layout to callers expecting old GC layout
    Block_layout *layout = (Block_layout *)aBlock;
    if ((layout->flags & BLOCK_HAS_EXTENDED_LAYOUT) ||
        !(layout->flags & BLOCK_HAS_SIGNATURE))
        return nullptr;

#if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
    if (layout->flags & BLOCK_SMALL_DESCRIPTOR) {
        auto *bds = (Block_descriptor_small *)_Block_get_descriptor(layout);
        return unwrap_relative_pointer<const char>(bds->layout);
    }
#endif

    Block_descriptor_3 *desc = _Block_descriptor_3(layout);
    return desc->layout;
}

const char * _Block_extended_layout(void *aBlock)
{
    // Don't return old GC layout to callers expecting extended layout
    Block_layout *layout = (Block_layout *)aBlock;
    if (!(layout->flags & BLOCK_HAS_EXTENDED_LAYOUT) ||
        !(layout->flags & BLOCK_HAS_SIGNATURE))
        return nullptr;

    const char *extLayout;
#if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
    if (layout->flags & BLOCK_SMALL_DESCRIPTOR) {
        auto *bds = (Block_descriptor_small *)_Block_get_descriptor(layout);
        if (layout->flags & BLOCK_INLINE_LAYOUT_STRING)
            extLayout = (const char *)(uintptr_t)bds->layout;
        else
            extLayout = unwrap_relative_pointer<const char>(bds->layout);
    } else
#endif
    {
        Block_descriptor_3 *desc3 = _Block_descriptor_3(layout);
        extLayout = desc3->layout;
    }

    // Return empty string (all non-object bytes) instead of NULL 
    // so callers can distinguish "empty layout" from "no layout".
    if (!extLayout)
        extLayout = "";
    return extLayout;
}

#if !TARGET_OS_WIN32
#pragma mark Compiler SPI entry points
#endif

    
/*******************************************************

Entry points used by the compiler - the real API!


A Block can reference four different kinds of things that require help when the Block is copied to the heap.
1) C++ stack based objects
2) References to Objective-C objects
3) Other Blocks
4) __block variables

In these cases helper functions are synthesized by the compiler for use in Block_copy and Block_release, called the copy and dispose helpers.  The copy helper emits a call to the C++ const copy constructor for C++ stack based objects and for the rest calls into the runtime support function _Block_object_assign.  The dispose helper has a call to the C++ destructor for case 1 and a call into _Block_object_dispose for the rest.

The flags parameter of _Block_object_assign and _Block_object_dispose is set to
    * BLOCK_FIELD_IS_OBJECT (3), for the case of an Objective-C Object,
    * BLOCK_FIELD_IS_BLOCK (7), for the case of another Block, and
    * BLOCK_FIELD_IS_BYREF (8), for the case of a __block variable.
If the __block variable is marked weak the compiler also or's in BLOCK_FIELD_IS_WEAK (16)

So the Block copy/dispose helpers should only ever generate the four flag values of 3, 7, 8, and 24.

When  a __block variable is either a C++ object, an Objective-C object, or another Block then the compiler also generates copy/dispose helper functions.  Similarly to the Block copy helper, the "__block" copy helper (formerly and still a.k.a. "byref" copy helper) will do a C++ copy constructor (not a const one though!) and the dispose helper will do the destructor.  And similarly the helpers will call into the same two support functions with the same values for objects and Blocks with the additional BLOCK_BYREF_CALLER (128) bit of information supplied.

So the __block copy/dispose helpers will generate flag values of 3 or 7 for objects and Blocks respectively, with BLOCK_FIELD_IS_WEAK (16) or'ed as appropriate and always 128 or'd in, for the following set of possibilities:
    __block id                   128+3       (0x83)
    __block (^Block)             128+7       (0x87)
    __weak __block id            128+3+16    (0x93)
    __weak __block (^Block)      128+7+16    (0x97)
        

********************************************************/

//
// When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
// to do the assignment.
//
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:
        /*******
        id object = ...;
        [^{ object; } copy];
        ********/

        _Block_retain_object(object);
        *dest = object;
        break;

      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:
        /*******
         // 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];
         ********/

        *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;
    }
}

// When Blocks or Block_byrefs hold objects their destroy helper routines call this entry point
// to help dispose of the contents
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;
    }
}

16、点击运行,编译成功。

注意:Libc-997.90.3和Libc-997.1.1的assumes.h含有#include <CrashReporterClient.h>,这时就需要引入CrashReporterClient.h文件并设置Preprocessor Macros为LIBC_NO_LIBCRASHREPORTERCLIENT。因为Libc-1244.50.9的assumes.h没有#include <CrashReporterClient.h>,所以不需要设置。

开始调试

1、在main.m文件中编写block代码

//
//  main.m
//  YLBBlock
//
//  Created by 余礼钵 on 2022/9/28.
//

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSObject *objc1 = [[NSObject alloc] init];
//        NSLog(@"外部:%ld",CFGetRetainCount((__bridge CFTypeRef)(objc1)));
        void (^ylbBlock)(void) = ^void {
            NSLog(@"内部: %ld",CFGetRetainCount((__bridge CFTypeRef)(objc1)));
        };
        ylbBlock();
//        NSLog(@"打印 %@", ylbBlock);
    }
    return 0;
}

2、设置断点:在runtime.cpp的_Block_copy方法里面设置断点。
但是发现Xcode并没有进入断点。

image.png

3、设置Enable Hardened Runtime:这时需要对Xcode进行设置。
YLBBlock->Build Settings->Enable Hardened Runtime,设置Enable Hardened Runtime的值为NO。

注意:Enable Hardened Runtime,默认是开启的,把它设置为No,dylib 就不需要签名了

image.png

4、如何断点还是没有进来,需要看下Signing是否设置了team,如果设置了,改为None

image.png

5、运行程序,成功进入断点。

image.png

探索Block底层逻辑

一、使用Xcode

1、成功运行以后,生成YLBBlock包

image.png

2、Xcode设置Always Show Disassembly
设置路径:Xcode->Debug->Debug Workflow->Always Show Disassembly

3、运行YLBBlock
通过_Block_copy方法的断点可以发现是runtime的objc_retainBlock方法调用_Block_copy。

注意:runtime的源码在objc4项目中

Xcode反汇编02.jpg

对应runtime源码

id objc_retainBlock(id x) {
    return (id)_Block_copy(x);
}

二、使用Hopper

获取伪代码
我们使用Hopper Disassembler对生成的YLBBlock包进行反汇编。

1、将YLBBlock包复制到桌面,打开Hopper Disassembler,将YLBBlock包拖入Hopper Disassembler中。

image.png

2、点击OK

image.png

3、切换代码模式:点击Hopper对应按钮进行切换

image.png

image.png

image.png

main函数对应的伪代码

int _main(int arg0, int arg1) {
    var_58 = objc_autoreleasePoolPush();
    [objc_alloc_init() retain];
    r0 = objc_retainBlock(&var_48);
    (*(r0 + 0x10))(r0);
    objc_storeStrong(&saved_fp - 0x20, 0x0);
    objc_storeStrong(&var_48 + 0x20, 0x0);
    objc_storeStrong(&saved_fp - 0x18, 0x0);
    objc_autoreleasePoolPop(var_58);
    return 0x0;
}

OC代码、伪代码对比

image.png

三、使用clang

通过clang把main.m文件转换为main.cpp文件:将含有Block语法的源代码转换为C++的源代码。

在main.m所在文件夹使用终端输入clang -rewrite-objc main.m就可以得到C++代码。

image.png

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSObject *objc1;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_objc1, int flags=0) : objc1(_objc1) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  NSObject *objc1 = __cself->objc1; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_f0_9h7p_2156qn19s5f7vx047m40000gn_T_main_417da2_mi_0,CFGetRetainCount((__bridge CFTypeRef)(objc1)));
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->objc1, (void*)src->objc1, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->objc1, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        NSObject *objc1 = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
        void (*ylbBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, objc1, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)ylbBlock)->FuncPtr)((__block_impl *)ylbBlock);
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

image.png

四、对比hopper伪代码和clang转换的C++代码

image.png

image.png

; ================ B E G I N N I N G   O F   P R O C E D U R E ================

        ; Variables:
        ;    saved_fp: 0
        ;    var_4: int32_t, -4
        ;    var_8: int32_t, -8
        ;    var_10: int64_t, -16
        ;    var_18: int64_t, -24
        ;    var_20: int64_t, -32
        ;    var_28: int64_t, -40
        ;    var_30: int64_t, -48
        ;    var_38: int64_t, -56
        ;    var_3C: int32_t, -60
        ;    var_40: int32_t, -64
        ;    var_48: int64_t, -72
        ;    var_4C: int32_t, -76
        ;    var_58: int64_t, -88
        ;    var_60: int64_t, -96
        ;    var_68: int64_t, -104
        ;    var_70: int64_t, -112
        ;    var_78: int64_t, -120
        ;    var_80: int64_t, -128

_main:
sub        sp, sp, #0x90
stp        fp, lr, [sp, #0x80]
add        fp, sp, #0x80
mov        w8, #0x0
str        w8, [sp, #0x80 + var_4C]
stur       wzr, [fp, var_4]
stur       w0, [fp, var_8]
stur       x1, [fp, var_10]
bl         imp___stubs__objc_autoreleasePoolPush ; objc_autoreleasePoolPush
str        x0, [sp, #0x80 + var_58]
adrp       x8, #0x100008000
ldr        x0, [x8]     ; objc_cls_ref_NSObject,_OBJC_CLASS_$_NSObject
bl         imp___stubs__objc_alloc_init ; objc_alloc_init
sub        x8, fp, #0x18
str        x8, [sp, #0x80 + var_68]
stur       x0, [fp, var_18]
add        x8, sp, #0x38
str        x8, [sp, #0x80 + var_80]
adrp       x9, #0x100004000 ; 0x100004010@PAGE
ldr        x9, [x9, #0x10] ; 0x100004010@PAGEOFF, __NSConcreteStackBlock_100004010,__NSConcreteStackBlock
str        x9, [sp, #0x80 + var_48]
mov        w9, #0xc2000000
str        w9, [sp, #0x80 + var_40]
str        wzr, [sp, #0x80 + var_3C]
adrp       x9, #0x100003000 ; 0x100003e68@PAGE
add        x9, x9, #0xe68 ; 0x100003e68@PAGEOFF, ___main_block_invoke
str        x9, [sp, #0x80 + var_38]
adrp       x9, #0x100004000 ; 0x100004048@PAGE
add        x9, x9, #0x48 ; 0x100004048@PAGEOFF, ___block_descriptor_40_e8_32s_e5_v8?0l
str        x9, [sp, #0x80 + var_30]
add        x8, x8, #0x20
str        x8, [sp, #0x80 + var_70]
ldur       x0, [fp, var_18] ; argument "instance" for method imp___stubs__objc_retain
bl         imp___stubs__objc_retain ; objc_retain
mov        x8, x0
ldr        x0, [sp, #0x80 + var_80] ; argument "instance" for method imp___stubs__objc_retainBlock
str        x8, [sp, #0x80 + var_28]
bl         imp___stubs__objc_retainBlock ; objc_retainBlock
sub        x8, fp, #0x20
str        x8, [sp, #0x80 + var_78]
stur       x0, [fp, var_20]
ldur       x0, [fp, var_20]
ldr        x8, [x0, #0x10]
blr        x8
ldr        x0, [sp, #0x80 + var_78] ; argument "addr" for method imp___stubs__objc_storeStrong
mov        x1, #0x0     ; argument "value" for method imp___stubs__objc_storeStrong
str        x1, [sp, #0x80 + var_60]
bl         imp___stubs__objc_storeStrong ; objc_storeStrong
ldr        x0, [sp, #0x80 + var_70] ; argument "addr" for method imp___stubs__objc_storeStrong
ldr        x1, [sp, #0x80 + var_60] ; argument "value" for method imp___stubs__objc_storeStrong
bl         imp___stubs__objc_storeStrong ; objc_storeStrong
ldr        x0, [sp, #0x80 + var_68] ; argument "addr" for method imp___stubs__objc_storeStrong
ldr        x1, [sp, #0x80 + var_60] ; argument "value" for method imp___stubs__objc_storeStrong
bl         imp___stubs__objc_storeStrong ; objc_storeStrong
ldr        x0, [sp, #0x80 + var_58] ; argument "pool" for method imp___stubs__objc_autoreleasePoolPop
bl         imp___stubs__objc_autoreleasePoolPop ; objc_autoreleasePoolPop
ldr        w0, [sp, #0x80 + var_4C]
ldp        fp, lr, [sp, #0x80]
add        sp, sp, #0x90
ret

hopper伪代码

int ___main_block_invoke(int arg0) {
    stack[-48] = CFGetRetainCount(*(arg0 + 0x20));
    r0 = NSLog(0x100004078);
    return r0;
}

clang转换的C++代码

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    NSObject *objc1 = __cself->objc1; // bound by copy
    
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_f0_9h7p_2156qn19s5f7vx047m40000gn_T_main_417da2_mi_0,CFGetRetainCount((__bridge CFTypeRef)(objc1)));
}

Objective C源代码

void (^ylbBlock)(void) = ^void {
    NSLog(@"内部: %ld",CFGetRetainCount((__bridge CFTypeRef)(objc1)));
};

关于objc_retainBlock的调用

我们通过objc项目里面的注释可以知道:The -fobjc-arc flag causes the compiler to issue calls to objc_{retain/release/autorelease/retain_block}
翻译过来就是:
-fobjc-arc标志导致编译器调用objc_{retain/release/autorelease/retain_block}

//
// The -fobjc-arc flag causes the compiler to issue calls to objc_{retain/release/autorelease/retain_block}
//

id objc_retainBlock(id x) {
    return (id)_Block_copy(x);
}

objc_retainBlock的作用
在Block.h里面的注释里也有说明:Create a heap based copy of a Block or simply add a reference to an existing one. This must be paired with Block_release to recover memory, even when running under Objective-C Garbage Collection.
翻译过来就是:
创建一个基于堆的Block副本,或者简单地添加一个对现有Block的引用。这必须与Block_release相匹配以恢复内存,即使在Objective-C垃圾收集下运行也是如此。

// Create a heap based copy of a Block or simply add a reference to an existing one.
// This must be paired with Block_release to recover memory, even when running
// under Objective-C Garbage Collection.
BLOCK_EXPORT void *_Block_copy(const void *aBlock)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);