[iOS 研习记]聊聊 iOS 中的 Mach-O
一 关于 Mach-O
Mach-O 的全称为 Mach Object,是 OS X 与 iOS 上的一种可执行文件格式。Mach 本身指一种操作系统的微内核标准,被用于 OS X 与 iOS 系统的内核中。相信对于移动端的 iOS 开发者来说,对 Mach-O 文件一定不陌生,我们编译打包的 iOS IPA 文件,内部其实就有一个可执行的 Mach-O 文件,我们开发的 framework 和.a 等动态库静态库中,也会包含 Mach-O 文件,本篇文章,我们就来详细看看 Mach-O 中究竟放的是什么,Mach-O 的结构是怎样的。
二 Mach-O 头信息
Mach-O 是一种文件格式,我们知道很多文件类型有包含有文件头,例如图片文件头中可能会包含图片的格式,编码方式等,压缩文件的文件头中包含压缩参数等等。相比,Mach-O 是一种更加复杂的文件,其文件头中提供的信息也更多。我们可以在 Github 上找到一款开源的 Mach-O 文件阅读软件:MachOView。可以尝试用其打开任意一个编译好的 IPA 包中的 Mach-O 文件,例如:
可以看到,格式化后的文件内容中,有一项是 Mach64 Header,这一项内容也是在 Mach-O 文件的最前部,这里就是 Mach-O 文件头。
关于 Mach-O 头部,我们可以在 usr/include/mach-o/Loader.h 文件中找到对应的定义,如下:
// 32位的Mach-O头struct mach_header { 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 */};// 64位的Mach-O头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 */};
复制代码
可以看到,32 位的 Mach-O 和 64 位的 Mach-O 最大的区别只在于 64 位的多了一个 reserved 字段,此字段当前并没有特殊意义,其预留给未来使用。下面我们来详细看下这些字段的意义。
1.magic
从其数据类型 uint32_t 也可以看出,magic 占 32 个二进制位。顾名思义,这个字段是一个魔数,用来区分当前 Mach-O 是 32 位的还是 64 位的,有两个宏对此魔数的值进行了定义:
// 32位#define MH_MAGIC 0xfeedface// 64位#define MH_MAGIC_64 0xfeedfacf
复制代码
你可以看下 MachOView 软件格式化后的此字段的值,是否和这些宏是对应的。
2.cputype
此字段占 32 个二进制位,用来描述 CPU 类型,相关宏定义在 mach/machine.h 头文件中,如下:
#define CPU_TYPE_ANY ((cpu_type_t) -1)#define CPU_TYPE_VAX ((cpu_type_t) 1)#define CPU_TYPE_MC680x0 ((cpu_type_t) 6)// 模拟器和Maac一般为此类型#define CPU_TYPE_X86 ((cpu_type_t) 7)#define CPU_TYPE_I386 CPU_TYPE_X86 #define CPU_TYPE_X86_64 (CPU_TYPE_X86 | CPU_ARCH_ABI64)#define CPU_TYPE_MC98000 ((cpu_type_t) 10)#define CPU_TYPE_HPPA ((cpu_type_t) 11)// iPhone真机一般为此类型#define CPU_TYPE_ARM ((cpu_type_t) 12)#define CPU_TYPE_ARM64 (CPU_TYPE_ARM | CPU_ARCH_ABI64)#define CPU_TYPE_ARM64_32 (CPU_TYPE_ARM | CPU_ARCH_ABI64_32)#define CPU_TYPE_MC88000 ((cpu_type_t) 13)#define CPU_TYPE_SPARC ((cpu_type_t) 14)#define CPU_TYPE_I860 ((cpu_type_t) 15)#define CPU_TYPE_POWERPC ((cpu_type_t) 18)#define CPU_TYPE_POWERPC64 (CPU_TYPE_POWERPC | CPU_ARCH_ABI64)
复制代码
其中的 cpu_type_t 其实就是 int 类型的别名。
3.cpusubtype
CPU 子类型,定义如下:
#define CPU_SUBTYPE_MASK 0xff000000 #define CPU_SUBTYPE_LIB64 0x80000000 #define CPU_SUBTYPE_PTRAUTH_ABI 0x80000000 #define CPU_SUBTYPE_ANY ((cpu_subtype_t) -1)#define CPU_SUBTYPE_MULTIPLE ((cpu_subtype_t) -1)#define CPU_SUBTYPE_LITTLE_ENDIAN ((cpu_subtype_t) 0)#define CPU_SUBTYPE_BIG_ENDIAN ((cpu_subtype_t) 1)// VAX的子类型#define CPU_SUBTYPE_VAX_ALL ((cpu_subtype_t) 0)#define CPU_SUBTYPE_VAX780 ((cpu_subtype_t) 1)#define CPU_SUBTYPE_VAX785 ((cpu_subtype_t) 2)#define CPU_SUBTYPE_VAX750 ((cpu_subtype_t) 3)#define CPU_SUBTYPE_VAX730 ((cpu_subtype_t) 4)#define CPU_SUBTYPE_UVAXI ((cpu_subtype_t) 5)#define CPU_SUBTYPE_UVAXII ((cpu_subtype_t) 6)#define CPU_SUBTYPE_VAX8200 ((cpu_subtype_t) 7)#define CPU_SUBTYPE_VAX8500 ((cpu_subtype_t) 8)#define CPU_SUBTYPE_VAX8600 ((cpu_subtype_t) 9)#define CPU_SUBTYPE_VAX8650 ((cpu_subtype_t) 10)#define CPU_SUBTYPE_VAX8800 ((cpu_subtype_t) 11)#define CPU_SUBTYPE_UVAXIII ((cpu_subtype_t) 12)// MC680子类型#define CPU_SUBTYPE_MC680x0_ALL ((cpu_subtype_t) 1)#define CPU_SUBTYPE_MC68030 ((cpu_subtype_t) 1)#define CPU_SUBTYPE_MC68040 ((cpu_subtype_t) 2)#define CPU_SUBTYPE_MC68030_ONLY ((cpu_subtype_t) 3)// I386子类型#define CPU_SUBTYPE_INTEL(f, m) ((cpu_subtype_t) (f) + ((m) << 4))#define CPU_SUBTYPE_I386_ALL CPU_SUBTYPE_INTEL(3, 0)#define CPU_SUBTYPE_386 CPU_SUBTYPE_INTEL(3, 0)#define CPU_SUBTYPE_486 CPU_SUBTYPE_INTEL(4, 0)#define CPU_SUBTYPE_486SX CPU_SUBTYPE_INTEL(4, 8) // 8 << 4 = 128#define CPU_SUBTYPE_586 CPU_SUBTYPE_INTEL(5, 0)#define CPU_SUBTYPE_PENT CPU_SUBTYPE_INTEL(5, 0)#define CPU_SUBTYPE_PENTPRO CPU_SUBTYPE_INTEL(6, 1)#define CPU_SUBTYPE_PENTII_M3 CPU_SUBTYPE_INTEL(6, 3)#define CPU_SUBTYPE_PENTII_M5 CPU_SUBTYPE_INTEL(6, 5)#define CPU_SUBTYPE_CELERON CPU_SUBTYPE_INTEL(7, 6)#define CPU_SUBTYPE_CELERON_MOBILE CPU_SUBTYPE_INTEL(7, 7)#define CPU_SUBTYPE_PENTIUM_3 CPU_SUBTYPE_INTEL(8, 0)#define CPU_SUBTYPE_PENTIUM_3_M CPU_SUBTYPE_INTEL(8, 1)#define CPU_SUBTYPE_PENTIUM_3_XEON CPU_SUBTYPE_INTEL(8, 2)#define CPU_SUBTYPE_PENTIUM_M CPU_SUBTYPE_INTEL(9, 0)#define CPU_SUBTYPE_PENTIUM_4 CPU_SUBTYPE_INTEL(10, 0)#define CPU_SUBTYPE_PENTIUM_4_M CPU_SUBTYPE_INTEL(10, 1)#define CPU_SUBTYPE_ITANIUM CPU_SUBTYPE_INTEL(11, 0)#define CPU_SUBTYPE_ITANIUM_2 CPU_SUBTYPE_INTEL(11, 1)#define CPU_SUBTYPE_XEON CPU_SUBTYPE_INTEL(12, 0)#define CPU_SUBTYPE_XEON_MP CPU_SUBTYPE_INTEL(12, 1)#define CPU_SUBTYPE_INTEL_FAMILY(x) ((x) & 15)#define CPU_SUBTYPE_INTEL_FAMILY_MAX 15#define CPU_SUBTYPE_INTEL_MODEL(x) ((x) >> 4)#define CPU_SUBTYPE_INTEL_MODEL_ALL 0// X86子类型#define CPU_SUBTYPE_X86_ALL ((cpu_subtype_t)3)#define CPU_SUBTYPE_X86_64_ALL ((cpu_subtype_t)3)#define CPU_SUBTYPE_X86_ARCH1 ((cpu_subtype_t)4)#define CPU_SUBTYPE_X86_64_H ((cpu_subtype_t)8)#define CPU_THREADTYPE_INTEL_HTT ((cpu_threadtype_t) 1)// MIPS子类型#define CPU_SUBTYPE_MIPS_ALL ((cpu_subtype_t) 0)#define CPU_SUBTYPE_MIPS_R2300 ((cpu_subtype_t) 1)#define CPU_SUBTYPE_MIPS_R2600 ((cpu_subtype_t) 2)#define CPU_SUBTYPE_MIPS_R2800 ((cpu_subtype_t) 3)#define CPU_SUBTYPE_MIPS_R2000a ((cpu_subtype_t) 4) /* pmax */#define CPU_SUBTYPE_MIPS_R2000 ((cpu_subtype_t) 5)#define CPU_SUBTYPE_MIPS_R3000a ((cpu_subtype_t) 6) /* 3max */#define CPU_SUBTYPE_MIPS_R3000 ((cpu_subtype_t) 7)// MC98000子类型#define CPU_SUBTYPE_MC98000_ALL ((cpu_subtype_t) 0)#define CPU_SUBTYPE_MC98601 ((cpu_subtype_t) 1)// HPPA子类型#define CPU_SUBTYPE_HPPA_ALL ((cpu_subtype_t) 0)#define CPU_SUBTYPE_HPPA_7100 ((cpu_subtype_t) 0) /* compat */#define CPU_SUBTYPE_HPPA_7100LC ((cpu_subtype_t) 1)// MC88000子类型#define CPU_SUBTYPE_MC88000_ALL ((cpu_subtype_t) 0)#define CPU_SUBTYPE_MC88100 ((cpu_subtype_t) 1)#define CPU_SUBTYPE_MC88110 ((cpu_subtype_t) 2)// SPARC子类型#define CPU_SUBTYPE_SPARC_ALL ((cpu_subtype_t) 0)// I860子类型#define CPU_SUBTYPE_I860_ALL ((cpu_subtype_t) 0)#define CPU_SUBTYPE_I860_860 ((cpu_subtype_t) 1)// PowerPC子类型#define CPU_SUBTYPE_POWERPC_ALL ((cpu_subtype_t) 0)#define CPU_SUBTYPE_POWERPC_601 ((cpu_subtype_t) 1)#define CPU_SUBTYPE_POWERPC_602 ((cpu_subtype_t) 2)#define CPU_SUBTYPE_POWERPC_603 ((cpu_subtype_t) 3)#define CPU_SUBTYPE_POWERPC_603e ((cpu_subtype_t) 4)#define CPU_SUBTYPE_POWERPC_603ev ((cpu_subtype_t) 5)#define CPU_SUBTYPE_POWERPC_604 ((cpu_subtype_t) 6)#define CPU_SUBTYPE_POWERPC_604e ((cpu_subtype_t) 7)#define CPU_SUBTYPE_POWERPC_620 ((cpu_subtype_t) 8)#define CPU_SUBTYPE_POWERPC_750 ((cpu_subtype_t) 9)#define CPU_SUBTYPE_POWERPC_7400 ((cpu_subtype_t) 10)#define CPU_SUBTYPE_POWERPC_7450 ((cpu_subtype_t) 11)#define CPU_SUBTYPE_POWERPC_970 ((cpu_subtype_t) 100)// ARM子类型#define CPU_SUBTYPE_ARM_ALL ((cpu_subtype_t) 0)#define CPU_SUBTYPE_ARM_V4T ((cpu_subtype_t) 5)#define CPU_SUBTYPE_ARM_V6 ((cpu_subtype_t) 6)#define CPU_SUBTYPE_ARM_V5TEJ ((cpu_subtype_t) 7)#define CPU_SUBTYPE_ARM_XSCALE ((cpu_subtype_t) 8)#define CPU_SUBTYPE_ARM_V7 ((cpu_subtype_t) 9) /* ARMv7-A and ARMv7-R */#define CPU_SUBTYPE_ARM_V7F ((cpu_subtype_t) 10) /* Cortex A9 */#define CPU_SUBTYPE_ARM_V7S ((cpu_subtype_t) 11) /* Swift */#define CPU_SUBTYPE_ARM_V7K ((cpu_subtype_t) 12)#define CPU_SUBTYPE_ARM_V8 ((cpu_subtype_t) 13)#define CPU_SUBTYPE_ARM_V6M ((cpu_subtype_t) 14) /* Not meant to be run under xnu */#define CPU_SUBTYPE_ARM_V7M ((cpu_subtype_t) 15) /* Not meant to be run under xnu */#define CPU_SUBTYPE_ARM_V7EM ((cpu_subtype_t) 16) /* Not meant to be run under xnu */#define CPU_SUBTYPE_ARM_V8M ((cpu_subtype_t) 17) /* Not meant to be run under xnu */// ARM64子类型#define CPU_SUBTYPE_ARM64_ALL ((cpu_subtype_t) 0)#define CPU_SUBTYPE_ARM64_V8 ((cpu_subtype_t) 1)#define CPU_SUBTYPE_ARM64E ((cpu_subtype_t) 2)#define CPU_SUBTYPE_ARM64_PTR_AUTH_MASK 0x0f000000#define CPU_SUBTYPE_ARM64_PTR_AUTH_VERSION(x) (((x) & CPU_SUBTYPE_ARM64_PTR_AUTH_MASK) >> 24)// ARM64_32子类型#define CPU_SUBTYPE_ARM64_32_ALL ((cpu_subtype_t) 0)#define CPU_SUBTYPE_ARM64_32_V8 ((cpu_subtype_t) 1)
复制代码
4.filetype
此字段标记 Mach-O 文件的具体类型,例如当前 Mach-O 是可执行文件还是库文件等,此字段占 32 个二进制位,值定义如下:
// 可重定位的目标文件,编译源码得到的中间结果#define MH_OBJECT 0x1 // 可执行的二进制文件,iOS应用IPA包中的可执行Mach-O就是此类型的#define MH_EXECUTE 0x2// 修复后的VM共享库文件#define MH_FVMLIB 0x3// 核心转储文件#define MH_CORE 0x4 // 预加载的可执行文件#define MH_PRELOAD 0x5// 动态库文件#define MH_DYLIB 0x6 // 动态链接器文件#define MH_DYLINKER 0x7 // 插件文件,非独立的二进制文件#define MH_BUNDLE 0x8// 仅用于静态链接的共享库文件#define MH_DYLIB_STUB 0x9// 辅助的符号文件及调试信息#define MH_DSYM 0xa// 内核扩展#define MH_KEXT_BUNDLE 0xb// 其他Mach-O组成的集合#define MH_FILESET 0xc
复制代码
5.ncmds
此字段描述需要加载的命令条数,与 Mach-O 文件中的 Load Commands 段中的数据对应。
6.sizeofcmds
与 Mach-O 文件中的 Load Commands 段中的数据对应,标记 Load Commands 段的长度。
7.flags
标志位字段,标记当前 Mach-O 文件的一些重要信息,这些 flags 是以二进制位或的关系定义的,也就是说可以同时拥有多个标记。定义如下:
// 未带未定义的符号,没有进一步的链接依赖关系#define MH_NOUNDEFS 0x1 // 目标文件是增量链接的输出,不能再次被链接#define MH_INCRLINK 0x2 // 目标文件是动态链接器的输入,不能再次静态链接#define MH_DYLDLINK 0x4// 加载时,对象文件的未定义引用由动态链接器绑定#define MH_BINDATLOAD 0x8// 该文件预先绑定了其动态未定义引用#define MH_PREBOUND 0x10// 目标文件的只读段与可读写段进行了区分#define MH_SPLIT_SEGS 0x20// 延迟init#define MH_LAZY_INIT 0x40// 两层名称绑定#define MH_TWOLEVEL 0x80// 扁平名称绑定#define MH_FORCE_FLAT 0x100// 子image中没有多定义的符号#define MH_NOMULTIDEFS 0x200// 不要让dyld将此可执行文件通知预绑定代理#define MH_NOFIXPREBINDING 0x400// 二进制文件不是预绑定的,但可以重新进行预绑定#define MH_PREBINDABLE 0x800 // 指示此二进制文件绑定到其依赖库的所有两级命名空间模块#define MH_ALLMODSBOUND 0x1000#define MH_SUBSECTIONS_VIA_SYMBOLS 0x2000// 二进制文件已被规范化为非绑定操作#define MH_CANONICAL 0x4000// 二进制文件使用了弱符号#define MH_WEAK_DEFINES 0x8000// 最终链接的image使用了弱符号#define MH_BINDS_TO_WEAK 0x10000// 允许栈可执行#define MH_ALLOW_STACK_EXECUTION 0x20000// 当设置此位时,二进制文件声明它可以安全地用于uid为零的进程#define MH_ROOT_SAFE 0x40000 // 当设置该位时,二进制文件声明它可以在issetugid为true的进程中安全使用#define MH_SETUID_SAFE 0x80000 // 当在dylib上设置此位时,静态链接器不需要检查依赖的dylib,以查看是否有重新导出的依赖dylib#define MH_NO_REEXPORTED_DYLIBS 0x100000// 对可执行文件启用地址空间布局随机化#define MH_PIE 0x200000 // 仅适用于dylibs。当链接到设置了此位的dylib时,如果没有从dylib引用符号,则静态链接器将自动不对dylib创建LC_LOAD_dylib加载命令#define MH_DEAD_STRIPPABLE_DYLIB 0x400000#define MH_HAS_TLV_DESCRIPTORS 0x800000// 将堆标记为不可执行#define MH_NO_HEAP_EXECUTION 0x1000000// 扩展应用中使用#define MH_APP_EXTENSION_SAFE 0x02000000// nlist符号表中列出的外部符号不包括dyld信息中列出的所有符号#define MH_NLIST_OUTOFSYNC_WITH_DYLDINFO 0x04000000#define MH_SIM_SUPPORT 0x08000000// 仅适用于dylibs。设置该位时,dylib是dyld共享缓存的一部分,而不是文件系统中的松散缓存。#define MH_DYLIB_IN_CACHE 0x80000000
复制代码
Mach-O 文件头的内容大致就只有这些,上面我们提到过,ncmds 和 sizeofcmds 描述了加载命令的条数和加载命令的内容大小,下面我们就来看下加载命令。
三 加载命令
在 Mach-O 文件中,加载命令段紧跟在 Mach-O 头数据后面,头数据中的字段告知了我们加载命令的条数和大小,通过这两个字段来具体的进行命令的解析和执行。
我们现在 MachOView 中简单浏览下这部分的数据结构,如下图:
可以看到,每条加载命令的开头结构都是一样的,即命令类型和当前命令的长度大小。其实,在 loader.h 头文件中有定义一个结构体来描述加载命令,如下:
struct load_command { uint32_t cmd; uint32_t cmdsize; };
复制代码
可以看到,每条加载命令在解析时,首先会获取到 cmd 和 cmdsize,cmd 用来标记加载命令是什么,cmdsize 指明当前加载命令的字节长度,通过这两个字段,可以对命令进行完整的解析。
下面列举了常见的命令类型,对应的宏定义:
// 将文件中的段(32位)映射到进程地址空间中#define LC_SEGMENT 0x1// 将文件中的段(64位)映射到进程地址空间中#define LC_SEGMENT_64 0x19// 链接编辑stab符号表信息#define LC_SYMTAB 0x2// 链接编辑gdb符号表信息#define LC_SYMSEG 0x3// 开启一个Mach线程,不开辟栈#define LC_THREAD 0x4// 开启一个UNIX线程#define LC_UNIXTHREAD 0x5// 加载指定的固定VM共享库#define LC_LOADFVMLIB 0x6 // 修复了VM共享库标识#define LC_IDFVMLIB 0x7// 对象标识信息#define LC_IDENT 0x8// 固定VM文件包含#define LC_FVMFILE 0x9// prepage命令#define LC_PREPAGE 0xa// 动态链接编辑符号表信息 #define LC_DYSYMTAB 0xb// 加载动态链接的共享库#define LC_LOAD_DYLIB 0xc// 动态链接共享库标识#define LC_ID_DYLIB 0xd// 加载动态链接器#define LC_LOAD_DYLINKER 0xe// 动态链接器标识#define LC_ID_DYLINKER 0xf// 动态预绑定的模块#define LC_PREBOUND_DYLIB 0x10
// 下面的命令与链接共享库有关// image routines#define LC_ROUTINES 0x11 /* image routines */#define LC_ROUTINES_64 0x1a#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 */// 加载允许丢失的动态链接共享库#define LC_LOAD_WEAK_DYLIB (0x18 | LC_REQ_DYLD)
// 唯一的UUID,表示当前二进制文件#define LC_UUID 0x1b// 进行本地代码签名#define LC_CODE_SIGNATURE 0x1d// 本地拆分段#define LC_SEGMENT_SPLIT_INFO 0x1e // 加载并重新导出dylib#define LC_REEXPORT_DYLIB (0x1f | LC_REQ_DYLD)// 将dylib加载延迟至首次使用#define LC_LAZY_LOAD_DYLIB 0x20// 加密的段信息#define LC_ENCRYPTION_INFO 0x21#define LC_ENCRYPTION_INFO_64 0x2C// 压缩的dyld信息#define LC_DYLD_INFO 0x22// 仅限压缩的dyld信息#define LC_DYLD_INFO_ONLY (0x22|LC_REQ_DYLD)// 向上加载dylib#define LC_LOAD_UPWARD_DYLIB (0x23 | LC_REQ_DYLD)// 为MacOSX最小操作系统版本构建#define LC_VERSION_MIN_MACOSX 0x24// 为iOS最小操作系统版本构建#define LC_VERSION_MIN_IPHONEOS 0x25// 为TVOS最小操作系统版本构建#define LC_VERSION_MIN_TVOS 0x2F// 为WATCHOS最小操作系统版本构建#define LC_VERSION_MIN_WATCHOS 0x30// 压缩的函数起始地址表#define LC_FUNCTION_STARTS 0x26// dyld要像环境变量一样处理的字符串#define LC_DYLD_ENVIRONMENT 0x27// 同LC_UNIXTHREAD#define LC_MAIN (0x28|LC_REQ_DYLD)// __text段中的非说明表#define LC_DATA_IN_CODE 0x29// 用于生成二进制文件的源代码版本#define LC_SOURCE_VERSION 0x2A// 从链接的dylibs复制的代码签名DR#define LC_DYLIB_CODE_SIGN_DRS 0x2B// MH_OBJECT文件中的链接器选项#define LC_LINKER_OPTION 0x2D // MH_OBJECT文件中的优化提示#define LC_LINKER_OPTIMIZATION_HINT 0x2E// Mach-O文件中包含的任意数据#define LC_NOTE 0x31// 为平台最小操作系统版本构建#define LC_BUILD_VERSION 0x32// 与linkedit_data_command一起使用#define LC_DYLD_EXPORTS_TRIE (0x33 | LC_REQ_DYLD)#define LC_DYLD_CHAINED_FIXUPS (0x34 | LC_REQ_DYLD)// 与fileset_entry_command一起使用#define LC_FILESET_ENTRY (0x35 | LC_REQ_DYLD)
复制代码
在加载命令段,通过解析出每条命令的类型和大小,就可以方便的获取到命令中具体的值,准确来说,一条加载命令是由类型,大小和值三部分组成的。不同类型的命令对应的值解析出来的结构也不同,下面我们来介绍几个常见的命令。
1.LC_SEGMENT/LS_SEGMENT_64
通过我们编译的 iOS 空项目的 Mach-O 文件可以看到,其中包含了很多条 LS_SEGMENT_64 加载命令,此命令描述了内核应该如何设置当前应用进行的内存空间,这些段将直接从 Mach-O 文件中对应的位置加载到内存里。LS_SEGMENT_64 命令的值的结构定义如下:
struct segment_command { /* for 32-bit architectures */ uint32_t cmd; /* LC_SEGMENT */ uint32_t cmdsize; /* includes sizeof section structs */ char segname[16]; /* segment name */ uint32_t vmaddr; /* memory address of this segment */ 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 */ uint32_t flags; /* flags */};
struct segment_command_64 { /* for 64-bit architectures */ uint32_t cmd; /* LC_SEGMENT_64 */ uint32_t cmdsize; /* includes sizeof section_64 structs */ char segname[16]; // 段的名称 uint64_t vmaddr; // 虚拟物理地址 uint64_t vmsize; // 为此段分配的虚拟内存大小 uint64_t fileoff; // 此段在文件中的偏移量 uint64_t filesize; // 此段在文件中占用的字节数 vm_prot_t maxprot; // 段的页面所需要的最高内存保护 vm_prot_t initprot; // 段页面初始时的内存保护 uint32_t nsects; // 段中区(section)的数量 uint32_t flags; // 标记位};
复制代码
可以看到,每个段加载命令中都提供了段的基本信息,包括名称,偏移地址,字节数,以及段有包含的分区的个数,通过此命令,进程虚拟内存的设置就变得很简单,只需要根据指令来读取信息并加载到内存即可。关于段的分析我们会在后面小节做具体的介绍。
2.LC_MAIN/LS_UNIXTHREAD
当所有库加载完成后,此命令用来进行二进制程序的主线程启动。
3.LC_CODE_SIGNATURE
Mach-O 包含的数字签名,如果此签名与代码不匹配,则进程会被强制关闭。LC_CODE_SIGNATURE 命令的结构如下:
struct linkedit_data_command { uint32_t cmd; /* LC_CODE_SIGNATURE, LC_SEGMENT_SPLIT_INFO, LC_FUNCTION_STARTS, LC_DATA_IN_CODE, LC_DYLIB_CODE_SIGN_DRS, LC_LINKER_OPTIMIZATION_HINT, LC_DYLD_EXPORTS_TRIE, or LC_DYLD_CHAINED_FIXUPS. */ uint32_t cmdsize; /* sizeof(struct linkedit_data_command) */ uint32_t dataoff; // 地址偏移量 uint32_t datasize; // 字节数};
复制代码
此命令的结构是一个复用的结构,很多命令都能解析成此结构,其中 dataoff 标记从文件的何处开始读取签名,datasize 标记签名所占字节数。我们可以通过一个 Mach-O 文件来验证下,首先在加载命令段找到 LC_CODE_SIGNATURE 命令,如下:
可以看到,其 offset 为 E8A0,size 为 4B00,相加后为 133A0,因此我们可以得知,签名所在的位置为偏移地址为 E8A0 的地方,最终结束的地址为 133A0,找到 Code Signature 段进行验证,如下:
可以看到,Code Signature 段的起始偏移位置就是 E8A0,最后 4 个字节的偏移位置为 13390,再加上最后的 4 个字节,结尾的位置刚好为 133A0,和我们计算的一致。
并非只有 LC_CODE_SIGNATURE 命令是采用偏移量和字节数的方式来进行加载,这种值结构的命令都是一样的逻辑,这里就不再赘述。
4.LC_SYMTAB
此命令用来加载此 Mach-O 文件的符号表,此命令中包含了 symbol table 和 string table 在 Mach-O 文件中的位置,LC_SYMTAB 命令的值定义如下:
struct symtab_command { uint32_t cmd; /* LC_SYMTAB */ uint32_t cmdsize; /* sizeof(struct symtab_command) */ uint32_t symoff; // 符号表起始位置的偏移量 uint32_t nsyms; // 符号表的字节数 uint32_t stroff; // 字符串表起始位置的偏移量 uint32_t strsize; // 字符串表的字节数};
复制代码
关于符号表和字符串表段,我们后面再详细介绍。
4.LC_DYSYMTAB
此命令用来加载动态库的符号表,对应的是 Dynamic Symbol Table 段的数据,此命令的值结构较复杂,定义如下:
struct dysymtab_command { uint32_t cmd; /* LC_DYSYMTAB */ uint32_t cmdsize; /* sizeof(struct dysymtab_command) */
// 本地符号,用来调试 uint32_t ilocalsym; // 本地符号的位置 uint32_t nlocalsym; // 本地符号的字节数 // 定义的外部符号 uint32_t iextdefsym; // 定义的外部符号位置 uint32_t nextdefsym; // 定义的外部符号字节数 // 未定义的符号 uint32_t iundefsym; // 未定义的符号位置 uint32_t nundefsym; // 未定义的符号字节数
// 动态绑定相关 uint32_t tocoff; /* file offset to table of contents */ uint32_t ntoc; /* number of entries in table of contents */ uint32_t modtaboff; /* file offset to module table */ uint32_t nmodtab; /* number of module table entries */ uint32_t extrefsymoff; /* offset to referenced symbol table */ uint32_t nextrefsyms; /* number of referenced symbol table entries */
// 间接符号表 uint32_t indirectsymoff; /* file offset to the indirect symbol table */ uint32_t nindirectsyms; /* number of indirect symbol table entries */
// 重定位相关 uint32_t extreloff; /* offset to external relocation entries */ uint32_t nextrel; /* number of external relocation entries */
// 本地条目相关 uint32_t locreloff; /* offset to local relocation entries */ uint32_t nlocrel; /* number of local relocation entries */
};
复制代码
5.LC_LOAD_DYLINKER
此命令告知内核需要调用的动态链接器的位置,值结构定义如下:
struct dylinker_command { uint32_t cmd; /* LC_ID_DYLINKER, LC_LOAD_DYLINKER or LC_DYLD_ENVIRONMENT */ uint32_t cmdsize; /* includes pathname string */ union lc_str name; // 包含链接器的路径名称字符串起始位置的偏移,真正的动态链接器一般为/usr/lib/dyld};
复制代码
6.LC_UUID
此命令加载时可以读取到一个 128 位的 UUID 值,结构如下:
struct uuid_command { uint32_t cmd; /* LC_UUID */ uint32_t cmdsize; /* sizeof(struct uuid_command) */ uint8_t uuid[16]; /* the 128-bit uuid */};
复制代码
7.LC_LOAD_DYLIB
此命令用来加载额外的动态库,这也是一个非常重要的命令,最简单的 iOS 应用也离不开动态库的使用,通过真实的 Mach-O 文件我们也可以看到,通常会有多条 LC_LOAD_DYLIB 命令,每条命令指定一个要加载的动态库。此命令的值结构定义如下:
struct dylib_command { uint32_t cmd; /* LC_ID_DYLIB, LC_LOAD_{,WEAK_}DYLIB, LC_REEXPORT_DYLIB */ uint32_t cmdsize; /* includes pathname string */ struct dylib dylib; // 动态库结构体};
struct dylib { union lc_str name; // 动态库所在路径字符串的起始位置(相对与当前命令起始的偏移字节) uint32_t timestamp; // 库编译时的时间戳 uint32_t current_version; // 动态库版本号 uint32_t compatibility_version; // 兼容版本号};
复制代码
以 UIKit 为例,一般的 iOS 应用都会使用到这个动态库,此命令如下:
8.LC_RPATH
此命令用来进行 @rpath 外部路径变量的映射,我们知道,除了系统本身提供了一些动态共享库外,我们也可以使用外部自定义的动态库,这类外部动态库在加载的时候也需要一个明确的路径,通常在加载外部动态库的命令中,库的路径是以 @rpath 开头的,Xcode 的编译选项中可以自定义设置外部库的寻找路径,同时使用此命令进行解析。此命令的值结构如下:
struct rpath_command { uint32_t cmd; /* LC_RPATH */ uint32_t cmdsize; /* includes string */ union lc_str path; };
复制代码
关于动态库这部分的内容,可以参考之前的一篇文章,其中有更详细的介绍。
https://my.oschina.net/u/2340880/blog/5323143
9.LC_FUNCTION_STARTS
此命令的结构与 LC_CODE_SIGNATURE 一致,用来描述函数表段所在的起始位置和字节数。关于函数表,后面会具体介绍。
四 段与分区
前面,我们将常见的加载命令进行了介绍,也有提到 LS_SEGMENT 是非常重要的一个命令,其指导着内核如果将 Mach-O 文件中的各个段数据加载到内存里。此命令的结构中有一个名为 segname 的字段,其表示要加载的段的名字,常用宏定义如下:
#define SEG_PAGEZERO "__PAGEZERO" // 空指针异常捕获段#define SEG_TEXT "__TEXT" // 代码段#define SEG_DATA "__DATA" // 数据段#define SEG_OBJC "__OBJC" // OC运行时段#define SEG_ICON "__ICON" // ICON段#define SEG_LINKEDIT "__LINKEDIT" // 链接器使用的符号和其他表段 #define SEG_UNIXSTACK "__UNIXSTACK" // unix栈段#define SEG_IMPORT "__IMPORT" // dyld段
复制代码
其中,__PAGEZERO 段在进入虚拟内存时是不会分配真实的物理地址的,其只是告诉开发者此段虚拟内存不能使用,最大的作用是如果产生了空指针,会访问到此段中的地址,会直接被捕获报错。
__TEXT 段为代码段,其中又包含多个区,命令中的 nsects 字段描述了当前段有多少个区,区的信息也在命令中进行读取,结构如下:
struct section_64 { /* for 64-bit architectures */ char sectname[16]; // 分区名 char segname[16]; // 段名 uint64_t addr; // 分区地址 uint64_t size; // 字节数 uint32_t offset; // 分区在文件中的偏移量 uint32_t align; // 对齐方式 uint32_t reloff; // 重定位条目在文件中的偏移量 uint32_t nreloc; // 重定位条目数 uint32_t flags; // 分区标记 uint32_t reserved1; // 预留 uint32_t reserved2; // 预留 uint32_t reserved3; // 预留};
复制代码
通过这些信息可以在 Mach-O 文件中找到具体的分区所在的位置,将其加载进内存。
__TEXT 段下,常见的区列举如下:
__text:主程序代码区
__stubs:动态链接桩区
__objc_methname:Objective-C 方法名区
__objc_classname:Objective-C 类名区
__objc_methtype:Objective-C 方法类型区
__cstring:硬编码的 C 语言字符串区
__entitlements:配置文件区
__DATA_CONST 段下,常见的分区列举如下:
__got:Non-Lazy Symbol Pointers 区
__cfstring:程序中使用的 CFStringRef 区
__objc_classlist:Objective-C 类列表区
__objc_protolist:Objective-C 原型列表区
__objc_imginfo:Objective-C 镜像信息区
__DATA 段下,常见的分区列举如下:
__objc_const:Objective-C 常量区
__objc_selrefs:Objective-C 自引用区
__objc_classrefs:Objective-C 类引用区
__objc_supperrefs:Objective-C 超类引用区
五 Mach-O 的”加载“到底加载的是什么
现在,我们对 Mach-O 文件的整体架构已经有了清晰的了解,如果将一个 Mach-O 文件比作一个完成的工程产品,则文件头好比是此产品的信息表,记录了产品的一些基础信息。加载命令则是产品的组装说明书,用来具体指导此工程产品的组装,之后的内容就是一个个独立的工程组件,我们只需要按照说明书的描述进行组装即可。下面我们对几个核心的区进行介绍。
1.代码段代码区
首先,对于一个 iOS 应用的 Mach-O 文件来说,__TEXT 段__text 分区是必不可少的,其便是程序的核心代码区,通过可视化的工具可以看到,其中数据就是汇编指令,如下:
2.代码段动态链接桩区
__TEXT 段__text 区中的代码在调用的动态链接函数需要进行绑定,__TEXT 段的__stubs 区便是留的动态链接的桩,其具体的解析在 Dynamic Symbol Table 动态符号表段中。举个例子,__stubs 区的符号解析结果如下:
可以看到,可视化工具中,这些桩已经被解析成了具体的符号,那么是如何解析的呢。其实也很简单,首先每 6 个字节对应一个符号,之所以是 6 个字节,是 LC_SEGMENT 命令中的 Size of Stubs 字段约定的,如下:
具体的符号定义在 Dynamic Symbol Table 里面,从前往后与__stubs 中的桩一一对应,可以观察下 Dynamic Symbol Table 表中的数据,如下:
可视化工具已经帮我们将符号进行了解析,实际上动态符号表中存放的只是符号所在位置的下标,并非是符号本身,所有符号都记录在 Symbol Table 中,以动态符号表中的第一个符号为例,其存储的值为 0xB7,转成 10 进制为 183,表示符号表中的第 183 个符号,找到 Symbol Table 中下标为 183 的符号,如下:
事实上,Symbol Table 中记录的是一种标准化的符号结构,其并不会存具体的符号字符串,所有字符串都存在字符串表中,以便节省内存。可以看到,Symbol Table 中第 183 个符号记录其在字符串表中的位置为 0xCE,我们在找到 String Table,其起始位置为 0xD0A0,加上这里的偏移量 0xCE 为 0xD16E,说明要解析的符号存放在 0xD16E 的位置,String Table 中记录的符号是以 0x00 为分割的,此处的后一个符号刚好就是_NSStringFromClass,如下:
到此,动态链接桩被解析完成。其实除了__TEXT 段的__stubs 区,__DATA_CONST 段的__got 区也是通过类似的方式来绑定符号的,只是__got 区的符号需要链接时绑定,而__stubs 区的符号允许运行时绑定,这里后面就不再赘述。
现在,让我们总结上符号绑定的完整过程。首先代码区中的符号需要到__stubs 区或__got 区进行绑定,__stubs 区和__got 区对应的加载命令记录了这两个去对应的符号解析的字节数以及在 Dynamic Symbol Table 表中的起始位置,Dynamic Symbol Table 表中记录了当前符号在 Symbol Table 表中的下标,Symbol 表记录了具体的符号实体,其中会包含当前符号在 String Table 中的位置,符号类型等信息,从 String Table 中找到具体的符号字符串。
3.Objective-C 方法名区,Objective-C 类名区,Objective-C 方法类型区,C 语言字符串区
__TEXT 段的的__objc_methname 区顾名思义,记录了应用中所有的 Objective-C 方法名,直接以字符串的方式存储。
__TEXT 段的的__objc_classname 区顾名思义,记录了应用中(非动态库)所有的 Objective-C 类名,直接以字符串的方式存储。
__TEXT 段的的__objc_methtype 区顾名思义,记录了应用中所有的 Objective-C 方法的类型,直接以字符串的方式存储。
__TEXT 段的的__cstring 区顾名思义,记录了应用中所有的 C 语言字符串,直接以字符串的方式存储。
六 通过 otool 工具解析 Mach-O
整体看来,Mach-O 文件的结构还是比较复杂的,如果没有可视化的工具,要解析此文件还是有些困难。除了 MachOView 工具外,我们还可以使用原生的 otool 命令来获取 Mach-O 文件中的而一些内容。文章的最后,我们来介绍几个常用的命令。
1.查看 Mach-O 文件头
命令:otool -h MachOTest.app/MachOTest 或 otool -hV MachOTest.app/MachOTest
hV 参数的命令会自动进行可视化。
示例结果:
2.查看 Mach-O 的加载命令
命令:otool -lV MachOTest.app/MachOTest
示例结果:
MachOTest.app/MachOTest:Load command 0 cmd LC_SEGMENT_64 cmdsize 72 segname __PAGEZERO vmaddr 0x0000000000000000 vmsize 0x0000000100000000 fileoff 0 filesize 0 maxprot --- initprot --- nsects 0 flags (none)Load command 1 cmd LC_SEGMENT_64 cmdsize 712 segname __TEXT vmaddr 0x0000000100000000 vmsize 0x0000000000004000 fileoff 0 filesize 16384 maxprot r-x initprot r-x nsects 8 flags (none)Section sectname __text segname __TEXT addr 0x0000000100001fb0 size 0x000000000000051d offset 8112 align 2^4 (16) reloff 0 nreloc 0 type S_REGULARattributes PURE_INSTRUCTIONS SOME_INSTRUCTIONS reserved1 0 reserved2 0Section sectname __stubs segname __TEXT addr 0x00000001000024ce size 0x000000000000003c offset 9422 align 2^1 (2) reloff 0 nreloc 0 type S_SYMBOL_STUBSattributes PURE_INSTRUCTIONS SOME_INSTRUCTIONS reserved1 0 (index into indirect symbol table) reserved2 6 (size of stubs)Section sectname __objc_methname segname __TEXT addr 0x000000010000250a size 0x0000000000000dec offset 9482 align 2^0 (1) reloff 0 nreloc 0 type S_CSTRING_LITERALSattributes (none) reserved1 0 reserved2 0Section sectname __objc_classname segname __TEXT addr 0x00000001000032f6 size 0x0000000000000070 offset 13046 align 2^0 (1) reloff 0 nreloc 0 type S_CSTRING_LITERALSattributes (none) reserved1 0 reserved2 0Section sectname __objc_methtype segname __TEXT addr 0x0000000100003366 size 0x0000000000000b29 offset 13158 align 2^0 (1) reloff 0 nreloc 0 type S_CSTRING_LITERALSattributes (none) reserved1 0 reserved2 0Section sectname __cstring segname __TEXT addr 0x0000000100003e8f size 0x0000000000000016 offset 16015 align 2^0 (1) reloff 0 nreloc 0 type S_CSTRING_LITERALSattributes (none) reserved1 0 reserved2 0Section sectname __entitlements segname __TEXT addr 0x0000000100003ea5 size 0x000000000000010e offset 16037 align 2^0 (1) reloff 0 nreloc 0 type S_REGULARattributes (none) reserved1 0 reserved2 0Section sectname __unwind_info segname __TEXT addr 0x0000000100003fb4 size 0x0000000000000048 offset 16308 align 2^2 (4) reloff 0 nreloc 0 type S_REGULARattributes (none) reserved1 0 reserved2 0Load command 2 cmd LC_SEGMENT_64 cmdsize 472 segname __DATA_CONST vmaddr 0x0000000100004000 vmsize 0x0000000000004000 fileoff 16384 filesize 16384 maxprot rw- initprot rw- nsects 5 flags SG_READ_ONLYSection sectname __got segname __DATA_CONST addr 0x0000000100004000 size 0x0000000000000060 offset 16384 align 2^3 (8) reloff 0 nreloc 0 type S_NON_LAZY_SYMBOL_POINTERSattributes (none) reserved1 10 (index into indirect symbol table) reserved2 0Section sectname __cfstring segname __DATA_CONST addr 0x0000000100004060 size 0x0000000000000020 offset 16480 align 2^3 (8) reloff 0 nreloc 0 type S_REGULARattributes (none) reserved1 0 reserved2 0Section sectname __objc_classlist segname __DATA_CONST addr 0x0000000100004080 size 0x0000000000000018 offset 16512 align 2^3 (8) reloff 0 nreloc 0 type S_REGULARattributes NO_DEAD_STRIP reserved1 0 reserved2 0Section sectname __objc_protolist segname __DATA_CONST addr 0x0000000100004098 size 0x0000000000000020 offset 16536 align 2^3 (8) reloff 0 nreloc 0 type S_REGULARattributes (none) reserved1 0 reserved2 0Section sectname __objc_imageinfo segname __DATA_CONST addr 0x00000001000040b8 size 0x0000000000000008 offset 16568 align 2^2 (4) reloff 0 nreloc 0 type S_REGULARattributes (none) reserved1 0 reserved2 0Load command 3 cmd LC_SEGMENT_64 cmdsize 632 segname __DATA vmaddr 0x0000000100008000 vmsize 0x0000000000004000 fileoff 32768 filesize 16384 maxprot rw- initprot rw- nsects 7 flags (none)Section sectname __objc_const segname __DATA addr 0x0000000100008000 size 0x0000000000001368 offset 32768 align 2^3 (8) reloff 0 nreloc 0 type S_REGULARattributes (none) reserved1 0 reserved2 0Section sectname __objc_selrefs segname __DATA addr 0x0000000100009368 size 0x0000000000000018 offset 37736 align 2^3 (8) reloff 0 nreloc 0 type S_LITERAL_POINTERSattributes NO_DEAD_STRIP reserved1 0 reserved2 0Section sectname __objc_classrefs segname __DATA addr 0x0000000100009380 size 0x0000000000000010 offset 37760 align 2^3 (8) reloff 0 nreloc 0 type S_REGULARattributes NO_DEAD_STRIP reserved1 0 reserved2 0Section sectname __objc_superrefs segname __DATA addr 0x0000000100009390 size 0x0000000000000008 offset 37776 align 2^3 (8) reloff 0 nreloc 0 type S_REGULARattributes NO_DEAD_STRIP reserved1 0 reserved2 0Section sectname __objc_ivar segname __DATA addr 0x0000000100009398 size 0x0000000000000008 offset 37784 align 2^3 (8) reloff 0 nreloc 0 type S_REGULARattributes (none) reserved1 0 reserved2 0Section sectname __objc_data segname __DATA addr 0x00000001000093a0 size 0x00000000000000f0 offset 37792 align 2^3 (8) reloff 0 nreloc 0 type S_REGULARattributes (none) reserved1 0 reserved2 0Section sectname __data segname __DATA addr 0x0000000100009490 size 0x0000000000000180 offset 38032 align 2^3 (8) reloff 0 nreloc 0 type S_REGULARattributes (none) reserved1 0 reserved2 0Load command 4 cmd LC_SEGMENT_64 cmdsize 72 segname __LINKEDIT vmaddr 0x000000010000c000 vmsize 0x0000000000008000 fileoff 49152 filesize 29600 maxprot r-- initprot r-- nsects 0 flags (none)Load command 5 cmd LC_DYLD_CHAINED_FIXUPS cmdsize 16 dataoff 49152 datasize 680Load command 6 cmd LC_DYLD_EXPORTS_TRIE cmdsize 16 dataoff 49832 datasize 216Load command 7 cmd LC_SYMTAB cmdsize 24 symoff 50072 nsyms 203 stroff 53408 strsize 6144Load command 8 cmd LC_DYSYMTAB cmdsize 80 ilocalsym 0 nlocalsym 175 iextdefsym 175 nextdefsym 8 iundefsym 183 nundefsym 20 tocoff 0 ntoc 0 modtaboff 0 nmodtab 0 extrefsymoff 0 nextrefsyms 0 indirectsymoff 53320 nindirectsyms 22 extreloff 0 nextrel 0 locreloff 0 nlocrel 0Load command 9 cmd LC_LOAD_DYLINKER cmdsize 32 name /usr/lib/dyld (offset 12)Load command 10 cmd LC_UUID cmdsize 24 uuid 05EC375B-F3C2-3C5A-99D2-28FB938FF144Load command 11 cmd LC_BUILD_VERSION cmdsize 32 platform IOSSIMULATOR minos 15.5 sdk 15.5 ntools 1 tool LD version 764.0Load command 12 cmd LC_SOURCE_VERSION cmdsize 16 version 0.0Load command 13 cmd LC_MAIN cmdsize 24 entryoff 8672 stacksize 0Load command 14 cmd LC_LOAD_DYLIB cmdsize 88 name /System/Library/Frameworks/Foundation.framework/Foundation (offset 24) time stamp 2 Thu Jan 1 08:00:02 1970 current version 1860.0.0compatibility version 300.0.0Load command 15 cmd LC_LOAD_DYLIB cmdsize 56 name /usr/lib/libobjc.A.dylib (offset 24) time stamp 2 Thu Jan 1 08:00:02 1970 current version 228.0.0compatibility version 1.0.0Load command 16 cmd LC_LOAD_DYLIB cmdsize 56 name /usr/lib/libSystem.B.dylib (offset 24) time stamp 2 Thu Jan 1 08:00:02 1970 current version 1311.120.1compatibility version 1.0.0Load command 17 cmd LC_LOAD_DYLIB cmdsize 96 name /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation (offset 24) time stamp 2 Thu Jan 1 08:00:02 1970 current version 1860.0.0compatibility version 150.0.0Load command 18 cmd LC_LOAD_DYLIB cmdsize 80 name /System/Library/Frameworks/UIKit.framework/UIKit (offset 24) time stamp 2 Thu Jan 1 08:00:02 1970 current version 5610.0.0compatibility version 1.0.0Load command 19 cmd LC_RPATH cmdsize 40 path @executable_path/Frameworks (offset 12)Load command 20 cmd LC_FUNCTION_STARTS cmdsize 16 dataoff 50048 datasize 24Load command 21 cmd LC_DATA_IN_CODE cmdsize 16 dataoff 50072 datasize 0Load command 22 cmd LC_CODE_SIGNATURE cmdsize 16 dataoff 59552 datasize 19200
复制代码
3.查看依赖的动态库
命令:otool -L MachOTest.app/MachOTest
示例结果:
MachOTest.app/MachOTest: /System/Library/Frameworks/Foundation.framework/Foundation (compatibility version 300.0.0, current version 1860.0.0) /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1311.120.1) /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation (compatibility version 150.0.0, current version 1860.0.0) /System/Library/Frameworks/UIKit.framework/UIKit (compatibility version 1.0.0, current version 5610.0.0)
复制代码
评论