dyld

dyld 简介

Posted by kunnan on July 8, 2018

前言

dylib 这种格式的表示是动态链接的,编译的时候不会被编译到执行文件中,在程序执行的时候才 link,这样就不用算到包的大小里,而且也能够不更新执行程序就能够更新库
  • 通过 otool 可以找到所需库在哪
    ➜  ~ otool -l tmp.arm64                                                                                 
    

程序加载:在程序执行 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
    

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

  • jtool 进行提取

    • 导出对应的模块

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

    • 定时清理DeviceSupport
    ➜  bin git:(master) ✗ cat 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.
    rm -rf /Users/devzkn/Library/Developer/Xcode/iOS\ DeviceSupport/*
    exit 0%  
    
    ➜  bin git:(master) ✗  cd /Users/devzkn/Library/Developer/Xcode/iOS\ DeviceSupport
    ➜  iOS DeviceSupport ls -lrt
    total 0
    drwxr-xr-x  5 devzkn  staff  160 May  4 08:58 9.3.3 (13G34)
    drwxr-xr-x  5 devzkn  staff  160 May  6 14:46 11.3 (15E216)
    drwxr-xr-x  5 devzkn  staff  160 Jul  3 11:49 8.1.1 (12B435)
    drwxr-xr-x  5 devzkn  staff  160 Jul  3 15:00 11.1.1 (15B150)
    drwxr-xr-x  5 devzkn  staff  160 Jul  3 15:10 11.2.5 (15D60)
    drwxr-xr-x  5 devzkn  staff  160 Jul  5 11:06 8.1.2 (12B440)
    drwxr-xr-x  5 devzkn  staff  160 Jul  5 11:26 8.4 (12H143)
    drwxr-xr-x  5 devzkn  staff  160 Jul  6 10:48 8.3 (12F70)
    drwxr-xr-x  5 devzkn  staff  160 Jul  6 19:32 8.2 (12D508)
    drwxr-xr-x  5 devzkn  staff  160 Jul  6 19:34 8.1.3 (12B466)
    drwxr-xr-x  5 devzkn  staff  160 Jul  7 10:42 10.2 (14C92)
    drwxr-xr-x  5 devzkn  staff  160 Jul  8 11:36 8.1 (12B411)
    

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。

dylb 的加载流程

在程序执行 Main 函数之前,都做了哪些事。

  • 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	
    
  • dyld.cpp

    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一些重点的code

  • 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";
    }
      
    

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,打印各阶段消耗的时间

    image

    	// 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]);
      
    

例子

通过 otool 可以找到所需库在哪

* ➜  ~ otool -l tmp.arm64

Load command 13
          cmd LC_LOAD_DYLIB
      cmdsize 80
         name /System/Library/Frameworks/Security.framework/Security (offset 24)
  •          name /usr/lib/libSystem.B.dylib (offset 24)
    1)libdispatch:GCD 2)libsystem_c:C语言库 3)libsystem_blocks:Block 4)libcommonCrypto:加密,比如md5
    
DYLD_PRINT_LIBRARIES
  • 打印什么库被加载了
  ➜  ~ export DYLD_PRINT_LIBRARIES=;/Users/devzkn/Downloads/kevinsoftware/ios-Reverse_Engineering/llvm-3.9.0.src/build/CMakeFiles/3.10.0-rc4/CompilerIdCXX/a.out 
  dyld: loaded: /Users/devzkn/Downloads/kevinsoftware/ios-Reverse_Engineering/llvm-3.9.0.src/build/CMakeFiles/3.10.0-rc4/CompilerIdCXX/a.out
  dyld: loaded: /usr/lib/libc++.1.dylib
  dyld: loaded: /usr/lib/libSystem.B.dylib
  dyld: loaded: /usr/lib/libc++abi.dylib
  dyld: loaded: /usr/lib/system/libcache.dylib
  dyld: loaded: /usr/lib/system/libcommonCrypto.dylib
  dyld: loaded: /usr/lib/system/libcompiler_rt.dylib
  dyld: loaded: /usr/lib/system/libcopyfile.dylib
  dyld: loaded: /usr/lib/system/libcorecrypto.dylib
  dyld: loaded: /usr/lib/system/libdispatch.dylib
  dyld: loaded: /usr/lib/system/libdyld.dylib
  dyld: loaded: /usr/lib/system/libkeymgr.dylib
  dyld: loaded: /usr/lib/system/liblaunch.dylib
  dyld: loaded: /usr/lib/system/libmacho.dylib
  dyld: loaded: /usr/lib/system/libquarantine.dylib
  dyld: loaded: /usr/lib/system/libremovefile.dylib
  dyld: loaded: /usr/lib/system/libsystem_asl.dylib
  dyld: loaded: /usr/lib/system/libsystem_blocks.dylib
  dyld: loaded: /usr/lib/system/libsystem_c.dylib
  dyld: loaded: /usr/lib/system/libsystem_configuration.dylib
  dyld: loaded: /usr/lib/system/libsystem_coreservices.dylib
  dyld: loaded: /usr/lib/system/libsystem_darwin.dylib
  dyld: loaded: /usr/lib/system/libsystem_dnssd.dylib
  dyld: loaded: /usr/lib/system/libsystem_info.dylib
  dyld: loaded: /usr/lib/system/libsystem_m.dylib
  dyld: loaded: /usr/lib/system/libsystem_malloc.dylib
  dyld: loaded: /usr/lib/system/libsystem_network.dylib
  dyld: loaded: /usr/lib/system/libsystem_networkextension.dylib
  dyld: loaded: /usr/lib/system/libsystem_notify.dylib
  dyld: loaded: /usr/lib/system/libsystem_sandbox.dylib
  dyld: loaded: /usr/lib/system/libsystem_secinit.dylib
  dyld: loaded: /usr/lib/system/libsystem_kernel.dylib
  dyld: loaded: /usr/lib/system/libsystem_platform.dylib
  dyld: loaded: /usr/lib/system/libsystem_pthread.dylib
  dyld: loaded: /usr/lib/system/libsystem_symptoms.dylib
  dyld: loaded: /usr/lib/system/libsystem_trace.dylib
  dyld: loaded: /usr/lib/system/libunwind.dylib
  dyld: loaded: /usr/lib/system/libxpc.dylib
  dyld: loaded: /usr/lib/closure/libclosured.dylib
  dyld: loaded: /usr/lib/libobjc.A.dylib
  

越狱检测

  • <mach-o/dyld.h>

    #import <mach-o/dyld.h>  
      
    int count = _dyld_image_count();//获得加载的动态库的数量
    for (int i=0; i<count; i++) {
        printf("%s", _dyld_get_image_name(i));//获得名字,然后遍历他们的名字,看看有没有 “MobileSubstrate” 关键字,有的话就是越狱的
        // 如果 _dyld_get_image_name() 里面包含 MobileSubstrate 就是越狱了
    }
    

Section

    • 动态库加载器/usr/lib/dyld

    • __DATA segment 的__dyld 是section占位符,用于动态链接器。
      

符号表

每个函数,全局变量和类都是通过符号的形式来定义和使用的,当把目标文件(.o)链接成一个执行文件(.out)时, 链接器在目标文件和动态库之间对符号做解析处理.

  • 符号表

  • 链接器通过动态库解析成符号会记录是通过哪个动态库解析的,路径也会一起记录

    ➜  ~ nm -nm tmp.arm64
    0000000000006e80 (__TEXT,__text) non-external -[ASSwitchIPOperation initWithTimeOut:Operation:tryTimes:]
    (undefined) external _CFDataCreate (from CoreFoundation)
    undefined 符号表示该文件类未实现的,所以在目标文件和 Fundation framework 动态库做链接处理时,链接器会尝试解析所有的 undefined 符号
    

See Also

/Users/devzkn/bin//knpost dyld dyld 简介 -t dyld
#原来""的参数,需要自己加上""

转载请注明: > dyld