El principio subyacente: el proceso de carga del enlazador dinámico de 12 dyld

1.Introducción a dyld

dyldPertenece a Apple 动态连接器y es un componente importante del sistema operativo de Apple.Después de compilar el sistema, se entrega a dylb para vincularlo y las bibliotecas dependientes se generan en archivos ejecutables. El nombre completo en inglés es: the dynamic link editorBrevemente presente la historia de dyld de Apple

  • dyld1: dyld 1 y enviado en 1996 como parte de NeXTStep 3.3. Antes de eso, NeXT usaba binarios estáticos. Vale la pena señalar que esto es anterior a la estandarización de las llamadas dlopen de POSIX. Ahora, dlopen existe en algunos Unixes. Son extensiones propietarias que la gente adoptó más tarde. Hay diferentes extensiones propietarias para NeXTStep, por lo que la gente escribió envoltorios de terceros en versiones anteriores de macOS 10 para admitir el software estándar de Unix. El problema es que no soportan una semántica homogénea. Hay algunos casos extremos extraños que no funcionan y terminan siendo un poco lentos. Antes del lanzamiento de macOS 10.0, Cheetah, también agregamos otra función, es decir, 预绑定. La vinculación previa es una técnica que probaremos 为系统中的每个dylib和您的应用程序找到固定地址,动态加载程序将所有内容加载到这些地址,如果成功,它将编辑所有这些二进制文件para incluir estas direcciones precalculadas y luego, la próxima vez que las coloque en la misma dirección, no necesita hacer ningún trabajo adicional.
  • dyld2: dyld 2 es de dyld 完全重写. Es compatible correctamente con la semántica del inicializador de C++, por lo que ampliamos ligeramente el formato mach-o y actualizamos dyld para una compatibilidad eficiente con la biblioteca de C++. También tiene una implementación nativa completa de dlopen y dlsym con la semántica correcta, no recomendamos usar la API heredada en este momento. Todavía están en macOS. Nunca se envían en ninguna de nuestras otras plataformas. Lo es 为速度而设计, ya que fue diseñado para la velocidad, por lo que es 理智检查有限. No tenemos el entorno de malware que tenemos hoy. Finalmente, mejoramos el rendimiento, porque mejoramos el rendimiento, pudimos deshacernos del enlace previo y reemplazarlo con el llamado 共享缓存. Entonces, ¿qué es un caché compartido? Bueno, se introdujo en iOS 3.1 y macOS Snow Leopard, y reemplazó por completo el enlace previo. Este es un único archivo que contiene la mayoría de los dylibs del sistema. Ya que los ponemos 合并到一个文件, podemos hacer ciertos tipos de optimizaciones. Podemos reorganizar todos sus segmentos de texto y todos los segmentos de datos y 重写他们的整个符号表, para reducir el tamaño, por lo que necesitamos 每个进程中装载更少的区域. También nos permite 打包二进制段y ahorra mucha memoria. En realidad, es un preenlazador para dylibs.
  • dyld3: dyld 3 es un nuevo enlazador dinámico que anunciamos hoy. Es un replanteamiento completo de cómo hacemos enlaces dinámicos, con la mayoría de las aplicaciones del sistema macOS activadas de forma predeterminada en la semilla de esta semana 2017年苹果操作系统y todas las aplicaciones del sistema en la plataforma 程序默认打开. 安全. Entonces, como dije, adaptamos muchas características de seguridad al dyld 2, pero es realmente difícil agregar esas cosas después del hecho. Creo que lo hemos hecho bien en los últimos años, pero es muy difícil de hacer. Entonces, ¿podemos ser más agresivos 安全检查y prediseñar la seguridad? Finalmente, testabilidad y confiabilidad. ¿Se puede hacer que dyld sea más fácil de probar? Entonces, Apple presenta toneladas de excelentes marcos de prueba como XCTest que debe usar y debemos usar, pero se basan en la funcionalidad de bajo nivel del enlazador dinámico para insertar estas bibliotecas en el proceso, por lo que básicamente no se pueden usar para probar el código dyld existente. , lo que también nos dificulta probar las funciones de seguridad y rendimiento. Entonces, ¿Cómo lo hacemos? Bueno, eso lo hemos puesto 大部分染料移到了流程之外. En este momento, es básicamente un demonio normal que podemos probar como todos los demás usando herramientas de prueba estándar, lo que nos permitirá mejorarlo más rápido en el futuro. También permite mantener los bits dyld del proceso lo más pequeños posible y reducidos 应用程序中的攻击表面. También acelera el inicio, 因为最快的代码是您从未编写过的代码,紧随其后的是您几乎从未执行的代码. dyld 3 está fuera de proceso mach-o解析器.

