基礎となる原理-12-dyldダイナミックリンカーローディングプロセス

1.dyldの紹介

dyldこれはAppleに属し动态连接器、Appleのオペレーティングシステムの重要なコンポーネントです。システムがコンパイルされた後、リンクのためにdylbに渡され、依存ライブラリが実行可能ファイルに生成されます。完全な英語名は次のとおりthe dynamic link editorです。Appleのdyldの歴史を簡単に紹介します

  • dyld1:dyld 1であり、1996年にNeXTStep3.3の一部として出荷されました。それ以前は、NeXTは静的バイナリを使用していました。これは、POSIXdlopen呼び出しの標準化よりも前のものであることに注意してください。現在、dlopenは一部のUnixに存在します。これらは、後で人々が採用した独自の拡張機能です。NeXTStepにはさまざまな独自の拡張機能があるため、標準のUnixソフトウェアをサポートするために以前のバージョンのmacOS10でサードパーティのラッパーを作成しました。問題は、それらが同種のセマンティクスを完全にサポートしていないことです。動作しない奇妙なエッジケースがいくつかあり、それらは少し遅くなってしまいます。macOS 10.0、Cheetahのリリース前に、別の機能も追加しました预绑定事前バインドは、これらの事前計算されたアドレスを含めるために試行する手法为系统中的每个dylib和您的应用程序找到固定地址,动态加载程序将所有内容加载到这些地址,如果成功,它将编辑所有这些二进制文件であり、次にそれらを同じアドレスに配置するときに、余分な作業を行う必要はありません。
  • dyld2:dyld2はdyldの完全重写です。C ++初期化セマンティクスを正しくサポートしているため、mach-o形式をわずかに拡張し、dyldを更新して効率的なC++ライブラリサポートを実現しました。また、正しいセマンティクスを備えた完全なネイティブdlopenおよびdlsym実装があります。現時点では、レガシーAPIの使用はお勧めしません。彼らはまだmacOS上にあります。他のプラットフォームでは出荷されません。スピードを重視して設計されている为速度而设计ので、です理智检查有限現在のマルウェア環境はありません。最後に、パフォーマンスを改善しました。パフォーマンスを改善したため、事前バインドを削除して、いわゆるに置き換えることができました共享缓存では、共有キャッシュとは何ですか?そうですね、iOS3.1とmacOSSnow Leopardで導入され、プリバインディングに完全に取って代わりました。これは、ほとんどのシステムdylibを含む単一のファイルです。それらを入れるので合并到一个文件、特定のタイプの最適化を行うことができます。すべてのテキストセグメントとすべてのデータセグメントを再配置でき重写他们的整个符号表、サイズを小さくするために、を行う必要があり每个进程中装载更少的区域ます。それはまた私達を可能に打包二进制段し、多くのメモリを節約します。これは、実際にはdylibのプリリンカーです。
  • dyld3:dyld 3は、本日発表した新しいダイナミックリンカーです。これは、ダイナミックリンクの方法を完全に再考したもので、今週のシードではほとんどのmacOSシステムアプリがデフォルトでオンになって2017年苹果操作系统おり、プラットフォーム上のすべてのシステムアプリがオンになっています程序默认打开安全それで、私が言ったように、私たちはdyld 2に多くの安全機能を後付けしました、しかし事後にそのようなものを追加することは本当に難しいです。ここ数年は順調に進んでいると思いますが、なかなか難しいです。では、より積極的安全检查でエンジニア前のセキュリティを確保できるでしょうか。最後に、テスト容易性と信頼性。dyldをテストしやすくすることはできますか?そのため、AppleはXCTestのような優れたテストフレームワークを数多く提供しており、これらを使用する必要がありますが、これらのライブラリをプロセスに挿入するためにダイナミックリンカーの低レベルの機能に依存しているため、基本的に既存のdyldコードのテストには使用できません。 、これにより、セキュリティ機能とパフォーマンス機能のテストも難しくなります。では、どうすればよいのでしょうか。さて、私たちはそれを置きました大部分染料移到了流程之外現在のところ、これは基本的には通常のデーモンであり、標準のテストツールを使用して他のすべての人と同じようにテストできます。これにより、将来的にはより速く改善できるようになります。また、プロセスのdyldビットを可能な限り小さく保ち、削減することもできます应用程序中的攻击表面また、起動を高速化します因为最快的代码是您从未编写过的代码,紧随其后的是您几乎从未执行的代码dyld3はアウトプロセスmach-o解析器です。

