objc的mapImage与loadImage底层探索

2,231 阅读7分钟

上一章应用加载流程探索我们已经了解了,程序启动加载的流程。编译过程:预编译->编译->汇编->链接->可执行mach-o。然后链接过程用到了dyld动态链接器。主要靠dyld来链接管理,了解了dyld的加载动态库流程。
9大步骤:
1.环境变量配置(环境,平台,版本,路径,主机信息)
2.共享缓存(mapSharedCache):(UIKit、CoreFoundation等)
3.主程序的初始化(instantiateFromLoadedImage)
4.加入动态库(loadInsertedDylib)
5.link主程序
6.link动态库
7.weakBind弱引用绑定主程序
8.initializeMainExecutable初始化
9.notifyMonitoringDyldMain通知dyld可以进行main()

深入分析了initializeMainExecutable 初始化流程,引入了今天研究的核心mapImage和loadImage。 在程序动态连接器初始化动态库中mapImage和loadImage的作用。

一. _objc_init初始化流程

1、环境变量的初始化

废话不多说,直接上代码:

void environ_init(void) 
{
....省略
        if (0 != strncmp(*p, "OBJC_", 5)) continue;

        if (0 == strncmp(*p, "OBJC_HELP=", 10)) {

            PrintHelp = true;

            continue;

        }

        if (0 == strncmp(*p, "OBJC_PRINT_OPTIONS=", 19)) {

            PrintOptions = true;

            continue;

        }

        if (0 == strncmp(*p, "OBJC_DEBUG_POOL_DEPTH=", 22)) {

            SetPageCountWarning(*p + 22);

            continue;

        }
....省略
}

代码对应xcode的设置 image.png 只保留: image.png 运行代码看控制台打印: image.png 我们就可以通过OBJC_DISABLE_NONPOINTER_ISA来设置xcode对isa的指针优化开关。 image.png 验证打印: image.png isa指针优化关闭,然后我们设置OBJC_PRINT_IMAGES=YES我们打印看看: image.png 打印了加载的动态库信息。

2、_objc_init解析

在objc4-838.1中添加代码: image.png

@interface NYPerson : NSObject

@end
@implementation NYPerson

__attribute__((constructor)) static void function1(){

    printf("function1 \n");

}

+(void)load
{

    NSLog(@"%s",__func__);

}

@end
//打印结果
**function**

**2022-05-22 12:24:43.678216+0800 SXObjcDebug[13073:387196] +[NYPerson load]**

**function1**

**2022-05-22 12:24:43.680619+0800 SXObjcDebug[13073:387196] Hello World!**

**Program ended with exit code: 0**

从打印结果得知,oc的static_init()中C++析构函数function在load之前执行。我们自己定义的C++析构函数function1在load之后执行。 image.png 由上图可知,_objc_init初始化配置了环境变量,创建线程的析构函数,初始化static静态数据, 分类表初始化,类表初始化,异常处理,缓存等操作。后面我们重点研究map_imagesload_images具体做了哪些处理。

3、load_images解析

来来来我们直接看代码:

void
load_images(const char *path __unused, const struct mach_header *mh)
{

    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
        didInitialAttachCategories = true;

        loadAllCategories();
    }

    // Return without taking locks if there are no +load methods here.

    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);//加锁

    // Discover load methods
    // 查找 load 方法
    {
        mutex_locker_t lock2(runtimeLock);

        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    //调用 +load 方法 ,包括类,和分类的load
    call_load_methods();
}

我们进入prepare_load_methods看看具体实现:

void prepare_load_methods(const headerType *mhdr)
{

    size_t count, i;

    runtimeLock.assertLocked();
    //非懒加载类,重写了load方法的类
    classref_t const *classlist = 

        _getObjc2NonlazyClassList(mhdr, &count);

    for (i = 0; i < count; i++) {

        schedule_class_load(remapClass(classlist[i]));//递归查找

    }

//非懒加载的分类
    category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);

    for (i = 0; i < count; i++) {

        category_t *cat = categorylist[i];

        Class cls = remapClass(cat->cls);

        if (!cls) continue;  // category for ignored weak-linked class

        if (cls->isSwiftStable()) {

            _objc_fatal("Swift class extensions and categories on Swift "

                        "classes are not allowed to have +load methods");

        }

        realizeClassWithoutSwift(cls, nil);

        ASSERT(cls->ISA()->isRealized());

        add_category_to_loadable_list(cat);//添加到分类表中

    }
}

