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

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 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的结构是怎样的。

[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_REGULAR
attributes PURE_INSTRUCTIONS SOME_INSTRUCTIONS
 reserved1 0
 reserved2 0
Section
  sectname __stubs
   segname __TEXT
      addr 0x00000001000024ce
      size 0x000000000000003c
    offset 9422
     align 2^1 (2)
    reloff 0
    nreloc 0
      type S_SYMBOL_STUBS
attributes 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_LITERALS
attributes (none)
 reserved1 0
 reserved2 0
Section
  sectname __objc_classname
   segname __TEXT
      addr 0x00000001000032f6
      size 0x0000000000000070
    offset 13046
     align 2^0 (1)
    reloff 0
    nreloc 0
      type S_CSTRING_LITERALS
attributes (none)
 reserved1 0
 reserved2 0
Section
  sectname __objc_methtype
   segname __TEXT
      addr 0x0000000100003366
      size 0x0000000000000b29
    offset 13158
     align 2^0 (1)
    reloff 0
    nreloc 0
      type S_CSTRING_LITERALS
attributes (none)
 reserved1 0
 reserved2 0
Section
  sectname __cstring
   segname __TEXT
      addr 0x0000000100003e8f
      size 0x0000000000000016
    offset 16015
     align 2^0 (1)
    reloff 0
    nreloc 0
      type S_CSTRING_LITERALS
attributes (none)
 reserved1 0
 reserved2 0
Section
  sectname __entitlements
   segname __TEXT
      addr 0x0000000100003ea5
      size 0x000000000000010e
    offset 16037
     align 2^0 (1)
    reloff 0
    nreloc 0
      type S_REGULAR
attributes (none)
 reserved1 0
 reserved2 0
Section
  sectname __unwind_info
   segname __TEXT
      addr 0x0000000100003fb4
      size 0x0000000000000048
    offset 16308
     align 2^2 (4)
    reloff 0
    nreloc 0
      type S_REGULAR
attributes (none)
 reserved1 0
 reserved2 0
Load 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_ONLY
Section
  sectname __got
   segname __DATA_CONST
      addr 0x0000000100004000
      size 0x0000000000000060
    offset 16384
     align 2^3 (8)
    reloff 0
    nreloc 0
      type S_NON_LAZY_SYMBOL_POINTERS
attributes (none)
 reserved1 10 (index into indirect symbol table)
 reserved2 0
Section
  sectname __cfstring
   segname __DATA_CONST
      addr 0x0000000100004060
      size 0x0000000000000020
    offset 16480
     align 2^3 (8)
    reloff 0
    nreloc 0
      type S_REGULAR
attributes (none)
 reserved1 0
 reserved2 0
Section
  sectname __objc_classlist
   segname __DATA_CONST
      addr 0x0000000100004080
      size 0x0000000000000018
    offset 16512
     align 2^3 (8)
    reloff 0
    nreloc 0
      type S_REGULAR
attributes NO_DEAD_STRIP
 reserved1 0
 reserved2 0
Section
  sectname __objc_protolist
   segname __DATA_CONST
      addr 0x0000000100004098
      size 0x0000000000000020
    offset 16536
     align 2^3 (8)
    reloff 0
    nreloc 0
      type S_REGULAR
attributes (none)
 reserved1 0
 reserved2 0
Section
  sectname __objc_imageinfo
   segname __DATA_CONST
      addr 0x00000001000040b8
      size 0x0000000000000008
    offset 16568
     align 2^2 (4)
    reloff 0
    nreloc 0
      type S_REGULAR
attributes (none)
 reserved1 0
 reserved2 0
Load 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_REGULAR
attributes (none)
 reserved1 0
 reserved2 0
Section
  sectname __objc_selrefs
   segname __DATA
      addr 0x0000000100009368
      size 0x0000000000000018
    offset 37736
     align 2^3 (8)
    reloff 0
    nreloc 0
      type S_LITERAL_POINTERS
attributes NO_DEAD_STRIP
 reserved1 0
 reserved2 0
Section
  sectname __objc_classrefs
   segname __DATA
      addr 0x0000000100009380
      size 0x0000000000000010
    offset 37760
     align 2^3 (8)
    reloff 0
    nreloc 0
      type S_REGULAR
attributes NO_DEAD_STRIP
 reserved1 0
 reserved2 0
Section
  sectname __objc_superrefs
   segname __DATA
      addr 0x0000000100009390
      size 0x0000000000000008
    offset 37776
     align 2^3 (8)
    reloff 0
    nreloc 0
      type S_REGULAR
attributes NO_DEAD_STRIP
 reserved1 0
 reserved2 0
Section
  sectname __objc_ivar
   segname __DATA
      addr 0x0000000100009398
      size 0x0000000000000008
    offset 37784
     align 2^3 (8)
    reloff 0
    nreloc 0
      type S_REGULAR
attributes (none)
 reserved1 0
 reserved2 0
Section
  sectname __objc_data
   segname __DATA
      addr 0x00000001000093a0
      size 0x00000000000000f0
    offset 37792
     align 2^3 (8)
    reloff 0
    nreloc 0
      type S_REGULAR
attributes (none)
 reserved1 0
 reserved2 0
Section
  sectname __data
   segname __DATA
      addr 0x0000000100009490
      size 0x0000000000000180
    offset 38032
     align 2^3 (8)
    reloff 0
    nreloc 0
      type S_REGULAR
attributes (none)
 reserved1 0
 reserved2 0
Load 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 680
Load command 6
      cmd LC_DYLD_EXPORTS_TRIE
  cmdsize 16
  dataoff 49832
 datasize 216
Load command 7
     cmd LC_SYMTAB
 cmdsize 24
  symoff 50072
   nsyms 203
  stroff 53408
 strsize 6144
Load 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 0
Load 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-28FB938FF144
Load command 11
      cmd LC_BUILD_VERSION
  cmdsize 32
 platform IOSSIMULATOR
    minos 15.5
      sdk 15.5
   ntools 1
     tool LD
  version 764.0
Load command 12
      cmd LC_SOURCE_VERSION
  cmdsize 16
  version 0.0
Load command 13
       cmd LC_MAIN
   cmdsize 24
  entryoff 8672
 stacksize 0
Load 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.0
compatibility version 300.0.0
Load 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.0
compatibility version 1.0.0
Load 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.1
compatibility version 1.0.0
Load 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.0
compatibility version 150.0.0
Load 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.0
compatibility version 1.0.0
Load 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 24
Load command 21
      cmd LC_DATA_IN_CODE
  cmdsize 16
  dataoff 50072
 datasize 0
Load 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)
目录
相关文章
|
Linux Swift iOS开发
iOS 逆向编程(十九)Mach-O 入门了解(如何通过 Mach-O 逆向 APP 思路)
iOS 逆向编程(十九)Mach-O 入门了解(如何通过 Mach-O 逆向 APP 思路)
209 0
|
存储 缓存 Swift
iOS逆向-day4:Mach-O
iOS逆向-day4:Mach-O
428 0
iOS逆向-day4:Mach-O
|
数据安全/隐私保护 iOS开发 MacOS
iOS逆向 12:Mach-O文件(下)
iOS逆向 12:Mach-O文件(下)
331 0
iOS逆向 12:Mach-O文件(下)
|
缓存 前端开发 Linux
iOS逆向 12:Mach-O文件(上)
iOS逆向 12:Mach-O文件(上)
437 0
iOS逆向 12:Mach-O文件(上)
|
11天前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台策略
在移动应用开发的战场上,安卓和iOS两大阵营各据一方。随着技术的演进,跨平台开发框架成为开发者的新宠,旨在实现一次编码、多平台部署的梦想。本文将探讨跨平台开发的优势与挑战,并分享实用的开发技巧,帮助开发者在安卓和iOS的世界中游刃有余。
|
2天前
|
Java 调度 Android开发
安卓与iOS开发中的线程管理差异解析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自拥有独特的魅力。如同东西方文化的差异,它们在处理多线程任务时也展现出不同的哲学。本文将带你穿梭于这两个平台之间,比较它们在线程管理上的核心理念、实现方式及性能考量,助你成为跨平台的编程高手。
|
4天前
|
存储 前端开发 Swift
探索iOS开发:从新手到专家的旅程
本文将带您领略iOS开发的奇妙之旅,从基础概念的理解到高级技巧的掌握,逐步深入iOS的世界。文章不仅分享技术知识,还鼓励读者在编程之路上保持好奇心和创新精神,实现个人成长与技术突破。
|
18天前
|
安全 数据处理 Swift
深入探索iOS开发中的Swift语言特性
本文旨在为开发者提供对Swift语言在iOS平台开发的深度理解,涵盖从基础语法到高级特性的全面分析。通过具体案例和代码示例,揭示Swift如何简化编程过程、提高代码效率,并促进iOS应用的创新。文章不仅适合初学者作为入门指南,也适合有经验的开发者深化对Swift语言的认识。
38 9
|
14天前
|
设计模式 Swift iOS开发
探索iOS开发:从基础到高级,打造你的第一款App
【10月更文挑战第40天】在这个数字时代,掌握移动应用开发已成为许多技术爱好者的梦想。本文将带你走进iOS开发的世界,从最基础的概念出发,逐步深入到高级功能实现,最终指导你完成自己的第一款App。无论你是编程新手还是有志于扩展技能的开发者,这篇文章都将为你提供一条清晰的学习路径。让我们一起开始这段旅程吧!