要約すると、WWDC17 :アプリケーションの起動時間:過去、現在、未来を見ることができます

1.1コンパイル

iOS開発では、プログラムを実行する場合はコンパイルする必要があります。そうしないと、コマンド+ bが成功したかどうかに関係なく、エラーが報告されて実行できなくなります。私たちが作成する高級言語は通常、基本言語をカプセル化し、ocの場合、c /c++をオブジェクト指向開発言語にカプセル化します。多くのAPIをカプセル化することで、開発効率が向上しました。
コンパイラを使用してプログラミング言語をマシンコードに変換します。マシンコードはCPUで実行され、アプリをより効率的に実行します。
コンパイラの構成:前段和后端

  • フロントエンド:構文解析。このメソッドは、中間コードを分析して生成します。
  • 后端:以中间代码为输入,根据不同架构生成不同机器代码。

iOS中使用的前端编译器是Clang,后端编译器是llvm

名前のないfile.jpg

1.2 iOS中的静态库和动态库的介绍

  • 静态库:链接时,静态库会被完整的复制到可执行文件中,被多次使用就会有多次拷贝。通常以.a.framework的形式。
  • 动态库:链接时不复制,程序启动后使用dyld加载,然后在决议符号。运行时由系统加载到内存,供程序使用。多个程序可以动态链接到这个动态库上,节约内存。由于是动态链接的,可以升级动态库,达到更新程序。但是目前来说,不是官方的话无法通过更新动态库的形式,达到热更新的效果。通常以.dylib.framework的形式。

スクリーンショット2021-07-086.10.56pm.png

2.dyld 流程分析

我们开发中都优化过app启动速度,提升用户体验。其中有一点是减少+load()使用,因为它在main函数之前就会调用,可以使用+initialize()替代。我们写个+load()方法,打断点看下bt情况。

スクリーンショット2021-07-093.09.31pm.png 可以发现dyld 是_dyld_start开始的,我们下载一份最新的dyld代码进行查看。

选择最新的dyld-852dyld点击下载

打开dyld-852全局搜索_dyld_start,选择arm64架构,发现是由汇编实现。

スクリーンショット2021-07-093.23.08pm.png 其中注释告诉我们这个方法会进入dyldbootstrap::start,全局搜索dyldbootstrap找到后在搜索start得到:

スクリーンショット2021-07-094.08.28pm.png 这个方法的关注return dyld::_main,dyld调用了main函数,传入参数macho_headerMach-o(可执行文件)的头部文件。

第一步环境变量相关处理

スクリーンショット2021-07-095.53.34pm.png スクリーンショット2021-07-095.59.29pm.png 主要是进行环境变量的处理,检查是否配置,没有的话使用默认值。

第二步加载共享缓存

スクリーンショット2021-07-096.11.56pm.png 检查共享缓存是否开启,以及是否映射到公共区域。

第三步dyld添加到UUID列表

スクリーンショット2021-07-096.21.27pm.png スクリーンショット2021-07-096.20.41pm.png 把dyld添加到UUID列表

第四步实例化主程序

スクリーンショット2021-07-118.42.20am.png 在映射可执行文件之前,需要制作一个ImageLoader*为已经映射在主可执行文件。

第五步 插入动态库

スクリーンショット2021-07-118.54.39am.png for循环加载所有DYLD_INSERT_LIBRARIES的库,调用loadInsertedDylib方法,记录插入的数量,插入库之后main,然后是其他。

第六步 链接主程序

スクリーンショット2021-07-135.05.50pm.png

第七步 链接所有插入的库