//进入add_category_to_loadable_list方法
void add_category_to_loadable_list(Category cat)

{
    IMP method;
    .........省略............
    
    loadable_categories[loadable_categories_used].cat = cat;

    loadable_categories[loadable_categories_used].method = method;

    loadable_categories_used++;

}

在进入schedule_class_load看代码:

static void schedule_class_load(Class cls)

{

    //不需要调用 super load --父类的load 优先添加到表中  --子类

    if (!cls) return;

    ASSERT(cls->isRealized());  // _read_images should realize

    if (cls->data()->flags & RW_LOADED) return;

    // Ensure superclass-first ordering

    schedule_class_load(cls->getSuperclass());//在父类中找load

    add_class_to_loadable_list(cls);

    cls->setInfo(RW_LOADED); 

}
//在进入add_class_to_loadable_list方法
void add_class_to_loadable_list(Class cls)
{

    IMP method;
    loadMethodLock.assertLocked();
    .........省略............
    
    //添加cls,method到表中
    loadable_classes[loadable_classes_used].cls = cls;

    loadable_classes[loadable_classes_used].method = method;

    loadable_classes_used++;

}

小结:load_images解析

1.当⽗类和⼦类都实现load函数时,⽗类的load⽅法执⾏顺序要优先于⼦类

2.当⼀个类未实现load⽅法时,不会调⽤⽗类load⽅法

3.类中的load⽅法执⾏顺序要优先于分类(Category)

4.load⽅法使⽤了锁,所以是线程安全的。

5.当有多个类别(Category)都实现了load⽅法,这⼏个load⽅法都会执⾏,但执⾏顺序不确定(其执⾏ 顺序与类别在Compile Sources中出现的顺序⼀致)

6.当然当有多个不同的类的时候,每个类load 执⾏顺序与其在Compile Sources出现的顺序⼀致

4、read_images解析

我们在objc4-838.1中找到对应代码map_images image.png 进入map_images_nolock代码:

void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],

                  const struct mach_header * const mhdrs[])
{

    static bool firstTime = YES;

    header_info *hList[mhCount];

    uint32_t hCount;

    size_t selrefCount = 0;

    // Perform first-time initialization if necessary.

    // This function is called before ordinary library initializers. 

    // fixme defer initialization until an objc-using image is found?

    if (firstTime) {
        preopt_init();//共享缓存的优化处理
    }

    if (PrintImages) {

        _objc_inform("IMAGES: processing %u newly-mapped images...\n", mhCount);

    }
    .........省略............
    // 统计所有的类

    int totalClasses = 0;

    int unoptimizedTotalClasses = 0;
    {
    .........省略............
    }
    if (firstTime) {

        sel_init(selrefCount);//c++的构造函数析构方法初始化

        arr_init();//初始化自动释放池
        .........省略............
   }
   .........省略............
   if (hCount > 0) {
        //研究的重点
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);

    }
    firstTime = NO;

    // Call image load funcs after everything is set up.

    for (auto func : loadImageFuncs) {

        for (uint32_t i = 0; i < mhCount; i++) {

            func(mhdrs[i]);

        }
    }
}

