前言
在如今的信息时代,移动应用的普及解决了很多的问题,为生活提供了更高的便利,我们每天使用的App当你打开时,应用程序是如何加载并运行的?从点击打开到第一个可视化界面,这期间都发生了什么?这篇文章主要了解一下应用程序的加载过程,是如何加载到内存的。
应用程序加载原理
编译流程
源文件
经过预编译
,也就是通过词法
和语法
分析预编译
完成后提供给编译器
进行编译
编译
后形成一些汇编文件
汇编文件
通过链接
装载进来,然后得到可执行文件
静态库和动态库
库
就是可执行的二进制文件,被操作系统加载到内存。
- 库的形式
- 静态库
- 动态库
- 静态库和动态库的区别
- 静态库一般是
.a/.lib
等格式,动态库一般是.so/.dll/.framework/.dylib
等格式 - 静态库通过静态链接装载,动态库通过动态链接装载
- 静态库一般是
- 静态库和动态库的链接结构图
- 静态库是一个一个装载,可能存在重复的静态库文件
- 动态库可以通过共享库文件进行加载,节省空间
库是如何加载到内存的
加载流程图
动态链接器dyld的加载流程大致如下:
- 在App启动时会加载
libSystem
Runtime
注册相关回调函数- 加载新的
images(镜像文件)
,把库文件映射到内存中 - 执行
map_images()
、load_images()
- 调用
main
函数
LLDB调试
根据上面的流程,我们在实际工程里通过LLDB
调试来分析一下应用程序加载的流程,从App启动到main
函数期间都做了什么?
- 首先创建一个空的工程,然后在
main
函数入口打个断点,开始运行
我们发现在
main
函数执行之前有个start
函数,接下来看一下这个start
可以看到
libdyld.dylib start
,这个start
是来自libdyld.dylib
动态库,但如何加载进来并调用的,仅从上面的结果无法知晓,接下来再下个符号断点。
- 在项目里添加
start
符号断点,然后再次运行
结果发现没有断住,直接还是走到了
main
函数,从上面的流程可以看到load
方法在main
函数之前,接下来在ViewController
的+load
方法里再打个断点。
ViewController
的load
方法打个断点,然后再次运行
发现在
main
函数之前先到load
方法
- 输入
bt
打印堆栈信息
可以看到通过dyld
调用了_dyld_start
方法,接下来我们可以在苹果官网下载到最新的dyld
的开源代码(本篇的版本是dyld-852
),从源码中看看dyld
的加载流程。
源码分析(正向推测)
从上面的分析我们得到了_dyld_start
函数,接下来我们拿到dyld
源码进行全局搜索。
在dyldStartup.s
文件中看到不同架构对应的汇编代码,根据注释可以看到dyldbootstrap::start
这个C++
函数,根据这个函数继续搜索,在dyldInitialization.cpp
中找到了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);
}
可以看到return
返回了dyld::_main
函数,这里的main
函数指的是dyld
的main
函数,接下来再看一下main
函数的实现。
dyld::_main
从dyld::_main
函数里包含800
多行代码,代码过多就不贴了,主要涉及以下一些流程:
- 条件准备(环境、平台、版本、路径、主机信息...)
instantiateFromLoadedImage
实例化主程序loadInsertedDylib
加载插入的动态库mapSharedCache
共享缓存加载link
主程序link
插入的动态库weakBind
弱引用绑定主程序initializeMainExecutable
初始化notifyMonitoringDyldMain
通知dyld可以进main函数了
根据dyld::_main
的返回值来看,返回的result
是由sMainExecutable
函数执行相关操作得到,sMainExecutable
就是加载相关的镜像文件。
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide,
int argc, const char* argv[], const char* envp[], const char* apple[],
uintptr_t* startGlue)
{
......
// <rdar://problem/12186933> do weak binding only after all inserted images linked
sMainExecutable->weakBind(gLinkContext); // 弱引用绑定
gLinkContext.linkingMainExecutable = false;
sMainExecutable->recursiveMakeDataReadOnly(gLinkContext);
......
#if SUPPORT_OLD_CRT_INITIALIZATION
// Old way is to run initializers via a callback from crt1.o
if ( ! gRunInitializersOldWay )
initializeMainExecutable();
#else
// run all initializers
initializeMainExecutable(); // 运行所有的初始化代码
#endif
// notify any montoring proccesses that this process is about to enter main()
notifyMonitoringDyldMain(); // 通知dyld可以进main函数了
......
{
// find entry point for main executable
result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();
if ( result != 0 ) {
......
} else {
// main executable uses LC_UNIXTHREAD, dyld needs to let "start" in program set up for main()
result = (uintptr_t)sMainExecutable->getEntryFromLC_UNIXTHREAD();
*startGlue = 0;
}
}
}
if (sSkipMain) {
notifyMonitoringDyldMain();
......
result = (uintptr_t)&fake_main;
*startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;
}
return result;
}
initializeMainExecutable
我们在dyld::_main
函数中可以看到一个重要的函数调用,就是initializeMainExecutable()
,对所有的初始化操作,再继续看看这个函数的实现。
void initializeMainExecutable()
{
.......
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]);
......
}
initializeMainExecutable
调用了runInitializers
,再全局搜索看一下runInitializers
的定义。
runInitializers
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);
}
根据上面的函数找到processInitializers
这个的调用。
processInitializers
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);
}
主要是递归加载镜像文件,再看看recursiveInitialization
这个函数定义
recursiveInitialization
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
......
if ( fState < dyld_image_state_dependents_initialized-1 ) {
uint8_t oldState = fState;
// break cycles
fState = dyld_image_state_dependents_initialized-1;
try {
......
// 依赖文件的初始化
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);
......
}
}
recursiveSpinUnLock();
}
recursiveInitialization
函数的主要流程:
- 拿到镜像文件路径,初始化操作
- 依赖文件初始化
- 本身自己文件的初始化
接着再全局搜索看一下
notifySingle
的定义。
notifySingle
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();
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
// 镜像路径加载和machHeader加载
(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
......
}
......
}
再看哪里调用了sNotifyObjCInit
。
sNotifyObjCInit
得到有这样的定义
static _dyld_objc_notify_init sNotifyObjCInit;
根据_dyld_objc_notify_init
搜索发现是registerObjCNotifiers
的第2个参数。
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;
......
}
sNotifyObjCInit = init
,再根据这个函数查找哪里调用了registerObjCNotifiers
函数。
registerObjCNotifiers
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);
}
根据一步步的跟踪,从单个镜像文件的加载就会来到_dyld_objc_notify_register
这个函数,而_dyld_objc_notify_register
来自于 libobjc.dylib
的 _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();
#if __OBJC2__
cache_t::init();
#endif
_imp_implementationWithBlock_init();
// 这里调用了dyld的notify_register方法
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
LLDB调试
通过上面的推导和源码分析,我们在实际工程中运行一下,验证流程是否和分析的一样。在_objc_init
方法里打个断点,运行然后在lldb
输入bt
,打印当前的堆栈信息。
可以看到红框标记的流程就是我们上面正向推测分析的流程,但红框上面的调用暂时还不知道,接着需要再分析后面的流程,通过反向推导再继续去探索。根据上面的堆栈信息,可以看到_objc_init
调用之前是_os_object_init
,再点击看一下具体信息。
详细信息里可以看到_os_object_init
函数来自libdispatch.dylib
文件,我们再从苹果的开源库下载libdispatch
的源代码。
源码分析(反向推导)
堆栈流程信息
根据上面的堆栈输出我们定位到了_os_object_init
函数,接下来在libdispatch
源码中搜索_os_object_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);
......
}
在_os_object_init
里调用了_objc_init
,在libdispatch
里搜索一下_objc_init
可以看到_objc_init
来自于libobjc
,结合上面的堆栈输出流程,大概可以列出这样的流程:
libSystem_initializer
~> libdispatch_init
~> _os_object_init
~> _objc_init
libdispatch_init
在libdispatch
的源码中搜索libdispatch_init
void
libdispatch_init(void)
{
......
_dispatch_hw_config_init();
_dispatch_time_init();
_dispatch_vtable_init();
_os_object_init();
_voucher_init();
_dispatch_introspection_init();
}
可以看到调用了_os_object_init
,验证了上面的流程的正确性,而对于libSystem_initializer
还需要下载libSystem
源代码。
libSystem_initializer
// libsyscall_initializer() initializes all of libSystem.dylib
// <rdar://problem/4892197>
__attribute__((constructor))
static void
libSystem_initializer(int argc,
const char* argv[],
const char* envp[],
const char* apple[],
const struct ProgramVars* vars)
{
......
libdispatch_init();
_libSystem_ktrace_init_func(LIBDISPATCH);
......
}
从libSystem
源代码搜索libSystem_initializer
方法也调用了libdispatch_init
,而在libSystem_initializer
之前是dyld
的doModInitFunctions
函数,接下来我们又回到dyld
。所以现在再调整一下调用流程。
doModInitFunctions
~> libSystem_initializer
~> libdispatch_init
~> _os_object_init
~> _objc_init
doModInitFunctions
void ImageLoaderMachO::doModInitFunctions(const LinkContext& context)
{
......
Initializer* inits = (Initializer*)(sect->addr + fSlide);
......
Initializer func = inits[j];
......
// 获取libSystem的路径,执行Initializer
func(context.argc, context.argv, context.envp, context.apple, &context.programVars);
......
}
再看一下doModInitFunctions
在哪里调用的
doInitialization
bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{
CRSetCrashLogMessage2(this->getPath());
// mach-o has -init and static initializers
doImageInit(context);
doModInitFunctions(context);
CRSetCrashLogMessage2(NULL);
return (fHasDashInit || fHasInitializers);
}
在doInitialization
函数里调用了doModInitFunctions
,而doInitialization
就是我们上面正向推测的recursiveInitialization
函数里执行了,这样整个流程就形成了闭合。
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
......
if ( fState < dyld_image_state_dependents_initialized-1 ) {
......
try {
......
// let objc know we are about to initialize this image
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);
......
}
......
}
}
总结
从源码分析流程和结合工程调试我们得到了这样一个函数调用链:
_dyld_start
~> dyld::_main
~> initializeMainExecutable
~> runInitializers
~> processInitializers
~> recursiveInitialization
~> doInitialization
~> doModInitFunctions
~> libSystem_initializer
~> libdispatch_init
~> _os_object_init
~> _objc_init
在recursiveInitialization
调用的notifySingle
,而定位到的函数registerObjCNotifiers
只是赋值操作,那何时执行调用呢?
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
}
}
再搜索sNotifyObjCMapped
看在哪里调用的,经过搜索得知在notifyBatchPartial
函数里调用的,而notifyBatchPartial
这个方法就是上面的函数赋值后注册了回调函数去执行了。
sNotifyObjCInit
方法是在notifySingle
调用的,而notifySingle
就是上面的recursiveInitialization
里执行了。因为recursiveInitialization
是个递归流程,当第一次进来先做初始化流程,再load
相关镜像文件。
参考视频
有关dyld2
和dyld3
的介绍,可以参考苹果WWDC的视频介绍。