iOS底层原理之类的加载原理(上)

前言

在上一篇iOS底层原理之dyld应用程序加载中我们知道了dyld加载完成之后会调用libsystem,然后进入libobjc,调用objc_init,今天让我们研究一下_objc_init和之后的流程。

资料

objc4-818.2.tar.gz

一、_objc_init

这里面进行了以下操作

  • environ_init 环境初始化
  • tls_init 线程k的绑定
  • static_init 全局静态c++函数的调用
  • runtime_init runtime运行时的初始化
    • objc::unattachedCategories.init(32);
    • objc::allocatedClasses.init();
  • exception_init 异常捕获初始化
  • cache_t::init 缓存初始化
  • _imp_implementationWithBlock_init 启动回调机制
  • _dyld_objc_notify_register //重点
    代码如下
void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    // 环境初始化
    environ_init();
    // 线程k的绑定
    tls_init();
    // 全局静态c++函数的调用
    static_init();
    /*
     * runtime运行时的初始化
     * objc::unattachedCategories.init(32);
     * objc::allocatedClasses.init();
     */
    runtime_init();
    // 异常捕获初始化
    exception_init();
#if __OBJC2__
    // 缓存初始化
    cache_t::init();
#endif
    // 启动回调机制
    _imp_implementationWithBlock_init();
     // 通知注册
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    // map_images()
    // load_images()
#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

1.environ_init

读取影响运⾏时的环境变量。如果需要,还可以打印环境变量帮助

void environ_init(void) 
{
    if (issetugid()) {
        // All environment variables are silently ignored when setuid or setgid
        // This includes OBJC_HELP and OBJC_PRINT_OPTIONS themselves.
        return;
    } 

    // Turn off autorelease LRU coalescing by default for apps linked against
    // older SDKs. LRU coalescing can reorder releases and certain older apps
    // are accidentally relying on the ordering.
    // rdar://problem/63886091
//    if (!dyld_program_sdk_at_least(dyld_fall_2020_os_versions))
//        DisableAutoreleaseCoalescingLRU = true;

    bool PrintHelp = false;
    bool PrintOptions = false;
    bool maybeMallocDebugging = false;

    // Scan environ[] directly instead of calling getenv() a lot.
    // This optimizes the case where none are set.
    for (char **p = *_NSGetEnviron(); *p != nil; p++) {
        if (0 == strncmp(*p, "Malloc", 6)  ||  0 == strncmp(*p, "DYLD", 4)  ||  
            0 == strncmp(*p, "NSZombiesEnabled", 16))
        {
            maybeMallocDebugging = true;
        }

        if (0 != strncmp(*p, "OBJC_", 5)) continue;
        
        if (0 == strncmp(*p, "OBJC_HELP=", 10)) {
            PrintHelp = true;
            continue;
        }
        if (0 == strncmp(*p, "OBJC_PRINT_OPTIONS=", 19)) {
            PrintOptions = true;
            continue;
        }
        
        if (0 == strncmp(*p, "OBJC_DEBUG_POOL_DEPTH=", 22)) {
            SetPageCountWarning(*p + 22);
            continue;
        }

        const char *value = strchr(*p, '=');
        if (!*value) continue;
        value++;
        
        for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
            const option_t *opt = &Settings[i];
            if ((size_t)(value - *p) == 1+opt->envlen  &&  
                0 == strncmp(*p, opt->env, opt->envlen))
            {
                *opt->var = (0 == strcmp(value, "YES"));
                break;
            }
        }
    }

    // Special case: enable some autorelease pool debugging
    // when some malloc debugging is enabled 
    // and OBJC_DEBUG_POOL_ALLOCATION is not set to something other than NO.
    if (maybeMallocDebugging) {
        const char *insert = getenv("DYLD_INSERT_LIBRARIES");
        const char *zombie = getenv("NSZombiesEnabled");
        const char *pooldebug = getenv("OBJC_DEBUG_POOL_ALLOCATION");
        if ((getenv("MallocStackLogging")
             || getenv("MallocStackLoggingNoCompact")
             || (zombie && (*zombie == 'Y' || *zombie == 'y'))
             || (insert && strstr(insert, "libgmalloc")))
            &&
            (!pooldebug || 0 == strcmp(pooldebug, "YES")))
        {
            DebugPoolAllocation = true;
        }
    }
    
    
//    for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
//        const option_t *opt = &Settings[i];
//        _objc_inform("%s: %s", opt->env, opt->help);
//        _objc_inform("%s is set", opt->env);
//    }

