dyld 简介:dylb 的加载流程

383 阅读6分钟

「这是我参与2022首次更文挑战的第11天,活动详情查看:2022首次更文挑战」。

前言

dyld 这种格式的表示是动态链接的,编译的时候不会被编译到执行文件中,在程序执行的时候才 link,这样就不用算到包的大小里,而且也能够不更新执行程序就能够更新库。

动态库加载器/usr/lib/dyld,__DATA segment的__dyld 是section占位符,用于动态链接器。

I 程序加载:在程序执行 Main 函数之前,都做了哪些事?

  • 当加载 Mach-O 文件时动态链接器会先检查共享内存是否有。每个进程都会在自己地址空间映射这些共享缓存,这样可以优化启动速度
  • 生成可执行文件后就是在启动时进行动态链接了,进行符号和地址的绑定。

首先会加载所依赖的 dylibs,修正地址偏移,因为 iOS 会用 ASLR 来做地址偏移避免攻击,确定 Non-Lazy Pointer 地址进行符号地址绑定,加载所有类; 最后执行 load 方法和 clang attribute 的 constructor 修饰函数。

  • /System/Library/Caches/com.apple.dyld: ios的共享缓存机制
iPhone:/System/Library/Caches/com.apple.dyld root# ls -lrt
total 394928
-rw-r--r-- 1 root wheel 404405507 Oct 14  2014 dyld_shared_cache_armv7s
-rwxr-xr-x 1 root admin         0 Apr 23  2017 enable-dylibs-to-override-cache*

  • ls -lrt /var/db/dyld/ : osx
因为 Fundation 还会依赖一些其它的动态库,其它的库还会再依赖更多的库,这样相互依赖的符号会很多,需要处理的时间也会比较长,这里系统上的动态链接器会使用共享缓存,共享缓存在 /var/db/dyld/
➜  ~ ls -rlt /var/db/dyld/
total 3255808
-rw-r--r--  1 root  wheel   518811648 Nov 27  2017 dyld_shared_cache_i386
-rw-r--r--  1 root  wheel      137234 Nov 27  2017 dyld_shared_cache_i386.map
-rw-r--r--  1 root  wheel  1147727872 Nov 27  2017 dyld_shared_cache_x86_64h
-rw-r--r--  1 root  wheel      291583 Nov 27  2017 dyld_shared_cache_x86_64h.map

1.1 分析dyld_shared_cache_armv7s 中的系统二进制文件

  • dsc_extractor
  • jtool 进行提取: 导出对应的模块
 jtool -extract UIKit dyld_shared_cache_armv7s

  • xcode在有新设备连接的时候,会自动提取系统库到~//Library/Developer/Xcode/iOS\ DeviceSupport

定时清理DeviceSupport