sel_init的主要作用:c++的构造函数析构方法初始化 image.png arr_init的主要作用:初始化自动释放池,散列表,关联对象初始化。 image.png 我们进入_read_images代码中:

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{

    header_info *hi;

    uint32_t hIndex;

    size_t count;

    size_t i;

    Class *resolvedFutureClasses = nil;

    size_t resolvedFutureClassCount = 0;

    static bool doneOnce;

    bool launchTime = NO;

    TimeLogger ts(PrintImageTimes);

    runtimeLock.assertLocked();
    .........省略............
    
    for (EACH_HEADER) {
            if (hi->info()->containsSwift()  &&

                hi->info()->swiftUnstableVersion() < objc_image_info::SwiftVersion3)

            {

                DisableNonpointerIsa = true;//指针优化

                if (PrintRawIsa) {

                    _objc_inform("RAW ISA: disabling non-pointer isa because "

                                 "the app or a framework contains Swift code "

                                 "older than Swift 3.0");

                }

                break;
            }

        }
   .........省略............
   int namedClassesSize = 

            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;

        gdb_objc_realized_classes =

            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

        ts.log("IMAGE TIMES: first time tasks");
   .........省略............
   static size_t UnfixedSelectors;
    {
        mutex_locker_t lock(selLock);

        for (EACH_HEADER) {

            if (hi->hasPreoptimizedSelectors()) continue;

            bool isBundle = hi->isBundle();

            SEL *sels = _getObjc2SelectorRefs(hi, &count);

            UnfixedSelectors += count;

            for (i = 0; i < count; i++) {

                const char *name = sel_cname(sels[i]);

                SEL sel = sel_registerNameNoLock(name, isBundle);

                if (sels[i] != sel) {

                    sels[i] = sel;//rebase //修护内存指针

                }
            }
        }
    }
    //ASLR 虚拟内存 的binding过程
    ts.log("IMAGE TIMES: fix up selector references");
    .........省略............
    #if SUPPORT_FIXUP

    // Fix up old objc_msgSend_fixup call sites

    for (EACH_HEADER) {

        message_ref_t *refs = _getObjc2MessageRefs(hi, &count);

        if (count == 0) continue;

        if (PrintVtables) {

            _objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "

                         "call sites in %s", count, hi->fname());

        }

        for (i = 0; i < count; i++) {

            fixupMessageRef(refs+i);

        }

    }
    ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");

#endif
//将所有`Protocol`都添加到`protocol_map`表中

    for (EACH_HEADER) {

        extern objc_class OBJC_CLASS_$_Protocol;

        Class cls = (Class)&OBJC_CLASS_$_Protocol;

        ASSERT(cls);

        NXMapTable *protocol_map = protocols();

        bool isPreoptimized = hi->hasPreoptimizedProtocols();
     .........省略............
     }     
.........省略............
//初始化所有⾮懒加载的类,进⾏`rw、ro`等操作
    for (EACH_HEADER) {
        classref_t const *classlist = hi->nlclslist(&count);

        for (i = 0; i < count; i++) {

            Class cls = remapClass(classlist[i]);

            if (!cls) continue;
            addClassTableEntry(cls);//递归调用加入表中
            if (cls->isSwiftStable()) {

                if (cls->swiftMetadataInitializer()) {

                    _objc_fatal("Swift class %s with a metadata initializer "

                                "is not allowed to be non-lazy",

                                cls->nameForLogging());
                }

            }
            realizeClassWithoutSwift(cls, nil);
        }
    }
.........省略............
}

进入NXCreateMapTable看看做了什么? image.png 创建了gdb_objc_realized_classes 这张表。
主要看readClass:方法的作用: image.png 我们在readClass中添加调试代码:

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)

{
    const char *mangledName = cls->nonlazyMangledName();

    const char *personName = "NYPerson";
    auto ny_ro = (const class_ro_t *)cls->data();
    auto ny_isMeta = ny_ro->flags & RO_META;
    if(strcasecmp(mangledName, personName)==0 && !ny_isMeta)  //过滤元类
    {

        printf("NYPerson");

    }
    .........省略............ 
}

进行断点调试,研究readClass都做了什么? image.png 通过断点我们发现并没有进行 or,rw等操作。 image.png 添加类到表中,并返回这个class.

小结

1: 加载所有类到类的gdb_objc_realized_classes表中。

2: 对所有类做重映射。

3: 将所有SEL都注册到namedSelectors表中。

4: 修复函数指针遗留。

5: 将所有Protocol都添加到protocol_map表中。

6: 对所有Protocol做重映射。

7: 初始化所有⾮懒加载的类,进⾏rw、ro等操作。

8:遍历已标记的懒加载的类,并做初始化操作。

9:处理所有Category,包括ClassMeta Class

10:初始化所有未初始化的类

5、非懒加载类的加载