スクリーンショット2021-07-135.07.35pm.png

第八步 弱符号绑定

スクリーンショット2021-07-1211.17.15am.png

第九步 运行所有初始化程序

スクリーンショット2021-07-1211.18.43am.png

第十步 找到主程序的入口

スクリーンショット2021-07-1211.20.32am.png

第四步实例化主程序分析

sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
// The kernel maps in main executable before dyld gets control.  We need to 
// make an ImageLoader* for the already mapped in main executable.内核映射在主可执行文件之前,dyld得到控制。我们需要制作一个ImageLoader*为已经映射在主可执行文件。
//
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);
		addImage(image);
		return (ImageLoaderMachO*)image;
//	}
	
//	throw "main executable not a known format";
}

复制代码

sMainExecutable表示主程序变量。通过instantiateFromLoadedImage方法初始化。在instantiateMainExecutable中返回一个ImageLoader类型的image

// create image for main executable创建镜像为主要可执行文件
ImageLoader* ImageLoaderMachO::instantiateMainExecutable(const macho_header* mh, uintptr_t slide, const char* path, const LinkContext& context)
{
	//dyld::log("ImageLoader=%ld, ImageLoaderMachO=%ld, ImageLoaderMachOClassic=%ld, ImageLoaderMachOCompressed=%ld\n",
	//	sizeof(ImageLoader), sizeof(ImageLoaderMachO), sizeof(ImageLoaderMachOClassic), sizeof(ImageLoaderMachOCompressed));
	bool compressed;
	unsigned int segCount;
	unsigned int libCount;
	const linkedit_data_command* codeSigCmd;
	const encryption_info_command* encryptCmd;
	sniffLoadCommands(mh, path, false, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd);//sniffLoadCommands确定文件是否有压缩的LINKEDIT以及具有的段数
	// instantiate concrete class based on content of load commands 实例化具体类基于加载命令的内容
	if ( compressed ) 
		return ImageLoaderMachOCompressed::instantiateMainExecutable(mh, slide, path, segCount, libCount, context);
	else
#if SUPPORT_CLASSIC_MACHO
		return ImageLoaderMachOClassic::instantiateMainExecutable(mh, slide, path, segCount, libCount, context);
#else
		throw "missing LC_DYLD_INFO load command";
#endif
}
复制代码

sniffLoadCommands函数获取mach-0类型文件的 load command的相关信息,并进行校验。ImageLoader是一个抽象的基类,以支持加载特定的可执行文件。对于使用中的每一个可执行文件,一个ImageLoader被实例化,ImageLoader基类做链接镜像的工作。

第九步执行初始化方法initializeMainExecutable分析

void initializeMainExecutable()
{
	// record that we've reached this step
	gLinkContext.startedInitializingMainExecutable = true;

	// run initialzers for any inserted dylibs为所有插入的动态库执行run initialzers方法
	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]);
		}
	}
	
	// run initializers for main executable and everything it brings up
	// 为主可执行文件和它所带来的一切运行初始化器
	sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
	
	// register cxa_atexit() handler to run static terminators in all loaded images when this process exits
	// 当程序退出的时候 注册 cxa_atexit() 处理所有加载过的镜像运行静态终止程序
	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]);
}
复制代码

for循环为所有插入的动态库执行run initialzers方法,注册cxa_atexit()处理程序,以便在此进程退出时在所有加载的镜像中运行静态终止程序

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

复制代码

processInitializersrunInitializers核心的调用

// <rdar://problem/14412057> upward dylib initializers can be run too soon
// To handle dangling dylibs which are upward linked but not downward, all upward linked dylibs
// have their initialization postponed until after the recursion through downward dylibs
// has completed.
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循环所有镜像列表,进行镜像实例化,建立一个向上的依赖关系列表
	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);
}
复制代码

对所有镜像文件for循环实例化,建立一个向上关系依赖列表核心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) {
				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
			// 让objc 知道我们要初始化这个镜像
			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();//解锁
}
复制代码

