1、MachO文件的概念
Mach-O 其实是Mach Object文件格式的缩写,是 mac 以及 iOS 上可执行文件的格式, 类似于 windows 上的 PE 格式 (Portable Executable), linux 上的 elf 格式 (Executable and Linking Format) 。常⻅的 .o,.a .dylib Framework,dyld .dsym。
2、整体结构如下图

2.1、Header(头部)
Header表明该文件是 Mach-O 格式,指定目标架构,还有一些其他的文件属性信 息,文件头信息影响后续的文件结构安排。
与 Mach-O 文件格式有关的结构体定义都可以从xnu源码的loader.h中找到。
32位和64位的mach_header的数据结构如下
/*
* The 32-bit mach header appears at the very beginning of the object file for
* 32-bit architectures.
*/
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 */
};
/* Constant for the magic field of the mach_header (32-bit architectures) */
#define MH_MAGIC 0xfeedface /* the mach magic number */
#define MH_CIGAM 0xcefaedfe /* NXSwapInt(MH_MAGIC) */
/*
* The 64-bit mach header appears at the very beginning of object files for
* 64-bit architectures.
*/
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魔数(特征数字),用来标记当前设备是大端序还是小端序。 -
cputype标识 CPU 的架构,例如 ARM、ARM64、X86_64 等。 -
cpusubtype标识 CPU 的具体类型,区分不同版本的处理器。 -
filetype由于 Mach-O 支持多种类型文件,所以此处引入了 filetype 字段来标明(.o,.a.dylibFramework,dyld.dsym等) -
ncmdsMach-O 文件中加载命令(load commands)的条数。 -
sizeofcmdsMach-O文件中加载命令(load commands)的总大小。 -
flags标识着 Mach-O 文件的一些重要信息(可以loader.h中查看结构,其中MH_PIE启用ASLR) -
reserved64位预留字段
2.2、Load commands
Load commands 是一张包含很多内容的表。内容包括区域的位置、符号表、动态符号表 等。用于告诉loader如何设置并加载二进制数据,对系统内核加载器和动态链接器(dyld)起指导作用。
load_command的数据结构
struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */
};
前 4 个字节表示类型,不同类型的 load command 作用不一样,紧跟其后的 4 个字节表示该 load command 的大小.
可以通过MachOView软件来查看类容(类型)

-
LC_SEGMENT_64将文件中(32位或64位)的段映射到进程地 址空间中 -
LC_DYLD_INFO_ONLY动态链接相关信息 -
LC_SYMTAB符号地址 -
LC_DYSYMTAB动态符号表地址 -
LC_LOAD_DYLINKERdyld加载 -
LC_UUID文件的UUID -
LC_VERSION_MIN_MACOSX支持最低的操作系统版本 -
LC_SOURCE_VERSION源代码版本 -
LC_MAIN设置程序主线程的入口地址和栈大小 -
LC_LOAD_DYLIB依赖库的路径,包含三方库 -
LC_FUNCTION_STARTS函数起始地址表 -
LC_CODE_SIGNATURE代码签名 -
LC_ENCRYPTION_INFO和LC_ENCRYPTION_INFO_64:加密信息,如果是从App Store上下载的应用,外面被加了一层壳,对应的加密标记(Crypt ID)不为0,如果不是App Store上下载的应用(例如PP助手上),或这个已经被脱过壳的,加密标记(Crypt ID)便为0
LC_SEGMENT(段)
LC_SEGMENT_64和LC_SEGMENT(32位系统)是加载的主要命令,翻译成中文叫做“段”,它负责指导内核来设置进程的内存空间,说白了,只要是这个类型的 load command,系统会将其指示的内容全部加载到指定的虚拟内存地址上来。
MachOView看到的内容

