系统底层源码分析(20)——dyld加载流程

  • 概念

Mach-O是一种文件格式,是mac上可执行文件的格式。编写的CC++swiftOC,最终编译链接生成Mach-O可执行文件。

链接的共用库分为静态库和动态库:静态库是编译时链接的库,需要链接进你的 Mach-O 文件里,如果需要更新就要重新编译一次,无法动态加载和更新;而动态库是运行时链接的库,使用 dyld 就可以实现动态加载。

dyld(the dynamic link editor)是苹果的动态链接器,是苹果操作系统的一个重要组成部分,在应用被编译打包成可执行文件格式的 Mach-O 文件之后,交由 dyld 负责链接,加载程序 。

编译过程
  • 加载流程

为了辅助探索底层,我们可以用符号断点看一下堆栈信息:

  1. 首先调用的是_dyld_start,我们马上进入dyld源码开始探究:
  1. 然后从汇编跳转调用start
uintptr_t start(const struct macho_header* appsMachHeader, int argc, const char* argv[], 
                intptr_t slide, const struct macho_header* dyldsMachHeader,
                uintptr_t* startGlue)
{
    slide = slideOfMainExecutable(dyldsMachHeader);//通过一个随机值来实现地址空间配置随机加载, 防止被攻击
    bool shouldRebase = slide != 0;
    ...
    // allow dyld to use mach messaging
    mach_init();//开放函数消息使用
    ...
    // set up random value for stack canary
    __guard_setup(apple);//设置堆栈保护
    ...
    // now that we are done bootstrapping dyld, call dyld's main
    uintptr_t appsSlide = slideOfMainExecutable(appsMachHeader);
    return dyld::_main(appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);//开始链接共享对象
}
  1. 接着进入_main,代码很多所以只看流程代码:
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
        int argc, const char* argv[], const char* envp[], const char* apple[], 
        uintptr_t* startGlue)
{
    ...
    {
        checkEnvironmentVariables(envp);//检查环境变量
        defaultUninitializedFallbackPaths(envp);
    }
    ...
    // load shared cache
    checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);//验证共享缓存路径
    ...
    if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
        mapSharedCache();//加载共享缓存
    }
    ...
    try {
        // add dyld itself to UUID list
        addDyldImageToUUIDList();//添加dyld到UUID列表
        ...
reloadAllImages:
        ...
        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);//加载插入的动态库
        }
        ...
        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();
            }
            ...
        }
        ...
        ImageLoader::applyInterposingToDyldCache(gLinkContext);//插入任何动态加载的镜像文件
        ...
        // run all initializers
        initializeMainExecutable(); //运行所有初始化程序
        ...
        notifyMonitoringDyldMain();//通知监听dyld的main

        // find entry point for main executable
        result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();//找到真正 main 函数入口并返回
        ...
    }
    ...
}
  • 流程:配置环境变量 -> 加载共享缓存 -> 实例化主程序 -> 加载动态库 -> 链接动态库 -> 运行所有初始化程序 -> 通知监听dyldmain -> 找到真正 main 函数入口
  1. 然后就开始初始化:
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);//通过instantiateMainExecutable中的sniffLoadCommands加载主程序其实就是对MachO文件中LoadCommons段的一系列加载
        addImage(image);//生成镜像文件后,添加到sAllImages全局镜像中,主程序永远是sAllImages的第一个对象
        return (ImageLoaderMachO*)image;
    }
    
    throw "main executable not a known format";
}
  1. 接着下一步就是加载插入的动态库:
static void loadInsertedDylib(const char* path)
{
    ImageLoader* image = NULL;
    unsigned cacheIndex;
    try {
        image = load(path, context, cacheIndex);
    }
    ...
}
  1. 最后就是链接动态库,插入任何动态加载的镜像文件。

  2. 然后就开始运行:

void initializeMainExecutable()
{
    ...
    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]);//为主要可执行文件及其带来的一切运行初始化程序
    ...
}
void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo)
{
    ...
    processInitializers(context, thisThread, timingInfo, up);//初始化准备
    ...
}
void ImageLoader::processInitializers(const LinkContext& context, mach_port_t thisThread,
                                     InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& images)
{
    ...
    // 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) {//遍历image.count
        images.images[i]->recursiveInitialization(context, thisThread, images.images[i]->getPath(), timingInfo, ups);//递归初始化镜像
    }
    ...
}
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
                                          InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
    ...
        try {
            ...
            context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);//单个镜像通知;获取到镜像的回调
            
            // initialize this image
            bool hasInitializers = this->doInitialization(context);
            ...
        }
    ...
}
bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{
    CRSetCrashLogMessage2(this->getPath());

    // mach-o has -init and static initializers
    doImageInit(context);
    doModInitFunctions(context);//比如会调用c++全局构造函数-初始化
    
    CRSetCrashLogMessage2(NULL);
    
    return (fHasDashInit || fHasInitializers);
}
void ImageLoaderMachO::doModInitFunctions(const LinkContext& context)
{
    ...
    if ( type == S_MOD_INIT_FUNC_POINTERS ) {
        Initializer* inits = (Initializer*)(sect->addr + fSlide);
        ...
        for (size_t j=0; j < count; ++j) {
            Initializer func = inits[j];
            ...
            {
                dyld3::ScopedTimer(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)fMachOData, (uint64_t)func, 0);
                func(context.argc, context.argv, context.envp, context.apple, &context.programVars);//就是调用libSystem_initializer
            }
            ...
        }
        ...
}
  1. 到这里就要跳转到Libsystem源码
__attribute__((constructor))
static void
libSystem_initializer(int argc,
              const char* argv[],
              const char* envp[],
              const char* apple[],
              const struct ProgramVars* vars)
{
    ...
    libdispatch_init();
    ...
}
  1. 接着又跳转到libdispatch源码
void
libdispatch_init(void)
{
    ...
    _dispatch_hw_config_init();
    _dispatch_time_init();
    _dispatch_vtable_init();
    _os_object_init();
    _voucher_init();
    _dispatch_introspection_init();
}
void
_os_object_init(void)
{
    _objc_init();
    ...
}
  1. 最后的最后终于调用了_objc_init,接着又要跳转到objc4源码
void _objc_init(void)
{
    ...
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

map_images : dyldimage 加载进内存时 , 会触发该函数.
load_images : dyld 初始化 image 会触发该方法. ( 我们所熟知的 load 方法也是在此处调用 ) .
unmap_image : dyldimage 移除时 , 会触发该函数 .

  1. 很不幸,又要跳回到dyld源码中:
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);
}

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;//load_images函数
    ...
    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());//获取镜像文件真实地址; 比如会调用[ViewController load]
        }
    }
}

这里可以看到调用的sNotifyObjCInit就是load_images函数。

(dyld -> libSystem -> libDispatch -> libObjc -> _objc_init)

  • 总结

推荐阅读更多精彩内容