dyld加载流程

文章详细阐述了iOS应用启动时的调用顺序,从ViewController的load方法开始,经过C++的构造函数slFunc,再到main函数的执行。同时,介绍了dyld的动态链接过程,包括静态库和动态库的优缺点,以及dyld如何加载和初始化应用程序。在dyld的dyld::_main函数中,执行了各种初始化操作,包括加载插入的动态库,弱符号绑定,以及运行初始化方法,最终找到并执行main函数。
摘要由CSDN通过智能技术生成

前言

创建project,ViewController重写load方法,main函数添加一个c++函数,查看调用顺序

@implementation ViewController
+ (void)load {
    NSLog(@"%s", __func__);
}
//。。。
@end
int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    
    NSLog(@"123123123");
    
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

__attribute__((constructor)) void slFunc() {
    printf("来了 : %s \n", __func__);
}
2023-02-13 10:01:06.913135+0800 嗷嗷[2677:54883] +[ViewController load] 来了 : slFunc 
2023-02-13 10:01:06.913956+0800 嗷嗷[2677:54883] 123123123

所以运行顺序是load -> C++方法 -> main,所以main函数并不都是最先执行的。

 

iOS编译过程和库

app启动前涉及编译过程动态库静态库

编译过程

 静态库

编译时被直接拷贝一份,复制到目标程序里的

  • 优点:编译完成,就没有作用了,目标程序无外部依赖,可直接运行。
  • 缺点:静态库会有两份,会使目标程序体积增加,影响内存、性能和速度。

动态库 

目标程序只会存储指向动态库的引用运行时才被载入。

  • 优点:1.减少打包之后app的大小;2.共享内存,同一份库可以被多个程序使用;3.更新动态库可以实现更新程序,运行时才载入,可随时替换库,不用重新编译代码。
  • 缺点:会有一部分性能损失,依赖外部环境,若环境缺了动态库,或版本不对,程序将不能运行。

图示如下,(图转载自Style_月月 ,感谢🙏)

 

dyld加载流程分析

dyId

dyld苹果的动态链接器the dynamic link editor),app被编译打包成可执行文件Mach-O后,交由dyld负责链接,加载程序

app启动流程如下图,(图转载自Style_月月 ,感谢🙏)

 

 app启动的起始点

于load方法处加个断电,bt输出堆栈信息

 

 所以App启动是从dyld中的dyld_start开始的。

dyld::_main函数源码分析

1.在dyld-852源码中,找到arm64相关汇编,接着会调用一个C++方法dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)

2.搜索dyldbootstrap,找到 start方法,发现核心是返回值调用了dyld::_main

namespace dyldbootstrap {
。。。

uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
				const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue)
{

    // Emit kdebug tracepoint to indicate dyld bootstrap has started <rdar://46878536>
    dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TIMING_BOOTSTRAP_START, 0, 0, 0, 0);

	// if kernel had to slide dyld, we need to fix up load sensitive locations
	// we have to do this before using any global variables
    rebaseDyld(dyldsMachHeader);

	// kernel sets up env pointer to be just past end of agv array
	const char** envp = &argv[argc+1];
	
	// kernel sets up apple pointer to be just past end of envp array
	const char** apple = envp;
	while(*apple != NULL) { ++apple; }
	++apple;

	// set up random value for stack canary
	__guard_setup(apple);

#if DYLD_INITIALIZER_SUPPORT
	// run all C++ initializers inside dyld
	runDyldInitializers(argc, argv, envp, apple);
#endif

	_subsystem_init(apple);

	// now that we are done bootstrapping dyld, call dyld's main
	uintptr_t appsSlide = appsMachHeader->getSlide();
	return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}
。。。

 dyld::_main实现

  1. 根据环境变量设置相应的值以及获取当前运行架构
  2. 检查是否开启了共享缓存,以及共享缓存是否映射到共享区域,例如CoreFoundation、UIKit等。
  3. 主程序初始化:调用instantiateFromLoadedImage函数实例化了一个ImageLoader对象
  4. 插入动态库:遍历环境变量DYLD_INSERT_LIBRARIES,调用loadInsertedDylib加载
  5. 链接主程序
  6. 链接动态库
  7. 弱符号绑定:sMainExecutable->weakBind(gLinkContext);
  8. 执行初始化方法:initializeMainExecutable
  9. 寻找入口main函数,从Load Command读取LC_MAIN入口

执行初始化方法:initializeMainExecutable

查看initializeMainExecutable源码,核心主要是循环执行runInitializers

 runInitializers的主要方法是processInitializers

 

进入processInitializers函数的源码实现,其中对镜像列表进行递归实例化 ,实例化函数为recursiveInitialization

 recursiveInitialization主要分成两部分(notifySingledoInitialization

 

 notifySingle

notifySingle的重点主要是(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());

搜索 sNotifyObjCInit,发现在registerObjCNotifiers函数中有赋值操作

继续搜索 registerObjCNotifiers,发现了_dyld_objc_notify_register,这个函数在之前探究OC初始化的时候见过

 去objc源码探究_dyld_objc_notify_register,果然是在_objc_init中调用了,接下来吧重心放在load_images

 load_images

进入load_images源码

 进入call_load_methods,这里在循环调用call_class_loads

进入call_class_loads,可以得出结论:load_images中调用了所有的load函数

 

load的源码链为:_dyld_start --> dyldbootstrap::start --> dyld::_main --> dyld::initializeMainExecutable --> ImageLoader::runInitializers --> ImageLoader::processInitializers --> ImageLoader::recursiveInitialization --> dyld::notifySingle(是一个回调处理) --> sNotifyObjCInit --> load_images(libobjc.A.dylib)

doInitialization 函数

doInitialization分成(doImageInit、doModInitFunctions)

  •  doImageInit主要是循环加载方法的调用,必须先运行libSystem初始化
  • doModInitFunctions主要用于加载所有Cxx文件

寻找主入口函数

汇编调试,先来到了load方法

 continue,来到c++函数slFunc

最后来到main函数

 

总结

 dyld加载流程如下,同时说明了为什么调用顺序是load-->Cxx-->main

 

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值