iOS 底层原理探索 之 应用程序加载原理dyld (下)

1,774 阅读6分钟
写在前面: iOS底层原理探究是本人在平时的开发和学习中不断积累的一段进阶之
路的。 记录我的不断探索之旅,希望能有帮助到各位读者朋友。

目录如下:

  1. iOS 底层原理探索 之 alloc
  2. iOS 底层原理探索 之 结构体内存对齐
  3. iOS 底层原理探索 之 对象的本质 & isa的底层实现
  4. iOS 底层原理探索 之 isa - 类的底层原理结构(上)
  5. iOS 底层原理探索 之 isa - 类的底层原理结构(中)
  6. iOS 底层原理探索 之 isa - 类的底层原理结构(下)
  7. iOS 底层原理探索 之 Runtime运行时&方法的本质
  8. iOS 底层原理探索 之 objc_msgSend
  9. iOS 底层原理探索 之 Runtime运行时慢速查找流程
  10. iOS 底层原理探索 之 动态方法决议
  11. iOS 底层原理探索 之 消息转发流程
  12. iOS 底层原理探索 之 应用程序加载原理dyld (上)

以上内容的总结专栏


细枝末节整理


前言

接着上一篇的内容我们从 _dyld_objc_notify_register(&map_images, load_images, unmap_image)开始:

_dyld_objc_notify_register(&map_images, load_images, unmap_image)

  1. 这个方法中的 map_images是在什么时候调用的呢?
  2. load_images方法执行的时候其内部的一个流程是什么样的?
  3. 最后,我们现在所在的 dyld 阶段是如何进入到 main 函数主程序的呢?

带着这些疑问开始我们今天的内容。

承接上节课的内容 在 doModInitFunctions 之后 我们加载 libSystem.B.dylib libSystem_initializerlibsidpatch.dylib libdispatch_initlibsidpatch.dylib _os_object_init 接着执行到 _objc_init。 在 _objc_init 中,我们调用 了 _dyld_objc_notify_register

_dyld_objc_notify_register

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

registerObjCNotifiers

// _dyld_objc_notify_init
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
	// 记录要调用的函数,完成赋值
	sNotifyObjCMapped	= mapped;
	sNotifyObjCInit		= init;
	sNotifyObjCUnmapped = unmapped;
        
        // 调用“映射”函数与所有镜像映射到目前为止
	try {
		notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
	}
    ...
}

notifyBatchPartial

此函数中 通过 (*sNotifyObjCMapped)(objcImageCount, paths, mhs); 完成了对 map_images 的调用。

static void notifyBatchPartial(dyld_image_states state, bool orLater, dyld_image_state_change_handler onlyHandler, bool preflightOnly, bool onlyObjCMappedNotification)
{
	... 
        
        // 告诉objc关于新的镜像		             
        if ( (onlyHandler == NULL) && ((state == dyld_image_state_bound) || (orLater && (dyld_image_state_bound > state))) && (sNotifyObjCMapped != NULL) ) {
        const char* paths[imageCount];
	const mach_header* mhs[imageCount];
	unsigned objcImageCount = 0;
        for (int i=0; i < imageCount; ++i) { ... }
        if ( objcImageCount != 0 ) {
            dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_MAP, 0, 0, 0);
	uint64_t t0 = mach_absolute_time();
	(*sNotifyObjCMapped)(objcImageCount, paths, mhs);
	uint64_t t1 = mach_absolute_time();			ImageLoader::fgTotalObjCSetupTime += (t1-t0);
	}
      }
    }
    allImagesUnlock();
    if ( dontLoadReason != NULL )
            throw dontLoadReason;
    if ( !preflightOnly && (state == dyld_image_state_dependents_mapped) ) {
	const struct mach_header* loadAddresses[imageCount];
	const char* loadPaths[imageCount];
	for(uint32_t i = 0; i<imageCount; ++i) {
	loadAddresses[i] = infos[i].imageLoadAddress;
	loadPaths[i] = infos[i].imageFilePath;
	}
	notifyMonitoringDyld(false, imageCount, loadAddresses, loadPaths);
	}
    }
}       

也就是 _dyld_objc_notify_register方法进来之后,系统记录下要调用的函数后 就 try map_images 完成调用, 下面看下 load_images

在上一篇的 recursiveInitialization 环节,

ImageLoader::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) {... }
			
	// 记录终止订单
	if ( this->needsTermination() )				context.terminationRecorder(this);
	// 让objc知道我们将要初始化这个镜像
	uint64_t t1 = mach_absolute_time();
	fState = dyld_image_state_dependents_initialized;
	oldState = fState;
       
        context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);


        // 初始化镜像
        // 初始化所有images load_images 赋值
        // map load - map() --> cxx = objc
        // notifySingle : load() 
        // cxx = SMobjcBuild
	bool hasInitializers = this->doInitialization(context);

	//  让任何人知道我们已经初始化了这个镜像
	fState = dyld_image_state_initialized;
	oldState = fState;
        
        //objc - SMObjc
        context.notifySingle(dyld_image_state_initialized, this, NULL);
			
	if ( hasInitializers ) { ... }
    }	
    catch (const char* msg) {
        // this image is not initialized
        fState = oldState;
        recursiveSpinUnLock();
        throw;
        }
    }
	
    recursiveSpinUnLock();
}

就会分析进入到 notifySingle:

notifySingle

static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{
    //dyld::log("notifySingle(state=%d, image=%s)\n", state, image->getPath());
    std::vector<dyld_image_state_change_handler>* handlers = stateToHandlers(state, sSingleHandlers);
    if ( handlers != NULL ) { ... }
    if ( state == dyld_image_state_mapped ) { ... }
    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)) { ... }
   }
}

