iOS 底层学习14

iOS 底层第14天的学习。在第12,13天的学习中, dyld 已经把 images 给映射过来了。但在何时加载到内存里的我们还不清楚,接下来继续进行探究。

_objc_init 探究

void _objc_init(void)
{
    // .... 
   // fixme defer initialization until an objc-using image is found?
   // 环境变量的初始化,可以打印环境变量
    environ_init();  
   //  
    tls_init();    
    static_init();
    runtime_init();
    exception_init();
#if __OBJC2__
    cache_t::init();
#endif
    _imp_implementationWithBlock_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}
environ_init
  • 环境变量的初始化
void environ_init(void) 
{
  // ...  
  // 循环输出环境变量
  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);
        }
  // ...
 
}
  • 把程序运行起来打印输出👇
// ... 
// 是否打印 load 方法
objc[6501]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
objc[6501]: OBJC_PRINT_LOAD_METHODS is set

objc[6501]: OBJC_DISABLE_PREOPTIMIZATION is set
objc[6501]: OBJC_DISABLE_TAGGED_POINTERS: disable tagged pointer optimization of NSNumber et al.
objc[6501]: OBJC_DISABLE_TAGGED_POINTERS is set
objc[6501]: OBJC_DISABLE_TAG_OBFUSCATION: disable obfuscation of tagged pointers
objc[6501]: OBJC_DISABLE_TAG_OBFUSCATION is set
// 是否启用 NONPOINTER_ISA
objc[6501]: OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields
objc[6501]: OBJC_DISABLE_NONPOINTER_ISA is set
// ... 
  • 这里的环境变量有很多,这些环境变量到底有什么用呢?就拿OBJC_PRINT_LOAD_METHODSOBJC_DISABLE_NONPOINTER_ISA 来举个🌰
  • 添加环境变量
  • 未打开 OBJC_DISABLE_NONPOINTER_ISAp/t 输出 isa,发现末尾是 1
(lldb) x/4gx p
0x1011b9960: 0x011d800100008141 0x0000000000000000
0x1011b9970: 0x75736956534e5b2d 0x6369506261546c61
(lldb) p/t 0x011d800100008141
(long) $4 = 0b0000000100011101100000000000000100000000000000001000000101000001
  • 打开 OBJC_DISABLE_NONPOINTER_ISA,p/t 输出 isa,发现末尾是 0,说明已经把 NONPOINTER_ISA 给关闭了现在是纯 isa
(lldb) x/4gx p
0x101505210: 0x0000000100008140 0x0000000000000000
0x101505220: 0x67616d49534e5b2d 0x6569467478655465
(lldb) p/t 0x011d800100008140
(long) $1 = 0b0000000100011101100000000000000100000000000000001000000101000000
  • 我们下来把 OBJC_PRINT_LOAD_METHODS 打开 ,就能打印输出在程序调用了 load 方法,这样就能更方便的优化代码和解决问题。

tls_init
  • 进入 static_init 查看源码
void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS
    pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
    _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}
  • tls_init 线程key的绑定,每条线程数据的析构函数

static_init
  • 进入 static_init 查看源码
/***********************************************************************
* 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();
    }
}
  • libobjcc++ 函数的调用,在 dyld:: doModFunction 之前就会调用。

runtime_init
  • 进入 runtime_init 查看源码
void runtime_init(void)
{
    objc::unattachedCategories.init(32);
    objc::allocatedClasses.init();
}
  • runtime_init runtime运行时的初始化。 里面有unattachedCategories ,allocatedClasses两张表

exception_init
  • 进入 exception_init 查看源码
/***********************************************************************
* exception_init
* Initialize libobjc's exception handling system.
* Called by map_images().
**********************************************************************/
void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}
  • exception_init 对系统异常的出来,并可进行自定义 handle
  • 进入 _objc_terminate
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)();
        }
    }
}
  • 查看 _objc_terminate 源码,发现当 catch 时会执行 uncaught_handler ,全局搜索 uncaught_handler
objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{
    objc_uncaught_exception_handler result = uncaught_handler;
    uncaught_handler = fn;
    return result;
}
  • 发现在 objc_setUncaughtExceptionHandler 有个赋值,这个赋值有何用的呢?我们弄一个 Demo 来测试一下
- (void)viewDidLoad {
    [super viewDidLoad];

    self.dataArray = @[@"测试数据0",
                       @"测试数据1",
                       @"测试数据2",
                       @"测试数据3",
                       @"测试数据3"];
}
- (IBAction)exceptionAction:(id)sender {
    NSLog(@"%@",self.dataArray[5]);
}
  • 上面的代码只要点了 exception 必定会报错,数据越界
  • 堆栈信息如下
  • objc_setUncaughtExceptionHandler进行拦截
 NSSetUncaughtExceptionHandler(&XKExceptionHandlers);

  • NSSetUncaughtExceptionHandler = objc_setUncaughtExceptionHandler
void XKExceptionHandlers(NSException *exception) {
    NSLog(@"%s",__func__);
·
}
  • 在程序启动时调用 NSSetUncaughtExceptionHandler,在XKExceptionHandlers 进行拦截
  • po 打印 exception 就能得到错误信息,接下来你就可以对 exception 进行收集了
map_image & load_image
  • 继续往下分析
void _objc_init(void)
{
    // .... 
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
  • 这里的 &map_images = 镜像文件映射 ,管理文件中和动态库中所有的符号,load_images = 镜像文件加载

那为何 &map_imagesload_images 多了个&,这是为何呢?

  • 因为 &map_images 是指针传递,那为何要指针传递呢?
  • 因为 &map_images 内部函数 和 调用 map_images 的函数里的要进行同步的变化,而load_images 只是单纯的镜像的加载
    • &map_images 内部函数 👇
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 的函数 = dyld: sNotifyObjCMapped👇
static void notifyBatchPartial(dyld_image_states state, bool orLater, dyld_image_state_change_handler onlyHandler, bool preflightOnly, bool onlyObjCMappedNotification)
{
  if ( objcImageCount != 0 ) {
      dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_MAP, 0, 0, 0);  
      uint64_t t0 = mach_absolute_time();
      (*sNotifyObjCMapped)(objcImageCount, paths, mhs);
      uint64_t t1 = mach_absolute_time();
      ImageLoader::fgTotalObjCSetupTime += (t1-t0);
    }
}

那为何 map_images 要进行同步变化呢?

  • 因为 map_images这个函数非常重要,在 map_images 内部流程—镜像文件的映射是非常耗时的,当中间发生任何变化都会导致数据的不同步,所以当 map_images 的指针地址发生变化时,在 sNotifyObjCMapped 同时也会发生变化,来确保数据的统一。

