应用加载流程探索 – dyld

接下来,我们开始探索应用的加载流程,应用程序在启动的时候系统究竟做了什么事情, 当我们command+r运行项目时,就会生成一个mach-o这样的可执行文件。

Mach-O文件

mach-o是一种用于可执行文件、目标文件,静态库,动态库的文件格式。 属于Mach-O格式的常见文件:

  • 目标文件 .o
  • 库文件
    • .a
    • .dylib
    • Framework架构师
  • 可执行文件
  • dyld ( 动态链接器 )
  • .dsym ( 符号表 )

ios16们的工程在生成可执行文件之前都需要经过很多的过程:

  • 预编译:主要去处理源代码中#开头的预编译镜像命令,删除注释,替换宏定义,将#import倒入的文件放在知道位置等操作。
  • 编译:进行词法分析,语法分析,语义分析,生成相应的汇编代码文件
  • 汇编:加编译完的汇编代码文件翻译成机器指令,生成.o文件
  • 链接:静态链接 和 动态链接

静态库的本质是一堆.o文件的集合,当我们的代码经过静态链接后我们的程序中就不会再存在静态库里;架构工程师动态库的本质是已经链接完全的镜像im架构师工资age(表示任意一种类型的可执行文件),镜像架构师证书image可以看成三种类型可执行文件dylib源码编辑器bundle,我们在项工作流程怎么写目中断点,用lldb输入image list查看当前镜像信息

应用加载流程探索 - dyld
我们可以看到非常镜像图片怎么弄多的框架信息,这些都是属ios下载于image镜像文件。

dyld

我们app启动镜像的时候,真正的加载过程是从一个exec()的函数开始,这个函数为我们的程序分配内存,创建进程,然后将app对应的可执行文件加载进内存,再将dyld加载到内存,dyld进行动态链接。 dyld具体的工作流程:

  • 1.找到可执行文架构工程师件中所依赖的动态库并加载到内存,这是一个递归加载的过程,因为可以会依赖其他动态库
  • 2.rebase和binding,此处涉及到架构师工资虚拟内存,略过
  • 3.调起main()

所以,想要研究dyld,我们需要在main之前进行断点,我们重写一个+lo架构师和程序员的区别ad()方法,查看其堆栈信息,因为+load方法肯定是在main之前执行的

应用加载流程探索 - dyld

_dyld_start

我们先来看一下,入口函数_dy源码编辑器ld_start,在dyld的源码中搜索到dyldStarios14.4.1更新了什么tup.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

我看不懂工作流程图汇编代码,可是堆栈信息我ios15们知道,他会调用dyldbootstrap::start

dyldbootstrap::start

dyldboots镜像图片怎么弄trap::start就是指dyldbootstrap这个命名空架构间作用域里的start函数。来到源码中 ,搜索dyldbootst镜像rap,然后找到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函数做好一系列的准备工作,然后调用dyldmain函数 dyld::_main

dyld::_main

直接点击跳转到dyldmain函数中 . 该函数是加载ap架构图p的主要函数.

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行代码,省略 
}

ios下载最下面是有个返回值镜像result,我们通过操作resu架构师工资lt的代码,来阅读其内部的代码工作流程,先将这1000行代码复制到一个空白文档中,全局搜索result,有1ios是苹果还是安卓6个。

// 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() { re源码网站turn 0; },没有任何操作,只是返回0,所以源码时代我们重点来看一下s源码编辑器下载MainExecutable具体都做了什么工作

sMainExecutable

我们先来查看其初始化的地方

// 为工程的可执行文件初始化一个 imageLoader,镜像文件的加载器
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);

其下面的代码才是我们关系的加架构师证书载过程,我们还是将他们复制到一个空白文档中,方便源码之家我们阅读。 我们在下面找到了一个函数initializeMainEx源码时代ecutable,其注释的含义就是进行所有初始化操作,我们到源码中搜索然后点击跳转查看其实现

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);
}

