本文主要介绍Mach-O文件格式以及通用二进制文件
![8314ed5c2d86a7eb79969e8d099f0ef8.png]()
Mach-O文件概述
Mach-O其实是Mach Object文件格式的缩写,是mac以及iOS上可执行文件的格式, 类似于windows上的PE格式 (Portable Executable ), linux上的elf格式 (Executable and Linking Format)
Mach-O是一种用于可执行文件、目标代码、动态库的文件格式。作为a.out格式的替代,Mach-O提供了更强的扩展性。
Mach-O文件格式
常见的Mach-O格式有以下几种
1、目标文件.o
2、库文件,细分主要有以下几种:
3、可执行文件
4、dyld
5、.dsym
我们可以通过终端的file指令来查看文件的类型
- <article class="_2rhmJa" deep="5"file 文件路径`
1、目标文件.o
#include <stdio.h>int main(){ printf("test\n"); return 0;
}
中间产物.o文件
其中.c到.out文件的区别是中间多了一个.o文件。而在我们的实际开发中,其实是有多个源码的,所以最终的可执行文件是由多个源码生成的,如下所示,将两个.o文件编译成一个可执行文件
通过clang生成.o文件: clang -c test1.c test.c
通过clang将.o文件编译成可执行文件:clang -o demo1 test1.o test.o
![5f3df1b381dd78db3c369c7ae090249e.webp]()
如果此时换一下链接顺序呢?,例如: clang -o demo2 test.o test1.o
多个源码一次性生成可执行文件:clang -o demo test1.c test.c
对比上述生成的三个可执行文件,是否是同一个?这里我们也通过md5生成的hash值进行对比
![8ec77aa755bd38dad547ea460c902649.webp]()
结论:通过对比发现,如果改变了.o文件的连接顺序,那么Mach-O的也会随之发生变化
这里可以通过 objdump查看Mach-o链接顺序,例如:
上述所说的链接顺序,对应到我们日常开发中是指工程中的 target -> Build Phase -> Compiles Sources,这里就对应源文件的编译顺序,如果源文件的顺序发生了变化,生成的可执行文件是不一样的
2、库文件
静态库 & 动态库
参考链接:
验证.a 、 .dylib是否是Mach-O文件
验证.a
验证.dylib
3、可执行文件
这里的可执行文件,即一般是指日常项目中,编译后生成的可执行未见,可以通过file 查看其文件类型
![6cb2bd41e854de305d3799e15635306f.webp]()
4、dyld
dyld(the dynamic link editor)是苹果的动态链接器,是苹果操作系统一个重要组成部分,在系统内核做好程序准备工作之后,交由dyld负责余下的工作。而且它是开源的,任何人可以通过苹果官网下载它的源码来阅读理解它的运作方式,了解系统加载动态库的细节。
共享缓存机制
在iOS系统中,每个程序依赖的动态库都需要通过dyld(位于/usr/lib/dyld)一个一个加载到内存,然而,很多系统库几乎是每个程序都会用到的,如果在每个程序运行的时候都重复的去加载一次,势必造成运行缓慢,为了优化启动速度和提高程序性能,共享缓存机制就应运而生。所有默认的动态链接库被合并成一个大的缓存文件,放到/System/Library/Caches/com.apple.dyld/目录下,按不同的架构保存分别保存着,
验证dyld
5、.dsym文件
dsym介绍
Xcode编译项目后,我们会看到一个同名的 dSYM 文件,dSYM 是保存 16 进制函数地址映射信息的中转文件,我们调试的 symbols 都会包含在这个文件中,并且每次编译项目的时候都会生成一个新的 dSYM 文件,位于/Users/<用户名>/Library/Developer/Xcode/Archives目录下,所以对于每一个发布版本我们都很有必要保存对应的 Archives 文件。
当我们软件 release 模式打包或上线后,不会像我们在 Xcode 中那样直观的看到用崩溃的错误,这个时候我们就需要分析 crash report 文件了,iOS设备中会有日志文件保存我们每个应用出错的函数内存地址,通过 Xcode 的 Organizer 可以将 iOS 设备中的 DeviceLog 导出成 crash 文件,这个时候我们就可以通过出错的函数地址去查询 dSYM 文件中程序对应的函数名和文件名。大前提是我们需要有软件版本对应的 dSYM 文件,这也是为什么我们很有必要保存每个发布版本的 Archives 文件了。
验证.dsym文件
通用二进制文件
mac系统所支持的cpu及硬件平台发生了很大的变化,为了解决软件在多个硬件平台上的兼容性问题,苹果开发了一个通用的二进制文件格式(Universal Binary),又称胖二进制(Fat Binary)。
苹果公司提出的一种程序代码。能同时适用多种架构的二进制文件
同一个程序包中同时为多种架构提供最理想的性能。
因为需要储存多种代码,通用二进制应用程序通常比单一平台二进制的程序要大。
但是 由于两种架构有共通的非执行资源(代码以外的),所以并不会达到单一版本的两倍之多。
而且由于执行中只调用一部分代码,运行起来也不需要额外的内存。
演示
在日常开发的项目中,可以通过Build Setting - Mach-O type,可以指定Mach-O文件的类型,如下所示
![80eb37bc43e086452223953b72dd0cd9.webp]()
一般我们通过真机生成的可执行文件,其架构是arm64,是一个单一架构
同时也可以在Build Setting - Architectures 中设置设置编译的架构
ARM架构
ARM架构过去称作进阶精简指令集机器(Advanced RISC Machine,更早称作:Acorn RISC Machine),是一个32位精简指令集(RISC)处理器架构,ARM处理器非常适用于移动通讯领域,符合其主要设计目标为低耗电的特性。
ARM和Intel处理器的第一个区别是,前者使用精简指令集(RISC),而后者使用复杂指令集(CISC)。
ARM处理器指令集:是指计算机ARM操作指令系统。
iOS设备支持的指令集
| ARM指令集 |
对应设备 |
| armv6 |
iPhone, iPhone 3G, iPod 1G/2G |
| armv7 |
iPhone 3GS, iPhone 4, iPhone 4S, iPod 3G/4G/5G, iPad, iPad 2, iPad 3, iPad Mini |
| armv7s |
iPhone 5, iPhone 5c, iPad 4 |
| arm64 |
iPhone X,iPhone 8(Plus),iPhone 7(Plus),iPhone 6(Plus),iPhone 6s(Plus), iPhone 5s, iPad Air(2), Retina iPad Mini(2,3) |
| arm64e |
iPhone XS\XR\XS Max |
参考链接:iOS 指令集架构 armv6、armv7、armv7s、arm64、arm64e、x86_64、i386
通用二进制文件源码
- 通过
CMD+shift+O搜索 fat.h
- 找到其中通用二进制文件的头部结构
fat_header如下所示
struct fat_header {
uint32_t magic; /* magic字段被定义为常量FAT_MAGIC,表示这是一个胖二进制 */
uint32_t nfat_arch; /* 表示有多少个Mach-O文件 */};
- 每个胖二进制都用
fat_arch结构表示,在fat_header之后,紧接着一个或多个连续的fat_arch结构体。
struct fat_arch {
cpu_type_t cputype; /* cpu类型 */
cpu_subtype_t cpusubtype; /* CPU的子类型 */
uint32_t offset; /* 指定了当前CPU架构数据相对于当前文件开头的偏移值 */
uint32_t size; /* 数据的大小 */
uint32_t align; /* 数据的内存对齐边界,取值必须是2的次方,它确保了当前CPU架构的目标文件在加载到内存中时,数据是经过内存优化对齐的 */};
终端命令
lipo演示
通过Hopper打开 可执行文件,此时可以看到是一个 FAT archive(胖二进制文件,表示支持多种架构),选择 aarch64架构
![53f6b30d87855738937b700b5b111717.webp]()
查看二进制文件中包含的架构:lipo -info 12-macho
![2c2f0abc1387e7e8dff408dbbb5cb9f3.webp]()
拆分:lipo 12-macho -thin armv7 -output macho_armv7,如果拆分没有的架构,会报错
查看拆分后的可执行文件类型:file macho_armv7
![315a641b6368a811781a095a992d52d1.webp]()
合并:lipo -create macho_armv7 macho_arm64 -output macho_v7_64
查看合并后的可执行文件类型:file macho_v7_64
![8f591292c0f0e485a7c5463f623b3e7c.webp]()
总结
作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS开发交流群:130 595 548,不管你是小白还是大牛都欢迎入驻 ,让我们一起进步,共同发展!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!)
Mach-O其实是Mach Object文件格式的缩写,是mac以及iOS上可执行文件的格式。是一种用于可执行文件、目标代码、动态库的文件格式。且Mach-O提供了更强的扩展性
常见的Mach-O格式:.o、库文件(.a、.dylib、.framework)、可执行文件、dyld、.dsym
.a + .h + sourceFile = .framwork
动态.framework:系统Framework库
静态.framwork:自定义的Framework库
查看文件类型命令:file 文件路径
查看Mach-O源文件的链接顺序:objdump --macho -d 可执行文件
dyld(the dynamic link editor)是苹果的动态链接器,mac中路径为/usr/lib
dSYM 是保存 16 进制函数地址映射信息的中转文件,位于/Users/<用户名>/Library/Developer/Xcode/Archives目录。可以用于通过出错的函数地址去查询 dSYM 文件中程序对应的函数名和文件名
通用二进制文件(Universal Binary,也称为胖二进制(Fat Binary))。主要适用于解决多个平台的兼容性问题
通过otool来查看fat_header信息:otool -f 可执行文件
lipo命令拆分、合并胖二进制文件
lipo -info MachO文件:使用lifo -info可以查看MachO文件包含的架构`
lipo MachO文件 –thin 架构 –output 输出文件路径:使用lifo –thin 拆分`某种架构
lipo -create MachO1 MachO2 -output 输出文件路径: 使用lipo -create 合并`多种架构