网络上讲解+load函数的文章很多很多,但我总觉得缺少点什么,主要表现在不够系统化,割裂的看待问题。本文只是谈一下个人的理解,主要涉及以下四个方面:

苹果开发文档对+load方法的介绍

dyld是如何加载objc的

objc是如何实现+load方法

+load方法的使用场景及注意事项

写过C语言的同学,一讲到初始化流程,首先想到的是main函数,但在oc里面main却不是第一个执行的函数,别大惊小怪,确实是这样的,且听下面的分解。

初识+load

首先我们来看一下苹果文档是如何介绍+load方法的

Discussion

The load message is sent to classes and categories that are both dynamically loaded and statically linked, but only if the newly loaded class or category implements a method that can respond.

The order of initialization is as follows:

All initializers in any framework you link to.

All +load methods in your image.

All C++ static initializers and C/C++ __attribute__(constructor) functions in your image.

All initializers in frameworks that link to you.

In addition:

A class’s +load method is called after all of its superclasses’ +load methods.

A category +load method is called after the class’s own +load method.

In a custom implementation of load you can therefore safely message other unrelated classes from the same image, but any load methods implemented by those classes may not have run yet.

这段苹果的描述具有很大的信息量,目前大多数文章只谈了in addition里面的内容,本文试图从更宏观的角度来讨论load初始化的过程。

dyld居功至伟

具有Linux下编程经验的同学,启动一个进程,一般都fork出一个新进程,然后再执行exec族函数,加载目标程序并运行。MAC或者iOS系统上执行一个进程,也有着类似的流程,但也有不同,具体可以参看内核XNU的源码。流程如下:

execve //用户双击某个app,用户态调用系统调用execve进入内核态

|--- __mac_execve

|--- exec_activate_image

|--- exec_mach_imgact

|--- load_machfile

|--- parse_machfile //解析Mach-o

|--- load_dylinker // 根据Mach-o中的 LC_LOAD_DYLINKER 来启动dyld

|--- parse_machfile // 解析 dyld ,这个过程中会解析出entry_point

|--- activate_exec_state

|--- thread_setentrypoint // 设置entry_point。

在thread_setentrypoint()函数里面,实际上是把entry_point的地址直接写入到了寄存器里面。

大家看到这里,一定会有两个疑问:

这里的entry_point是什么鬼?

entry_point用来干嘛的?

举一个例子:animal,dog,cat三个类,其中animal是父类,dog和cat是子类

#import "Animal.h"

@implementation Animal

+ (void)load {

NSLog(@"Animal Load");

}

@end

#import "Dog.h"

@implementation Dog

+ (void)load {

NSLog(@"Dog Load");

}

@end

#import "Cat.h"

@implementation Cat

+ (void)load {

NSLog(@"Cat Load");

}

@end

#import

__attribute__((constructor)) void init_funcs() {

printf("--------init funcs.--------\n");

}

int main(int argc, const char * argv[]) {

@autoreleasepool {

// insert code here...

NSLog(@"Hello, World!");

}

return 0;

}

对于该段代码,那么他链接了哪些静态库和动态库呢?以下是在 Xcode 中看到的 Link 列表:

由于本段代码太过于简单, 所以没有链接额外的动态库,图中显示的个数也为0。

除了明面上的链接库,另外还隐含的链接有基础的 framework,可以通过 otool查看:

$ otool -L ThinkiOS

ThinkiOS:

/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation

/usr/lib/libobjc.A.dylib

/usr/lib/libSystem.B.dylib

/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation

发现有两个默认添加的 lib:libobjc 即 objc 和 runtime,libSystem 中包含了很多系统级别 lib库,如:

libdispatch ( GCD )

libsystem_c ( C语言库 )

libsystem_blocks ( Block )

此时我们打一个符号断点,运行到_objc_init函数时停下来,看一下此时的调用栈,其内容为:

发现_dyld_start是开始的地方,后面又调用了_main函数,注意此时的_main不是应用程序的main入口,以下截至dyld的开源代码,对_dyld_start和_main的描述:

根据注释里的描述,内核态加载dyld,并跳转到_dyld_start(该函数位于dyldStartup.s中

),dyld_start是用户态执行的入口,所以上面的调用栈第一个就是_dyld_start。第二个是该函数会返回应用程序main函数的地址。

