前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >iOS底层分析之应用程序加载流程

iOS底层分析之应用程序加载流程

作者头像
CC老师
发布2022-01-14 15:05:32
7090
发布2022-01-14 15:05:32
举报
文章被收录于专栏:HelloCode开发者学习平台

准备资料

  • dyld源码
  • Libsystem源码

整个编译过程大致分为:

  • 预编译(由Xcode完成)
  • 编译(由Xcode完成)
  • 汇编
  • 可执行文件

预编译

即编译之前要做的事情,通常来说,预编译分为:

  • 宏定义
  • 文件包含
  • 条件编译

宏定义

又叫宏替换,只做替换不做计算。宏定义的写法如下:

代码语言:javascript
复制
#define 标识符 字符串

文件包含

顾名思义就是用来讲一个文件包含到另一个文件中的宏。在OC层面,我们通常使用

代码语言:javascript
复制
#include:包含一个源文件代码

#import:包含一个源文件代码

@class:声明一个类,通常在.h文件里会用到,如何.m里去import它。

(滑动显示更多)

条件编译

代码语言:javascript
复制
条件编译就是在编译之前预处理器根据预处理指令判断对应的条件,
如果条件满足就将对应的代码编译进去,否则代码就根本不进入编译环节。

条件编译举例:
1.#if 
2.#ifdef 判断某个宏是否被定义, 若已定义, 执行后面的语句
3.#ifndef 与#ifdef相反, 判断某个宏是否未被定义
4.#elif 若#if, #ifdef, #ifndef或前面的#elif条件不满足, 则执行#elif之后的语句, 相当于C语法中的else-if
5.#else 与#if, #ifdef, #ifndef对应, 若这些条件不满足, 则执行#else之后的语句, 相当于C语法中的else
6.#endif #if, #ifdef, #ifndef这些条件命令的结束标志.
7.#if 与 #ifdef 的区别:#if是判断后面的条件语句是否成立,#ifdef是判断某个宏是否被定义过。要区分开

(滑动显示更多)

为了加快编译,避免多个文件使用同一个文件而导致多次引用相同文件的情况,苹果提供了预编译头的概念,也就是我们通常所使用.pch文件,在.pch里面定义、引用的文件、变量是全局的且只会编译一次,所以我们可以把常用的东西定义在里面。

可执行文件

动态库和静态库

  • 静态库格式:.a等
  • 动态库格式:.framework、.dylib、.tbd等

加载方式:

静态库是一个一个状态进内存,容易出现重复而浪费的情况;动态库是你有需要才会去加载,这样大大节省了空间。

加载过程:

  • app启动
  • 加载相应的库
  • 注册库的回调函数_dyld_objc_notify_register
  • 加载库的内存映射
  • 执行map_images、Load_images
  • 调用main函数

接下来我们通过源码分析看一下,main函数之前的流程走向.

dyld分析

(dyld又叫动态链接器)

dyld流程:

代码语言:javascript
复制
dyld_start

dyldbootstrap::start

dyld::_main

        环境、平台、版本、主机信息等准备工作

        instantiateFromLoadedImage实例化主程序

        link主程序

        weakBind若引用绑定主程序

        notifyMonitoringDyldMain通知dyld可以进行main()函数调用

(滑动显示更多)

我们需要一个在main之前就执行的函数,暂时选定load函数吧:

我们需要一个在main之前就执行的函数,暂时选定load函数吧:

我们发现,最先执行到的是dyld里的_dyld_start,接下来我们下载dyld源码打开源码,搜索_dyld_start,我们会发现有好几个__dyld_start:定义,由于当前的运行设备是iPhone11,所以我们只需要看#if __arm64__这段:

代码语言:javascript
复制
#if __arm64__
.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)
...
#endif // __arm64__

(滑动显示更多)

我们发现注释里面有一个call指令,调用了dyldbootstrap的start函数,我们在dyld工程里全局搜索dyldbootstrap:

定位到start函数

代码语言:javascript
复制
uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue)
{
    ...
    //重定位dyld
    rebaseDyld(dyldsMachHeader);
    //将envp指针指向argv最后一个
    const char** envp = &argv[argc+1];
    //将apple指针指向argv末尾
    const char** apple = envp;
    while(*apple != NULL) { ++apple; }
    ++apple;
    ///为堆栈设置随机值
    __guard_setup(apple);
    ...
    //已完成dyld引导,接下来执行main
    uintptr_t appsSlide = appsMachHeader->getSlide();
    return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}

(滑动显示更多)

进入_main

由于_main函数有好几百行代码,如果我们每一行都去分析,会很耗费精力,我们可以结合最后的result返回值,以及我们一开始就知道的程序加载流程,来分析核心代码:

代码语言:javascript
复制
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
int argc, const char* argv[], const char* envp[], const char* apple[], 
uintptr_t* startGlue){
    ...
    // 加载共享缓存
    checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
    if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
#if TARGET_OS_SIMULATOR
        if ( sSharedCacheOverrideDir)
            mapSharedCache();
#else
        mapSharedCache();
#endif
    }

    //实例化主程序
    /**  
    instantiateFromLoadedImage内部做了3件事情:
    判断machO是否兼容
    初始化ImageLoader
    加载初始化后的ImageLoader
    */
    sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
    ...
    //  加载插入的库,即动态库
    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(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
    ...
    // <rdar://problem/12186933> do weak binding only after all inserted images linked
    // 弱引用绑定(在所有的iamge镜像文件链接之后,才会进行弱引用绑定符号表)
    sMainExecutable->weakBind(gLinkContext);
    ...
    // run all initializers
    // 运行所有初始化程序
    initializeMainExecutable();
    // 通知dyld进行main函数调用
    notifyMonitoringDyldMain();
    ...
    // main executable uses LC_UNIXTHREAD, dyld needs to let "start" in program set up for main()
    // 通过LC_UNIXTHREAD,主程序找到main()函数入口
    result = (uintptr_t)sMainExecutable->getEntryFromLC_UNIXTHREAD();
    ...
    return result;
}

(滑动显示更多)

接下来,我们探究一下initializeMainExecutable

代码语言:javascript
复制
void initializeMainExecutable()
{
    // run initialzers for any inserted dylibs
    //拿到所有的镜像文件
    ImageLoader::InitializerTimingList initializerTimes[allImagesCount()];
    initializerTimes[0].count = 0;
    const size_t rootCount = sImageRoots.size();
    if ( rootCount > 1 ) {
        //遍历所有镜像文件,并且执行Initializers初始化方法
        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]);
    ...
}

(滑动显示更多)

进入runInitializers

代码语言:javascript
复制
void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo)
{
    ...
    processInitializers(context, thisThread, timingInfo, up);
    context.notifyBatch(dyld_image_state_initialized, false);
    ...
}

(滑动显示更多)

点击进入processInitializers

代码语言:javascript
复制
void ImageLoader::processInitializers(const LinkContext& context, mach_port_t thisThread,
InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& images)
{
    ...
    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(const

代码语言:javascript
复制
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,

  InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
...
try {
    //初始化依赖文件
    for(unsigned int i=0; i < libraryCount(); ++i) {
        ImageLoader* dependentImage = libImage(i);
        ...
    }
    ...
    ///让对象知道我们正在初始化,通知对象
    uint64_t t1 = mach_absolute_time();
    fState = dyld_image_state_dependents_initialized;
    oldState = fState;
    context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);

    //初始化镜像文件
    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);
    ...
}

(滑动显示更多)

点击notifySingle发现并没有找到函数定义,改为搜索context.notifySingle:

代码语言:javascript
复制
gLinkContext.notifySingle = &notifySingle;

(滑动显示更多)

发现重定向到函数地址¬ifySingle,我们点击它:

