07--应用加载02--应用加载流程[_objc_init][read_images]

TOC

_objc_init:初始化流程

  • _objc_init 源码
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();
    tls_init();
    static_init();
    runtime_init();
    exception_init();
    cache_init();
    _imp_implementationWithBlock_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

我们先不要着急分析流程,看到最后一行代码:_dyld_objc_notify_register。这个很明显是 _dyld 里面的一个方法,上一篇文章中分类了 _dyld 的加载流程。

App加载分析

所以这里从 _dyld 中将这个方法的源码拿了过来,下面是 _dyld_objc_notify_register 的源码

void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped)
{
    dyld::registerObjCNotifiers(mapped, init, unmapped);
}
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
    // record functions to call
    sNotifyObjCMapped   = mapped;
    sNotifyObjCInit     = init;
    sNotifyObjCUnmapped = unmapped;
    ……
}

源码很简单,仅仅是记录了这三个方法的地址,在需要的时候进行一个调用。在我们平时开发中,需要用到地址传递的时候,一般是需要修改这个变量的值,那么可以猜测这个方法的作用是将 _dyld 中的 mappedImage 回传给 objc,这里的对内容放在后面分析,下面对这些初始化方法进行一个简单的介绍。

environ_init();

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

1. 在终端中输入 export OBJC_HELP=1 可以输出所有环境变量

OBJC_HELP

如果没有输出,尝试再执行一个 open 命令

2. 在Xcode 源码中输出环境变量

在这里手动修改:PrintHelp=true;

image

3. OBJC_DISABLE_NONPOINTER_ISA 环境变量

  • 设置 OBJC_DISABLE_NONPOINTER_ISAYES

    image

    设置之后的打印出来的isa结果

    image

    设置之前的isa结果

    image

    因为系统做了优化,对isa联合体做了平移操作,所以会不一样
    OBJC_PRINT_LOAD_METHODS

4. 环境变量 OBJC_PRINT_LOAD_METHODS

  • 设置 OBJC_PRINT_LOAD_METHODS 为 YES

    image
  • 打印所有的 +load 方法

    image

作用:可以对这些方法做方法优化,避免全局搜索找到的load方法(可能没有用到)

tls_init();

关于线程的绑定,比如每个线程数的析构函数

static_init();

  • 运行C++ 的静态构造函数。
  • 在 _dyld 调用我们自己的静态构造函数之前调用
  • libc 会调用 _objc_init(),所以我们必须自己做。

runtime_init();

是空实现!就是说 objc 的锁是完成采用C++那一套的, oc中不需要做任何处理。

exception_init();

初始化 libobjc 的异常处理系统。比如监控下一行注册异常的回调的代码。

cache_init();

_imp_implementationWithBlock_init()

_dyld_objc_notify_register()

仅供 objc 运行时使用
注册处理程序,以便在映射、取消映射和初始化 objc-image 时调用
Dyld将使用包含 objc-image-info 的景象文件的数组,回调 mapped 函数

  • 研究对象
    • map_images(重点)
    • load_images(重点)
    • unmap_image:主要做卸载相关的操作,不做重点研究

_objc_init:map_images 流程

read_images 注释