通过
mach-o/loader.h 找到数据结构
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]; /* 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 */
};
-
cmd该加载命令类型,为LC_SEGMENT_64(64位)或LC_SEGMENT(32位) -
cmdsize该加载命令大小,包括segement下session结构所占大小 -
segname[16]segment 名字 -
vmaddr为当前segment分配的虚拟内存地址 -
vmsize为当前segment分配的虚拟内存大小 -
fileoff当前segment在 Mach-O 文件中的偏移量 -
filesize当前segment在 Mach-O 文件中占用的字节 -
maxprotsegment所在页所需要的最高内存保护 -
initprotsegment所在页原始内存保护 -
nsectssegment中section数量 -
flags标识符
大致可以这么理解:系统 Mach-O 从fileoff处加载filesie大小的内容到虚拟内存vmaddr处,大小为vmsize,segment页权限initport进行初始化,这些权限可以被修改,但是不能超过maxprot的值。通俗来说就是,fileoff和filesie指导和说明了内容从哪里来,vmaddr和vmsize指导和说明了文件到哪里去。需要留意,对某些segment来说,vmsize可能会大于 filesize,如__DATA、__LINKEDIT。
segname分类(用下划线和大写字母组成)
-
__PAGEZERO:静态链接器创建了__PAGEZERO命名的段作为可执行文件的第一个段,该段在文件中所占大小为0,在 32 位系统上,加载到虚拟内存中是 0x4000,也就是 16kb,在 64 位系统上,加载到虚拟未存中是 0x100000000,也就是 4GB。是一个不可读、不可写、不可执行的空间,能够在空指针访问时抛出异常。 -
__TEXT:代码段,里面包含了可执行代码和其他一些只读数据,该段是可读、可执行,但是不可写。 -
__DATA:数据段,里面主要是存放将会被更改的数据,该段是可读、可写,但不可执行。 -
__LINKEDIT:包含需要被动态链接器使用的符号和其他表,包括符号表、字符串表等,可读,但不可写不可执行。
segname下的section
segname为__TEXT和__DATA两个segment可以进一步分解为section。之所以按照segment -> section的结构组织方式,是因为在同一个segment下的section,可以控制相同的权限,也可以不完全按照Page的大小进行内存对其,节省内存的空间。而segment对外整体暴露,在程序载入阶段映射成一个完整的虚拟内存,更好的做到内存对齐。
从mach-o/loader.h文件中可以找到section的数据结构
struct section { /* for 32-bit architectures */
char sectname[16]; /* name of this section */
char segname[16]; /* segment this section goes in */
uint32_t addr; /* memory address of this section */
uint32_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) */
};
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:section名字 -
segname:section所在的segment名称 -
addr:section所在的内存地址 -
size:section的大小 -
offset:section所在的文件偏移 -
align:section的内存对齐边界 (2 的次幂) -
reloff:重定位信息的文件偏移 -
nreloc:重定位条目的数目 -
flags:标志属性 -
reserved:保留字段
使用 MachOView 进行查看OC工程的macho文件

各个section作用如下:
-
__TEXT.__text:主程序代码 -
__TEXT.__stubs、__TEXT.__stub_helper:用于帮助动态链接器绑定符号 -
__TEXT.__const:const关键字修饰的常量 -
__TEXT.__objc_methodname:OC方法名 -
__TEXT.__cstring:只读的C语言字符串 -
__TEXT.__objc_classname:OC类名 -
__TEXT.__objc_methtype:OC方法类型(方法签名) -
__TEXT.__gcc_except_tab、__ustring、__unwind_info:GCC编译器自动生成,用于确定异常发生是栈所对应的信息(包括栈指针、返回地址及寄存器信息等) -
__DATA.__got:全局非懒绑定符号指针表 -
__DATA.__la_symbol_ptr:懒绑定符号指针表 -
__DATA.__mod_init_func:C++类的构造函数 -
__DATA.__const:未初始化过的常量 -
__DATA.__cfstring:Core Foundation字符串 -
__DATA.__objc_classlist:OC类列表 -
__DATA.__objc_nlclslist:实现+load方法的 OC 类列表 -
__DATA.__catlist:OC 分类(Category)列表 -
__DATA.__protolist:OC 协议(Protocol)列表 -
__DATA.__imageinfo:镜像信息,可用它区别 OC 1.0与2.0 -
__DATA.__const:OC 初始化过的常量 -
__DATA.__selrefs:OC 选择器(SEL)引用列表 -
__DATA.__protorefs:OC 协议引用列表 -
__DATA.__classrefs:OC 类引用列表 -
__DATA.__superrefs:OC 超类(即父类)引用列表 -
__DATA.__ivar:OC 类的实例变量 -
__DATA.__objc_data:OC 初始化过的变量 -
__DATA.__data:实际初始化数据段 -
__DATA.__common:未初始化过的符号申明 -
__DATA.__bss:未初始化的全局变量
使用 MachOView 进行查看Swift工程的macho文件

在Swift的macho文件中,types是4字节存储信息,存储的是相对地址
注意:swift5的macho会多出几个section(目前知道的就是下面几个,后面了解再补充)
__swift5_types:存储的是Class、Struct、Enum的描述(Descriptor)偏移信息
__swift5_fieldmd:存储的是属性描述(属性信息)的偏移信息
__swift5_reflstr:存储的是属性名称
__swift5_protos:存储的是代理的描述
2.3、Data
Data 区主要就是负责代码和数据记录的。Mach-O 是以 Segment 这种结构来组织数据 的,一个 Segment 可以包含 0 个或多个 Section。根据 Segment 是映射的哪一个 Load Command,Segment 中 section 就可以被解读为是是代码,常量或者一些其他的数据类 型。在装载在内存中时,也是根据 Segment 做内存映射的。