代码语言:javascript
复制
static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)

{
    ...
    (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
    ...
}

(滑动显示更多)

搜索sNotifyObjCInit

我们发现,sNotifyObjCInit的赋值来源于

registerObjCNotifiers函数的第二个参数,

接下来我们搜一下registerObjCNotifiers看看哪里调用了:

代码语言:javascript
复制
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);
}

(滑动显示更多)

由于我们在dyly源码工程里搜不到有哪个上层函数调用了_dyld_objc_notify_register,所以我们下一个符号断点看看哪里调用了:

运行

我们发现,_dyld_objc_notify_register由_objc_init调用。

至此,关于图片 dyld部分的代码已经分析完了,接下来进入libobjc工程,打开objc工程,由于我们前面分析到_dyld_objc_notify_register这个流程,我们在objc工程全局搜索一下_dyld_objc_notify_register:

代码语言:javascript
复制
void _objc_init(void)
{
    ...
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    ...
}

(滑动显示更多)

发现在_objc_init会调用到_dyld_objc_notify_register这个函数,于是我们下断点,打印堆栈信息查看完整的流程走向:

按照先后顺序,_objc_init是在recursiveInitialization之后执行,而_dyld_objc_notify_register就像block回调一样,由notifySingle声明,最后_objc_init里调用。

当_objc_init一顿初始化操作之后,调用_dyld_objc_notify_register告诉dyld我已经初始化完毕了。

而中间的recursiveInitialization、doInitialization等我们没有办法从上一步的recursiveInitialization继续分析下去,所以我们这里采用逆向推导思维:由结果反推。

_objc_init里会调用_dyld_objc_notify_register,这个我们在前面已经分析了。

_objc_init由上层_os_object_init发起调用,_os_object_init存在于库里

  • libdispatch

打开libdispatch工程,搜索_objc_init

由此可以说明,objc工程里的_objc_init函数,的确是由libdispatch工程的_os_object_init函数发起的。

按照前面的思路,继续搜素libdispatch_init

代码语言:javascript
复制
DISPATCH_EXPORT DISPATCH_NOTHROW
void
libdispatch_init(void)
{
    ...
    _os_object_init();
    ...
}

(滑动显示更多)

libdispatch_init由libSystem_initializer发起,搜索:

发现libSystem_initializer源自于Libsystem库。

  • Libsystem 源码下载

Libsystem工程里搜索libSystem_initializer:

代码语言:javascript
复制
__attribute__((constructor))
static void
libSystem_initializer(int argc,
      const char* argv[],
      const char* envp[],
      const char* apple[],
      const struct ProgramVars* vars)
{
    ...
    libdispatch_init();
    ...
}

(滑动显示更多)

继续查看图片 发现ImageLoaderMachO::doModInitFunctions存在于dyld库,

于是我们回到dyld工程,搜索ImageLoaderMachO::doModInitFunctions:

代码语言:javascript
复制
void ImageLoaderMachO::doModInitFunctions(const LinkContext& context)
{
    Initializer func = (Initializer)((uint8_t*)this->machHeader() + funcOffset);
    ...
    func(context.argc, context.argv, context.envp, context.apple, &context.programVars);
    ...
}

(滑动显示更多)

继续往上一层搜索

ImageLoaderMachO::doInitialization

代码语言:javascript
复制
bool ImageLoaderMachO::doInitialization(const LinkContext& context){
    ...
    //点击doModInitFunctions,会跳到ImageLoaderMachO::doModInitFunctions
    doModInitFunctions(context);
    ...
}

(滑动显示更多)

搜索ImageLoader::recursiveInitialization

代码语言:javascript
复制
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
  InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
    //初始化镜像文件(点击doInitialization会跳转ImageLoaderMachO::doInitialization)
    bool hasInitializers = this->doInitialization(context);
}

(滑动显示更多)

接下来我们做一个测试:

ViewController.m里:

代码语言:javascript
复制
@implementation ViewController
+ (void)load{
    [super load];
    NSLog(@"我是ViewController load函数");
}
...
@end

(滑动显示更多)

main.m文件里:

代码语言:javascript
复制
int main(int argc, char * argv[]) {
    NSLog(@"我是main.m        main函数");
    ...
}
///定义一个C++函数
__attribute__((constructor)) void SSJFun(){
    printf("\n我是main.m        SSJFun函数\n");
}

(滑动显示更多)

目的是看一下这三个的打印顺序,看运行结果:

我们发现,执行顺序load函数 > C++函数(SSJFun) > main函数,那么这是为什么呢?我们分析一下。

打开objc源码,嗖嗖load_images:

load_images 会调用所有load方法

代码语言:javascript
复制
void
load_images(const char *path __unused, const struct mach_header *mh)
{
    ...
    // Discover load methods
    {
        ...
        prepare_load_methods((const headerType *)mh);
    }
    ...
}

(滑动显示更多)

进入prepare_load_methods:

代码语言:javascript
复制
void prepare_load_methods(const headerType *mhdr)
{
    ...
    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        // 进入类的load方法
        schedule_class_load(remapClass(classlist[i]));
    }
    category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    ...
}

(滑动显示更多)

进入schedule_class_load:

代码语言:javascript
复制
static void schedule_class_load(Class cls)
{
    if (!cls) return;
    ...
    if (cls->data()->flags & RW_LOADED) return;

    schedule_class_load(cls->getSuperclass());
    
    add_class_to_loadable_list(cls);
    
    cls->setInfo(RW_LOADED); 
}

/**
    我们发现schedule_class_load是一个递归调用函数,
    会沿着cls及其父类一层层执行add_class_to_loadable_list
*/

(滑动显示更多)

进入add_class_to_loadable_list:

代码语言:javascript
复制
void add_class_to_loadable_list(Class cls)
{
    IMP method;
    ...
    method = cls->getLoadMethod();
    if (!method) return;  // Don't bother if cls has no +load method
    ...
    /**
        loadable_classes[loadable_classes_used]得到的是结构体,
        将类名和load方法IMP写到loadable_classes_used(下标)对应的结构体里
    */ 
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}
/**
    我们发现,schedule_class_load在递归查找method的IMP,
    具体是什么方法的IMP我们还不得而知,继续往下看~
*/

(滑动显示更多)

进入getLoadMethod:

代码语言:javascript
复制
IMP 
objc_class::getLoadMethod(){
    ...
    const method_list_t *mlist;
    ...
    mlist = ISA()->data()->ro()->baseMethods();
    if (mlist) {
        for (const auto& meth : *mlist) {
            const char *name = sel_cname(meth.name());
            if (0 == strcmp(name, "load")) {
                return meth.imp(false);
            }
        }
    }
    return nil;
}
/**
    我们发现,getLoadMethod是寻找load方法的imp的过程,
*/

(滑动显示更多)

回到 -> prepare_load_methods函数 继续分析

代码语言:javascript
复制
void prepare_load_methods(const headerType *mhdr)
{
    ...
    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        // 进入类的load方法
        schedule_class_load(remapClass(classlist[i]));
    }
    /**
        经过上面几个函数的分析,我们可以得出结论:
        上面这段for循环,目的是把所有主类的继承链关系类的load方法装载到loadable_classes里
    */

    //接下来看看分类做了什么
    category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    ...
    add_category_to_loadable_list(cat);
}

(滑动显示更多)

进入add_category_to_loadable_list:

代码语言:javascript
复制
void add_category_to_loadable_list(Category cat)
{
    IMP method;
    ...
    method = _category_getLoadMethod(cat);
    if (!method) return;
    ...
    loadable_categories[loadable_categories_used].cat = cat;
    loadable_categories[loadable_categories_used].method = method;
    loadable_categories_used++;
}