主要实现了notifySingledoInitialization函数,先看下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 ) {
		dyld_image_info info;
		info.imageLoadAddress	= image->machHeader();
		info.imageFilePath		= image->getRealPath();
		info.imageFileModDate	= image->lastModified();
		for (std::vector<dyld_image_state_change_handler>::iterator it = handlers->begin(); it != handlers->end(); ++it) {
			const char* result = (*it)(state, 1, &info);
			if ( (result != NULL) && (state == dyld_image_state_mapped) ) {
				//fprintf(stderr, "  image rejected by handler=%p\n", *it);
				// make copy of thrown string so that later catch clauses can free it
				const char* str = strdup(result);
				throw str;
			}
		}
	}
	if ( state == dyld_image_state_mapped ) {
		// <rdar://problem/7008875> Save load addr + UUID for images from outside the shared cache
		// <rdar://problem/50432671> Include UUIDs for shared cache dylibs in all image info when using private mapped shared caches
		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);
		}
	}
    // mach message csdlc about dynamically unloaded images
	if ( image->addFuncNotified() && (state == dyld_image_state_terminated) ) {
		notifyKernel(*image, false);
		const struct mach_header* loadAddress[] = { image->machHeader() };
		const char* loadPath[] = { image->getPath() };
		notifyMonitoringDyld(true, 1, loadAddress, loadPath);
	}
}

复制代码

notifySingle函数中核心代码如下:

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);
		}
	}
复制代码

(*sNotifyObjCInit)(image->getRealPath(), image->machHeader())notifySingle的核心。sNotifyObjCInit是:

static _dyld_objc_notify_init		sNotifyObjCInit;
复制代码

全局搜索得到

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;
	sNotifyObjCUnmapped = unmapped;

	// call 'mapped' function with all images mapped so far
	try {
		notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
	}
	catch (const char* msg) {
		// ignore request to abort during registration
	}

	// <rdar://problem/32209809> call 'init' function on all images already init'ed (below libSystem)
	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());
		}
	}
}

复制代码

在方法registerObjCNotifiers中进行了复制init继续搜索registerObjCNotifiers调用出发现调用出_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);
}
复制代码

这个方法:仅供objc运行时使用,注册处理程序,当objc镜像被映射、未映射和初始化时调用。我们在objc源码中搜索得到:

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_init中调用并传入了参数。所以在sNotifyObjCInit的赋值在objcload_images中调用,load_images会调用所有的+load方法。

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
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}

复制代码

会调用核心call_load_methods方法

void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();

    do {
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        more_categories = call_category_loads();

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}

复制代码

执行dowhile循环。调用所有挂起的类和类+加载方法,Category +load方法直到父类的+load之后才被调用。

static void call_class_loads(void)
{
    int i;
    
    // Detach current loadable list.
    struct loadable_class *classes = loadable_classes;
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;
    
    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
        load_method_t load_method = (load_method_t)classes[i].method;
        if (!cls) continue; 

        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
        }
        (*load_method)(cls, @selector(load));
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}
复制代码

断点验证下

スクリーンショット2021-07-125.25.36pm.png 可以得出load的加载流程。_dyld_start->dyldbootstrap::start->dyld::_main->dyld::initializeMainExecutable()->ImageLoader::runInitializers->ImageLoader::processInitializers->ImageLoader::recursiveInitialization->dyld::notifySingle->load_images ->+load

_objc_init探索

	// initialize this image
	bool hasInitializers = this->doInitialization(context);
复制代码

查看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);
}
复制代码

先看doImageInit的实现

