MachO文件概述
Mach-O
其实是Mach Object
文件格式的缩写,是macOS
以及iOS
上可执行文件的格式,类似于Windows
上的PE
格式(Portable Executable
),Linux
上的ELF
格式(Executable and Linking Format
)
Mach-O
文件格式
Mach-O
是一种用于可执行文件、目标代码、动态库的文件格式。作为a.out
格式的替代,Mach-O
提供了更强的扩展性属于
MachO
格式的常见文件
- 目标文件
.o
- 库文件:
.a
、.dylib
、Framework
- 可执行文件
dyld
.dSYM
file
命令使用
file
命令,用来探测给定文件的类型语法
file (选项) (参数)
选项
-b:列出辨识结果时,不显示文件名称; -c:详细显示指令执行过程,便于排错或分析程序执行的情形; -f<名称文件>:指定名称文件,其内容有一个或多个文件名称时,让file依序辨识这些文件,格式为每列一个文件名称; -L:直接显示符号连接所指向的文件类别; -m<魔法数字文件>:指定魔法数字文件; -v:显示版本信息; -z:尝试去解读压缩文件的内容。
参数
文件:要确定类型的文件列表,多个文件之间使用空格分开,可以使用
Shell
通配符匹配多个文件
案例1:
生成目标文件,查看文件格式
使用
clang
命令,将test.m
生成目标文件clang -c test.m -o test.o
使用
file
命令,查看目标文件的文件类型file test.o ------------------------- test.o: Mach-O 64-bit object x86_64
案例2:
生成可执行文件,查看文件格式
使用
clang
命令,将test.o
生成可执行文件clang test.o
使用
file
命令,查看可执行文件的文件类型file a.out ------------------------- a.out: Mach-O 64-bit executable x86_64
案例3:
使用
clang
命令,可以将目标文件生成可执行文件,也可以直接将.m
源文件生成可执行文件将目标文件生成
test1
可执行文件clang -o test1 test.o
将
.m
源码文件生成test2
可执行文件clang -o test2 test.m
不同方式生成的可执行文件,使用
HASH
验证它们的一致性md5 a.out md5 test1 md5 test2 ------------------------- MD5 (a.out) = e3e459acc7a9aecee970ed12a0c1368d MD5 (test1) = e3e459acc7a9aecee970ed12a0c1368d MD5 (test2) = e3e459acc7a9aecee970ed12a0c1368d
在源码没有变化的时候,使用
clang
编译出的二进制文件是一致的
案例4:
将多个
.m
源码文件,生成一个可执行文件创建
test1.m
文件,写入以下代码:#import
int main(){ return 1; } 创建
test2.m
文件,写入以下代码:#import
int test2(){ return 2; } 将多个源码文件,生成一个可执行文件
clang -o test1 test1.m test2.m
案例5:
将多个目标文件,链接成一个可执行文件
使用
clang
命令,将test1.m
和test2.m
生成目标文件clang -c test1.m test2.m
将多个目标文件,链接成一个可执行文件
clang -o test2 test2.o test1.o
不同方式生成的可执行文件,使用
HASH
验证它们的一致性md5 test1 md5 test2 ------------------------- MD5 (test1) = 1c9ad179590712c5a2a8683af9173d6e MD5 (test2) = 40f53096fe90fab7a531cee885e0b8f1
源码并没有发生变化,但两个可执行文件生成的
HASH
值不同。问题的本质,两次生成可执行文件时,编译源文件的顺序是不一样的使用
objdump
命令,分别查看test1
和test2
可执行文件的代码段objdump --macho -d test1 ------------------------- test1: (__TEXT,__text) section _main: 100003f80: 55 pushq %rbp 100003f81: 48 89 e5 movq %rsp, %rbp ... _test2: 100003fa0: 55 pushq %rbp 100003fa1: 48 89 e5 movq %rsp, %rbp ...
objdump --macho -d test2 ------------------------- test2: (__TEXT,__text) section _test2: 100003f90: 55 pushq %rbp 100003f91: 48 89 e5 movq %rsp, %rbp ... _main: 100003fa0: 55 pushq %rbp 100003fa1: 48 89 e5 movq %rsp, %rbp ...
两个可执行文件的代码截然不同
可执行文件是一个或多个目标文件的集合,会受到文件编译顺序的影响。原理和
Xcode
中的Compile Sources
一样
使用相同编译顺序,将多个目标文件,链接成一个可执行文件
clang -o test3 test1.o test2.o
使用
HASH
验证它们的一致性md5 test1 md5 test3 ------------------------- MD5 (test1) = 1c9ad179590712c5a2a8683af9173d6e MD5 (test3) = 1c9ad179590712c5a2a8683af9173d6e
多个目标文件,当源码和编译顺序相同,使用
clang
编译出的二进制文件是一致的
案例6:
查看
.a
的文件格式file libAFNetworking.a ------------------------- libAFNetworking.a: current ar archive
案例7:
查看
.dylib
的文件格式file libAFNetworking.dylib ------------------------- libAFNetworking.dylib: Mach-O 64-bit dynamically linked shared library x86_64
案例8:
查看
dyld
的文件格式使用
find
命令,找到dyld
文件路径find /usr -name "dyld" ------------------------- find: /usr/sbin/authserver: Permission denied /usr/lib/dyld /usr/share/file/magic/dyld
查看文件格式
file /usr/lib/dyld ------------------------- /usr/lib/dyld: Mach-O universal binary with 2 architectures: [i386:Mach-O dynamic linker i386] [x86_64:Mach-O 64-bit dynamic linker x86_64] /usr/lib/dyld (for architecture i386): Mach-O dynamic linker i386 /usr/lib/dyld (for architecture x86_64): Mach-O 64-bit dynamic linker x86_64
- 动态链接器
案例9:
查看
.dSYM
的文件格式右键
TestInject.app.dSYM
文件,显示包内容进入
Contents/Resources/DWARF
目录,找到TestInject
文件查看文件格式
file TestInject ------------------------- TestInject: Mach-O 64-bit dSYM companion file x86_64
- 二进制符号文件,常用于分析
App
的崩溃信息
可执行文件
项目中,在
Build Settings
的Mach-O Type
配置项,默认为Executable
(可执行文件)
在
iOS11
及以上系统中,项目生成的可执行文件为arm64
单一架构file WeChat ------------------------- WeChat: Mach-O 64-bit executable arm64
使用
iOS10.3
系统编译项目file WeChat ------------------------- WeChat: Mach-O universal binary with 2 architectures: [arm_v7:Mach-O executable arm_v7] [arm64:Mach-O 64-bit executable arm64] WeChat (for architecture armv7): Mach-O executable arm_v7 WeChat (for architecture arm64): Mach-O 64-bit executable arm64
- 格式变为通用二进制文件,包含
armv7
和arm64
两种架构项目中,
Build Settings
中包含架构的设置
Architectures
:设置支持的架构Build Active Architecture Only
:只编译当前设备支持的架构,Debug
模式默认YES
$(ARCHS_STANDARD)
:Xcode
内置的环境变量,不同Xcode
版本值不一样,当前版本下表示armv7 + arm64
案例1:
增加一个兼容架构
iOS
系统下,还有一个armv7s
架构,支持iPhone5
、iPhone5c
打开项目,在
Build Settings
中的Architectures
配置项,选择other
,增加armv7s
编译项目
file WeChat ------------------------- WeChat: Mach-O universal binary with 3 architectures: [arm_v7:Mach-O executable arm_v7] [arm_v7s:Mach-O executable arm_v7s] [arm64:Mach-O 64-bit executable arm64] WeChat (for architecture armv7): Mach-O executable arm_v7 WeChat (for architecture armv7s): Mach-O executable arm_v7s WeChat (for architecture arm64): Mach-O 64-bit executable arm64
- 同时包含三种架构,
armv7
、armv7s
、arm64
通用二进制文件
通用二进制文件(
Universal Binary
)
- 苹果公司提出的一种程序代码,能同时适用多种架构的二进制文件
- 同一个程序包中同时为多种架构提供最理想的性能
- 因为需要储存多种代码,通用二进制应用程序通常比单一平台二进制的程序要大
- 由于两种架构有共通的非执行资源(代码以外的),所以并不会达到单一版本的两倍之多
- 由于执行中只调用一部分代码,运行起来也不需要额外的内存
- 也被称为“胖二进制文件”(
Fat Binary
)
lipo
命令使用
lipo -info
,可以查看MachO
文件包含的架构lipo -info MachO文件
使用
lipo -thin
,拆分某种架构lipo MachO文件 -thin 架构 -output 输出文件路径
使用
lipo -create
,合并多种架构lipo -create MachO1 MachO2 -output 输出文件路径
案例1:
从通用二进制文件中拆分架构
查看可执行文件
lipo -info WeChat ------------------------- Architectures in the fat file: WeChat are: armv7 armv7s arm64
armv7
、armv7s
、arm64
三种架构使用
lipo
命令,拆分出arm64
架构lipo WeChat -thin arm64 -output WeChat_arm64
使用
lipo
命令,拆分出armv7
架构lipo WeChat -thin armv7 -output WeChat_armv7
案例2:
合并架构
使用
lipo
命令,将arm64
和armv7
架构合并lipo -create WeChat_arm64 WeChat_armv7 -output WeChat_64_v7
查看
WeChat_64_v7
可执行文件lipo -info WeChat_64_v7 ------------------------- Architectures in the fat file: WeChat_64_v7 are: armv7 arm64
- 包含
armv7
、arm64
两种架构
MachO文件结构
因为
MachO
文件本身是一种文件格式,所以我们一定需要了解其文件内部结构
Mach-O
文件结构
Header
:包含该二进制文件的一般信息
- 字节顺序、架构类型、加载指令的数量等
- 使得可以快速确认一些信息,比如当前文件用于
32位
还是64位
,对应的处理器是什么、文件类型是什么
Load Commands
:一张包含很多内容的表
- 内容包括区域的位置、符号表、动态符号表等
Data
:通常是对象文件中最大的部分
- 包含
Segement
的具体数据
通用二进制文件,最上面是
Fat Header
,包含自身信息和架构信息。多架构之间,对应多套Mach-O
的文件结构
otool
命令用来查看可执行文件的
MachO
信息Usage: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool [-arch arch_type] [-fahlLDtdorSTMRIHGvVcXmqQjCP] [-mcpu=arg] [--version]
案例1:
查看可执行文件的
Header
信息otool -f WeChat
案例2:
查看
App
所使用的动态库otool -L WeChat
案例3:
查看
ipa
是否已砸壳otool -l WeChat | grep cryptid -A 1 -B 5
cryptid
为0
,表示已砸壳
MachOView
使用
MachOView
打开
- 包含一个
Fat Header
和三个架构的文件结构cputype
:CPU
类型,比如ARM
cpusubtype
:CPU
的具体类型,arm64
、armv7
offset
:偏移值size
:当前架构大小align
:对齐方式。16384
,即:16字节
armv7
架构的偏移值为16384
,大小为79104
。armv7s
架构的偏移值等于v7
架构的偏移值 + 大小
,即95488
,由于16字节
对齐,即98304
。arm64
架构同理向后排列
Header
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
:魔数,快速定位属于64位
还是32位
cputype
:CPU
类型,比如ARM
cpusubtype
:CPU
的具体类型,arm64
、armv7
filetype
:文件类型,例如:可执行文件ncmds
:LoadCommands
的条数sizeofcmds
:LoadCommands
的大小flags
:标志位,标识二进制文件支持的功能。主要是和系统加载、链接有关reserved
:占位符
Load Commands
Load Commands
的数据结构
__PAGEZERO
:空指针陷阱段,映射到虚拟内存空间的第一页,用于捕捉对NULL
指针的引用
VM Address
:虚拟内存地址VM Size
:虚拟内存大小File Offset
:数据在文件中偏移量File Size
:数据在文件中的大小
__PAGEZERO
:自身不占大小,仅占用4G
虚拟内存大小。用于区分不同架构的系统,在arm64
架构下为0X100000000
,在非arm64
架构下为0X4000
可以使用
size
命令,查看MachO
的内存分布size -l -m -x WeChat
__TEXT
:代码段,包含执行代码及其他只读数据。该段为只读数据段
File Offset
为0
,表示文件从代码段开始
__DATA
:数据段,包括全局变量等。该段可读、可写
__LINKEDIT
:包含需要被动态链接器使用的信息
- 包括符号表、字符串表、重定位项表、签名等。该段和
__PAGEZERO
一样,末尾没有额外的Section
信息。File Offset + File Size
即为MachO
文件末尾,等于文件的大小
LC_DYLD_INFO_ONLY
:动态链接相关信息
Rebase
:用于重定向。Rebase Info Offset
表示函数开始位置。Rebase Info Size
表示从开始位置往后多少字节的数据需要重定向。当启动App
加载MachO
时,从Offset
函数开始位置+ ASLR
,后面的数据同理,一直加到Size
大小的位置结束Binding
:绑定数据,外部符号需要将地址进行绑定。从开始位置,往后多少字节的数据需要绑定Weak Binding
:弱绑定数据Lazy Binding
:懒绑定数据,由dyld
记录下来,使用的时候进行绑定Export
:对外开放的函数,提供给外部调用
LC_SYMTAB
:符号表地址
LC_DYSYMTAB
:动态符号表地址
LC_LOAD_DYLINKER
:使用何种动态加载器,iOS
系统上为dyld
LC_UUID
:MachO
文件的唯一标识
crash
文件中也会有,用来检查crash
文件与dYSM
文件是否匹配
LC_VERSION_MIN_MACOSX
:支持最低的系统版本
LC_SOURCE_VERSION
:源代码版本
LC_MAIN
:设置程序主线程的入口地址和栈大小
LC_ENCRYPTION_INFO_64
:加密信息
LC_LOAD_DYLIB (XXX)
:依赖库的路径,包含三方库
LC_RPATH
:@rpath
路径
LC_FUNCTION_STARTS
:函数起始地址表
LC_DATA_IN_CODE
: 定义在代码段内的非指令的表
LC_CODE_SIGNATURE
: 代码签名
Data
Load Commands
以下,都属于数据部分,包含__TEXT
、__DATA
、__LINKEDIT
__TEXT
:
Section 含义 __text
代码实现,文件从代码段开始 __stubs
符号桩,本质上就是一段会跳转到 Lazy Binding
表中,对应项指针指向的地址的代码__stubs_helper
辅助函数,上述 Lazy Binding
表中没有找到符号地址都指向这里__objc_methname
OC
方法名称__objc_classname
OC
类名__objc_methtype
OC
方法类型__cstring
cstring
字符串常量__unwid_info
用于存储异常情况信息
__DATA
:
Section 含义 __got
非 Lazy Binding
的符号表__la_symbol_prt
Lazy Binding
符号表,每个表项中的符号一开始指向__stubs_helper
__cfstring
CoreFoundation String
__objc_classlist
OC
类列表__objc_protollist
OC
协议列表__objc_imageinfo
OC
镜像信息__objc_const
OC
类信息、方法列表、属性列表、变量列表__objc_selfrefs
OC
类实例自引用(self
)__objc_classfrefs
OC
类类自引用__objc_superrefs
OC
类超类引用(super
)__objc_ivar
OC
属性__objc_data
OC
类ISA
_data
存放数据
总结
MachO
概述:
MachO
属于一种文件格式- 包含目标文件、可执行文件、静态库、动态库、
dyld
、.dSYM
等file
命令,用来探测给定文件的类型通用二进制文件
- 集合多种架构,也被称为“胖二进制文件”
lipo
命令,通过-thin
拆分架构,通过-create
合并架构
MachO
文件结构
Header
:用于快速确定该文件的CPU
类型、文件类型Load Commands
:指示加载器如何设置并加载二进制数据Data
:存放代码、数据、字符串常量、类、方法等数据