用上面同样的方法进行断点调试:

static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();
.........省略............ 
    auto ro = (const class_ro_t *)cls->data();

    auto isMeta = ro->flags & RO_META;

    const char *mangledName = cls->nonlazyMangledName();

    const char *personName = "NYPerson";

    auto ny_ro = (const class_ro_t *)cls->data();

    auto ny_isMeta = ny_ro->flags & RO_META;

    if(strcasecmp(mangledName, personName)==0 && !ny_isMeta)
    {

        printf("NYPerson");

    }
.........省略............ 
//给rw开辟内存空间,然后将ro的数据“拷⻉”到rw⾥⾯。

rw = objc::zalloc<class_rw_t>();

rw->set_ro(ro);

rw->flags = RW_REALIZED|RW_REALIZING|isMeta;

cls->setData(rw);
.........省略............ 

//递归调⽤realizeClassWithoutSwift,对⽗类和元类进⾏初始化

supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);

metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
.........省略............ 
//设置⽗类,isa指针的初始化

cls->setSuperclass(supercls);

cls->initClassIsa(metacls);
}

添加NYPerson代码:

@interface NYPerson : NSObject

@property (nonatomic,copy) NSString *name;

@property (nonatomic,copy) NSString *gogo;

- (void)test1;

- (void)test2;

- (void)test3;

@end

@implementation NYPerson

__attribute__((constructor)) static void function1(){

    printf("function1 \n");

}

+(void)load

{

    NSLog(@"%s",__func__);

}

- (void)test1{}

- (void)test2{}

- (void)test3{}

@end

打印结果: image.png image.png 打印中发现realizeClassWithoutSwift中对类进行了or rw操作。

小结realizeClassWithoutSwift对非懒加载类进行加载,并给rw开辟内存空间,然后将ro的数据“拷⻉”到rw⾥⾯。递归调⽤realizeClassWithoutSwift,对⽗类和元类进⾏初始化。最后设置⽗类,isa指针的初始化。

6、总结

1.环境变量的初始化:通过xcode设置或代码设置OBJC_DEBUG_POOL_DEPTH等设置,可以开启对应log打印。

2._objc_init解析:

  • environ_init():环境变量的初始化
  • tls_init:创建线程的析构函数
  • static_init:运⾏C++静态构造函数
  • runtime_init:分类表初始化,类表初始化
  • cache_t::init():缓存的初始化
  • _imp_implementationWithBlock_init:关于macOS的相关操作。
  • didCallDyldNotifyRegister:标识对_dyld_objc_notify_register的调⽤已完成。

3.load_images解析:

  • 1.当⽗类和⼦类都实现load函数时,⽗类的load⽅法执⾏顺序要优先于⼦类
  • 当⼀个类未实现load⽅法时,不会调⽤⽗类load⽅法
  • 类中的load⽅法执⾏顺序要优先于分类(Category)
  • load⽅法使⽤了锁,所以是线程安全的。
  • 当有多个类别(Category)都实现了load⽅法,这⼏个load⽅法都会执⾏,但执⾏顺序不确定(其执⾏顺序与类别在Compile Sources中出现的顺序⼀致)
  • 当然当有多个不同的类的时候,每个类load 执⾏顺序与其在Compile Sources出现的顺序⼀致 4.read_images解析:
  • 加载所有类到类的gdb_objc_realized_classes表中。
  • 对所有类做重映射。
  • 将所有SEL都注册到namedSelectors表中。
  • 修复函数指针遗留。
  • 将所有Protocol都添加到protocol_map表中。
  • 对所有Protocol做重映射
  • 初始化所有⾮懒加载的类,进⾏rw、ro等操作。
  • 遍历已标记的懒加载的类,并做初始化操作。
  • 处理所有Category,包括Class和Meta Class。
  • 初始化所有未初始化的类。

5.非懒加载类的加载: realizeClassWithoutSwift对非懒加载类进行加载,并给rw开辟内存空间,然后将ro的数据“拷⻉”到rw⾥⾯。递归调⽤realizeClassWithoutSwift,对⽗类和元类进⾏初始化。最后设置⽗类,isa指针的初始化。