接下来,我们开始探索应用的加载流程,应用程序在启动的时候系统究竟做了什么事情, 当我们command+r运行项目时,就会生成一个mach-o
这样的可执行文件。
Mach-O文件
mach-o
是一种用于可执行文件、目标文件,静态库,动态库的文件格式。
属于 Mach-O
格式的常见文件:
- 目标文件 .o
- 库文件
- .a
- .dylib
- Framework
- 可执行文件
- dyld ( 动态链接器 )
- .dsym ( 符号表 ) 我们的工程在生成可执行文件之前都需要经过很多的过程:
- 预编译:主要去处理源代码中
#
开头的预编译命令,删除注释,替换宏定义,将#import倒入的文件放在知道位置等操作。 - 编译:进行词法分析,语法分析,语义分析,生成相应的汇编代码文件
- 汇编:加编译完的汇编代码文件翻译成机器指令,生成.o文件
- 链接:静态链接 和 动态链接
静态库的本质是一堆.o文件的集合,当我们的代码经过静态链接后我们的程序中就不会再存在静态库里;动态库的本质是已经链接完全的镜像image(表示任意一种类型的可执行文件),镜像image可以看成三种类型
可执行文件
、dylib
、bundle
,我们在项目中断点,用lldb输入image list
查看当前镜像信息 我们可以看到非常多的框架信息,这些都是属于image镜像文件。
dyld
我们app启动的时候,真正的加载过程是从一个exec()
的函数开始,这个函数为我们的程序分配内存,创建进程,然后将app对应的可执行文件加载进内存,再将dyld加载到内存,dyld进行动态链接。
dyld具体的工作流程:
- 1.找到可执行文件中所依赖的动态库并加载到内存,这是一个递归加载的过程,因为可以会依赖其他动态库
- 2.rebase和binding,此处涉及到虚拟内存,略过
- 3.调起
main()
所以,想要研究dyld,我们需要在main之前进行断点,我们重写一个+load()方法,查看其堆栈信息,因为+load方法肯定是在main之前执行的
_dyld_start
我们先来看一下,入口函数_dyld_start
,在dyld的源码中搜索到dyldStartup.s
中我们找到其汇编实现,我们找一下arm64架构的源码
#if __arm64__ && !TARGET_OS_SIMULATOR
.text
.align 2
.globl __dyld_start
__dyld_start:
mov x28, sp
and sp, x28, #~15 // force 16-byte alignment of stack
mov x0, #0
mov x1, #0
stp x1, x0, [sp, #-16]! // make aligned terminating frame
mov fp, sp // set up fp to point to terminating frame
sub sp, sp, #16 // make room for local variables
...省略...
// call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
bl __ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pm
...省略...
// LC_MAIN case, set up stack for call to main()
Lnew: mov lr, x1 // simulate return address into _start in libdyld.dylib
#if __LP64__
ldr x0, [x28, #8] // main param1 = argc
add x1, x28, #16 // main param2 = argv
add x2, x1, x0, lsl #3
add x2, x2, #8 // main param3 = &env[0]
mov x3, x2
Lapple: ldr x4, [x3]
add x3, x3, #8
我看不懂汇编代码,可是堆栈信息我们知道,他会调用dyldbootstrap::start
dyldbootstrap::start
dyldbootstrap::start
就是指dyldbootstrap
这个命名空间作用域里的start
函数。来到源码中 ,搜索dyldbootstrap
,然后找到start
函数 .
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);
}
其作用就是初始化,为main函数做好一系列的准备工作,然后调用 dyld
的 main
函数 dyld::_main
。
dyld::_main
直接点击跳转到 dyld
- main
函数中 . 该函数是加载 app
的主要函数.
uintptr_t _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, int argc, const char* argv[], const char* envp[], const char* apple[], uintptr_t* startGlue) {
···将近1000行代码,省略···
}
其最下面是有个返回值result
,我们通过操作result
的代码,来阅读其内部的代码,先将这1000行代码复制到一个空白文档中,全局搜索result
,有16个。
// if this is host dyld, check to see if iOS simulator is being run
const char* rootPath = _simple_getenv(envp, "DYLD_ROOT_PATH");
if ( (rootPath != NULL) ) {
// look to see if simulator has its own dyld
char simDyldPath[PATH_MAX];
strlcpy(simDyldPath, rootPath, PATH_MAX);
strlcat(simDyldPath, "/usr/lib/dyld_sim", PATH_MAX);
int fd = dyld3::open(simDyldPath, O_RDONLY, 0);
if ( fd != -1 ) {
const char* errMessage = useSimulatorDyld(fd, mainExecutableMH, simDyldPath, argc, argv, envp, apple, startGlue, &result);
if ( errMessage != NULL )
halt(errMessage);
return result;
}
}
// try using launch closure
if ( mainClosure != nullptr ) {
CRSetCrashLogMessage("dyld3: launch started");
if ( mainClosure->topImage()->fixupsNotEncoded() )
sLaunchModeUsed |= DYLD_LAUNCH_MODE_MINIMAL_CLOSURE;
Diagnostics diag;
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(canUseClosureFromDisk, mainExecutableCDHash, mainFileInfo, envp, bootToken);
if ( mainClosure != nullptr ) {
diag.clearError();
sLaunchModeUsed |= DYLD_LAUNCH_MODE_BUILT_CLOSURE_AT_LAUNCH;
if ( mainClosure->topImage()->fixupsNotEncoded() )
sLaunchModeUsed |= DYLD_LAUNCH_MODE_MINIMAL_CLOSURE;
else
sLaunchModeUsed &= ~DYLD_LAUNCH_MODE_MINIMAL_CLOSURE;
launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH,
mainExecutableSlide, argc, argv, envp, apple, diag, &result, startGlue, &closureOutOfDate, &recoverable);
}
}
if ( launched ) {
gLinkContext.startedInitializingMainExecutable = true;
if (sSkipMain)
result = (uintptr_t)&fake_main;
return result;
}
{
// find entry point for main executable
result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();
if ( result != 0 ) {
// main executable uses LC_MAIN, we need to use helper in libdyld to call into main()
if ( (gLibSystemHelpers != NULL) && (gLibSystemHelpers->version >= 9) )
*startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;
else
halt("libdyld.dylib support not present for LC_MAIN");
}
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();
if (dyld3::kdebug_trace_dyld_enabled(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE)) {
dyld3::kdebug_trace_dyld_duration_end(launchTraceID, DBG_DYLD_TIMING_LAUNCH_EXECUTABLE, 0, 0, 2);
}
ARIADNEDBG_CODE(220, 1);
result = (uintptr_t)&fake_main;
*startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;
}
我们找到了四处操作了result的地方,仔细查看,我们能够看出操作result的地方只有result = (uintptr_t)sMainExecutable->
和result = (uintptr_t)&fake_main
,
我们先来看fake_main做了什么事情, int fake_main() { return 0; }
,没有任何操作,只是返回0
,所以我们重点来看一下sMainExecutable
具体都做了什么工作
sMainExecutable
我们先来查看其初始化的地方
// 为工程的可执行文件初始化一个 imageLoader,镜像文件的加载器
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
其下面的代码才是我们关系的加载过程,我们还是将他们复制到一个空白文档中,方便我们阅读。
我们在下面找到了一个函数initializeMainExecutable
,其注释的含义就是进行所有初始化操作,我们到源码中搜索然后点击跳转查看其实现
void initializeMainExecutable() {
// record that we've reached this step
gLinkContext.startedInitializingMainExecutable = true;
// 为所有动态库进行初始化
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]);
}
}
// 为我们的主可执行文件进行初始化
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]);
}
初始化主要是使用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
,查看其内部实现
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
进行初始化库
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();
}
经过前面的流程,我们终于找到真正初始化的地方了,其内部使用mach_absolute_time
让objc知道初始化image这个可执行文件,因为我们所有初始化可执行文件都需要依赖ObjC
;context.notifySingle
进行单个通知工作, ,doInitialization
调用init方法,dyld_image_state_initialized
让所有人知道已完成image初始化工作。
notifySingle
static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo){
…省略部分…
if ( state == dyld_image_state_mapped ) {
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);
}
}
…省略部分…
}
我们看到在初始化image时,将state
传递的是dyld_image_state_dependents_initialized
,notifySingle
中会调用sNotifyObjCInit
这个函数,我们源码中全局搜索,发现其赋值的位置在registerObjCNotifiers
,再次全局搜索registerObjCNotifiers
的调用位置,找到了_dyld_objc_notify_register
,继续搜索_dyld_objc_notify_register
,找到不调用的地方,说明该函数不是在dyld
中进行的调用的,我们打开objc的源码进行搜索,发现其是在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();
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
}
_objc_init
现在我们来探究一下_objc_init
,首先来看一下谁对他进行的调用,objc源码中进行断点调试
我们发现是libdispatch.dylib _os_object_init
,说明是在GCD的源码中进行的调用,下面的调用栈最终指向的是libSystem.B.dylib libSystem_initializer
。不再进行更深入的查找了,我们主要还是来看_dyld_objc_notify_register
调用是传递了三个参数 , 这三个分别代表:
map_images
:dyld
将image
加载进内存时 , 会触发该函数load_images
:dyld
初始化image
会触发该方法. ( 我们所熟知的load
方法也是在此处调用 )unmap_image
:dyld
将image
移除时 , 会触发该函数 和dyld源码对比,我们发现load_images
对应的就是registerObjCNotifiers
中的sNotifyObjCInit
,notifySingle
中的(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
,相当于就是执行了_objc_init
中的load_images
这个函数。为什么+load
会在main
函数之前执行,其实就是因为load_images
在此执行会加载所有的+load
方法。
doInitialization
调用完load_images
后,下面将继续执行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);
}
doImageInit
、doModInitFunctions
两个函数中,都有一句报错信息,... that does not link with libSystem.dylib
,说明libSystem.dylib
这个动态库必须在第一个初始化,否则其他无法初始化,因为libSystem.dylib
中的方法才能初始化_objc_init
,其他的动态库需要依赖_objc_init
才可以进行初始化。
上面我们知道load_images
的调用位置,那map_images
是在哪里调用的呢,我们全局搜索后发现notifyBatchPartial
方法中进行的调用, 继续查找其调用位置notifyBatch
、registerImageStateBatchChangeHandler
、registerObjCNotifiers
,我们再次查找notifyBatch
,在之前调用递归processInitializers
的方法下面,找到了其调用ImageLoader::runInitializers
,也就是说dyld
在初始化动态库的时候就会调用map_images
和load_images
这两个函数。 map_images
和load_images
这两个函数具体功能,再下篇文章中再来了解。
总结
dyld - 加载动态库和可执行文件的初始化操作:
- 当
dyld
加载到开始链接主程序的时候, 递归调用recursiveInitialization
函数 - 这个函数第一次执行 , 进行
libsystem
的初始化, 会走到doInitialization
->doModInitFunctions
->libSystemInitialized
libsystem
的初始化,它会调用起libdispatch_init
,libdispatch
的init
会调用_os_object_init
,这个函数里面调用了_objc_init
_objc_init
中注册并保存了map_images
、load_images
、unmap_image
函数地址- 注册完毕继续回到
recursiveInitialization
递归下一次调用,例如libobjc
, 当libobjc
来到recursiveInitialization
调用时,会触发notifySingle
调用sNotifyObjCInit
回调函数。也就是libobjc
中的load_images