本文我们主要探索应用程序的加载流程,也就是main
方法之前,链接器都做了什么。了解这些对我们项目的启动优化
有很大帮助。
编译过程和库
编译的过程
我们知道库是一种可执行文件
,从源代码到可执行文件工经历了下面几个步骤:
源文件
:主要就是我们写的代码,.h、.m、.cpp等文件。预编译
:主要处理哪些源代码文件中以#
开始的预编译指令,比如#include
、#define
、删除所有的注释//
和/* */
、添加行号和文件名标识、保留所有的#pragma
编译期指令、产生.i
文件。编译
:将预处理
完的文件进行词法分析
、语义分析
及优化后输出汇编代码文件即.s
文件。汇编
:将汇编代码
转变成机器可以执行的指令即.o
文件。链接
:对.o文件中引用其他库的地方进行引用,生成最后的可执行文件
动态库和静态库
我们项目中经常会使用到动态库
和静态库
,它们的区别是:
- 静态库:在链接阶段会将汇编生成的目标文件和引用的库一起链接打包到可执行文件中。即静态库在
链接
阶段就被载入了。- 优点:编译完成之后的目标程序没有
外部依赖
,可以直接运行。 - 缺点:静态库可以会有多份,会导致
目标程序
体积增加,对内存、性能、速度消耗较大。
- 优点:编译完成之后的目标程序没有
- 动态库:程序编译并不会链接到目标代码中,在程序可执行文件里面会保留对动态库的引用,在程序
运行时
才被载入,苹果大部分官方的库都是动态库
。- 优点:
- 可以减少
App
包体积大小:因为不需要拷贝到目标程序
中,所以不会影响目标程序
的体积. - 共享内存,节约资源:同一份库可以被多个程序使用。
- 可以更新动态库,而目标程序不需要重新编译:这是因为动态库
运行时
才载入,可以随时对库进行替换,而·不需要重新编译代码。
- 可以减少
- 缺点:由于是
运行时
载入会带来一部分性能损失,使用动态库使得程序依赖于外部环境,如果环境缺少了动态库程序就无法运行。
- 优点:
dyld是什么
dyld
是动态链接器
,目前最新的版本是dyld3
,我们首先看一下dyld
的版本演变。在dyld
之前,NeXT
使用的是静态二进制
数据。
dyld
版本演变
dyld1.0
(1996-2004)- 包含在
NeXTStep 3.3
中 - 历史早于标准化
POSIX dlopen()
的调用 macOS 10
之前编写第三方包装器用来以支持标准Unix
软件,但是这些包装器并不能完美的支持相同的语义,在边界情况不能正常工作。- 在大多数使用
C++
动态库的系统之前编写的。 - 在
mac OS 10.0
增加了预绑定
。使用预绑定
技术为系统中所有的dylib
和我们的程序找到固定地址
,动态加载器
将会加载这些地址的所有内容。
- 包含在
dyld2.0
(2004-2007)- 包含在
macOS Tiger
中 - 相比
1.0
版本是完全重写(Complete rewrite)的。 - 支持了
C++
初始化语义,扩展了mach-o
格式。 - 有完整的本地(native)
dlopen
和dlsym
的实现。 2.0
版本设计的目标是提高速度
,仅进行有限的及安全性检查。- 提高了安全性。
- 减少
预编译
的工作量(时长)
- 包含在
dyld2.x
(2007-2017)- 增加了更多的基础架构和平台,比如
x86
、x86_64
、arm
、arm64
、iOS
、tvOS
、watchOS
。 - 增强了安全性。增加
代码签名
和ASLR
(地址空间配置随机加载),增加了mach-o
头文件中的项目边界检查
功能它可以避免恶意二进制数据的加入。 - 增强了性能:用
共享缓存
代替了预绑定
。共享缓存
是一个包含大部分系统dylib
的单文件(Single file),可以节省大量内存,它实际是预链接
库。
- 增加了更多的基础架构和平台,比如
dyld3
(2017-至今)- 完全改变
动态链接器
的概念 - 默认适用于大部分
Apple OS
系统应用。 - 完全替代了
dyld2.x
。 - 提高了性能,尽量提高启动速度和运行速度。
- 提高安全性:将大多数
dyld
移出进程,允许部分dyld
驻留在进程之中,驻留部分尽可能小,从而减少受攻击的面积。 - 可测试性和可靠性
- 完全改变
dyld 2
和dyld 3
加载过程的区别
-
dyld 2
的加载过程Parse mach-o headers
:分析mach-o
文件,通过分析mach-o
文件弄清楚需要那些库,这些库可能需要其他库,所以会进行递归分析,直到获得所有dylib
的完整图。一般普通iOS
程序需要3-600个dylib
,数据庞大,需要进行大量的处理。Map mach-o files
:映射所有mach-o
文件,将它们放入地址空间Perform symbol lookups
:执行符号查找,例如程序使用了printf
函数,将会查找printf
是否在库系统中,然后找到它的地址,将它复制到你的程序中的函数指针。Bind and rebase
:绑定和基址重置,复制3
步的指针,由于使用随机地址,所有指针必须使用基址。Run initializers
:运行初始化器,接下来准备执行main
函数。
流程图如下图,其中红色表示影响性能和安全性的步骤:
-
dyld3
加载过程dyld3
包括三个部分:-
An out of process MachO parser/compiler
:进程外mach-o
分析器和编译器。Resolves all search paths, @rpaths, environment variables
:解析所有搜索路径、rpaths
、环境变量。Parses the mach-o binaries
:分析mach-o
二进制数据Perform symbol lookups
:执行符号查找,例如程序使用了printf
函数,将会查找printf
是否在库系统中,然后找到它的地址,将它复制到你的程序中的函数指针。Creates a launch closure with results
:创建收尾处理
-
An in-process engine that runs launch
:进程内引擎执行启动收尾处理,进驻在内存中。Validates launch closure
:检查启动收尾处理是否正确。Maps in all dylibs
:映射到所有的dylib
中Applies fixups
:应用修正Run initializers
:运行初始化器,接下来准备执行main
函数。
-
A launch closure caching service
:启动收尾缓存服务。大部分程序启动会使用缓存但始终不需要调用进程外mach-o
分析器和编译器,启动收尾比mach-o
更简单,启动收尾文件是内存映射文件,不需要用复杂的方法进行分析从而提高速度。流程图(来源WWDC ppt)如下:
-
注:本小节内容来源于WWDC2017 App Startup Time: Past, Present, and Future感兴趣的童鞋可以查看视频。
dyld加载流程分析
通过上一小节我们其实对dyld
的加载有一个初步的了解了,本小节主要通过看源码来探索一下加载流程。本小节需要的源码有:
dyld
libobjc
libSystem
libdispatch
源码可直接去苹果Source Browser
下载
dyld
start探索
-
通过
main
函数因为我们程序的入口是
main
函数,dyld
是在main
之前执行的,我们很容易想到在main
函数打一个断点,然后查看调用堆栈信息来查看dyld
的具体调用方法:(lldb) bt * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 * frame #0: 0x000000010000339b DyldTest`main(argc=3, argv=0x00007ffeefbff500) at main.m:13:5 frame #1: 0x00007fff6e7d3cc9 libdyld.dylib`start + 1 复制代码
通过这个方式我们看到了
start
,但是通过打符号断点并没有找到start
方法,所以这种方式无效。 -
通过
load
方法根据我们的经验,我们知道
load
方法是在main
函数之前执行的,我们通过load
方法能不能找到dyld
的入口呢,心动不如行动,我们试一下,在ViewController
类加入load
方法,打上断点:(lldb) bt * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 * frame #0: 0x00000001000032d7 DyldTest`+[ViewController load](self=ViewController, _cmd="load") at ViewController.m:19:5 frame #1: 0x00007fff6d61e560 libobjc.A.dylib`load_images + 1529 frame #2: 0x000000010001626c dyld`dyld::notifySingle(dyld_image_states, ImageLoader const*, ImageLoader::InitializerTimingList*) + 418 frame #3: 0x0000000100029fe9 dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 475 frame #4: 0x00000001000280b4 dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 188 frame #5: 0x0000000100028154 dyld`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 82 frame #6: 0x00000001000166a8 dyld`dyld::initializeMainExecutable() + 199 frame #7: 0x000000010001bbba dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 6667 frame #8: 0x0000000100015227 dyld`dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*) + 453 frame #9: 0x0000000100015025 dyld`_dyld_start + 37 复制代码
通过这个堆栈我们看到了
_dyld_start
就是dyld
开始的函数,我们依次探讨堆栈里的方法。
_dyld_start
我们首先在dyld
源码中搜索_dyld_start
,发现是一段汇编代码
通过注释我们可以看到,调用的是dyldbootstrap
的start
函数。
dyldbootstrap::start
我们在源码中搜索dyldbootstrap
找到命名空间
,继续查找start
函数
// This is code to bootstrap dyld. This work in normally done for a program by dyld and crt.
// In dyld we have to do this manually.
//
uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue)
{
///省略代码
// 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
。
dyld::_main
这个方法很长(900+行),可以从返回值倒退看这个方法都做了什么。方法太长我们省略大部分代码(因为返回值和mainExecutable
相关,所以截取的代码基本都和mainExecutable
相关):
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide,
int argc, const char* argv[], const char* envp[], const char* apple[],
uintptr_t* startGlue)
{
///省略代码
// Grab the cdHash of the main executable from the environment
// 创建主程序cdHash的空间
uint8_t mainExecutableCDHashBuffer[20];
const uint8_t* mainExecutableCDHash = nullptr;
if ( const char* mainExeCdHashStr = _simple_getenv(apple, "executable_cdhash") ) {
unsigned bufferLenUsed;
if ( hexStringToBytes(mainExeCdHashStr, mainExecutableCDHashBuffer, sizeof(mainExecutableCDHashBuffer), bufferLenUsed) )
mainExecutableCDHash = mainExecutableCDHashBuffer;
}
/////配置信息,获取主程序的mach-o header、silder(ASLR的偏移值)
getHostInfo(mainExecutableMH, mainExecutableSlide);
///通过silder+ASLR可以找到信息
uintptr_t result = 0;
sMainExecutableMachHeader = mainExecutableMH;
sMainExecutableSlide = mainExecutableSlide;
///省略代码
//设置上下文,将这里所有的变量放到了gLinkContext中了,保存起来
setContext(mainExecutableMH, argc, argv, envp, apple);
//配置进程是否受限,envp是环境变量
configureProcessRestrictions(mainExecutableMH, envp);
configureProcessRestrictions(mainExecutableMH, envp);
///检测是否强制dyld3
// Check if we should force dyld3. Note we have to do this outside of the regular env parsing due to AMFI
if ( dyld3::internalInstall() ) {
if (const char* useClosures = _simple_getenv(envp, "DYLD_USE_CLOSURES")) {
if ( strcmp(useClosures, "0") == 0 ) {
sClosureMode = ClosureMode::Off;
} else if ( strcmp(useClosures, "1") == 0 ) {
#if !__i386__ // don't support dyld3 for 32-bit macOS
sClosureMode = ClosureMode::On;
sClosureKind = ClosureKind::full;
#endif
} else if ( strcmp(useClosures, "2") == 0 ) {
sClosureMode = ClosureMode::On;
sClosureKind = ClosureKind::minimal;
} else {
dyld::warn("unknown option to DYLD_USE_CLOSURES. Valid options are: 0 and 1\n");
}
}
}
#if TARGET_OS_OSX
///受限制的进程,环境变量可能会变化,需要重新设置
if ( !gLinkContext.allowEnvVarsPrint && !gLinkContext.allowEnvVarsPath && !gLinkContext.allowEnvVarsSharedCache ) {
pruneEnvironmentVariables(envp, &apple);
// set again because envp and apple may have changed or moved
setContext(mainExecutableMH, argc, argv, envp, apple);
}
else
#endif
{
///检查环境变量
checkEnvironmentVariables(envp);
///default value for DYLD_FALLBACK_FRAMEWORK_PATH, if not set in environment
///如果没有环境变量 设置默认值
defaultUninitializedFallbackPaths(envp);
}
// load shared cache 加载共享缓存
checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide)
#if !TARGET_OS_SIMULATOR
///是否有启动闭包 dyld3有闭包
if ( sClosureMode == ClosureMode::Off ) {
if ( gLinkContext.verboseWarnings )
dyld::log("dyld: not using closures\n");
} else {
///dyld3有闭包
///设置加载启动模式
sLaunchModeUsed = DYLD_LAUNCH_MODE_USING_CLOSURE;
///配置闭包
const dyld3::closure::LaunchClosure* mainClosure = nullptr;
dyld3::closure::LoadedFileInfo mainFileInfo;
mainFileInfo.fileContent = mainExecutableMH;
mainFileInfo.path = sExecPath;
// check for closure in cache first
// 判断缓存中是否已经有闭包
if ( sSharedCacheLoadInfo.loadAddress != nullptr ) {
mainClosure = sSharedCacheLoadInfo.loadAddress->findClosure(sExecPath);
if ( gLinkContext.verboseWarnings && (mainClosure != nullptr) )
dyld::log("dyld: found closure %p (size=%lu) in dyld shared cache\n", mainClosure, mainClosure->size());
if ( mainClosure != nullptr )
sLaunchModeUsed |= DYLD_LAUNCH_MODE_CLOSURE_FROM_OS;
}
///如果闭包已失效
if ( (mainClosure != nullptr) && !closureValid(mainClosure, mainFileInfo, mainExecutableCDHash, true, envp) ) {
mainClosure = nullptr;
sLaunchModeUsed &= ~DYLD_LAUNCH_MODE_CLOSURE_FROM_OS;
}
///没有闭包创建一个
if ( (mainClosure == nullptr) && allowClosureRebuilds ) {
// if forcing closures, and no closure in cache, or it is invalid, check for cached closure
if ( !sForceInvalidSharedCacheClosureFormat )
mainClosure = findCachedLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, bootToken);
if ( mainClosure == nullptr ) {
// if no cached closure found, build new one
mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, bootToken);
if ( mainClosure != nullptr )
sLaunchModeUsed |= DYLD_LAUNCH_MODE_BUILT_CLOSURE_AT_LAUNCH;
}
}
// try using launch closure 使用启动闭包
if ( mainClosure != nullptr ) {
CRSetCrashLogMessage("dyld3: launch started");
if ( mainClosure->topImage()->fixupsNotEncoded() )
sLaunchModeUsed |= DYLD_LAUNCH_MODE_MINIMAL_CLOSURE;
bool closureOutOfDate;
bool recoverable;
///启动闭包
bool launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH,
mainExecutableSlide, argc, argv, envp, apple, diag, &result, startGlue, &closureOutOfDate, &recoverable);
/// 如果启动失败
if ( !launched && closureOutOfDate && allowClosureRebuilds ) {
// closure is out of date, build new one
mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, bootToken);
///重新启动
launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH,
mainExecutableSlide, argc, argv, envp, apple, diag, &result, startGlue, &closureOutOfDate, &recoverable);
}
///启动成功 返回main函数
if ( launched ) {
gLinkContext.startedInitializingMainExecutable = true;
if (sSkipMain)
result = (uintptr_t)&fake_main;
return result;
}
///不是dyld3的省略代码
}
#endif
///省略代码
// load any inserted libraries插入动态库
if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)
loadInsertedDylib(*lib);
}
///弱引用绑定主程序
sMainExecutable->weakBind(gLinkContext);
// run all initializers
// 运行所有initializers
initializeMainExecutable();
// notify any montoring proccesses that this process is about to enter main()
/// 通知可以进入main函数了
notifyMonitoringDyldMain();
}
复制代码
大体流程就是:
- 配置环境变量
- 检查共享缓存是否开启,以及共享缓存是否映射到共享区域
- 主程序初始化即
instantiateFromLoadedImage
- 插入动态库
- link主程序
- link动态库
- 弱符号绑定
- 执行初始化方法
- 主程序入口
dyld::initializeMainExecutable
主要是循环遍历执行runInitializers
void initializeMainExecutable()
{
// record that we've reached this step
gLinkContext.startedInitializingMainExecutable = true;
// run initialzers for any inserted dylibs
ImageLoader::InitializerTimingList initializerTimes[allImagesCount()];
initializerTimes[0].count = 0;
const size_t rootCount = sImageRoots.size();
if ( rootCount > 1 ) {
///遍历 执行
for(size_t i=1; i < rootCount; ++i) {
sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
}
}
// run initializers for main executable and everything it brings up
sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
// register cxa_atexit() handler to run static terminators in all loaded images when this process exits
if ( gLibSystemHelpers != NULL )
(*gLibSystemHelpers->cxa_atexit)(&runAllStaticTerminators, NULL, NULL);
// dump info if requested
if ( sEnv.DYLD_PRINT_STATISTICS )
ImageLoader::printStatistics((unsigned int)allImagesCount(), initializerTimes[0]);
if ( sEnv.DYLD_PRINT_STATISTICS_DETAILS )
ImageLoaderMachO::printStatisticsDetails((unsigned int)allImagesCount(), initializerTimes[0]);
}
复制代码
ImageLoader::runInitializers
核心代码是调用processInitializers
方法
void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo)
{
uint64_t t1 = mach_absolute_time();
mach_port_t thisThread = mach_thread_self();
ImageLoader::UninitedUpwards up;
up.count = 1;
up.imagesAndPaths[0] = { this, this->getPath() };
///调用
processInitializers(context, thisThread, timingInfo, up);
context.notifyBatch(dyld_image_state_initialized, false);
mach_port_deallocate(mach_task_self(), thisThread);
uint64_t t2 = mach_absolute_time();
fgTotalInitTime += (t2 - t1);
}
复制代码
ImageLoader::processInitializers
对镜像列表调用recursiveInitialization
函数进行递归实例化
void ImageLoader::processInitializers(const LinkContext& context, mach_port_t thisThread,
InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& images)
{
uint32_t maxImageCount = context.imageCount()+2;
ImageLoader::UninitedUpwards upsBuffer[maxImageCount];
ImageLoader::UninitedUpwards& ups = upsBuffer[0];
ups.count = 0;
// Calling recursive init on all images in images list, building a new list of
// uninitialized upward dependencies.
// 递归实例化
for (uintptr_t i=0; i < images.count; ++i) {
images.imagesAndPaths[i].first->recursiveInitialization(context, thisThread, images.imagesAndPaths[i].second, timingInfo, ups);
}
// If any upward dependencies remain, init them.
if ( ups.count > 0 )
processInitializers(context, thisThread, timingInfo, ups);
}
复制代码
ImageLoader::recursiveInitialization
主要是加载完镜像后通知出去
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
///递归锁
recursive_lock lock_info(this_thread);
recursiveSpinLock(lock_info);
if ( fState < dyld_image_state_dependents_initialized-1 ) {
uint8_t oldState = fState;
// break cycles 结束递归
fState = dyld_image_state_dependents_initialized-1;
try {
// initialize lower level libraries first
for(unsigned int i=0; i < libraryCount(); ++i) {
ImageLoader* dependentImage = libImage(i);
if ( dependentImage != NULL ) {
// don't try to initialize stuff "above" me yet
if ( libIsUpward(i) ) {
uninitUps.imagesAndPaths[uninitUps.count] = { dependentImage, libPath(i) };
uninitUps.count++;
}
else if ( dependentImage->fDepth >= fDepth ) {
dependentImage->recursiveInitialization(context, this_thread, libPath(i), timingInfo, uninitUps);
}
}
}
// record termination order
if ( this->needsTermination() )
context.terminationRecorder(this);
// let objc know we are about to initialize this image
// 让objc知道我们要初始化此镜像
uint64_t t1 = mach_absolute_time();
fState = dyld_image_state_dependents_initialized;
oldState = fState;
context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
// initialize this image
/// 初始化镜像
bool hasInitializers = this->doInitialization(context);
// let anyone know we finished initializing this image
/// 让任何人都知道我们完成了这个镜像的初始化
fState = dyld_image_state_initialized;
oldState = fState;
context.notifySingle(dyld_image_state_initialized, this, NULL);
if ( hasInitializers ) {
uint64_t t2 = mach_absolute_time();
timingInfo.addTime(this->getShortName(), t2-t1);
}
}
catch (const char* msg) {
// this image is not initialized
fState = oldState;
recursiveSpinUnLock();
throw;
}
}
recursiveSpinUnLock();
}
复制代码
dyld::notifySingle
static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{
//dyld::log("notifySingle(state=%d, image=%s)\n", state, image->getPath());
std::vector<dyld_image_state_change_handler>* handlers = stateToHandlers(state, sSingleHandlers);
if ( handlers != NULL ) {
dyld_image_info info;
info.imageLoadAddress = image->machHeader();
info.imageFilePath = image->getRealPath();
info.imageFileModDate = image->lastModified();
for (std::vector<dyld_image_state_change_handler>::iterator it = handlers->begin(); it != handlers->end(); ++it) {
const char* result = (*it)(state, 1, &info);
if ( (result != NULL) && (state == dyld_image_state_mapped) ) {
//fprintf(stderr, " image rejected by handler=%p\n", *it);
// make copy of thrown string so that later catch clauses can free it
const char* str = strdup(result);
throw str;
}
}
}
if ( state == dyld_image_state_mapped ) {
// <rdar://problem/7008875> Save load addr + UUID for images from outside the shared cache
// <rdar://problem/50432671> Include UUIDs for shared cache dylibs in all image info when using private mapped shared caches
if (!image->inSharedCache()
|| (gLinkContext.sharedRegionMode == ImageLoader::kUsePrivateSharedRegion)) {
dyld_uuid_info info;
if ( image->getUUID(info.imageUUID) ) {
info.imageLoadAddress = image->machHeader();
addNonSharedCacheImageUUID(info);
}
}
}
if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && image->notifyObjC() ) {
uint64_t t0 = mach_absolute_time();
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
///重点操作
(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
uint64_t t1 = mach_absolute_time();
uint64_t t2 = mach_absolute_time();
uint64_t timeInObjC = t1-t0;
uint64_t emptyTime = (t2-t1)*100;
if ( (timeInObjC > emptyTime) && (timingInfo != NULL) ) {
timingInfo->addTime(image->getShortName(), timeInObjC);
}
}
// mach message csdlc about dynamically unloaded images
if ( image->addFuncNotified() && (state == dyld_image_state_terminated) ) {
notifyKernel(*image, false);
const struct mach_header* loadAddress[] = { image->machHeader() };
const char* loadPath[] = { image->getPath() };
notifyMonitoringDyld(true, 1, loadAddress, loadPath);
}
}
复制代码
其重点是(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
,我们全局搜索sNotifyObjCInit
并没有实现,但是有赋值操作:
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;
// call 'mapped' function with all images mapped so far
try {
notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
}
catch (const char* msg) {
// ignore request to abort during registration
}
// <rdar://problem/32209809> call 'init' function on all images already init'ed (below libSystem)
for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) {
ImageLoader* image = *it;
if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) {
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
}
}
}
复制代码
registerObjCNotifiers
是在_dyld_objc_notify_register
调用,而_dyld_objc_notify_register
函数是在libobjc
源码_objc_init
代用的,所以sNotifyObjCInit
的赋值
的就是objc
中的load_images
,而load_images
会调用所有的+load
方法。所以综上所述,notifySingle
是一个回调函数
,所以我们继续看load_images
方法
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();
#if __OBJC2__
cache_t::init();
#endif
_imp_implementationWithBlock_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
复制代码
load_images
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();
}
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
// 调用了load方法
call_load_methods();
}
复制代码
方法比较简单,主要调用了call_load_methods
方法
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
复制代码
call_load_methods
方法的核心就是循环调用load
方法。
总结
load的源码链为:_dyld_start
–> dyldbootstrap::start
–> dyld::_main
–> dyld::initializeMainExecutable
–> ImageLoader::runInitializers
–> ImageLoader::processInitializers
–> ImageLoader::recursiveInitialization
–> dyld::notifySingle
(是一个回调处理) –> sNotifyObjCInit
–> load_images(libobjc.A.dylib)