//    if (!os_feature_enabled_simple(objc4, preoptimizedCaches, true)) {
//        DisablePreoptCaches = true;
//    }

    // Print OBJC_HELP and OBJC_PRINT_OPTIONS output.
    if (PrintHelp  ||  PrintOptions) {
        if (PrintHelp) {
            _objc_inform("Objective-C runtime debugging. Set variable=YES to enable.");
            _objc_inform("OBJC_HELP: describe available environment variables");
            if (PrintOptions) {
                _objc_inform("OBJC_HELP is set");
            }
            _objc_inform("OBJC_PRINT_OPTIONS: list which options are set");
        }
        if (PrintOptions) {
            _objc_inform("OBJC_PRINT_OPTIONS is set");
        }

        for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
            const option_t *opt = &Settings[i];            
            if (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);
            if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env);
        }
    }
}
  • 通过设置OBJC_HELP和OBJC_PRINT_OPTIONS在控制台可以打印出环境变量
    设置步骤: target -> Edit scheme -> Run -> Arguments -> Environments Variables
  • 通过终端命令打印
    export OBJC_HELP=1

2.static_init

  • 静态初始化
  • 运行C++静态构造函数。
  • libc在dyld调用静态构造函数之前调用了_objc_init(),
  • 所以我们必须自己动手。
    看里面调用的两个方法好像是对macho里面的header和segement做的处理,没有想明白
/***********************************************************************
* static_init
* Run C++ static constructor functions.
* libc calls _objc_init() before dyld would call our static constructors, 
* so we have to do it ourselves.
**********************************************************************/
static void static_init()
{
    size_t count;
    auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
    for (size_t i = 0; i < count; i++) {
        inits[i]();
    }
    auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count);
    for (size_t i = 0; i < count; i++) {
        UnsignedInitializer init(offsets[i]);
        init();
    }
}
// getLibobjcInitializers
struct UnsignedInitializer {
private:
    uintptr_t storage;
public:
    UnsignedInitializer(uint32_t offset) {
        storage = (uintptr_t)&_mh_dylib_header + offset;
    }

    void operator () () const {
        using Initializer = void(*)();
        Initializer init =
            ptrauth_sign_unauthenticated((Initializer)storage,
                                         ptrauth_key_function_pointer, 0);
        init();
    }
};

uint32_t *getLibobjcInitializerOffsets(const headerType *mhdr, size_t *outCount) {
    unsigned long byteCount = 0;
    uint32_t *offsets = (uint32_t *)getsectiondata(mhdr, "__TEXT", "__objc_init_offs", &byteCount);
    if (outCount) *outCount = byteCount / sizeof(uint32_t);
    return offsets;
}

3.runtime_init

void runtime_init(void)
{
    objc::unattachedCategories.init(32);
    objc::allocatedClasses.init();
}

4.exception_init

  • exception_init
  • 初始化 libobjc 的异常处理系统。
  • 由 map_images() 调用。
void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}
  • 自定义std::终止处理程序。
  • 未捕获的异常回调实现为std::terminate处理程序。
  • 1.检查是否存在活动异常
  • 2.如果是,检查是否是Objective-C异常
  • 3.如果是这样,用对象调用我们注册的回调。
  • 4.最后,调用前面的终止处理程序。
/***********************************************************************
* _objc_terminate
* Custom std::terminate handler.
*
* The uncaught exception callback is implemented as a std::terminate handler. 
* 1. Check if there's an active exception
* 2. If so, check if it's an Objective-C exception
* 3. If so, call our registered callback with the object.
* 4. Finally, call the previous terminate handler.
**********************************************************************/
static void (*old_terminate)(void) = nil;
static void _objc_terminate(void)
{
    if (PrintExceptions) {
        _objc_inform("EXCEPTIONS: terminating");
    }

    if (! __cxa_current_exception_type()) {
        // No current exception.
        (*old_terminate)();
    }
    else {
        // There is a current exception. Check if it's an objc exception.
        @try {
            __cxa_rethrow();
        } @catch (id e) {
            // It's an objc object. Call Foundation's handler, if any.
            (*uncaught_handler)((id)e);
            (*old_terminate)();
        } @catch (...) {
            // It's not an objc object. Continue to C++ terminate.
            (*old_terminate)();
        }
    }
}

5._imp_implementationWithBlock_init

启用回调机制