// 里面就不一一分析了,跟前面的类一样,
// 也是寻找load方法的IMP,并且写入loadable_categories的过程。

(滑动显示更多)

继续回到load_images函数:

代码语言:javascript
复制
load_images(const char *path __unused, const struct mach_header *mh)
{
    ...
    // 添加类的load方法   到loadable_classes、
    // 添加分类的load方法 到loadable_categories
    prepare_load_methods((const headerType *)mh);

    // -> 接下来分析这个函数
    call_load_methods();
}

(滑动显示更多)

进入call_load_methods:

代码语言:javascript
复制
void call_load_methods(void)
    ...
    do {
        // loadable_classes_used已经在prepare_load_methods函数里赋值,统计load的个数,也可以认为是下标吧
        while (loadable_classes_used > 0) {
            //这里面就是调用Class的load方法
            call_class_loads();
        }
        //这里面就是调用category的load方法
        more_categories = call_category_loads();
    } while (loadable_classes_used > 0  ||  more_categories);
    ...
}

(滑动显示更多)

到此,我们可以得出一个结论,load_images函数会调用:

  • 所有非懒加载Class的load方法
  • 所有非懒加载category的load方法

为什么C++方法会自动调用,什么时候调用?

我们先在SSJFun方法打个断点,控制台bt一下看看堆栈信息:

我们发现,SSJFun是由dyld_sim的上层函数doModInitFunctions调用的,

而doModInitFunctions是由doInitialization调用的。

打开dyld源码,搜索doInitialization,我们找到这段代码:

代码语言:javascript
复制
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
  InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
  {
      ...
      context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
      bool hasInitializers = this->doInitialization(context);
      ...
  }
  /**
  notifySingle在前面已经分析过类,它最终会调用_dyld_objc_notify_register的第二个参数,也就是load_images函数;
  load_images则会调用load方法;
  notifySingle在doInitialization之前先执行,
  所以`load方法`在`SSJFun方法`之前调用就很明白了。
  */

(滑动显示更多)

为什么main函数最后执行?

我们知道,最先执行的是_dyld_start,dyld源码搜素_dyld_start:

发现_dyld_start最后会调起main()函数;

回到工程,打开DeBug调试:

我们发现_dyld_start执行完之后,确实会执行main函数,再一次证明了main函数是在dyld之后执行。

registerObjCNotifiers里初始化的函数,分别在哪里调用呢?

我们知道,_objc_init里进行一系列初始化后,会调用_dyld_objc_notify_register函数,继而会进入dyld的registerObjCNotifiers:

代码语言:javascript
复制
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
    sNotifyObjCMapped = mapped;

    sNotifyObjCInit = init;

    sNotifyObjCUnmapped = unmapped;
    ...
}

(滑动显示更多)

找寻sNotifyObjCMapped调用

全局搜索sNotifyObjCMapped,找到它的调用函数notifyBatchPartial,搜索notifyBatchPartial找到它的上层调用:

找寻sNotifyObjCInit调用

全局搜索sNotifyObjCInit,找到它的调用函数notifySingle,搜索notifySingle找到上层调用:

代码语言:javascript
复制
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
  InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
    ...
    // 有依赖才会调用
    /** 举个例子,比如是A库依赖于B库:
    第一次recursiveInitialization调用,由于A库的初始化是在doInitialization完成,
    所以第一次进来的时候A库为空,自然不存在所谓的依赖库,第一个notifySingle不执行
    */
    context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
    // 初始化
    bool hasInitializers = this->doInitialization(context);
    ...
    context.notifySingle(dyld_image_state_initialized, this, NULL);
    ...
}

(滑动显示更多)

这边做个总结,应用程序从启动到objc_init:

代码:

链接:pan.baidu.com/s/1Bse22q_f…

密码:du3f(包含Demo、dyld源码、libdispatch源码、Libsystem源码、objc4源码)

原文链接:https://juejin.cn/post/6989269646247460872

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-11-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 HelloCoder全栈小集 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档