写点什么

[iOS 研习记] 聊聊 iOS 中的 Mach-O

作者:珲少
  • 2022 年 9 月 28 日
    上海
  • 本文字数:19592 字

    阅读完需:约 64 分钟

[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)
复制代码


发布于: 刚刚阅读数: 3
用户头像

珲少

关注

还未添加个人签名 2022.07.26 加入

还未添加个人简介

评论

发布
暂无评论
[iOS研习记]聊聊iOS中的Mach-O_珲少_InfoQ写作社区