/***********************************************************************
* map_images
* Process the given images which are being mapped in by dyld.
* Calls ABI-agnostic code after taking ABI-specific locks.
*
* Locking: write-locks 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,这个函数的注释说“处理给定的镜像文件(在dyld中映射进去的镜像)”,这句注释正好验证了我们上面的猜测——registerObjCNotifiers 方法的作用是将 _dyld 中的 mappedImage 回传给 objc

下面来分析 map_images_nolock 方法的流程

read_images 要读懂的问题

  1. 我们的dyld主题的思维是加载库-镜像文件,但是镜像文件怎么读取的?
  2. 我们的macho里面的数据怎么到我们的内存?
  3. 有没有不在macho里面的数据同样也可以在内存找到?
  4. sel方法编号的加载?
  5. 什么是懒加载类和非懒加载类?
  6. 类是如何加载实现的 -ro-rw 的关系?
  7. 协议&分类里面的数据是如何加载的?

read_images 初体验

在dyld的源码中去查找_dyld_objc_notify_register方法实现

image

1. 如何开展dyld研究

  • 看注释 “// record functions to call”——记录被调用的函数
  • 可以看出来,如果要研究这三个参数,和明显要研究这三个函数被调用的地方
  • 以第一个参数为例,找到第一个函数被调用的地方
image

2. 在objc源码中分析,定位到_read_images方法

  • _objc_init->map_images

    image
  • map_images->map_images_nolock

    image
  • map_images_nolock->_read_images

    image

3. 怎么分析 _read_images方法

这个方法有400多行,逐行读肯定不行

  1. 我们先将所有有注释的代码块折叠起来

  2. 方法的准备条件

    image
  3. 方法的流程

    image

    我们可以看到每个代码块都有很标准的注释和日志输出,整个流程一目了然:

    1. 加载所有类到类的 gdb_objc_realized_classes 表中
    2. 对所有类做重映射
    3. 将所有SEL都注册到 namedSelecotors 表中
    4. 修复函数指针遗留
    5. 将所有 Protocol 都添加到 protocol_map 表中
    6. 对所有 Protocol 做重映射
    7. 初始化所有非懒加载的类,进行 rw、ro等操作
    8. 遍历已标记的懒加载的类,并做初始化操作
    9. 处理所有 Category,包括 ClassMeta Class
    10. 初始化所有未初始的类。

4. 表的介绍

  • gdb_objc_realized_classes:无论是否实现,只要不在 dyld 共享缓存中的已命名类的表
  • allocatedClasses:通过 objc_allocateClassPair 已分配的所有类(和元类)的表
  • namedSelecotors:方法编号的表
  • protocol_map:协议的表

题外话

  1. 为什么类的开头是 NS
    NextStep的简称
    乔帮主曾经被苹果踢出去了,然后又回来了
  2. NX开头的又代表什么含义
    NX表示一种CPU的计数,“禁止执行的意思”

read_images 流程分析

第一流程:查找类。修复未解决的future类。标记 bundle类。

流程代码
  1. 从编译后的类列表中取出所有类, 获取到的是一个 classref_t 类型指针
    classref_t *classlist = _getObjc2ClassList(hi, &count);

  2. 遍历数组中会去除 OS_dispatch_queue_concurrent OS_xpc_object NSRunloop 等系统类, 例如 CFFoundation libdispatch 中的类, 以及自己创建的类.

    通过readClass函数获取处理后的新类, 内部主要操作 ro 和 rw 结构体

  3. 初始化所有懒加载的类需要的内存空间,现在数据没有加载

    image

第二流程:修复类的重映射(一般不会走进来)

image

注释

  1. 类列表和非懒加载类类表保持未重映射
  2. 重映射类和super类,用于消息分发
  3. 将未映射class和super class重映射,被remap的类都是非懒加载类

第三流程:修复SEL引用

image

将所有 SEL 都注册到 namedSelecotors 表中

第四流程:修复旧的objc_msgSend_fixup调用站点,有条件才进来,不做分析

image

第五流程:查找协议protocols。修复协议protocols引用

image

_getObjc2ProtocolList 读协议存到 protocol_map

第六流程:修复协议的引用

image

预先优化的图像可能已经在正确的位置了,但并不能确定。所以需要重新映射协议的引用

第七流程:实现非懒加载类(重点流程)

  • 实现了load方法的类
  • 静态实例变量的类(常见的是单例)
image

第八流程:实现未来类。(条件依赖第一个流程)

image

第九流程:查找分类。(默认找的是懒加载分类-实现了load)

  • 注册分类并关联目标类
  • 重新构建类的方法列表(attachLists流程)

因为分类的方法也是放到了类的方法列表里面,而且是通过 attachLists 流程,插入了方法列表的前面,所以会造成分类方法“覆盖”主类方法的现象。

image

第十流程:实现所有未实现的类

image
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342