背景
工作以来代码中涉及Mach-o文件的操作和理解内容较多,一直也没有做一个比较全面的学习,今天先简单整理一下相关的内容,step by step,冲鸭!
What
Mach-O简述
一句话,Mach-O是mac以及ios上的一种文件格式,用于存储程序、库的标准格式,是Mach object的缩写。那既然是文件格式,有哪些文件是Mach-O格式呢?常见的Mach-O文件类型有如下几种:
- 目标文件.o
- 库文件 .a(静态库文件,其实就是多个.o文件合并在一起)、.dylib(动态库文件)、.framework
- 可执行文件
- 动态链接/编辑器:dyld、.dsym(符号表,存储二进制文件符号信息的文件)
在Xcode中也可以查看Mach-O文件类型:
命令查看文件类型:file 文件
Mach-O可以是多架构的二进制文件,即通用二进制文件,也称为胖二进制文件
Mach-O文件结构
Mach-O这种文件格式是哪种格式呢?
Mach-O文件的基本结构如上图所示,文件主要包括三部分:Header、Load Command、Data。
- Header:保存文件的基本信息,如文件类型、CPU架构信息、加载命令个数等
- Load Command:根据这里的数据确定内存分布(本身不包含数据,类似于一个指针)
- Data:存放具体的代码和数据,是Mach-O文件真正的内容数据。
Header
文件头信息在<mach-o/loader.h>中定义。
(32位和64位结构不同)64位header结构体如下:
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 */
};
- magic指定是32位还是64位
- cputype和cpusubtype是表示cpu的架构是x86还是x64等,即平台和版本
- filetype:文件类型:标识是执行文件还是动态库等
- ncmds: 表示接下来的加载命令的个数
- sizeofcmds: 加载命令的总长度
- flags: ldid动态加载需要的标记位
- 最后的保留位不解释
Mach-O支持多种文件类型,filetype字段用于标识,这些文件类型定义在loader.h文件中:
#define MH_OBJECT 0x1 /* Target 文件:编译器对源码编译后得到的中间结果 */
#define MH_EXECUTE 0x2 /* 可执行二进制文件 */
#define MH_FVMLIB 0x3 /* VM 共享库文件(还不清楚是什么东西) */
#define MH_CORE 0x4 /* Core 文件,一般在 App Crash 产生 */
#define MH_PRELOAD 0x5 /* preloaded executable file */
#define MH_DYLIB 0x6 /* 动态库 */
#define MH_DYLINKER 0x7 /* 动态连接器 /usr/lib/dyld */
#define MH_BUNDLE 0x8 /* 非独立的二进制文件,往往通过 gcc-bundle 生成 */
#define MH_DYLIB_STUB 0x9 /* 静态链接文件(还不清楚是什么东西) */
#define MH_DSYM 0xa /* 符号文件以及调试信息,在解析堆栈符号中常用 */
#define MH_KEXT_BUNDLE 0xb /* x86_64 内核扩展 */
Load Command
Load command描述了文件中数据的具体组织结构,不同的数据类型使用不同的加载命令。它的大小和数目在header中已经被提供,如上图所示,在<mach-o/loader.h>中定义了load command的结构:
struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */
};
Load Command中存储着不同类型的command,如LC_SEGMENT_64、LC_UUID、LC_MAIN…,command主要有两种类型:指向具体数据、不指向具体数据。不同类型的command对应着不同的结构体,load_command类似于基类,其他类型的command结构体“继承”自这个command:(来自网友总结)
我们分析、使用最多的是LC_SEGMENT_64命令,该命令表示将相应的segment映射到虚拟地址空间中,一个程序一般会分为多个段,每一个段有唯一的段名,不同类型的数据放入不同的段中,常见的段有__PAGZERO、__TEXT、 __DATA、__LINKEDIT、__DATA_CONST,该command的数据结构定义在<mach-o.h/loader.h>中:
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]; /* 表示是段的名称。常见的有:__PAGEZERO、__LINKEDIT、__TEXT、__DATA */
uint64_t vmaddr; /* 当前segment加载的虚拟内存起始地址 */
uint64_t vmsize; /* 段所占的虚拟内存的大小 */
uint64_t fileoff; /* segment在文件中的偏移 */
uint64_t filesize; /* segment在文件中的长度 */
vm_prot_t maxprot; /* 最大的保护级别 */
vm_prot_t initprot; /* 初始化的保护级别 */
uint32_t nsects; /* 包含sections的个数 */
uint32_t flags; /* 标志位 */
};
其中,__TEXT段是代码段,里面主要存放代码,该段是可读可执行,但是不可写;__DATA段是数据段,里面主要是存放数据,该段是可读可写,但是不可执行。Segment段可以进一步分解为section,nsects表示该段包含的section的个数。segment负责内存对齐以及保持section相对位置不变,section负责数据、代码的存储。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 */
};
Data
该部分包含segment的具体数据,由Segment段和Section节组成,load_command和data部分的关系如下所示,类似于指针和数据:
How
Mach-O文件中存储者程序的代码和数据,在程序运行时系统会为其创建一个进程,以及分配虚拟内存空间,同时把程序文件中的内容加载到虚拟内存空间中。进程的映像(image)其实就是程序磁盘文件(Mach-O文件)在内存中的一个副本。Image是Mach-O文件在内存中的一个副本。对进程映像(image)操作的API都在<mach-o/dyld.h>中声明。
ASLR
简单理解:当app被加载到内存时,会有一个整体的地址偏移,使得Mach-O文件的虚拟内存有一个整体偏移。
Mach-O类型文件工具
系统自带工具
- file : 查看mach-o的文件类型
- nm: 查看mach-o文件的符号表
- otool: 查看mach-o特定部分和段的内容
- lipo: 常用于多架构mach-o文件的处理
Xcode调试命令
- image list
- image lookup …
第三方工具
- MachOView:GUI工具查看Mach-O文件
- Hopper : 反汇编查看Mach-O文件