Mach-O是什么 Mach-O 是 Mach object 文件格式的缩写,它是一种用于记录可执行文件、对象代码、共享库、动态加载代码和内存转储的文件格式。类似于windows上的PE文件以及linux上的ELF文件。作为 a.out 格式的替代品,Mach-O提供更多的可扩展性和更快的符号表信息存取。大多数基于 Mach 内核的操作系统都使用 Mach-O。NeXTSTEP、OS X 和 iOS 是使用这种格式作为本地可执行文件、库和对象代码的例子。
Mach-O文件包含固定大小的头部(Header),几个可变大小的加载命令(LoadCommands),一个或多个代码段(Segment),每个代码段可以包含一个或多个代码区(Section)。
胖二进制文件 为了使文件能运行在多个平台,出现了胖二进制文件(Fat Binary),又叫通用二进制文件(Universal Binary)。使用lipo命令可以列出胖文件中的体系结构类型。胖二进制文件实际上是一个包装器(Wrapper),一种将用于多种体系结构的Mach-O文件连接起来的简单存档。
胖二进制文件由胖头部(Fat Header)以及后面的 Mach-O 文件组成。胖头部包含一个幻数,后接一个整数值,表示二进制文件驻留在胖文件中的体系结构数量。
1 2 3 4 5 6 7 #define FAT_MAGIC 0xcafebabe #define FAT_CIGAM 0xbebafeca /* NXSwapLong(FAT_MAGIC) */ struct fat_header { uint32_t magic; /* FAT_MAGIC 标记,表示是Fat的文件类型,是固定的0xcafebabe或者是0xbebafeca */ uint32_t nfat_arch; /* number of structs that follow 包含了多个少Mach-O文件 */ };
再后面是一系列胖体系结构指示符(fat_arch),每个指示符用于胖文件中包含的一种体系结构。
1 2 3 4 5 6 7 struct fat_arch { cpu_type_t cputype; /* cpu specifier (int) 支持的CPU类型,一般有ARMV7,ARM64,X86,X86_64这几种类型 */ cpu_subtype_t cpusubtype; /* machine specifier (int) 子CPU类型*/ uint32_t offset; /* file offset to this object file 当前架构的 Mach-O 文件的数据相对于文件开头的偏移位置 */ uint32_t size; /* size of this object file 数据的大小*/ uint32_t align; /* alignment as a power of 2 数据的内存对齐边界 */ };
offset 在armv7中是16384(0x4000),arm64中是4294967296 (0x10000 0000)
注意:尽管胖文件中的Mach-O文件遵循其所在体系结构的字节序,但fat_header和fat_arch总是大端字节储存。 MacOS是小端序的。
合并和瘦身 Xcode 在编译 iOS 程序可以选择同时支持 ARMV7 和 ARM64,编译 macOS 程序也可以选择同时支持 x86 和 x86_64,但是如果一个程序需要同时支持 iOS 和 macOS 的时候,Xcode 不能自动生成,可以使用 lipo 命令手动对文件进行合并。
1 $lipo -create test_iPhone test_macOS -output test_all
由于每个 CPU 平台都是单独的一个 Mach-O 文件,然后合成的 Fat 文件,所以体积会变大,比如某个程序我们只需要支持 ARM64,就可以把其他平台给移除掉,这样就能起到 “瘦身” 的作用,使用 lipo 命令移除其他平台。
1 $lipo -thin arm64 ~/debugserver -output ~/debugserver
Mach-O结构 Mach-O 没有类似于 XML、YAML、JSON 等诸如此类的特殊格式,它只是一个二进制字节流,被划分为了有意义的数据块。这些块包含元信息,比如,字节顺序、cpu 类型、块的大小,等等。
典型的 Mach-O 文件包含三个区域:
Header-包含该二进制文件的一般信息:字节顺序、(魔数)、cpu 类型、LoadCommands的数量等等。
LoadCommands -它是一张包含很多内容的表,内容包括区域的位置、符号表、动态符号表等。每个加载指令都包含一个元信息,比如指令类型、名称、在二进制文件中的位置等等。加载Mach-O文件时会使用这里的数据来确定内存的分布
Data-通常是对象文件中最大的部分。每一个segment的具体数据都保存在这里。主要包含代码、数据,例如符号表,动态符号表等等。
Headers的主要作用就是帮助系统迅速的定位Mach-O文件的运行环境,以下是mach_header与mach_header_64的定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 /* * The 32-bit mach header appears at the very beginning of the object file for * 32-bit architectures. */ struct mach_header { uint32_t magic; /* mach magic number identifier 和 fat_hader 里的 magic 类似,也是一个标记,32 位的值是 MH_MAGIC,64位的值是 MH_CIGAM_64。 */ cpu_type_t cputype; /* cpu specifier 与 fat_arch 里的 cputype 的含义一样。*/ cpu_subtype_t cpusubtype; /* machine specifier 与 fat_arch 里的 cpusubtype 的含义一样*/ uint32_t filetype; /* type of file 可执行文件就是 MH_EXECUTE,如果是动态库就是 MH_DYLIB */ uint32_t ncmds; /* number of load commands 表示 Mach-O 文件中 load command (加载命令)的个数 */ uint32_t sizeofcmds; /* the size of all the load commands 表示load command (加载命令) 占用的字节总大小 */ uint32_t flags; /* flags 表示dyld加载时文件的标志信息*/ }; /* Constant for the magic field of the mach_header (32-bit architectures) */ #define MH_MAGIC 0xfeedface /* the mach magic number */ #define MH_CIGAM 0xcefaedfe /* NXSwapInt(MH_MAGIC) */ /* * The 64-bit mach header appears at the very beginning of object files for * 64-bit architectures. */ struct mach_header_64 { uint32_t magic; /* mach magic number identifier */ cpu_type_t cputype; /* cpu specifier */ cpu_subtype_t cpusubtype; /* machine specifier */ uint32_t filetype; /* type of file */ uint32_t ncmds; /* number of load commands */ uint32_t sizeofcmds; /* the size of all the load commands */ uint32_t flags; /* flags */ uint32_t reserved; /* reserved 系统保留字段 */ }; /* Constant for the magic field of the mach_header_64 (64-bit architectures) */ #define MH_MAGIC_64 0xfeedfacf /* the 64-bit mach magic number */ #define MH_CIGAM_64 0xcffaedfe /* NXSwapInt(MH_MAGIC_64) */
Mach-O文件不仅仅用来实现可执行文件,同时还用来实现了其他内容。
filetype: ``` #define MH_OBJECT 0x1 /* relocatable object file 一个中间的,可重定位的目标文件,也可用于内核扩展(传统具有.o后缀)/ #define MH_EXECUTE 0x2 / demand paged executable file 一个标准的按需分页的可执行文件*/ #define MH_FVMLIB 0x3 /* fixed VM shared library file CoreDump / #define MH_CORE 0x4 / core file 用于存储中止程序的地址空间的文件,包含”核心转储“的核心文件(core file)/ #define MH_PRELOAD 0x5 / preloaded executable file / #define MH_DYLIB 0x6 / dynamically bound shared library 一个动态共享库*/ #define MH_DYLINKER 0x7 /* dynamic link editor 一个特殊共享库,是一个动态链接器 / #define MH_BUNDLE 0x8 / dynamically bound bundle file 在运行时以编程方式加载进应用程序中的插件代码*/ #define MH_DYLIB_STUB 0x9 /* shared library stub for static / / linking only, no section contents / #define MH_DSYM 0xa / companion file with only debug / / sections / #define MH_KEXT_BUNDLE 0xb / x86_64 kexts 内核扩展文件*/
1 2 3 4 Mach-O headers还包含了一些很重要的dyld的加载参数 flags:
/* Constants for the flags field of the mach_header / #define MH_NOUNDEFS 0x1 / the object file has no undefinedreferences 目标文件没有未定义的符号,不存在链接依赖*/ #define MH_INCRLINK 0x2 /* the object file is the output of an incremental link against a base file and can’t be link edited again / #define MH_DYLDLINK 0x4 / the object file is input for the 目标文件是动态链接输入文件,不能被再次静态链接 dynamic linker and can’t be staticly link edited again / #define MH_BINDATLOAD 0x8 / the object file’s undefined references are bound by the dynamic linker when loaded. / #define MH_PREBOUND 0x10 / the file has its dynamic undefined references prebound. / #define MH_SPLIT_SEGS 0x20 / the file has its read-only and 只读 segments 和 可读写 segments 分离 read-write segments split / #define MH_LAZY_INIT 0x40 / the shared library init routine is to be run lazily via catching memory faults to its writeable segments (obsolete) / #define MH_TWOLEVEL 0x80 / the image is using two-level name space bindings */ /*MH_NO_HEAP_EXECUTION 堆内存不可执行 MH_PIE 允许随机的地址空间 MH_ALLOW_STACK_EXECUTION 栈内存可执行代码,一般是默认关闭的。 MH_NO_HEAP_EXECUTION 堆内存无法执行代码 */
1 2 3 4 5 6 7 8 9 ## LoadCommands Headers 之后就是 Load Commands,其占用的内存和加载命令的总数在 Headers 中已经指出。 load_command 数据结构: ``` struct load_command { uint32_t cmd; /* type of load command 加载命令类型 */ uint32_t cmdsize; /* total size of command in bytes 命令的总大小,以字节为单位*/ };
cmd结构类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 /* Constants for the cmd field of all load commands, the type */ #define LC_SEGMENT 0x1 /* segment of this file to be mapped */ #define LC_SYMTAB 0x2 /* link-edit stab symbol table info */ #define LC_SYMSEG 0x3 /* link-edit gdb symbol table info (obsolete) */ #define LC_THREAD 0x4 /* thread */ #define LC_UNIXTHREAD 0x5 /* unix thread (includes a stack) */ #define LC_LOADFVMLIB 0x6 /* load a specified fixed VM shared library */ #define LC_IDFVMLIB 0x7 /* fixed VM shared library identification */ #define LC_IDENT 0x8 /* object identification info (obsolete) */ #define LC_FVMFILE 0x9 /* fixed VM file inclusion (internal use) */ #define LC_PREPAGE 0xa /* prepage command (internal use) */ #define LC_DYSYMTAB 0xb /* dynamic link-edit symbol table info */ #define LC_LOAD_DYLIB 0xc /* load a dynamically linked shared library */ #define LC_ID_DYLIB 0xd /* dynamically linked shared lib ident */ #define LC_LOAD_DYLINKER 0xe /* load a dynamic linker */ #define LC_ID_DYLINKER 0xf /* dynamic linker identification */ #define LC_PREBOUND_DYLIB 0x10 /* modules prebound for a dynamically */ /* linked shared library */ #define LC_ROUTINES 0x11 /* image routines */ #define LC_SUB_FRAMEWORK 0x12 /* sub framework */ #define LC_SUB_UMBRELLA 0x13 /* sub umbrella */ #define LC_SUB_CLIENT 0x14 /* sub client */ #define LC_SUB_LIBRARY 0x15 /* sub library */ #define LC_TWOLEVEL_HINTS 0x16 /* two-level namespace lookup hints */ #define LC_PREBIND_CKSUM 0x17 /* prebind checksum */ /* * load a dynamically linked shared library that is allowed to be missing * (all symbols are weak imported). */ #define LC_LOAD_WEAK_DYLIB (0x18 | LC_REQ_DYLD) #define LC_SEGMENT_64 0x19 /* 64-bit segment of this file to be mapped */ #define LC_ROUTINES_64 0x1a /* 64-bit image routines */ #define LC_UUID 0x1b /* the uuid */ #define LC_RPATH (0x1c | LC_REQ_DYLD) /* runpath additions */ #define LC_CODE_SIGNATURE 0x1d /* local of code signature */ #define LC_SEGMENT_SPLIT_INFO 0x1e /* local of info to split segments */ #define LC_REEXPORT_DYLIB (0x1f | LC_REQ_DYLD) /* load and re-export dylib */ #define LC_LAZY_LOAD_DYLIB 0x20 /* delay load of dylib until first use */ #define LC_ENCRYPTION_INFO 0x21 /* encrypted segment information */ #define LC_DYLD_INFO 0x22 /* compressed dyld information */ #define LC_DYLD_INFO_ONLY (0x22|LC_REQ_DYLD) /* compressed dyld information only */ #define LC_LOAD_UPWARD_DYLIB (0x23 | LC_REQ_DYLD) /* load upward dylib */ #define LC_VERSION_MIN_MACOSX 0x24 /* build for MacOSX min OS version */ #define LC_VERSION_MIN_IPHONEOS 0x25 /* build for iPhoneOS min OS version */ #define LC_FUNCTION_STARTS 0x26 /* compressed table of function start addresses */ #define LC_DYLD_ENVIRONMENT 0x27 /* string for dyld to treat like environment variable */ #define LC_MAIN (0x28|LC_REQ_DYLD) /* replacement for LC_UNIXTHREAD */ #define LC_DATA_IN_CODE 0x29 /* table of non-instructions in __text */ #define LC_SOURCE_VERSION 0x2A /* source version used to build binary */ #define LC_DYLIB_CODE_SIGN_DRS 0x2B /* Code signing DRs copied from linked dylibs */ #define LC_ENCRYPTION_INFO_64 0x2C /* 64-bit encrypted segment information */ #define LC_LINKER_OPTION 0x2D /* linker options in MH_OBJECT files */ #define LC_LINKER_OPTIMIZATION_HINT 0x2E /* optimization hints in MH_OBJECT files */ #ifndef __OPEN_SOURCE__ #define LC_VERSION_MIN_TVOS 0x2F /* build for AppleTV min OS version */ #endif /* __OPEN_SOURCE__ */ #define LC_VERSION_MIN_WATCHOS 0x30 /* build for Watch min OS version */
结构
介绍
LC_SEGMENT、LC_SEGMENT_64
将 segment 映射到进程的内存空间,
LC_ID_DYLIB
动态库
LC_UUID
二进制文件 id,与符号表 uuid 对应,可用作符号表匹配
LC_LOAD_DYLINKER
启动动态加载器,
LC_SYMTAB
描述在 __LINKEDIT 段的哪找字符串表、符号表
LC_CODE_SIGNATURE
代码数字签名等
LC_THREAD
开启一个MACH线程,但是不分配栈空间
LC_UNIXTHREAD
开启一个UNIX线程
LC_ENCRYPTION_INFO
加密二进制文件
LC_RPATH
程序运行时的查找路径
LC_VERSION_MIN_IPHONEOS
支持最低的 iOS 版本号
LC_MAIN
记录了可执行文件的主函数main()的位置
LC_MAIN加载命令中的Entry Offset字段+基地址(RVA选项下的文件头部地址)= IDA中左侧函数_main的地址。
1 2 3 4 5 6 struct entry_point_command { uint32_t cmd; /* LC_MAIN only used in MH_EXECUTE filetypes */ uint32_t cmdsize; /* 24 */ uint64_t entryoff; /* file (__TEXT) offset of main() main() 函数的文件偏移*/ uint64_t stacksize;/* if not zero, initial stack size */ };
Segment & Section 加载数据时,主要加载的就是LC_SEGMET
或LC_SEGMENT_64
。LC_SEGMENT
的数据结构是这样的。这里大部分的数据是用来帮助内核将Segment映射到虚拟内存的
segment_command:
1 2 3 4 5 6 7 8 9 10 11 12 13 struct segment_command { /* for 32-bit architectures */ uint32_t cmd; /* LC_SEGMENT */ uint32_t cmdsize; /* includes sizeof section structs */ char segname[16]; /* segment name 段的名称,常见的段名称有 __PAGEZERO、__LINKEDIT、__TEXT、__DATA*/ uint32_t vmaddr; /* memory address of this segment 段要加载的虚拟内存地址 未偏移),由于 ALSR,程序会在进程加上一段偏移量(slide),真实的地址 = vm address + slide*/ uint32_t vmsize; /* memory size of this segment 段所占的虚拟内存的大小*/ uint32_t fileoff; /* file offset of this segment 段数据所有的文件中的偏移地址*/ uint32_t filesize; /* amount to map from the file 段数据的大小*/ vm_prot_t maxprot; /* maximum VM protection 页面所需要的最高内存保护*/ vm_prot_t initprot; /* initial VM protection 页面初始的内存保护*/ uint32_t nsects; /* number of sections in segment 标示了Segment中有多少secetion */ uint32_t flags; /* flags 段的标志信息*/ };
1 2 3 4 5 __PAGEZERO : 可执行文件有的,动态库里没有。这个段开始地址为0(NULL指针指向的位置),是一个不可读、不可写、不可执行的空间,能够在空指针访问时抛出异常。这个段的大小,32位上是 0x4000,64位上是 4G。 __TEXT:代码段,里面主要是存放代码的,该段是可读可执行,但是不可写。 __DATA :数据段,里面主要是存放数据,该段是可读可写,但不可执行。 __LINKEDIT :用于存放签名信息,该段是只可读,不可写不可执行。 包含需要被动态链接器使用的信息,包括符号表、字符串表、重定位项表等 __OBJC 包含会被Objective Runtime使用到的一些数据
section:
1 2 3 4 5 6 7 8 9 10 11 12 13 struct section { /* for 32-bit architectures */ char sectname[16]; /* name of this section section的名称,最长 16 字节大小*/ char segname[16]; /* segment this section goes in 节区所在的段名*/ uint32_t addr; /* memory address of this section 节区所在的内存地址*/ uint32_t size; /* size in bytes of this section 节区所在的大小*/ uint32_t offset; /* file offset of this section 节区所在文件偏移*/ uint32_t align; /* section alignment (power of 2) 节区的内存对齐边界*/ uint32_t reloff; /* file offset of relocation entries 重定位信息的文件偏移*/ uint32_t nreloc; /* number of relocation entries 重定位条目的个数 */ uint32_t flags; /* flags (section type and attributes) 节区的标志属性 如果是 SG_PROTECTED_VERSION_1,表示该段是经过加密的*/ uint32_t reserved1; /* reserved (for offset or index) */ uint32_t reserved2; /* reserved (for count or sizeof) */ };
TEXT 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 _text:主程序代码 __cstring: 硬编码的字符串 去重后的C字符串 const 初始化过的常量 __stubs 桩代码 符号桩。本质上是一小段会直接跳入lazybinding的表对应项指针指向的地址的代码。 __stub_helper:辅助函数。上述提到的lazybinding的表中对应项的指针在没有找到真正的符号地址的时候,都指向这。 __symbolstub1:用于动态链接的存根 unwind_info:用于存储处理异常情况信息 __objc_methname:Objective-C的方法名 __objc_classname:Objective-C的类名 __cstring:硬编码的字符串 _DATA __data 初始化可变的数据 const 没有初始化过的常量 __objc_imageinfo 镜像信息 ,在运行时初始化时 objc_init,调用 load_images 加载新的镜像到 infolist 中 __lazy_symbol:懒加载,延迟加载节,通过dyld_stub_binder辅助链接 _got:存储引用符号的实际地址,类似于动态符号表 __la_symbol_ptr: lazy-binding的指针表,每个表项中的指针一开始指向stub_helper __nl_symbol_ptr:非lazy-binding的指针表,每个表项中的指针都指向一个在装载过程中,被动态链机器搜索完成的符号 __mod_init_func:初始化的全局函数地址,在主要之前被调用 初始化函数,在main之前调用 __mod_term_func:结束函数地址 终止函数,在main返回之后调用 __cfstring:Core Foundation用到的字符串(OC字符串) __objc_classlist 类列表 __objc_classrefs 引用的类
Symbol Table & String Table Symbol 表的头信息是在 LoadCommand 里的 LC_SYMTAB,其中 symoff 表示符号表的偏移。符号表的结构是一个连续的列表,其中的每一项都是一个 struct nlist。
nlist:
1 2 3 4 5 6 7 8 9 struct nlist { union { uint32_t n_strx; /*符号名在字符串表中的偏移量 */ } n_un; uint8_t n_type; uint8_t n_sect; /*节的索引*/ int16_t n_desc; uint32_t n_value; /*函数对应的地址*/ };
动态库载入信息 Dynamic Loader Info
入口函数 Function Starts
符号表 Symbol Table
动态库符号表 Dynamic Symbol Table
字符串表 String Table
Mach-O执行 当你点击一个icon启动应用程序的时候,系统在内部大致做了如下几件事:
内核(OS Kernel)创建一个进程,分配虚拟的进程空间等等,加载动态链接器。
通过动态链接器加载主二进制程序引用的库、绑定符号。
启动程序
内核处理流程 二进制文件加载过程 下面列出来在加载二进制文件过程中依次调用的函数
1 2 3 4 5 6 7 execve __mac_execve exec_activate_image exec_mach_imgact load_machfile parse_machfile load_dylinker
加载dyld过程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 load_dylinker{ get_macho_vnode{ //读取dyld的fat_header vn_rdwr{} //读取dyld的mach_header vn_rdwr{} } parse_machfile{ //Map the load commands into kernel memory. vn_rdwr{} load_segment{ //这里进行了slide偏移, 并且在对_TEXT segment 进行映射时重新定位了, result->mach_header, 这个的原理像elf的segment加载时, 把elf-header算在第一个segment上. map_segment{} } } }
dyld处理流程 dyld 的处理过程在 dyld.cpp, 从 LC_MAIN 拿到地址后转到 dyld.cpp/_main()
执行
一切的开始是dyldbootstrap::start这个函数.它去调用了dyld::main函数.这个函数从外部传入Mach-O的header,在dyld::main中,dyld会去设置运行环境,配置相关的环境变量.
处理环境变量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 uintptr_t _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, int argc, const char* argv[], const char* envp[], const char* apple[], uintptr_t* startGlue) { [...] configureProcessRestrictions(mainExecutableMH); #if __MAC_OS_X_VERSION_MIN_REQUIRED if ( gLinkContext.processIsRestricted ) { pruneEnvironmentVariables(envp, &apple); // set again because envp and apple may have changed or moved setContext(mainExecutableMH, argc, argv, envp, apple); } else #endif { checkEnvironmentVariables(envp); defaultUninitializedFallbackPaths(envp); } configureProcessRestrictions //对 ios 和 osx 做了区分, ios 默认不支持任何环境变量 | checkEnvironmentVariables //检查环境变量, 之后调用下一个函数做处理 | processDyldEnvironmentVariable //处理环境变量, 设置gLinkContext
这里有一个关键的过程 setContext(mainExecutableMH, argc, argv, envp, apple); 设置上下文需要使用到的全局变量.
解析macho执行文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 uintptr_t _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, int argc, const char* argv[], const char* envp[], const char* apple[], uintptr_t* startGlue) { [...] // instantiate ImageLoader for main executable sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath); gLinkContext.mainExecutable = sMainExecutable; gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH); 从 instantiateFromLoadedImage 开始解析 instantiateFromLoadedImage // 检查文件格式, 加载主可执行文件, 记录该image到全局环境变量 instantiateFromLoadedImage:dyld.cpp { // 处理加载命令, 根据加载命令处理加载可执行文件 ImageLoaderMachO::instantiateMainExecutable:ImageLoaderMachO.cpp { // 处理, 区分加载命令 ImageLoaderMachO::sniffLoadCommands:ImageLoaderMachO.cpp // 根据加载命令, 开始加载可执行文件 ImageLoaderMachOCompressed::instantiateMainExecutable:ImageLoaderMachOCompressed.cpp { // 创建ImageLoaderMachOCompressed对象 ImageLoaderMachOCompressed::instantiateStart:ImageLoaderMachOCompressed.cpp // 根据加载命令填充ImageLoaderMachOCompressed对象 ImageLoaderMachOCompressed::instantiateFinish:ImageLoaderMachOCompressed.cpp } } // 记录image到全局变量 addImage:dyld.cpp }
加载共享动态库 在环境变量配置完毕后,dyld会去加载共享缓存
加载的步骤是先通过checkShareRegionDisable函数检查是否被关闭,iOS下必须开启共享缓存,如果没有被禁用,那么就会调用mapSharedCache函数去加载,当然实际加载是在该函数内调用的loadDyldCache函数,加载共三种,fast Path(已经加载的不需要再加载),slow path(第一次调用则去加载.mapCacheSystemWide),还有一种是模拟器下(simulator)的.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 uintptr_t _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, int argc, const char* argv[], const char* envp[], const char* apple[], uintptr_t* startGlue) { [...] // load shared cache checkSharedRegionDisable(); #if DYLD_SHARED_CACHE_SUPPORT if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) { mapSharedCache(); // 判断是否存在共享动态库, 如果存在直接使用, 否则进行加载, gLinkContext记录共享库地址 } else { dyld_kernel_image_info_t kernelCacheInfo; bzero(&kernelCacheInfo.uuid[0], sizeof(uuid_t)); kernelCacheInfo.load_addr = 0; kernelCacheInfo.fsobjid.fid_objno = 0; kernelCacheInfo.fsobjid.fid_generation = 0; kernelCacheInfo.fsid.val[0] = 0; kernelCacheInfo.fsid.val[0] = 0; task_register_dyld_shared_cache_image_info(mach_task_self(), kernelCacheInfo, true, false); } #endif
当共享缓存被加载后,接下来,dyld就会继续在main函数中加载我们的主程序也就是我们的可执行文件。我们在方法中找到instantiateFromLoadedImage这个函数,在这个函数里,dyld会实例化我们的可执行文件。它实际上是通过我们传进来的machO的header判断当前cpu是否支持当前我们的machO的架构.如果支持则调用instantiateMainExecutable函数去实例化我们的可执行文件,并添加到imageList中。
当然可执行文件的实例化是在instantiateMainExecutable函数内部实现的,在该函数内部先调用了sniffLoadCommands,这个函数通过读取loadCommand段内的信息去加载。在sniffLoadCommands中严格判断了loadCommands的条数,不能超过255条,依赖的库不能超过4095个。最后该函数会修改Compress值,外部的instantiateMainExecutable函数会通过这个值来决定加载主程序的方式。在主程序被实例化加载后,接下来dyld就会继续在main函数中去加载我们插入的动态库,具体加载函数在loadInsertedDylib里进行。
加载DYLD_INSERT_LIBRARIES的动态库 1 2 3 4 5 6 7 8 9 10 11 uintptr_t _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, int argc, const char* argv[], const char* envp[], const char* apple[], uintptr_t* startGlue) { [...] // load any inserted libraries if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) { for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) loadInsertedDylib(*lib); }
loadInsertedDylib:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 loadInsertedDylib:dyld.cpp { load:dyld.cpp { loadPhase0:dyld.cpp loadPhase1:dyld.cpp loadPhase2:dyld.cpp loadPhase3:dyld.cpp loadPhase4:dyld.cpp loadPhase5:dyld.cpp loadPhase5check:dyld.cpp loadPhase5load:dyld.cpp loadPhase5stat:dyld.cpp loadPhase5load:dyld.cpp loadPhase5open:dyld.cpp loadPhase6 { checkandAddImage::dyld.cpp } } }
加载依赖动态库 1 2 3 4 5 6 7 8 9 10 11 12 uintptr_t _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, int argc, const char* argv[], const char* envp[], const char* apple[], uintptr_t* startGlue) { [...] link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1); sMainExecutable->setNeverUnloadRecursive(); if ( sMainExecutable->forceFlat() ) { gLinkContext.bindFlat = true; gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding; }
从 link 函数开始加载依赖动态库,在link方法中不光是链接我们的插入动态库,还会在函数内通过recursiveLoadLibraries函数循环加载我们的所有的依赖库.在加载后再Rebase每一个都添加上偏移值以得到真正的依赖库的地址也就是重定位。在链接定位后,还是在这个函数中继续对依赖库进行符号绑定,弱绑定等一系列操作,当这些都做完了主程序也就被加载链接完成。跟主程序加载链接一致,dyld当得知插入依赖库长度大于0会遍历加载链接这些库.链接完毕后就会将主程序与这些库绑定起来。
link
1 2 3 4 5 6 7 8 9 10 11 12 13 14 link:dyld.cpp { ImageLoader::link:ImageLoader.cpp { ImageLoader::recursiveLoadLibraries:ImageLoader.cpp { ImageLoaderMachO::doGetDependentLibraries:ImageLoader.cpp libraryLocator:dyld.cpp { load:dyld.cpp } } } }
在这些依赖库的操作全部完成,就会调用initializeMainExecutable函数来初始化我们的主程序!
接下来会调用loadImages函数
load_images调用了call_load_methods,函数内就是循环调用我们的Objc类的load方法。接下来dyld就会调用doModInitFunctions这个函数会调用执行我们程序的特殊函数,比如全局的C++的构造方法.其实实质上就是dyld会读取Mach-O里DATA段中的init_func这个字段进行调用里面的函数。
最终一系列的操作完毕后,dyld就会去查找我们主程序的入口,对应我们Mach-O的LC_MAIN.在找到后返回一个result结果,也就调起了我们主程序的main函数,结束掉dyld_start整个流程。
参考文献:
[1]解读Mach-O文件格式
[2]mach-o格式分析
[3]Mach-O 文件格式解析
[4]探秘 Mach-O 文件
[5]http://www.bubuko.com/infodetail-2243416.html
[6]https://www.jianshu.com/p/8498cec10a41
[7]https://satanwoo.github.io/2017/06/13/Macho-1/
[8]https://jmpews.github.io/2017/02/27/darwin/PWN%E4%B9%8Bmacho%E5%8A%A0%E8%BD%BD%E8%BF%87%E7%A8%8B/
[9]https://www.jianshu.com/p/7ad7b3ba7985
[10]https://www.jianshu.com/p/4d86de908721
[11]https://juejin.im/post/5c8e6f5c518825458b3ba6a4 这个写的比较清楚