read_images

  • 进入map_images
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[]) {
    // ... 省略部分代码

   // 我们要分析的核心
    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }
}
  • 进入_read_images,我们先按住 cmd + opt + 箭头< ,全局分析一下 _read_images 到底做了哪些处理。
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
  //  省略一些参数 ... 
  // 1、条件控制进行第一次加载
   if (!doneOnce) 
   { ... }
   ts.log("IMAGE TIMES: first time tasks");

   // Fix up @selector references
   // 2、修复预编译阶段 @selector 的混乱问题
   static size_t UnfixedSelectors 
   { ... }
   ts.log("IMAGE TIMES: fix up selector references");
 
   // 3. 从macho读取地址 并给  class 的赋值
   for (EACH_HEADER)  { ... }
   ts.log("IMAGE TIMES: discover classes");

   // 4.修复映射一些没有被镜像文件加载进来的类
   if (!noClassesRemapped()) {
        for (EACH_HEADER) { ... }
   }
   ts.log("IMAGE TIMES: remap classes");
   // 5.修复一些消息
   // Fix up old objc_msgSend_fixup call sites
    for (EACH_HEADER) { ... }
    ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
  
    // 6.  readProtocol 
    // Discover protocols. Fix up protocol refs.
    for (EACH_HEADER) { ... }
    ts.log("IMAGE TIMES: discover protocols");
   // 7.修复没有被加载的协议
   // Fix up @protocol references
    for (EACH_HEADER) { ... }
    ts.log("IMAGE TIMES: fix up @protocol references");

   // 8.分类的处理
     if (didInitialAttachCategories) {
          for (EACH_HEADER) {
              load_categories_nolock(hi);
          }
    }
    ts.log("IMAGE TIMES: discover categories");

   // 9. 类的加载和处理
   // Realize non-lazy classes (for +load methods and static instances)
   for (EACH_HEADER) {
     ...
      realizeClassWithoutSwift(cls, nil);
   }
   ts.log("IMAGE TIMES: realize non-lazy classes");
   // 10.优化哪些被侵犯的类 
  { ... }
    ts.log("IMAGE TIMES: realize future classes");
}

  • 已知 read_images里做了那么多的事,但最重要目标是要去寻找 加载镜像文件去读取 class
  • 继续探索找到有关 class的代码块
  // Discover classes. Fix up unresolved future classes. Mark bundle classes.
    bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
    for (EACH_HEADER) {
        // { ... }
        classref_t const *classlist = _getObjc2ClassList(hi, &count);
        // { ... }
        for (i = 0; i < count; i++) {
            Class cls = (Class)classlist[I];
            Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
            if (newCls != cls  &&  newCls) {
                // Class was moved but not deleted. Currently this occurs 
                // only when the new class resolved a future class.
                // Non-lazily realize the class below.
                 { ... }
            }
        }
    }
  • 找到了 readClass 但不知道它到底做了什么,我们跑程序运行起来看一下

  • 继续 step

  • 发现了cls 已经有了名字,我们可以肯定 readClass 内部对 进行了处理,那到底是怎么处理的?

  • 进入 readClass

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->nonlazyMangledName();

    if (missingWeakSuperclass(cls)) { ... }
    Class replacing = nil;
    if (mangledName != nullptr) {
        //   future class.
        if (Class newCls = popFutureNamedClass(mangledName)) {
            // This name was previously allocated as a future class.
            // Copy objc_class to future class's struct.
            // Preserve future's rw data block.
            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;
}

  • 查看源代码,静态分析在 readClass 有做 ro,rw 的处理,接下来进行动态调试确认分析结果是否正确
  • 添加 XKStudent 只对普通 class 进行分析,忽略系统 class
  • 动态调试分析👇
  • 没有进入if (Class newCls = popFutureNamedClass(mangledName)) { ro,rw 处理 } 而是跳过了直接来到了👇
  • 由动态调试可知在 readClass 里并没有对 ro,rw 进行处理,而是来了addNamedClass ,addClassTableEntry
  • 进入 addNamedClass , 把 name,cls地址 插入到 hashmap 里进行关联
static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
    Class old;
    if ((old = getClassExceptSomeSwift(name))  &&  old != replacing) {
       // ... 
    } else {
        NXMapInsert(gdb_objc_realized_classes, name, cls);
    }
   // ...
}
  • 进入 addClassTableEntry , 把类的元类也加入到 table
static void
addClassTableEntry(Class cls, bool addMeta = true)
{
    runtimeLock.assertLocked();

    // This class is allowed to be a known class via the shared cache or via
    // data segments, but it is not allowed to be in the dynamic table already.
    auto &set = objc::allocatedClasses.get();

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

    if (!isKnownClass(cls))
        set.insert(cls);
    if (addMeta)
        addClassTableEntry(cls->ISA(), false);
}

总结

  • 今天我们分析了 objc_init 初始化做了哪些事情
    • environ_init();
    • tls_init();
    • static_init();
    • runtime_init();
    • exception_init();
    • cache_t::init();
    • _dyld_objc_notify_register
  • _dyld_objc_notify_register 开始分析 read_images
  • 得知 read_images 的目标就是为了 读取类
  • 而在 readClass我们只是得知 class地址与name进行了关联meta class加入到 table 里,class 里的 ro,rw 在哪里进行赋值的? 期待下一次的分析
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容