/// Initialize the trampoline machinery. Normally this does nothing, as
/// everything is initialized lazily, but for certain processes we eagerly load
/// the trampolines dylib.
void
_imp_implementationWithBlock_init(void)
{
#if TARGET_OS_OSX
    // Eagerly load libobjc-trampolines.dylib in certain processes. Some
    // programs (most notably QtWebEngineProcess used by older versions of
    // embedded Chromium) enable a highly restrictive sandbox profile which
    // blocks access to that dylib. If anything calls
    // imp_implementationWithBlock (as AppKit has started doing) then we'll
    // crash trying to load it. Loading it here sets it up before the sandbox
    // profile is enabled and blocks it.
    //
    // This fixes EA Origin (rdar://problem/50813789)
    // and Steam (rdar://problem/55286131)
    if (__progname &&
        (strcmp(__progname, "QtWebEngineProcess") == 0 ||
         strcmp(__progname, "Steam Helper") == 0)) {
        Trampolines.Initialize();
    }
#endif
}

6._dyld_objc_notify_register

//
//注意:只供objc运行时使用
//在映射、取消映射和初始化objc映像时要调用的寄存器处理程序。
//Dyld将使用包含objc image info部分的image数组回调“mapped”函数。
//那些是dylibs的image的ref计数将被自动缓冲,因此objc将不再需要这样做
//对它们调用dlopen(),以防止它们被卸载。在调用\u dyld \u objc \u notify \u register()期间,
//dyld将使用已加载的objc image调用“mapped”函数。在以后的dlopen()调用中,
//dyld还将调用“mapped”函数。Dyld将在调用Dyld时调用“init”函数
//image中的初始值设定项。这是objc调用该映像中任何+load方法的时候。
//

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped);
· map_images
  • 处理由dyld映射的给定image。
  • 在获取ABI特定锁后调用ABI不可知代码。
  • 锁定:写入锁定runtimeLock
void
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    mutex_locker_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}
· map_images_nolock
void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    static bool firstTime = YES;
    header_info *hList[mhCount];
    uint32_t hCount;
    size_t selrefCount = 0;


    // 如有必要,执行第一次初始化。
    // 此函数在普通库初始化程序之前调用。
    // fixme 延迟初始化,直到找到使用 objc 的image?
    if (firstTime) {
        preopt_init();
    }

    if (PrintImages) {
        _objc_inform("IMAGES: processing %u newly-mapped images...\n", mhCount);
    }


    // 查找所有带有 Objective-C 元数据的image。
    hCount = 0;

    // Count classes. Size various table based on the total.
    int totalClasses = 0;
    int unoptimizedTotalClasses = 0;
    {
        uint32_t i = mhCount;
        while (i--) {
            const headerType *mhdr = (const headerType *)mhdrs[i];

            auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
            if (!hi) {
                // no objc data in this entry
                continue;
            }
            
            if (mhdr->filetype == MH_EXECUTE) {
                // Size some data structures based on main executable's size
#if __OBJC2__
                // If dyld3 optimized the main executable, then there shouldn't
                // be any selrefs needed in the dynamic map so we can just init
                // to a 0 sized map
                if ( !hi->hasPreoptimizedSelectors() ) {
                  size_t count;
                  _getObjc2SelectorRefs(hi, &count);
                  selrefCount += count;
                  _getObjc2MessageRefs(hi, &count);
                  selrefCount += count;
                }
#else
                _getObjcSelectorRefs(hi, &selrefCount);
#endif
                
#if SUPPORT_GC_COMPAT
                // Halt if this is a GC app.
                if (shouldRejectGCApp(hi)) {
                    _objc_fatal_with_reason
                        (OBJC_EXIT_REASON_GC_NOT_SUPPORTED, 
                         OS_REASON_FLAG_CONSISTENT_FAILURE, 
                         "Objective-C garbage collection " 
                         "is no longer supported.");
                }
#endif
            }
            
            hList[hCount++] = hi;
            
            if (PrintImages) {
                _objc_inform("IMAGES: loading image for %s%s%s%s%s\n", 
                             hi->fname(),
                             mhdr->filetype == MH_BUNDLE ? " (bundle)" : "",
                             hi->info()->isReplacement() ? " (replacement)" : "",
                             hi->info()->hasCategoryClassProperties() ? " (has class properties)" : "",
                             hi->info()->optimizedByDyld()?" (preoptimized)":"");
            }
        }
    }

    // Perform one-time runtime initialization that must be deferred until 
    // the executable itself is found. This needs to be done before 
    // further initialization.
    // (The executable may not be present in this infoList if the 
    // executable does not contain Objective-C code but Objective-C 
    // is dynamically loaded later.
    if (firstTime) {
        sel_init(selrefCount);
        arr_init();

#if SUPPORT_GC_COMPAT
        // Reject any GC images linked to the main executable.
        // We already rejected the app itself above.
        // Images loaded after launch will be rejected by dyld.

        for (uint32_t i = 0; i < hCount; i++) {
            auto hi = hList[i];
            auto mh = hi->mhdr();
            if (mh->filetype != MH_EXECUTE  &&  shouldRejectGCImage(mh)) {
                _objc_fatal_with_reason
                    (OBJC_EXIT_REASON_GC_NOT_SUPPORTED, 
                     OS_REASON_FLAG_CONSISTENT_FAILURE, 
                     "%s requires Objective-C garbage collection "
                     "which is no longer supported.", hi->fname());
            }
        }
#endif

#if TARGET_OS_OSX
        // Disable +initialize fork safety if the app is too old (< 10.13).
        // Disable +initialize fork safety if the app has a
        //   __DATA,__objc_fork_ok section.

//        if (!dyld_program_sdk_at_least(dyld_platform_version_macOS_10_13)) {
//            DisableInitializeForkSafety = true;
//            if (PrintInitializing) {
//                _objc_inform("INITIALIZE: disabling +initialize fork "
//                             "safety enforcement because the app is "
//                             "too old.)");
//            }
//        }

        for (uint32_t i = 0; i < hCount; i++) {
            auto hi = hList[i];
            auto mh = hi->mhdr();
            if (mh->filetype != MH_EXECUTE) continue;
            unsigned long size;
            if (getsectiondata(hi->mhdr(), "__DATA", "__objc_fork_ok", &size)) {
                DisableInitializeForkSafety = true;
                if (PrintInitializing) {
                    _objc_inform("INITIALIZE: disabling +initialize fork "
                                 "safety enforcement because the app has "
                                 "a __DATA,__objc_fork_ok section");
                }
            }
            break;  // assume only one MH_EXECUTE image
        }
#endif

    }

    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

    firstTime = NO;
    
    // 一切设置完毕后调用image加载函数。
    for (auto func : loadImageFuncs) {
        for (uint32_t i = 0; i < mhCount; i++) {
            func(mhdrs[i]);
        }
    }
}

