九 OC底层原理 APP 的加载流程
前言
当我们的应用程序被打开的时候,
kernel(内核)
就会将应用程序加载到内存中,同时kernel
又回加载另一个程序,就是我们的dyld(动态链接器)
, 不管是应用程序,还是dyld 都是一个可执行文件,在MacOS系统中称做Mach-o
一. Mach-o 介绍
-
当我们编译一个 ios 程序的时候,可以在 程序的根目录下的文件夹
products
中看到一个.app
的文件,右击该文件 查看包内容,就一个看到程序的同名的Mach-o 的一个可执行文件 -
现在我们利用
MachOView
软件,查看Mach-o
的机构
image.png
1.1 mach-o 文件结构
- Header:
Mach Header
里会有Mach-O
的CPU
信息,以及Load Command
的信息。 - Load commands : 包含
Mach-O
里命令类型信息,名称和二进制文件的位置。 - Data:
Data
由Segment
的数据组成,是Mach-O
占比最多的部分,有代码有数据,比如符号表。Data 共三个 Segment,TEXT、DATA、LINKEDIT
。其中 TEXT 和 DATA 对应一个或多个 Section,LINKEDIT 没有 Section
![](https://img.haomeiwen.com/i2145709/d76556375e0643ec.png)
1.2 Segment
一般Mach-O文件有多个段(Segement
),每个段有不同的功能,一般包括:
-
__TEXT
: 代码段,包含头文件、代码和只读常量。只读不可修改 -
__DATA
: 包含全局变量,静态变量等,该段可写; -
__LINKEDIT
: 包含了方法和变量的元数据(位置,偏移量),以及代码签名等信息。只读不可修改。
二 APP 加载
-
我们在 程序的 main 函数执行之前 下个断点, 我们可以看到在执行
main()
函数之前,调用的是start
,同时,这一流程是由libdyld.dylib
库执行的。dyld
是开源库,可以下载源码探索。
dyld 开源下载地址
image.png
-
为了更详细的看见执行过程 我们在
ViewController
的load
函数下个断点
image.png
2.1 __dyld_start
我们在开源代码中的 dyldStartup.s
找到了 __dyld_start的函数入口,在函数实现中找到了这样一段注释,这样我们可以得知 __dyld_start是一个汇编函数,其内调用了 dyldbootstrap::start
![](https://img.haomeiwen.com/i2145709/6be23897c0cb08e1.png)
2.2 dyldbootstrap::start
![](https://img.haomeiwen.com/i2145709/e06d4339943976ef.png)
这个函数注释表示,这段代码是用来启动 dyld
的,一般情况下程序的启动工作都是 dyld和crt
完成的,在dyld
中需要我们手动完成
这段代码主要执行的三个事情
2.3 rebaseDyld
![](https://img.haomeiwen.com/i2145709/1f1eda9508e73bae.png)
方法注释表示:在磁盘中,在
dyld 的 Data segment
中所有的指针都是链在一起的,需要修复执行正确的指针,所有的镜像的基地址都是0,所以偏移量slide
就是加载地址
2.4 __guard_setup
// set up random value for stack canary
就是 设置栈 溢出保护
2.5 dyld::_main
执行了dyld
的 main
函数
三 dyld::_main
3.1 dyld::_main 入口解析
![](https://img.haomeiwen.com/i2145709/6cf7794424b944ea.png)
注释:表示这是
dyld
的入口,内核加载 dyld
并且跳转到了 __dyld_start
函数中,设置一些注册,并且回掉了这个方法
//
// Entry point for dyld. The kernel loads dyld and jumps to __dyld_start which
// sets up some registers and call this function.
//
// Returns address of main() in target program which __dyld_start jumps to
//
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide,
int argc, const char* argv[], const char* envp[], const char* apple[],
uintptr_t* startGlue)
{
······
// 准备dyld的启动工作,配置相关的环境变量
······
CRSetCrashLogMessage("dyld: launch started");
// 设置上下文
setContext(mainExecutableMH, argc, argv, envp, apple);
······
// 检测线程是否受限
configureProcessRestrictions(mainExecutableMH, envp);
{
// 检测环境变量
checkEnvironmentVariables(envp);
defaultUninitializedFallbackPaths(envp);
}
······
// 打印opts
if ( sEnv.DYLD_PRINT_OPTS )
printOptions(argv);
// 打印环境变量
if ( sEnv.DYLD_PRINT_ENV )
printEnvironmentVariables(envp);
······
// 获取程序架构
getHostInfo(mainExecutableMH, mainExecutableSlide);
// load shared cache 检测共享缓存禁用状态
checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
#if TARGET_OS_SIMULATOR
if ( sSharedCacheOverrideDir)
mapSharedCache();
#else
// 加载共享缓存库
mapSharedCache();
#endif
}
······
// add dyld itself to UUID list 将dyld 添加到UUID列表中
addDyldImageToUUIDList();
······
// instantiate ImageLoader for main executable 实例化主程序
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
······
// load any inserted libraries 加载插入的动态库
if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)
loadInsertedDylib(*lib);
}
······
// 链接主程序
link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
······
// link any inserted libraries 链接所有插入的动态库
// do this after linking main executable so that any dylibs pulled in by inserted
// dylibs (e.g. libSystem) will not be in front of dylibs the program uses
if ( sInsertedDylibCount > 0 ) {
for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
ImageLoader* image = sAllImages[i+1];
link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
image->setNeverUnloadRecursive();
}
if ( gLinkContext.allowInterposing ) {
// only INSERTED libraries can interpose
// register interposing info after all inserted libraries are bound so chaining works
for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
ImageLoader* image = sAllImages[i+1];
image->registerInterposing(gLinkContext);
}
}
}
······
// <rdar://problem/12186933> do weak binding only after all inserted images linked
// //弱符号绑定
sMainExecutable->weakBind(gLinkContext);
sMainExecutable->recursiveMakeDataReadOnly(gLinkContext);
CRSetCrashLogMessage("dyld: launch, running initializers");
// run all initializers 执行所有初始化方法
initializeMainExecutable();
// notify any montoring proccesses that this process is about to enter main()
// notifyMonitoringDyldMain监听dyld的main
notifyMonitoringDyldMain();
// find entry point for main executable
// 找到主程序的入口
result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();
return result;
}
3.2 主要过程-简述
- 设置运行环境,为可执行文件的加载做准备工作
- 映射共享缓存到当前进程的逻辑内存空间
- 实例话主程序
- 加入插入的动态库
- 链接主程序
- 链接插入的动态库
- 执行弱符号绑定
- 执行初始化方法
- 查找程序入口并返回main()
3.3.1 checkSharedRegionDisable 检测共享缓存
函数底部的注释,表示iOS不能在共享区域以外运行
static void checkSharedRegionDisable(const dyld3::MachOLoaded* mainExecutableMH, uintptr_t mainExecutableSlide)
{
#if __MAC_OS_X_VERSION_MIN_REQUIRED
// if main executable has segments that overlap the shared region,
// then disable using the shared region
if ( mainExecutableMH->intersectsRange(SHARED_REGION_BASE, SHARED_REGION_SIZE) ) {
gLinkContext.sharedRegionMode = ImageLoader::kDontUseSharedRegion;
if ( gLinkContext.verboseMapping )
dyld::warn("disabling shared region because main executable overlaps\n");
}
#if __i386__
if ( !gLinkContext.allowEnvVarsPath ) {
// <rdar://problem/15280847> use private or no shared region for suid processes
gLinkContext.sharedRegionMode = ImageLoader::kUsePrivateSharedRegion;
}
#endif
#endif
// iOS cannot run without shared region
}
3.3.2 mapSharedCache()
函数的核心就是 loadDyldCache()
共享区域的共享的实现
bool loadDyldCache(const SharedCacheOptions& options, SharedCacheLoadInfo* results)
{
results->loadAddress = 0;
results->slide = 0;
results->errorMessage = nullptr;
#if TARGET_OS_SIMULATOR
// simulator only supports mmap()ing cache privately into process
return mapCachePrivate(options, results);
#else
if ( options.forcePrivate ) {
// 如果为强制私有类型 缓存就只映射到当前进程
// mmap cache into this process only
return mapCachePrivate(options, results);
}
else {
// 缓存已经映射到共享区域的时候
// fast path: when cache is already mapped into shared region
bool hasError = false;
// 复制已有的缓存
if ( reuseExistingCache(options, results) ) {
hasError = (results->errorMessage != nullptr);
} else {
// 当前进程就是第一个加载缓存的进程
// slow path: this is first process to load cache
hasError = mapCacheSystemWide(options, results);
}
return hasError;
}
#endif
}
3.3.3 instantiateFromloadedImage
isCompatibleMachO
是检查Mach-O的subtype是否是当前cpu可以支持; 内核会映射到主可执行文件中,我们需要为映射到主可执行文件的文件,创建ImageLoader
。
instantiateMainExecutable
就是实例化可执行文件, 这个期间会解析LoadCommand
, 这个之后会发送 dyld_image_state_mapped
通知; 在此方法中,读取image
,然后addImage()
到镜像列表。
addImage()
就是将image
添加到 imageList
。因此我们可以lldb
调试image list
命令,就可以看到第一个就是macho
static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path)
{
// try mach-o loader
if ( isCompatibleMachO((const uint8_t*)mh, path) ) {
ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);
addImage(image);
return (ImageLoaderMachO*)image;
}
throw "main executable not a known format";
}
3.3.4 loadInsertedDylib
加载插入的动态库
3.3.5 link(sMainExecutable,...) 链接主程序 和 link(image,...) 链接所有插入的动态库
对上面生成的 Image
进行链接。这个过程就是将加载进来的二进制变为可用状态的过程。其主要做的事有对image
进行 load(加载)
,rebase(基地址复位)
,bind(外部符号绑定)
,我们可以查看源码:
void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool preflightOnly, bool neverUnload, const RPathChain& loaderRPaths, const char* imagePath)
{
......
this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths, imagePath);
......
this->recursiveRebaseWithAccounting(context);
......
this->recursiveBindWithAccounting(context, forceLazysBound, neverUnload);
}
recursiveLoadLibraries
: 递归加载所有依赖库进内存。
recursiveRebase
: 递归对自己以及依赖库进行rebase
操作
recursiveBindWithAccounting
: 对库中所有nolazy
的符号进行bind
,一般的情况下多数符号都是lazybind
的,他们在第一次使用的时候才进行bind
。
3.3.6 initializeMainExecutable()
void initializeMainExecutable()
{
// record that we've reached this step
gLinkContext.startedInitializingMainExecutable = true;
// 调用每个image 的 initalizer 方法进行初始化,
// 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]);
}
3.3.7 ImageLoader::runInitializers 流程
-
ImageLoader::runInitializers
image.png
-
processInitializers
image.png
在这里,对镜像表中的所有镜像执行recursiveInitialization ,创建一个未初始化的向上依赖新表。如果依赖中未初始化完毕,则继续执行processInitializers,直到全部初始化完毕。
-
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
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();
}
在这个函数中 我们主要关注
- context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
- doInitialization(context)
- context.notifySingle(dyld_image_state_initialized, this, NULL);
3.3.7.1 context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
通知objc我们要初始化这个镜像,这里 通过 notifySingle 函数对sNotifyObjCInit 进行函数调用。
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);
(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
...
}
}
...
}
获取镜像文件的真实地址 (*sNotifyObjCInit)(image->getRealPath(), image->machHeader()
, 但是根据最开始的堆栈信息 可以看到 下一步就应该是调用 load_images
![](https://img.haomeiwen.com/i2145709/3c08008050834234.png)
主要是因为 (*sNotifyObjCInit)(image->getRealPath(), image->machHeader()
这是一个回调函数
在if判断条件中对sNotifyObjCInit
进行了非空判断,也就是有值,在本文件搜索,发现它在 registerObjCNotifiers
中被赋值
3.3.7.2 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;
···
接着 全局搜索 registerObjCNotifiers
, 发现 只有 _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);
}
参数:
- _dyld_objc_notify_mapped : dyld 将 image 加载进内存时 , 会触发该函数.
- _dyld_objc_notify_init : dyld 初始化 image 会触发该方法. ( 我们所熟知的 load 方法也是在此处调用 ) .
- unmap__dyld_objc_notify_unmapped: dyld 将 image 移除时 , 会触发该函数 .
我们在 下一个符号断点 _dyld_objc_notify_register
![](https://img.haomeiwen.com/i2145709/83497ec7eb330a89.png)
可以看到
_dyld_objc_notify_register
之前调用了 _objc_init
3.3.7.3 _objc_init
_dyld_objc_notify_register
函数是供 objc runtime
使用的,当objc
镜像被映射,取消映射,和初始化时 被调用的注册处理器。我们可以在libobjc.A.dylib
库里,_objc_init
函数中找到其调用。
/***********************************************************************
* _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(); //线程 key 的绑定
static_init(); //初始化系统内置的 C++ 静态构造函数
runtime_init(); //主要是运行时的初始化,主要分为两部分:分类初始化和类的表初始化
exception_init(); // 初始化libobjc异常处理
cache_init(); //缓存初始化
_imp_implementationWithBlock_init(); //启动机制回调
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
3.3.8 doInitialization
我们从最新的堆栈信息 可以看到 recursiveInitialization()
函数,之后就调用了doInitialization()
![](https://img.haomeiwen.com/i2145709/f1e758aa8b9cb3b7.png)
在 doModInitFunctions之后 会 先执行 libSystem_initializer,保证系统库优先初始化完毕,在这里初始化 libdispatch_init,进而在_os_object_init 中 调用 _objc_init。
由于 runtime 向 dyld 绑定了回调,当 image 加载到内存后,dyld 会通知 runtime 进行处理
runtime 接手后调用 map_images 做解析和处理,接下来 load_images 中调用 call_load_methods 方法,遍历所有加载进来的 Class,按继承层级依次调用 Class 的 +load 方法和其 Category 的 +load 方法。
至此,可执行文件和动态库中所有的符号(Class,Protocol,Selector,IMP,…)都已经按格式成功加载到内存中,被 runtime 所管理,在这之后,runtime 的那些方法(动态添加 Class、swizzle 等等才能生效)
四 总结- 流程
![](https://img.haomeiwen.com/i2145709/cb8258acbf2738ed.png)