void ImageLoaderMachO::doImageInit(const LinkContext& context)
{
	if ( fHasDashInit ) {
		const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
		const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
		const struct load_command* cmd = cmds;
		for (uint32_t i = 0; i < cmd_count; ++i) {
			switch (cmd->cmd) {
				case LC_ROUTINES_COMMAND:
					//mach-o方法平移得到方法函数
					Initializer func = (Initializer)(((struct macho_routines_command*)cmd)->init_address + fSlide);
#if __has_feature(ptrauth_calls)
					func = (Initializer)__builtin_ptrauth_sign_unauthenticated((void*)func, ptrauth_key_asia, 0);
#endif
					// <rdar://problem/8543820&9228031> verify initializers are in image
					if ( ! this->containsAddress(stripPointer((void*)func)) ) {
						dyld::throwf("initializer function %p not in mapped image for %s\n", func, this->getPath());
					}
					if ( ! dyld::gProcessInfo->libSystemInitialized ) {
						// <rdar://problem/17973316> libSystem initializer must run first
						//libSystem初始化之前必须先运行
						dyld::throwf("-init function in image (%s) that does not link with libSystem.dylib\n", this->getPath());
					}
					if ( context.verboseInit )
						dyld::log("dyld: calling -init function %p in %s\n", func, this->getPath());
					{
						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);
					}
					break;
			}
			cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
		}
	}
}
复制代码

进入doModInitFunctions源码实现,这个方法中加载了所有Cxx文件验证一下

スクリーンショット2021-07-125.47.00pm.pngdoModInitFunctions后调用类的。objc_init调用在哪?
使用符号断点,断住后打印堆栈信息:

截屏2021-07-12 下午5.57.06.png 会在libSystem库中调用libSystem_initializerlibSystem中搜索

截屏2021-07-12 下午6.08.22.png 会对这2个库进行初始化。继续在libdispatch 下载 中搜索libdispatch_init_os_object_init 截屏2021-07-12 下午6.17.12.png 其中有_os_object_init的 实现继续查看

截屏2021-07-12 下午6.18.45.png 所以object 实现_objc_init函数

截屏2021-07-13 上午11.01.14.png

综上所述:初始化_objc_init 会调用_dyld_objc_notify_register通知,第二个参数load_images,收到通知回调sNotifyObjcInit处理函数。
总结:_dyld_start-->dyld::dyldbootstrap -->dyld::_main-->dyld::initializeMainExecutable()-->ImageLoader::runInitializers-->ImageLoader::processInitializers-->ImageLoader::recursiveInitialization-->ImageLoaderMachO::doInitialization-->ImageLoaderMachO::doModInitFunctions-->libSystem_initializer(libSystem.B.dylib)-->libdispatch_init(libdispatch.dylib)-->_os_object_init(libdispatch.dylib)-->_objc_init(libobjc.A.dylib).

总结

我们在iOS运行程序时,我们要把我们写的代码转换为机器识别的代码。在这个过程中,我们会使用一些封装的三方库,或者引用系统依赖的库来实现某些功能。

  • 编译过程中有前端和后端的概念,iOS中前端时Clang编译器,主要进行词法分析,语法分析生成中间代码,后端通常是llvm编译器,后端编译器把中间代码根据不同架构进行编译生成机器可运行的代码类型为Mach-o类型。
  • 在编译过程我们需要加载一些三方的库和系统库。库通常分为动态库和静态库,动态库在内存中只存在一份,程序使用它的时候直接引用它的映射就可以了,而静态库则要加载到所需的程序,多个程序的话需要加载多份。
  • dyldはAppleのダイナミックリンカーです。最も古いのは、dyld1が事前バインド技術を使用して、対応するライブラリのアドレスを計算し、後で直接ロードすることです。その後、効率を上げるためにdyld1が書き直され、主に共有キャッシュ戦略を採用し、共通ライブラリをファイルにパッケージ化し、最適化し、シンボルテーブルを書き直し、メモリを節約するdyld2になりました。スピードを重視して設計されています。dyld3は、dyld2に基づいて多くのセキュリティポリシーを追加します。
  • dyldのロードプロセスdyldライブラリのstart関数がmain関数に入ると、最初に環境変数を処理し、次に共有キャッシュをロードして、dyld自体をUDIDリストに追加します。メインプログラムをインスタンス化し、挿入されたダイナミックライブラリをロードし、ライブラリをリンクします。すべての初期化ルーチンを実行して、メインプログラムエントリを見つけます。

おおよそのプロセス

dyld加载流程.png

おすすめ

転載: juejin.im/post/6984333280086605837