Solo un resumen, puedes ver WWDC17 : Hora de lanzamiento de la aplicación: pasado, presente y futuro

1.1 Compilar

En el desarrollo de iOS, necesitamos compilar si queremos ejecutar el programa. De lo contrario, se informará un error y no se podrá ejecutar, ya sea que el comando +b sea exitoso. Los lenguajes de alto nivel que escribimos generalmente encapsulan los lenguajes básicos y, para oc, encapsulan c/c++ en lenguajes de desarrollo orientados a objetos. Al encapsular muchas API, se ha mejorado nuestra eficiencia de desarrollo.
Usamos un compilador para convertir nuestro lenguaje de programación en código de máquina, que se ejecuta en la CPU, lo que hace que nuestra aplicación se ejecute de manera más eficiente.
La composición del compilador: 前段和后端.

  • Front-end: análisis de sintaxis, este método analiza y genera código intermedio.
  • 后端:以中间代码为输入,根据不同架构生成不同机器代码。

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

archivo sin nombre.jpg

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

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

Captura de pantalla 2021-07-08 6.10.56 pm.png

2.dyld 流程分析

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

Captura de pantalla 2021-07-09 3.09.31 pm.png 可以发现dyld 是_dyld_start开始的,我们下载一份最新的dyld代码进行查看。

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

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

Captura de pantalla 2021-07-09 3.23.08 pm.png 其中注释告诉我们这个方法会进入dyldbootstrap::start,全局搜索dyldbootstrap找到后在搜索start得到:

Captura de pantalla 2021-07-09 4.08.28 pm.png 这个方法的关注return dyld::_main,dyld调用了main函数,传入参数macho_headerMach-o(可执行文件)的头部文件。

第一步环境变量相关处理

Captura de pantalla 2021-07-09 5.53.34 pm.png Captura de pantalla 2021-07-09 5.59.29 pm.png 主要是进行环境变量的处理,检查是否配置,没有的话使用默认值。

第二步加载共享缓存

Captura de pantalla 2021-07-09 6.11.56 pm.png 检查共享缓存是否开启,以及是否映射到公共区域。

第三步dyld添加到UUID列表

Captura de pantalla 2021-07-09 6.21.27 pm.png Captura de pantalla 2021-07-09 6.20.41 pm.png 把dyld添加到UUID列表

第四步实例化主程序

Captura de pantalla 2021-07-11 8.42.20 am.png 在映射可执行文件之前,需要制作一个ImageLoader*为已经映射在主可执行文件。

第五步 插入动态库

Captura de pantalla 2021-07-11 8.54.39 am.png for循环加载所有DYLD_INSERT_LIBRARIES的库,调用loadInsertedDylib方法,记录插入的数量,插入库之后main,然后是其他。

第六步 链接主程序

Captura de pantalla 2021-07-13 5.05.50 pm.png

第七步 链接所有插入的库

Captura de pantalla 2021-07-13 5.07.35 pm.png

第八步 弱符号绑定

Captura de pantalla 2021-07-12 11.17.15 a.m.png

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

Captura de pantalla 2021-07-12 11.18.43 a.m.png

第十步 找到主程序的入口

Captura de pantalla 2021-07-12 11.20.32 am.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);
}
复制代码

断点验证下

Captura de pantalla 2021-07-12 5.25.36 pm.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文件验证一下

Captura de pantalla 2021-07-12 5.47.00 pm.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 es el enlazador dinámico de Apple. La primera es que dyld1 usa tecnología de enlace previo para calcular la dirección de la biblioteca correspondiente y cargarla directamente más tarde. Más tarde, para mejorar la eficiencia, se reescribió dyld1 y se convirtió en dyld2, que adoptó principalmente la estrategia de caché compartida, empaquetó la biblioteca común en un archivo, la optimizó, reescribió la tabla de símbolos y ahorró memoria. Diseñado para la velocidad. dyld3 agrega muchas políticas de seguridad sobre la base de dyld2.
  • Proceso de carga de dyld Después de que la función de inicio de la biblioteca dyld ingrese a la función principal, primero procesa las variables de entorno, luego carga el caché compartido y agrega el propio dyld a la lista UDID. Cree una instancia del programa principal, cargue las bibliotecas dinámicas insertadas, vincule las bibliotecas. Ejecute todas las rutinas de inicialización para encontrar la entrada principal del programa.

Proceso aproximado

dyld加载流程.png

Supongo que te gusta

Origin juejin.im/post/6984333280086605837
Recomendado
Clasificación