也就是在 notifySingle 中调用了load_images

接下来我们看看,应用程序加载过程中,是如何从 dyld 进入到 main() 的。

dyld_start 汇编源码

通过分析 dyld_start 源码,发现在dyld的最后,会调起系统的 main()函数。

#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
#if __LP64__
	ldr     x0, [x28]               // get app's mh into x0
	ldr     x1, [x28, #8]           // get argc into x1 (kernel passes 32-bit int argc as 64-bits on stack to keep alignment)
	add     x2, x28, #16            // get argv into x2
#else
	ldr     w0, [x28]               // get app's mh into x0
	ldr     w1, [x28, #4]           // get argc into x1 (kernel passes 32-bit int argc as 64-bits on stack to keep alignment)
	add     w2, w28, #8             // get argv into x2
#endif
	adrp	x3,___dso_handle@page
	add 	x3,x3,___dso_handle@pageoff // get dyld's mh in to x4
	mov	x4,sp                   // x5 has &startGlue

	// 调用 dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
	bl	__ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pm
	mov	x16,x0                  // save entry point address in x16
#if __LP64__
	ldr     x1, [sp]
#else
	ldr     w1, [sp]
#endif
	cmp	x1, #0
	b.ne	Lnew

	// LC_UNIXTHREAD way, 清理堆栈然后跳转到结果
#if __LP64__
	add	sp, x28, #8             // restore unaligned stack pointer without app mh
#else
	add	sp, x28, #4             // restore unaligned stack pointer without app mh
#endif
#if __arm64e__
	braaz   x16                     // jump to the program's entry point
#else
	br      x16                     // jump to the program's entry point
#endif

	// LC_MAIN case, 设置调用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
#else
	ldr	w0, [x28, #4]       // main param1 = argc
	add	x1, x28, #8         // main param2 = argv
	add	x2, x1, x0, lsl #2
	add	x2, x2, #4          // main param3 = &env[0]
	mov	x3, x2
Lapple:	ldr	w4, [x3]
	add	x3, x3, #4
#endif
	cmp	x4, #0
	b.ne	Lapple		    // main param4 = apple
#if __arm64e__
	braaz   x16
#else
	br      x16
#endif

#endif // __arm64__ && !TARGET_OS_SIMULATOR

接下来真机验证:

验证

我们知道 我们准备的项目 先是 [viewcontroller load] 调用 然后是 cxx 方法 ,最后进入到 main()函数,那么,在项目中,我们在cxx方法处打一个断点,开始调试看看:

image.png

断点进来之后,debug显示汇编代码,然后逐步执行,看到在 dyld_start 流程的最后,会主动的去调起 main 函数

image.png

到这里,我们就解释了上篇文章抛出的三个问题。 所以,补充一下昨天的 dyld流程图:

dyld流程图

myNoteBase_副本.001.jpeg

 dyld 加载应用的完整流程 .001.jpeg

补充

首先要探索的流程就是 map_images

map_images

/***********************************************************************
* map_images
* 处理dyld映射到的给定镜像
* 在使用特定于abi的锁后调用与abi无关的代码
*
*  锁定:写锁runtimeLock
**********************************************************************/
void
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    mutex_locker_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}

之后就从 map_images 来到了 map_images_nolock

map_images_nolock

/***********************************************************************
* map_images_nolock
* 处理dyld映射到的给定镜像
* 所有的类注册和修正都被执行(或延迟等待)
* 发现丢失的超类等,并调用+load方法
*
* Info[]是自下而上的顺序,即libobjc将在早些时候
* 数组比任何链接到libobjc的库
*
* 锁定:loadMethodLock(旧的)或runtimeLock(新的)由map_images获取
**********************************************************************/
#if __OBJC2__
#include "objc-file.h"
#else
#include "objc-file-old.h"
#endif

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;

    // 如果需要,执行首次初始化
    // 该函数在普通库初始化器之前调用
    // 修复延迟初始化直到找到一个使用objc的镜像?
    if (firstTime) { ... }
    
    if (PrintImages) { ... }
    
    // 找到所有带有Objective-C元数据的镜像
    hCount = 0;

    // Count classes. Size various table based on the total.
    int totalClasses = 0;
    int unoptimizedTotalClasses = 0;
    {
        uint32_t i = mhCount;
        while (i--) { ... }
    }
    
    //  执行必须延迟到的一次性运行时初始化 
    //  可执行文件本身被找到。这需要提前完成
    //  进一步的初始化
    //  可执行文件可能不在这个infoList中,如果

    //  可执行程序不包含Objective-C代码,而是Objective-C
    //  稍后动态加载
    if (firstTime) { ... }
    
    
    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

    firstTime = NO;
    
    // 在一切设置完成后调用镜像加载函数
    for (auto func : loadImageFuncs) {
        for (uint32_t i = 0; i < mhCount; i++) {
            func(mhdrs[i]);
        }
    }

找到所有带有Objective-C元数据的镜像后,之后就开始对镜像内容进行初始处理。

总结

用户将我们开发的app安装到手机后,一直存储在手机的磁盘中,一旦用户点击了我们的app,那么,系统就会进入到dyld流程中,为我们的app中所使用的动静态库做链接,加载所有的镜像文件,接着就是加载我们项目中的所有类文件,最终,进入到 main() 函数,之后是 AppDelegateSecen 来处理我们为用户开发的各种功能。

本篇内容算是,上述流程中 dyld 流程探索的一个总结, 下一篇,我们开始类的加载流程探索。 大家,加油!!!