rm -rf ~/Library/Developer/Xcode/iOS\ DeviceSupport/*

➜  Housekeeper git:(develop) cat ~/bin/knclean
#!/bin/sh
# The ~/Library/Developer/Xcode/iOS DeviceSupport folder is basically only needed to symbolicate crash logs.
# You could completely purge the entire folder. Of course the next time you connect one of your devices, Xcode would redownload the symbol data from the device.
# I clean out that folder once a year or so by deleting folders for versions of iOS I no longer support or expect to ever have to symbolicate a crash log for.
killall -9 Xcode
killall -9 com.apple.CoreSimulator.CoreSimulatorService

rm -rf ~/Library/Developer/Xcode/iOS\ DeviceSupport/*
rm -rf ~/Library/Developer/Xcode/DerivedData/*
rm -rf ~/Library/Developer/Xcode/Archives/*
rm -rf ~/Library/Developer/Xcode/Products/*

rm -rf ~/Library/Developer/CoreSimulator/Devices/*

killall -9 com.apple.CoreSimulator.CoreSimulatorService
killall -9 Xcode
rm -rf ~/.Trash/
exit 0%                                                                                                                                                                          ➜  Housekeeper git:(develop) 

1.2 dyld 做了些什么事

程序在运行时会依赖很多系统动态库。系统动态库通过动态库器(/usr/lib/dyld )加载起加载到内存。

  • 1)kernel 做启动程序初始准备,开始由dyld负责。
  • 2)基于非常简单的原始栈为 kernel 设置进程来启动自身。
  • 3)使用共享缓存来处理递归依赖带来的性能问题,ImageLoader 会读取二进制文件,其中包含了我们的类,方法等各种符号。 (共享缓存技术)

共享缓存在系统启动后被加载到内存中,当有新的程序加载时会先到共享缓存里面寻找;如果找到,就直接将共享缓存中的地址映射到目标进程的内存地址空间,极大提高加载效率。

  • 4)立即绑定 non-lazy 的符号并设置用于 lazy bind 的必要表,将这些库 link 到执行文件里。
  • 5)为可执行文件运行静态初始化。
  • 6)设置参数到可执行文件的 main 函数并调用它。
  • 7)在执行期间,通过绑定符号处理对 lazily-bound 符号存根的调用提供 runtime 动态加载服务(通过 dl*() 这个 API ),并为gdb和其它调试器提供钩子以获得关键信息。runtime 会调用 map_images 做解析和处理,load_images 来调用 call_load_methods 方法遍历所有加载了的 Class,按照继承层级依次调用 +load 方法。
load_images(const char *path __unused, const struct mach_header *mh)

获取所有类的列表然后收集其中的 +load 方法;Class 的 +load 是先执行的,然后执行 Category 的

void prepare_load_methods(const headerType *mhdr)


遍历 Class 的 +load 方法时会执行 schedule_class_load 这个方法,这个方法会递归到根节点来满足 Class 收集完整关系树的需求。

void call_load_methods(void)

创建一个 autoreleasePool 使用函数指针来动态调用类和 Category 的 +load 方法

  • 8)在 mian 函数返回后运行 static terminator。 在某些情况下,一旦 main 函数返回,就需要调用 libSystem 的 _exit。

1.3 dylb 的加载流程

  • 从dyldStartup.s 汇编文件开始执行
__dyld_start:
	popl	%edx		# edx = mh of app
	pushl	$0		# push a zero for debugger end of frames marker
	movl	%esp,%ebp	# pointer to base of kernel frame
	andl    $-16,%esp       # force SSE alignment
	subl	$32,%esp	# room for locals and outgoing parameters
  	
	call    L__dyld_start_picbase
L__dyld_start_picbase:	
	popl	%ebx		# set %ebx to runtime value of picbase
  
   	movl	Lmh-L__dyld_start_picbase(%ebx), %ecx # ecx = prefered load address
   	movl	__dyld_start_static_picbase-L__dyld_start_picbase(%ebx), %eax
	subl    %eax, %ebx      # ebx = slide = L__dyld_start_picbase - [__dyld_start_static_picbase]
	addl	%ebx, %ecx	# ecx = actual load address
	# call dyldbootstrap::start(app_mh, argc, argv, slide, dyld_mh, &startGlue)
	call	__ZN13dyldbootstrap5startEPK12macho_headeriPPKclS2_Pm	

1//设置上下文信息、	//配置进程是否受限
2//获取当前运行架构信息
3//加载可执行文件并生成一个ImageLoader实例对象
4//检查共享缓存是否映射到共享区域
5//加载所有DYLD_INSERT_LIBRARIES指定的库
		// load any inserted libraries
		if	( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
			for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 
				loadInsertedDylib(*lib);
		}
6//链接主程序
7//链接插入的动态库,执行符号替换
8//执行初始化方法,+load、constructor方法就是在这里执行的。
  
9、寻找主程序入口,来到我们熟悉的main 函数
  

  • dyld.cpp的ImageLoaderMachO 如下
// 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实例
		ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);
		//将image加到全局sAllImages,并更新mapped range
		//如果设置了DYLD_PRINT_LIBRARIES环境变量,将会打印当前加载的image
		addImage(image);
		return (ImageLoaderMachO*)image;
	}
  	
	throw "main executable not a known format";
}

1.4 环境变量 EnvironmentVariables

// state of all environment variables dyld uses
//
struct EnvironmentVariables {
	const char* const *			DYLD_FRAMEWORK_PATH;
	const char* const *			DYLD_FALLBACK_FRAMEWORK_PATH;
	const char* const *			DYLD_LIBRARY_PATH;
	const char* const *			DYLD_FALLBACK_LIBRARY_PATH;
	const char* const *			DYLD_INSERT_LIBRARIES;
	const char* const *			LD_LIBRARY_PATH;			// for unix conformance
	const char* const *			DYLD_VERSIONED_LIBRARY_PATH;
	const char* const *			DYLD_VERSIONED_FRAMEWORK_PATH;
	bool						DYLD_PRINT_LIBRARIES_POST_LAUNCH;
	bool						DYLD_BIND_AT_LAUNCH;
	bool						DYLD_PRINT_STATISTICS;
	bool						DYLD_PRINT_STATISTICS_DETAILS;
	bool						DYLD_PRINT_OPTS;
	bool						DYLD_PRINT_ENV;
	bool						DYLD_DISABLE_DOFS;
	bool						DYLD_PRINT_CS_NOTIFICATIONS;
                            //  DYLD_SHARED_CACHE_DONT_VALIDATE ==> sSharedCacheIgnoreInodeAndTimeStamp
                            //  DYLD_SHARED_CACHE_DIR           ==> sSharedCacheDir
							//	DYLD_ROOT_PATH					==> gLinkContext.rootPaths
							//	DYLD_IMAGE_SUFFIX				==> gLinkContext.imageSuffix
							//	DYLD_PRINT_OPTS					==> gLinkContext.verboseOpts
							//	DYLD_PRINT_ENV					==> gLinkContext.verboseEnv
							//	DYLD_FORCE_FLAT_NAMESPACE		==> gLinkContext.bindFlat
							//	DYLD_PRINT_INITIALIZERS			==> gLinkContext.verboseInit
							//	DYLD_PRINT_SEGMENTS				==> gLinkContext.verboseMapping
							//	DYLD_PRINT_BINDINGS				==> gLinkContext.verboseBind
							//  DYLD_PRINT_WEAK_BINDINGS		==> gLinkContext.verboseWeakBind
							//	DYLD_PRINT_REBASINGS			==> gLinkContext.verboseRebase
							//	DYLD_PRINT_DOFS					==> gLinkContext.verboseDOF
							//	DYLD_PRINT_APIS					==> gLogAPIs
							//	DYLD_IGNORE_PREBINDING			==> gLinkContext.prebindUsage
							//	DYLD_PREBIND_DEBUG				==> gLinkContext.verbosePrebinding
							//	DYLD_NEW_LOCAL_SHARED_REGIONS	==> gLinkContext.sharedRegionMode
							//	DYLD_SHARED_REGION				==> gLinkContext.sharedRegionMode
							//	DYLD_PRINT_WARNINGS				==> gLinkContext.verboseWarnings
							//	DYLD_PRINT_RPATHS				==> gLinkContext.verboseRPaths
							//	DYLD_PRINT_INTERPOSING			==> gLinkContext.verboseInterposing
							//  DYLD_PRINT_LIBRARIES			==> gLinkContext.verboseLoading
};
  

  • 重要的环境变量
	//如果设置了DYLD_PRINT_OPTS环境变量打印参数
	//如果设置了DYLD_PRINT_ENV环境变量打印环境变量
    

  • 在xocode 中设置环境变量:DYLD_PRINT_STATISTICS、DYLD_PRINT_STATISTICS_DETAILS,打印各阶段消耗的时间

	// dump info if requested
	//DYLD_PRINT_STATISTICS
	if ( sEnv.DYLD_PRINT_STATISTICS )
		ImageLoader::printStatistics((unsigned int)allImagesCount(), initializerTimes[0]);
	//DYLD_PRINT_STATISTICS_DETAILS
	if ( sEnv.DYLD_PRINT_STATISTICS_DETAILS )
		ImageLoaderMachO::printStatisticsDetails((unsigned int)allImagesCount(), initializerTimes[0]);


  • getenv
char * getenv(const char *name) {
  static void *handle;      // 1
  static char * (*real_getenv)(const char *); // 2
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{  // 3
    handle = dlopen("/usr/lib/system/libsystem_c.dylib", RTLD_NOW);
    assert(handle);
    real_getenv = dlsym(handle, "getenv");
});
  if (strcmp(name, "HOME") == 0) { // 4
return "/"; }
  return real_getenv(name); // 5
}


II 例子

由于篇幅原因,更多内容请关注 #小程序:iOS逆向,只为你呈现有价值的信息,专注于移动端技术研究领域;更多服务和咨询请关注#公众号:iOS逆向。