我们看到最后的注释,其如果有向上的依赖源码之家就进行递归调用初始化他们。内部其实是通过recursiveIn工作流程模板itialization进行初始化库

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让ob架构师jc知道初始化im架构age这个可执行文件,因为工作流是什么意思我们所有初始化架构图可执行文件都需要ios是苹果还是安卓依赖ObjCcontext.notifySingle进行单个通知工作, ,doInitialization调用init方法,dyld_image_state_initiali工作流程zed让所有人知道已完成image初始化工作。

notiios模拟器fySingle
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_initializednotifySingle中会调用sNotifyObj工作流程组织CIniios是苹果还是安卓t这个函数,我们源码中工作流程图全局搜索,发现其赋值的位ios模拟器置在registerObjCNotifiers,再次全局搜ios下载re镜像画面什么梗gisterObjCNotifiers的调用位置,找到了_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_i镜像翻转怎么弄nit

现在我们来探究一下_objc_init,首先来看一下谁对他进行的调用,objc源码中进行断点调试

应用加载流程探索 - dyld
ios是什么意思们发现是libdispatch.dylib _os_object_init,说明是在GCD的源码中进行的调用,下面的调用栈最终指向的是libSystem.B.dylib libSystem_initiios系统alizer。不再进行更深入的查找了,我们主要还是来看架构图_dyld_源码交易平台objc_notify_register调用是传递了三个参数 , 这三个分别代表:

  • map_images : dyldimage 加载进内存时 , 会触发该函数
  • load_images : dyld 初始化 image 会触发该方法. ( 我们所熟知的 load 方法也是在此处架构师和程序员的区别调用 )
  • unmap_源码中的图片image : dyldimage 移除时 , 会触发该函数

和dyld源码对比,我们发现load_images对应的就是registerObjCNotifiers中的sNotifyObjCInitnotifySingle中的(*sNotifyObjCInit)(image->getRealPa架构师th(), image->machHeader());,相当于就是源码执行了_objc_init源码交易平台load_images这个函数。为什么+load会在main函数之前执行,其实就是因为load_images在此执ios16行会加载所有的+load方法。

doI源码1688nitialization

调用完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);  
}

doImageIn镜像翻转怎么弄itdoModInitFunctions两个函数中,都有一句报错信息,... that does not link w架构是什么意思ith libSystem.dylib,说明libSystem.dylib这个动态库必须在第一个初始化,否则其他无法初始化,因为libSystem架构图模板.源码交易平台dylib中的方法才能初始化_objc_init,其他的动态库需要依赖_objc_init才可以进行初始化。

上面我们知道load_imagios是苹果还是安卓es的调用位置,那ma源码之家p_images是在哪里调架构是什么意思用的呢,我们全局搜索后发现notifyBatchPartial方法中进行的调用, 继续查找其调用位置notifyBatchregisterImageStateBatchChangeHandlerregisterObjCNotifiers,我们再次查找notifyBatch,在之前调用递归processInitializers的方法下面,找到了其调用ImageLoader::runInitializers,也就是说dyld在初始化动态库的时候就会调用map_imagesload_images这两个函数。 map_imagesload_images这两个函数具体功能,再下篇镜像投屏文章中再来了解。

总结

dyld – 加载动态库和可执行文件的初始化操作:

  • dyldios系统 加载到开始链接主程序的时候, 递归调用 recursiveInitialization 函数
  • 这个函数第一次执行 , 进行 libs镜像是什么意思ystem 的初架构师始化, 会走到 doInitialization -> doModInitFunctions -> libSystemInitialized
  • libsystem 的初始化,它会调用起 libdispatch_initlibdispatch架构图模板 init 会调用 _os_object_init,这个函数里面调用了 _objc_init
  • _objc_工作流程init 中注册并保存了 map_imagesload_imagesunmap_image 函数工作流程怎么写地址
  • 注册完毕继续回到 recursiveInitialization 递归下一次调用,例如 libobjc, 当 libobjc 来到 recursiveInitialization 调用时,会触发 notifySingle 调用 里注册源码好的sNotifyObjCInit回调函数。也就是 libobjc中的load_images

发表评论

提供最优质的资源集合

立即查看 了解详情