什么是 Mach-O
Mach-O 其实是 Mach Object 文件格式的缩写,它是一种用于可执行文件、目标代码、动态库的文件格式,作为 a.out 格式的替代, Mach-O 提供了更强的扩展性。在 OS X 中,内核扩展、命令行工具、应用程序、框架和库(共享和静态)是使用 Mach-O 文件实现的。可以在 Mach-O Programming Topics 查看相关介绍。
分析 Mach-O 文件的工具
-
/usr/bin/lipo
查看二进制文件信息,可以生成或者拆分多架构文件 -
/usr/bin/file
显示文件的类型。对于多架构文件,它会显示构成存档的每个图像的类型。 -
/usr/bin/otool
列出了 Mach-O 文件中特定部分和段的内容。它包括每个支持的体系结构的符号反汇编器,并且它知道如何格式化许多常见节类型的内容。 -
/usr/bin/pagestuff
显示关于组成图像的每个逻辑页面的信息,包括每个页面中包含的部分和符号的名称。此工具不适用于包含多个架构的图像的二进制文件。 -
/usr/bin/nm
允许查看目标文件符号表的内容。 - MachoView.app
在查看 Mach-O 文件之前,有必要了解MachOView.app 工具,MachOView工具属于免费开源项目,源代码可在Github-MachOView下载,可以在 Mac 中查看 Mach-O文件的详细信息
Mach-O 文件结构
一个 Mach-O 文件包含三个主要区域(如下所示):
- Header:指定文件的目标架构
- Load commands :指定文件的逻辑结构和文件在虚拟内存中的布局。
- Raw segment data:包含加载命令中定义的段的原始数据。
当然,可以通过 MachOView 对 App 可执行文件进行查看,Mach-O 文件里面的内容如下图所示:
Mach64 Header
每个 Mach-O 文件的开头都有一个 Header ,用于将文件标识为 Mach-O 文件。Header 包含该二进制文件的一般信息。字节顺序、架构类型、加载指令的数量等。使得可以快速确认一些信息,比如当前文件用于32位还是64位,对应的处理器是什么、文件类型是什么
Mach64 Header 结构
在 macho/loader.h 中,可以查看到 Mach-O header 信息结构代码如下:
struct mach_header_64 {
uint32_t magic; // 64位还是32位
cpu_type_t cputype;
cpu_subtype_t cpusubtype; // CPU 子类型,比如 armv8 CPU_SUBTYPE_ARM_64
uint32_t filetype; // 文件类型 MH_EXECUTE
uint32_t ncmds; // load commands 的数量
uint32_t sizeofcmds; // load commands 大小
uint32_t flags; // 标签
uint32_t reserved; // 保留字段
};
如上面代码所示,包含了
-
magic
表示是 64 位还是 32 位的、 -
cputype
CPU 类型,比如CPU_TYPE_ARM
,可以在macho/machine.h
文件中查看其他。 -
cpusubtype
CPU 子类型,比如CPU_SUBTYPE_ARM64_ALL
,可以在macho/machine.h
文件中查看其他。 -
filetype
文件类型 -
ncmds
load commands 的数量和大小等文件信息 -
sizeofcmds
load commands 大小 -
flags
标志位,标识二进制文件支持的功能。 -
reserved
保留字段
Mach-O 文件类型
其中,文件类型 filetype
表示了当前 Mach-O 属于哪种类型。下面源代码列举了 filetype
的全部类型:
/*
* Constants for the filetype field of the mach_header
*/
#define MH_OBJECT 0x1 /* relocatable object file */
#define MH_EXECUTE 0x2 /* demand paged executable file */
#define MH_FVMLIB 0x3 /* fixed VM shared library file */
#define MH_CORE 0x4 /* core file */
#define MH_PRELOAD 0x5 /* preloaded executable file */
#define MH_DYLIB 0x6 /* dynamically bound shared library */
#define MH_DYLINKER 0x7 /* dynamic link editor */
#define MH_BUNDLE 0x8 /* dynamically bound bundle file */
#define MH_DYLIB_STUB 0x9 /* shared library stub for static
linking only, no section contents */
#define MH_DSYM 0xa /* companion file with only debug
sections */
#define MH_KEXT_BUNDLE 0xb /* x86_64 kexts */
#define MH_FILESET 0xc /* a file composed of other Mach-Os to
be run in the same userspace sharing
a single linkedit. */
其中,部分类型是我们开发中常用的:
- OBJECT,指的是 .o 文件或者 .a 文件;
- EXECUTE,指的是 IPA 拆包后的可执行文件;
- DYLIB,指的是 .dylib 或 .framework 文件;
- DYLINKER,指的是动态链接器;
- DSYM,指的是保存有符号信息用于分析闪退信息的文件。
Load Commands
紧随 Header 的是一系列的 Load Commands ,负责描述文件在虚拟内存中逻辑结构和布局,这些加载指令清晰地告诉加载器如何处理二进制数据,有些命令是由内核处理的,有些是由动态链接器处理的。除了其他信息,Load Commands 可以指定:
文件在虚拟内存中的初始布局
符号表的位置(用于动态链接)
程序主线程的初始执行状态
包含主可执行文件的导入符号定义的共享库的名称
Load Commands结构
macho/loader.h 中,可以查看到 load_command
信息结构代码如下:
struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */
};
Load Commands 类型
下面列举了 load commands
全部的类型:
/* Constants for the cmd field of all load commands, the type */
#define LC_SEGMENT 0x1 /* segment of this file to be mapped */
#define LC_SYMTAB 0x2 /* link-edit stab symbol table info */
#define LC_SYMSEG 0x3 /* link-edit gdb symbol table info (obsolete) */
#define LC_THREAD 0x4 /* thread */
#define LC_UNIXTHREAD 0x5 /* unix thread (includes a stack) */
#define LC_LOADFVMLIB 0x6 /* load a specified fixed VM shared library */
#define LC_IDFVMLIB 0x7 /* fixed VM shared library identification */
#define LC_IDENT 0x8 /* object identification info (obsolete) */
#define LC_FVMFILE 0x9 /* fixed VM file inclusion (internal use) */
#define LC_PREPAGE 0xa /* prepage command (internal use) */
#define LC_DYSYMTAB 0xb /* dynamic link-edit symbol table info */
#define LC_LOAD_DYLIB 0xc /* load a dynamically linked shared library */
#define LC_ID_DYLIB 0xd /* dynamically linked shared lib ident */
#define LC_LOAD_DYLINKER 0xe /* load a dynamic linker */
#define LC_ID_DYLINKER 0xf /* dynamic linker identification */
#define LC_PREBOUND_DYLIB 0x10 /* modules prebound for a dynamically */
/* linked shared library */
#define LC_ROUTINES 0x11 /* image routines */
#define LC_SUB_FRAMEWORK 0x12 /* sub framework */
#define LC_SUB_UMBRELLA 0x13 /* sub umbrella */
#define LC_SUB_CLIENT 0x14 /* sub client */
#define LC_SUB_LIBRARY 0x15 /* sub library */
#define LC_TWOLEVEL_HINTS 0x16 /* two-level namespace lookup hints */
#define LC_PREBIND_CKSUM 0x17 /* prebind checksum */
/*
* load a dynamically linked shared library that is allowed to be missing
* (all symbols are weak imported).
*/
#define LC_LOAD_WEAK_DYLIB (0x18 | LC_REQ_DYLD)
#define LC_SEGMENT_64 0x19 /* 64-bit segment of this file to be
mapped */
#define LC_ROUTINES_64 0x1a /* 64-bit image routines */
#define LC_UUID 0x1b /* the uuid */
#define LC_RPATH (0x1c | LC_REQ_DYLD) /* runpath additions */
#define LC_CODE_SIGNATURE 0x1d /* local of code signature */
#define LC_SEGMENT_SPLIT_INFO 0x1e /* local of info to split segments */
#define LC_REEXPORT_DYLIB (0x1f | LC_REQ_DYLD) /* load and re-export dylib */
#define LC_LAZY_LOAD_DYLIB 0x20 /* delay load of dylib until first use */
#define LC_ENCRYPTION_INFO 0x21 /* encrypted segment information */
#define LC_DYLD_INFO 0x22 /* compressed dyld information */
#define LC_DYLD_INFO_ONLY (0x22|LC_REQ_DYLD) /* compressed dyld information only */
#define LC_LOAD_UPWARD_DYLIB (0x23 | LC_REQ_DYLD) /* load upward dylib */
#define LC_VERSION_MIN_MACOSX 0x24 /* build for MacOSX min OS version */
#define LC_VERSION_MIN_IPHONEOS 0x25 /* build for iPhoneOS min OS version */
#define LC_FUNCTION_STARTS 0x26 /* compressed table of function start addresses */
#define LC_DYLD_ENVIRONMENT 0x27 /* string for dyld to treat
like environment variable */
#define LC_MAIN (0x28|LC_REQ_DYLD) /* replacement for LC_UNIXTHREAD */
#define LC_DATA_IN_CODE 0x29 /* table of non-instructions in __text */
#define LC_SOURCE_VERSION 0x2A /* source version used to build binary */
#define LC_DYLIB_CODE_SIGN_DRS 0x2B /* Code signing DRs copied from linked dylibs */
#define LC_ENCRYPTION_INFO_64 0x2C /* 64-bit encrypted segment information */
#define LC_LINKER_OPTION 0x2D /* linker options in MH_OBJECT files */
#define LC_LINKER_OPTIMIZATION_HINT 0x2E /* optimization hints in MH_OBJECT files */
#define LC_VERSION_MIN_TVOS 0x2F /* build for AppleTV min OS version */
#define LC_VERSION_MIN_WATCHOS 0x30 /* build for Watch min OS version */
#define LC_NOTE 0x31 /* arbitrary data included within a Mach-O file */
#define LC_BUILD_VERSION 0x32 /* build for platform min OS version */
#define LC_DYLD_EXPORTS_TRIE (0x33 | LC_REQ_DYLD) /* used with linkedit_data_command, payload is trie */
#define LC_DYLD_CHAINED_FIXUPS (0x34 | LC_REQ_DYLD) /* used with linkedit_data_command */
#define LC_FILESET_ENTRY (0x35 | LC_REQ_DYLD) /* used with fileset_entry_command */
通过 MachOView 来继续查看 LoadCommands 内容
对应的描述如下表格:
load commands Type | 描述 |
---|---|
LG_SEGMENT_64 | 将文件中(32位或64位)的段映射到进程地址空间中 |
LC_DYLD_INFO_ONLY | 动态链接相关信息 rebase、bind |
LC_SYMTAB | 符号表 |
LC_DYSYMTAB | 动态符号表 |
LC_UUID | 文件的 UUID |
LC_VERSION_MIN_IPHONES | 支持最低的操作系统版本 |
LC_SOURCE_VERSION | 源代码版本 |
LC_MAIN | 程序入口地址和栈大小 |
LC_LOAD_DYLIB | 依赖系统库路径 |
LC_RPATH | 运行时优先搜索依赖库的目录路径 |
LC_FUNCTION_STARTS | 函数起始地址表 |
LC_CODE_SIGNATURE | 代码签名 |
Segment_Command
LC_SEGMENT_64
和 LC_SEGMENT
是加载的主要命令,它负责指导内核来设置进程的内存空间,下面是
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]; /* segment name */
uint64_t vmaddr; /* memory address of this segment */
uint64_t vmsize; /* memory size of this segment */
uint64_t fileoff; /* file offset of this segment */
uint64_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 */
};
-
cmdsize
代表load command
的大小 -
segname
段名称-
__PAGEZERO
作为可执行文件的第一个段。该段位于虚拟内存位置 0 并且没有分配保护权限。该__PAGEZERO段是当前体系结构的一个完整 VM 页面的大小。因为__PAGEZERO
段中没有数据,所以不占用文件中的空间 -
__TEXT
对应的就是代码段 -
__DATA
对应的是可读/可写的数据 -
__OBJC
包含由 Objective-C 语言运行时支持库使用的数据 -
__LINKEDIT
是支持dyld的,里面包含一些符号表等数据 -
__IMPORT
包含符号存根和指向未在可执行文件中定义的符号的非惰性指针。此段仅为针对 IA-32 架构的可执行文件生成。
-
-
VM Address
段的虚拟内存地址 -
VM Size
段的虚拟内存大小 -
file offset
段在文件中偏移量 -
file size
:段在文件中的大小
将该段对应的文件内容加载到内存中:从offset处加载 file size大小到虚拟内存 vmaddr处 -
nsects
标示了Segment中有多少secetion
通过 MachOView 查看 TEXT 段 内容
Raw segment data
在 Load Commands 之后,所有的 Mach-O 文件都包含一个或多个 Segment 的数据。Segment 的每个部分都包含某种特定类型的代码或数据,每个 Segment 定义了一个虚拟内存区域,动态链接器将其映射到进程的地址空间。Segments 和 Sections 的确切数量和布局由加载命令和文件类型指定。
Section_64 结构
macho/loader.h 中,可以查看到 section_64
信息结构代码如下:
struct section_64 { /* for 64-bit architectures */
char sectname[16]; /* name of this section */
char segname[16]; /* segment this section goes in */
uint64_t addr; /* memory address of this section */
uint64_t size; /* size in bytes of this section */
uint32_t offset; /* file offset of this section */
uint32_t align; /* section alignment (power of 2) */
uint32_t reloff; /* file offset of relocation entries */
uint32_t nreloc; /* number of relocation entries */
uint32_t flags; /* flags (section type and attributes)*/
uint32_t reserved1; /* reserved (for offset or index) */
uint32_t reserved2; /* reserved (for count or sizeof) */
uint32_t reserved3; /* reserved */
};
-
sectname
比如_text、stubs -
segname
该 section 所属的 segment ,比如__TEXT(主程序),__DATA(数据段) -
addr
该 section在内存的起始位置 -
size
该 section 的大小 -
offset
该 section 的文件偏移 -
align
字节大小对齐 -
reloff
重定位入口的文件偏移 -
nreloc
需要重定位的入口数量 -
flags
包含 section 的 type 和 attributes
Section 的类型
名称 | 描述 | 所属部分 |
---|---|---|
__TEXT,__text | 可执行机器码。编译器通常只在此部分中放置可执行代码,而不放置任何类型的表或数据。 | __TEXT |
__TEXT,__cstring | 常量 C 字符串。静态链接器在构建最终产品时合并常量 C 字符串值,删除重复项。 | __TEXT |
__TEXT,__picsymbol_stub | 与位置无关的间接符号存根 | __TEXT |
__TEXT,__symbol_stub | 间接符号存根。 | __TEXT |
__TEXT,__const | 初始化常量。 | __TEXT |
__TEXT,__literal4 | 4 字节字面量 | __TEXT |
__TEXT,__literal8 | 8 字节字面量 | __TEXT |
__DATA,__data | 已初始化的可变变量 | __DATA |
__DATA,__la_symbol_ptr | 懒加载符号指针 | __DATA |
__DATA,__nl_symbol_ptr | 非懒加载符号指针 | __DATA |
__DATA,__dyld | 动态链接器使用的占位符部分 | __DATA |
__DATA,__const | 初始化的可重定位常量 | __DATA |
__DATA,__mod_init_func | 静态构造函数 | __DATA |
__DATA,__mod_term_func | 模块终止功能 | __DATA |
__DATA,__bss | 未初始化静态变量的数据 | __DATA |
__DATA,__common | 位于全局范围内的变量声明 | __DATA |