二、_read_images

/***********************************************************************
* _read_images
* 对链接中的标头执行初始处理
* 以 headerList 开头的列表。
*
* 调用者:map_images_nolock
*
* Locking:map_images获取的runtimeLock
**********************************************************************/
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) {
// 省略代码  300+的代码太长了
    //条件控制第一次加载
 	if (!doneOnce) {...}
 	 // 修复预编译阶段@selector的混乱问题
    static size_t UnfixedSelectors; {...}
        //错误混乱的类处理
    bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
 //修复、重新映射没有被镜像文件加载进来的类
    if (!noClassesRemapped()) {...}
    // 修复旧的 objc_msgSend 修复调用站点
    for (EACH_HEADER) {
        message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
        ... }
     // 发现协议
    for (EACH_HEADER) {
        extern objc_class OBJC_CLASS_$_Protocol;
      ... }
    //修复没有加载的协议
    for (EACH_HEADER) {
        if (launchTime && hi->isPreoptimized())
    ... }
    //分类的加载
    if (didInitialAttachCategories) {
        for (EACH_HEADER) {
            load_categories_nolock(hi);
        }
    }
    // 实现非懒加载类(用于+load 方法和静态实例)
    for (EACH_HEADER) {
        classref_t const *classlist = hi->nlclslist(&count);
       ...  }
    // 实现没有被处理的类,优化被入侵的类
    if (resolvedFutureClasses) {...}
   ...
}

1.readClass