看到这里应该知道上文提及的entry_point是一个什么东西了吧,entry_point的值就是_dyld_start,它的作用上面也分析了。

dyld其中一个功能就是:Runs static initializers for the executable.

dyld will run any static initializers in the executable (most often constructors for global C++ objects and +load methods for Objective-C classes, though there are also __attribute__((constructor)) functions for plain C). A list of initializers is stored in a separate __DATA,__mod_init_func section in the binary, and is simply a set of addresses into the __TEXT,__text section which dyld calls in order of appearance. Initializer functions are passed the same arguments as main.

程序还自动Link了Libsystem库,我们查看一下其初始化函数,也是调用栈中的 libSystem_initializer函数:

图中的红框中,已经标明了该函数是__attribute__(constructor). 根据上面英文描述,dyld会调用该函数,那我们就从源代码来分析这一段流程。

dyld的_main函数会调用initializeMainExecutable()初始化函数

在initializeMainExecutable()中,则会调用每个dylib的初始化函数:

runInitializers()最终会调用到doModInitFunctions()函数:

此处的LC_SEGMENT_COMMAND和S_MOD_INIT_FUNC_POINTERS又代表什么含义呢?这就涉及到Mach-O文件格式了,借助MachOView工具来看一下libSystem.B.dylib库:

上述代码片段中,获取初始化函数地址的代码为:

Initializer* inits = (Initializer*)(sect->addr + fSlide);

MachOView工具里面显示的Address和offset的value都是8720,换算成16进制,则为0x2210,那么地址0x00002210是什么呢?

找到_DATA_,_mod_init_func里面的内容,就会看到初始化函数的地址:

再到_TEXT_,_text_里面查看该地址0x0000195D的内容:

最终就是调用libSystem_initializer()函数。

看到这里,上面那段英文是不是秒懂了:)

同时也就应该明白了苹果官网对于load描述中的

All initializers in any framework you link to.

一句话有了更深的理解了吧。接下来我们开始分析load函数

揭开+load庐山真面目

在libSystem_initializer函数中,会调用libdispatch_init();函数,libdispatch也是开源代码:

DISPATCH_EXPORT DISPATCH_NOTHROW

void

libdispatch_init(void)

{

dispatch_assert(DISPATCH_ROOT_QUEUE_COUNT == 2 * DISPATCH_QOS_MAX);

......

_dispatch_hw_config_init();

_dispatch_time_init();

_dispatch_vtable_init();

_os_object_init();

_voucher_init();

_dispatch_introspection_init();

}

在libdispatch_init()会调用_os_object_init():

void

_os_object_init(void)

{

_objc_init();

Block_callbacks_RR callbacks = {

sizeof(Block_callbacks_RR),

(void (*)(const void *))&objc_retain,

(void (*)(const void *))&objc_release,

(void (*)(const void *))&_os_objc_destructInstance

};

_Block_use_RR2(&callbacks);

#if DISPATCH_COCOA_COMPAT

const char *v = getenv("OBJC_DEBUG_MISSING_POOLS");

_os_object_debug_missing_pools = v && !strcmp(v, "YES");

#endif

}

最终调用到了_objc_init函数,也是objc的入口函数:

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

* _objc_init

* Bootstrap initialization. Registers our image notifier with dyld.

* Called by libSystem BEFORE library initialization time

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

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

lock_init();

exception_init();

_dyld_objc_notify_register(&map_images, load_images, unmap_image);

}

其中_dyld_objc_notify_register作用表示向dyld注册回调函数,与初始化相关的就是第二个参数load_images。

看一下dyld里面是如何实现的:

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;

......

}

将值保存在全局变量中。

在样例程序的任一load处打断点,其调用栈为:

来分析一下dyld::notifySingle的实现:

```Java

static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)

{

......

if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && image->notifyObjC() ) {

uint64_t t0 = mach_absolute_time();

(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());

c语言先调用load函数,透过源码全流程分析+load函数初始化相关推荐

  1. 【Android 逆向】Dalvik 函数抽取加壳 ( 类加载流程分析 | native 函数查询 | dalvik_system_DexFile.cpp#defineClassNative 函数 )

    文章目录 前言 一.查询 defineClassNative 函数 二.dalvik_system_DexFile.cpp#Dalvik_dalvik_system_DexFile_defineCla ...

  2. 【Android 逆向】Dalvik 函数抽取加壳 ( 类加载流程分析 | Class.cpp#findClassNoInit 函数 | DexFile.cpp#dexFindClass 函数分析 )

    文章目录 前言 一.Class.cpp#dvmDefineClass 函数分析 二.Class.cpp#findClassNoInit 函数分析 三.DexFile.cpp#dexFindClass ...

  3. 追源索骥:透过源码看懂Flink核心框架的执行流程

    https://www.cnblogs.com/bethunebtj/p/9168274.html 追源索骥:透过源码看懂Flink核心框架的执行流程 前言 1.从 Hello,World WordC ...

  4. 追源索骥:透过源码看懂Flink核心框架的执行流程--来自GitHub

    追源索骥:透过源码看懂Flink核心框架的执行流程 联系qq2499496272可进行删除,需要文件版本的私聊!!~ 文章目录 追源索骥:透过源码看懂Flink核心框架的执行流程 前言 1.从 ~~H ...

  5. 【Android 逆向】Dalvik 函数抽取加壳 ( 类加载流程分析 | DexPathList#findClass 函数分析 | DexFile#loadClassBinaryName 函数 )

    文章目录 前言 一.DexPathList.java#findClass 类加载函数源码分析 二.DexFile.java#loadClassBinaryName 函数源码分析 前言 上一篇博客 [A ...

  6. 透过源码领悟GCC到底在干些什么

    GCC源码分析(一)--介绍与安装 GCC源码分析(一)--介绍与安装 目录(?)[-] 一GCC的作用和运行机制 二GCC的安装 上半年一直在做有关GCC和LD的项目,到现在还没做完.最近几天编程的 ...

  7. 透过源码领悟GCC到底在干些什么(收集整理)

    GCC源码分析(一)--介绍与安装 目录(?)[-] 一GCC的作用和运行机制 二GCC的安装 上半年一直在做有关GCC和LD的项目,到现在还没做完.最近几天编程的那台电脑坏了,所以趁此间隙写一点相关 ...

  8. 透过源码详解Spring Security 初始化流程

    Spring Security在3.2版本之后支持Java Configuration,即:通过Java编码形式配置Spring Security,可不再依赖XML文件配置,本文采用Java Conf ...

  9. 【面试必备】透过源码角度一步一步带你分析 ArrayList 扩容机制

    该文已加入开源文档:JavaGuide(一份涵盖大部分Java程序员所需要掌握的核心知识).地址:https://github.com/Snailclimb/JavaGuide. 一 先从 Array ...

最新文章

  1. SpringCloud Alibaba微服务实战(二) - Nacos服务注册与restTemplate消费
  2. 实现2D全景图的中心视野变换
  3. JavaFX技巧9:请勿混用Swing / JavaFX
  4. springboot事物注解不生效_springboot事务不生效的几种解决方案
  5. 初识 Vue(10)---(计算属性的 setter 和 getter)
  6. 一步一步学EF系列【6、IOC 之AutoFac】
  7. IOS开发之视图和视图控制器
  8. vmware虚拟机挂载Windows磁盘的两种方法
  9. 如何更高效地使用 OkHttp
  10. mybatis的二表联合查询
  11. CSS:使用CSS绘制三角形
  12. 基于BP神经网络识别手写字体MINST字符集
  13. 环评师考各个科目有哪些备考的好方法?
  14. 青龙2.9及2.8Faker仓库互助教程
  15. 同比日期的获取(公历与农历)
  16. SRF04绿色小屏的作用
  17. 网络(八)之OSPF协议的原理及配置
  18. 微信小程序趋势及前景,详细的Android学习指南
  19. Studing Git
  20. 【MPLAB X IPE】:XIPE烧写教程

热门文章

  1. pandas数据切片
  2. NoSQL技术入门简介
  3. KDD 2021 | 谷歌DHE:不使用embedding table的类别型特征embedding
  4. Tiktok才是跨境卖家的未来?
  5. 人工智能重点汇总(搜索策略、博弈、贝叶斯、SVM、神经网络、弧相容、SVM、决策树、反向传播、卷积神经网络)
  6. 词向量算法—Word2Vec和GloVe
  7. 机器学习第六回——降维+异常检测
  8. Redis基础(一)——NoSQL
  9. 2019PKU\THU WC题解
  10. 论文阅读笔记二十八:You Only Look Once: Unified,Real-Time Object Detection(YOLO v1 CVPR2015)...