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 <Foundation/Foundation.h> int main(){ return 1; }创建
test2.m文件,写入以下代码:#import <Foundation/Foundation.h> 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] <object file> ... -f print the fat headers -a print the archive header -h print the mach header -l print the load commands -L print shared libraries used -D print shared library id name -t print the text section (disassemble with -v) -x print all text sections (disassemble with -v) -p <routine name> start dissassemble from routine name -s <segname> <sectname> print contents of section -d print the data section -o print the Objective-C segment -r print the relocation entries -S print the table of contents of a library (obsolete) -T print the table of contents of a dynamic shared library (obsolete) -M print the module table of a dynamic shared library (obsolete) -R print the reference table of a dynamic shared library (obsolete) -I print the indirect symbol table -H print the two-level hints table (obsolete) -G print the data in code table -v print verbosely (symbolically) when possible -V print disassembled operands symbolically -c print argument strings of a core file -X print no leading addresses or headers -m don't use archive(member) syntax -B force Thumb disassembly (ARM objects only) -q use llvm's disassembler (the default) -Q use otool(1)'s disassembler -mcpu=arg use `arg' as the cpu for disassembly -j print opcode bytes -P print the info plist section as strings -C print linker optimization hints --version print the version of /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool
案例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类型,比如ARMcpusubtype:CPU的具体类型,arm64、armv7offset:偏移值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类型,比如ARMcpusubtype:CPU的具体类型,arm64、armv7filetype:文件类型,例如:可执行文件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_methnameOC方法名称__objc_classnameOC类名__objc_methtypeOC方法类型__cstringcstring字符串常量__unwid_info用于存储异常情况信息
__DATA:
Section 含义 __got非 Lazy Binding的符号表__la_symbol_prtLazy Binding符号表,每个表项中的符号一开始指向__stubs_helper__cfstringCoreFoundation String__objc_classlistOC类列表__objc_protollistOC协议列表__objc_imageinfoOC镜像信息__objc_constOC类信息、方法列表、属性列表、变量列表__objc_selfrefsOC类实例自引用(self)__objc_classfrefsOC类类自引用__objc_superrefsOC类超类引用(super)__objc_ivarOC属性__objc_dataOC类ISA_data存放数据
总结
MachO概述:
MachO属于一种文件格式- 包含目标文件、可执行文件、静态库、动态库、
dyld、.dSYM等file命令,用来探测给定文件的类型通用二进制文件
- 集合多种架构,也被称为“胖二进制文件”
lipo命令,通过-thin拆分架构,通过-create合并架构
MachO文件结构
Header:用于快速确定该文件的CPU类型、文件类型Load Commands:指示加载器如何设置并加载二进制数据Data:存放代码、数据、字符串常量、类、方法等数据


