/***********************************************************************
* readClass
* 读取由编译器编写的类和元类。
* 返回新的类指针。 这可能是:
* - cls
* - nil(cls 缺少弱链接超类)
* - 别的东西(这个class的空间被未来的class保留了)
*
* 请注意,此功能执行的所有工作均由
* mustReadClasses()。 不要在不更新该功能的情况下更改该功能。
*
* Locking:由map_images或objc_readClassPair获取的runtimeLock
**********************************************************************/
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->nonlazyMangledName();
    //容错处理(情况一):return nil
    if (missingWeakSuperclass(cls)) {
        // 没有父类 (可能是弱链接). 
        // 否认对这个子类的任何了解。
        if (PrintConnecting) {
            _objc_inform("CLASS: IGNORING class '%s' with "
                         "missing weak-linked superclass", 
                         cls->nameForLogging());
        }
        addRemappedClass(cls, nil);
        cls->setSuperclass(nil);
        return nil;
    }
    
    cls->fixupBackwardDeployingStableSwift();

    Class replacing = nil;
    if (mangledName != nullptr) {
        //通常情况下并不会在此处进行rw与ro的处理
        if (Class newCls = popFutureNamedClass(mangledName)) {
          
            if (newCls->isAnySwift()) {
                _objc_fatal("Can't complete future class request for '%s' "
                            "because the real class is too big.",
                            cls->nameForLogging());
            }

            class_rw_t *rw = newCls->data();
            const class_ro_t *old_ro = rw->ro();
            memcpy(newCls, cls, sizeof(objc_class));

            // Manually set address-discriminated ptrauthed fields
            // so that newCls gets the correct signatures.
            newCls->setSuperclass(cls->getSuperclass());
            newCls->initIsa(cls->getIsa());

            rw->set_ro((class_ro_t *)newCls->data());
            newCls->setData(rw);
            freeIfMutable((char *)old_ro->getName());
            free((void *)old_ro);

            addRemappedClass(cls, newCls);

            replacing = cls;
            cls = newCls;
        }
    }
    
    if (headerIsPreoptimized  &&  !replacing) {
        // class list built in shared cache
        // fixme strict assert doesn't work because of duplicates
        // ASSERT(cls == getClass(name));
        ASSERT(mangledName == nullptr || getClassExceptSomeSwift(mangledName));
    } else {
        if (mangledName) { //some Swift generic classes can lazily generate their names
            addNamedClass(cls, mangledName, replacing);
        } else {
            Class meta = cls->ISA();
            const class_ro_t *metaRO = meta->bits.safe_ro();
            ASSERT(metaRO->getNonMetaclass() && "Metaclass with lazy name must have a pointer to the corresponding nonmetaclass.");
            ASSERT(metaRO->getNonMetaclass() == cls && "Metaclass nonmetaclass pointer must equal the original class.");
        }
        addClassTableEntry(cls);
    }

    // for future reference: shared cache never contains MH_BUNDLEs
    if (headerIsBundle) {
        cls->data()->flags |= RO_FROM_BUNDLE;
        cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
    }
    
    return cls;
}

2.addNamedClass

/***********************************************************************
* addNamedClass
* 将 name => cls 添加到命名的非元类映射。
* 警告重复的类名并保留旧的映射。
* 锁定:runtimeLock 必须由调用者持有
**********************************************************************/
static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
    runtimeLock.assertLocked();
    Class old;
    if ((old = getClassExceptSomeSwift(name))  &&  old != replacing) {
        inform_duplicate(name, old, cls);

         // getMaybeUnrealizedNonMetaClass 使用名称查找。
         // 名称查找未找到的类必须在
         // 二级元->非元表。
        addNonMetaClass(cls);
    } else {
        NXMapInsert(gdb_objc_realized_classes, name, cls);
    }
    ASSERT(!(cls->data()->flags & RO_META));
}

3.addClassTableEntry(关键函数)

将类添加到表里面,如果addMeta为真,并且将当前类的元类也添加到所有的表中

/***********************************************************************
* addClassTableEntry
* 将一个类添加到所有类的表中。 如果 addMeta 为真,
* 也会自动添加类的元类。
* 锁定:runtimeLock 必须由调用者持有。
**********************************************************************/
static void
addClassTableEntry(Class cls, bool addMeta = true)
{
    runtimeLock.assertLocked();

    // 该类允许通过共享缓存或数据段成为已知类,但不允许已经在动态表中。
    auto &set = objc::allocatedClasses.get();

    ASSERT(set.find(cls) == set.end());

    if (!isKnownClass(cls))
        set.insert(cls);
    if (addMeta)
        //将元类添加到所有表中
        addClassTableEntry(cls->ISA(), false);
}

4.调试read_images

readClass通过探索源码可以知道,这是个读取类的方法的函数,那么在此函数里面添加打印事件

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->nonlazyMangledName();
    
    const char *LGPersonName = "LGPerson";
   if (strcmp(mangledName, LGPersonName) == 0) {
        // 普通写得类 他是如何
        printf("%s -: 要研究的: - %s\n",__func__,mangledName);
    }
    ...
}

调试结果

readClass -: 要研究的: - LGPerson

总结

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值