1、Mach-O简介
Mach-O是Mach object的缩写,是Mac/iOS上用于存储程序、库等的标准格式,在 xnu源码 中可以看到Mach-O格式的详细定义,属于Mach-O格式的文件类型有11种:
#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 */
源码位置:/EXTERNAL_HEADERS/mach-o/loader.h
2、常见的Mach-O文件类型
2.1、MH_OBJECT
2.1.1、目标文件 .o
目标文件源文件和可执行文件的中间产物,如下图所示:
可以手动创建一个C语言文件 test.c,通过命令行编译该文件 $ clang -c test.c 生成test.o文件,再通过file指令查看:
$ file test.o
test.o: Mach-O 64-bit object x86_64
2.1.2、静态库文件 .a
静态库其实就是N个.o合并在一起,找一个静态库文件liby.a,通过file指令查看:
$ file liby.a
liby.a: Mach-O universal binary with 3 architectures: [arm64:current ar archive random librarycurrent ar archive random library] [arm64e:current ar archive random librarycurrent ar archive random library] [x86_64:current ar archive random librarycurrent ar archive random library]
liby.a (for architecture arm64): current ar archive random library
liby.a (for architecture arm64e): current ar archive random library
liby.a (for architecture x86_64): current ar archive random library
2.2、MH_EXECUTE:可执行文件
将test.c文件生成可执行文件test2:$ clang -o test2 test.c,再通过file指令查看test2:
$ file test2
test2: Mach-O 64-bit executable x86_64
2.3、MH_DYLIB:动态文件
2.3.1、.dylib后缀文件
找到一个动态库文件,通过file指令查看:
$ file libgmalloc.dylib
libgmalloc.dylib: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit dynamically linked shared library x86_64
- Mach-O 64-bit dynamically linked shared library x86_64] [arm64e:Mach-O 64-bit dynamically linked shared library arm64e
- Mach-O 64-bit dynamically linked shared library arm64e]
libgmalloc.dylib (for architecture x86_64): Mach-O 64-bit dynamically linked shared library x86_64
libgmalloc.dylib (for architecture arm64e): Mach-O 64-bit dynamically linked shared library arm64e
输出结果显示,.dylib文件是Mach-O格式文件,并且是一个动态库文件。
2.3.2、.framework/xx
找到XCTest.framework中的XCTest,通过 file指令查看:
$ file XCTest
XCTest: Mach-O universal binary with 4 architectures: [arm_v7:Mach-O dynamically linked shared library arm_v7
- Mach-O dynamically linked shared library arm_v7] [arm_v7s:Mach-O dynamically linked shared library arm_v7s
- Mach-O dynamically linked shared library arm_v7s] [arm64:Mach-O 64-bit dynamically linked shared library arm64
- Mach-O 64-bit dynamically linked shared library arm64] [arm64e:Mach-O 64-bit dynamically linked shared library arm64e
- Mach-O 64-bit dynamically linked shared library arm64e]
XCTest (for architecture armv7): Mach-O dynamically linked shared library arm_v7
XCTest (for architecture armv7s): Mach-O dynamically linked shared library arm_v7s
XCTest (for architecture arm64): Mach-O 64-bit dynamically linked shared library arm64
XCTest (for architecture arm64e): Mach-O 64-bit dynamically linked shared library arm64e
2.4、MH_DYLINKER:动态链接编辑器
所在路径:/usr/lib/dyld,通过file指令查看:
$ file dyld
dyld: Mach-O universal binary with 3 architectures: [i386:Mach-O dynamic linker i386
- Mach-O dynamic linker i386] [x86_64:Mach-O 64-bit dynamic linker x86_64
- Mach-O 64-bit dynamic linker x86_64] [arm64e]
dyld (for architecture i386): Mach-O dynamic linker i386
dyld (for architecture x86_64): Mach-O 64-bit dynamic linker x86_64
dyld (for architecture arm64e): Mach-O 64-bit dynamic linker arm64e
2.5、MH_DSYM:存储着二进制文件符号信息的文件
找到 dSYM(符号表)文件夹中和项目同名的文件(.dSYM/Contents/Resources/DWARF/xx),并通过file指令查看:
$ file iOSDemo
iOSDemo: Mach-O 64-bit dSYM companion file arm64
3、Universal Binary(通用二进制文件)
观察XCTest的文件描述信息可知,它是一个universal binary,支持4种架构,它就是一个通用二进制文件。
通用二进制文件有以下特点:
- 同时适用多种架构的二进制文件,包含了多种不同架构的独立的二进制文件。
- 因为需要存储多种架构的代码,通用二进制文件通常比单一平台二进制的程序要大。
- 由于两种架构有共同的一些资源,所以并不会达到单一版本的两倍之多。
- 由于执行过程中,只调用了一部分代码,运行起来也不需要额外的内存。
- 因为文件比原来的要大,也被称为“胖二进制文件”(Fat Binary)
在Xcode的Build Settings中,可以通过修改Architectures配置来决定最终生成的二进制文件支持的架构。当使用Hopper查看通用二进制文件时,会提示FAT archive
可以通过lipo指令查看支持的架构:$ lipo -info XCTest
Architectures in the fat file: XCTest are: armv7 armv7s arm64 arm64e
也可通过lipo命令把胖二进制文件转换成瘦二进制文件(指定一个支持架构):
$ lipo XCTest -thin armv7 -output XCTest_armv7
lipo也可以把两种架构的瘦二进制文件合并成一个胖二进制文件:
$ lipo -create XCTest_armv7 XCTest_arm64 -output XCTest
4、Mach-O的基本结构
根据 苹果官方文档 描述,Mach-O文件分为三个部分:Header信息、Load commands信息、Data信息。
Header:文件类型、目标架构类型等
Load commands:文件在虚拟内存中的逻辑、布局
Raw segment data:在Load commands中定义的Segment的原始数据
以暴走漫画的可执行文件WebToon为例,可以使用 otool 命令查看Mach-O特定部分和段的信息比如:
查看引用的动态库:$ otool -L WebToon
查看header信息:$ otool -h WebToon
更多操作可以在终端输入 $ otool 查看使用帮助
也可以使用GUI工具 MachOView 来查看,Mach-O的Header信息:
Mach-O的Load commands信息,包含内存分段信息告诉你每个段有多大,大概在哪里开始,还包含所加载的动态库:
Mach-O的Data中保存了内存中的真实信息,比如class-dump就是读取了Data中的头文件信息:
5、dyld和Mach-O
dyld(动态链接编辑器)它可以加载以下类型的Mach-O文件:
1、MH_EXECUTE(可执行文件)
2、MH_DYLIB(动态库)
3、MH_BUNDLE(动态绑定的文件)
App的可执行文件、动态库都是由dyld负责加载的,可以通过查看dyld源码/scr/dyld2.cpp 中进行验证:
const mach_header* mh = (mach_header*)firstPages;
switch ( mh->filetype ) {
case MH_EXECUTE:
case MH_DYLIB:
case MH_BUNDLE:
break;
default:
throw "mach-o, but wrong filetype";
}