Mach O - ShenYj/ShenYj.github.io GitHub Wiki
Mach-O
(Mach Object
) 是 macOS
、iOS
、iPad OS
存储程序和库的文件格式。
对应系统通过应用二进制接口(application binary interface
,缩写为 ABI
)来运行该格式的文件。
Mach-O
格式用来替代 BSD
系统的 a.out
格式。
Mach-O
文件格式保存了在 编译过程和链接过程中产生的机器代码和数据,从而为静态链接和动态 链接的代码提供了单一文件格式。
Mach-O
=Mach Header
(文件配置) +Load Commands
+Raw segment data
二进制代码 (bss段、data段、text段)
-
Mach Header
: 文件类型,目标架构类型等-
Mach-O
文件也分为多种类型, 比如可执行文件、静态库文件/目标文件等,可以参考XNU
源码查看具体类型 - 使用
otool
的-h
参数可以查看Mach Header
信息
-
-
Load Commands
: 描述文件在虚拟内存中的逻辑结构、布局- 如常提到的内存五大区(text代码段、data数据段、bss段)这些段(即
Segment
)就是通过Load Commands
来描述的,有哪些段,每个段内有哪些信息 -
Segments
(segment commands): 指定操作系统应该将Segments
加载到内存中的什么位置,以及为该Segments
分配的字节数。还指定文件中的哪些字节属于该Segments
,以及文件包含多少sections
. 始终是4096字节
或4 KB
的倍数,其中4096字节
是最小大小。 -
Section
: 所有Sections
都在每个segment
之后一个接一个的描述。Sections
里面定义其名称,在内存中的地址,大小,文件中section
数据的偏移量和segment
名称。段之前始终是4096字节或4 KB的倍数,其中 4096字节是最小大小
现在段是 16 KB的倍数,在macOS_x86_64上是16 KB,在 iOS 上是32KB - 使用
otool
的-l
参数可以查看Load Commands
信息
- 如常提到的内存五大区(text代码段、data数据段、bss段)这些段(即
-
Raw segment data
: 在Load Commands
中定义的Segment
的原始数据-
Load Commands
像是一个索引、目录,具体的数据在这里
-
otool
-
部分参数说明
-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
除了通过
otool
在终端内查看外,还可以借助MachOView
工具查看
还可以使用
size -l -m -x
来查看Mach-O
的内存分布
Segment __PAGEZERO: 0x100000000 (zero fill) (vmaddr 0x0 fileoff 0)
Segment __TEXT: 0x48000 (vmaddr 0x100000000 fileoff 0)
Section __text: 0x38670 (addr 0x100004c60 offset 19552)
Section __stubs: 0x7fe (addr 0x10003d2d0 offset 250576)
Section __swift5_typeref: 0x1217 (addr 0x10003dace offset 252622)
Section __cstring: 0x272a (addr 0x10003ecf0 offset 257264)
Section __const: 0x2480 (addr 0x100041420 offset 267296)
Section __swift5_reflstr: 0x57a (addr 0x1000438a0 offset 276640)
Section __swift5_fieldmd: 0x798 (addr 0x100043e1c offset 278044)
Section __swift5_types: 0x114 (addr 0x1000445b4 offset 279988)
Section __objc_methname: 0x1e07 (addr 0x1000446c8 offset 280264)
Section __swift5_capture: 0x580 (addr 0x1000464d0 offset 287952)
Section __swift5_proto: 0x124 (addr 0x100046a50 offset 289360)
Section __swift5_assocty: 0x1d0 (addr 0x100046b74 offset 289652)
Section __swift5_builtin: 0x1cc (addr 0x100046d44 offset 290116)
Section __swift5_protos: 0x20 (addr 0x100046f10 offset 290576)
Section __swift5_entry: 0x4 (addr 0x100046f30 offset 290608)
Section __entitlements: 0x114 (addr 0x100046f34 offset 290612)
Section __unwind_info: 0xd08 (addr 0x100047048 offset 290888)
Section __eh_frame: 0x2b0 (addr 0x100047d50 offset 294224)
total 0x4338c
Segment __DATA_CONST: 0x4000 (vmaddr 0x100048000 fileoff 294912)
Section __got: 0x1120 (addr 0x100048000 offset 294912)
Section __const: 0x1610 (addr 0x100049120 offset 299296)
Section __objc_classlist: 0xb0 (addr 0x10004a730 offset 304944)
Section __objc_catlist: 0x28 (addr 0x10004a7e0 offset 305120)
Section __objc_protolist: 0x50 (addr 0x10004a808 offset 305160)
Section __objc_imageinfo: 0x8 (addr 0x10004a858 offset 305240)
total 0x2860
Segment __DATA: 0x8000 (vmaddr 0x10004c000 fileoff 311296)
Section __objc_const: 0x1670 (addr 0x10004c000 offset 311296)
Section __objc_selrefs: 0x5a0 (addr 0x10004d670 offset 317040)
Section __objc_protorefs: 0x50 (addr 0x10004dc10 offset 318480)
Section __objc_classrefs: 0x148 (addr 0x10004dc60 offset 318560)
Section __objc_data: 0x2610 (addr 0x10004dda8 offset 318888)
Section __data: 0xd30 (addr 0x1000503b8 offset 328632)
Section __bss: 0x19c0 (addr 0x1000510f0 zerofill)
Section __common: 0x1c0 (addr 0x100052ab0 zerofill)
total 0x6c68
Segment __LINKEDIT: 0xa0000 (vmaddr 0x100054000 fileoff 344064)
total 0x1000f4000
上面的是一个 Swift 为主的项目,也包含少量 OC 代码
Segment __PAGEZERO: 0x100000000 (zero fill) (vmaddr 0x0 fileoff 0)
Segment __TEXT: 0x4d0000 (vmaddr 0x100000000 fileoff 0)
Section __text: 0x440430 (addr 0x1000036d0 offset 14032)
Section __stubs: 0x984 (addr 0x100443b00 offset 4471552)
Section __stub_helper: 0xfd8 (addr 0x100444484 offset 4473988)
Section __gcc_except_tab: 0x78fc (addr 0x10044545c offset 4478044)
Section __objc_methname: 0x43dd2 (addr 0x10044cd58 offset 4509016)
Section __cstring: 0x2100b (addr 0x100490b30 offset 4786992)
Section __ustring: 0xa870 (addr 0x1004b1b3c offset 4922172)
Section __objc_classname: 0x452c (addr 0x1004bc3ac offset 4965292)
Section __objc_methtype: 0x827c (addr 0x1004c08d8 offset 4983000)
Section __const: 0x1350 (addr 0x1004c8b60 offset 5016416)
Section __entitlements: 0x1b7 (addr 0x1004c9eb0 offset 5021360)
Section __unwind_info: 0x39fc (addr 0x1004ca068 offset 5021800)
Section __eh_frame: 0x2598 (addr 0x1004cda68 offset 5036648)
total 0x4cc918
Segment __DATA: 0x190000 (vmaddr 0x1004d0000 fileoff 5046272)
Section __nl_symbol_ptr: 0x8 (addr 0x1004d0000 offset 5046272)
Section __got: 0x478 (addr 0x1004d0008 offset 5046280)
Section __la_symbol_ptr: 0xcb0 (addr 0x1004d0480 offset 5047424)
Section __mod_init_func: 0x18 (addr 0x1004d1130 offset 5050672)
Section __const: 0x7808 (addr 0x1004d1150 offset 5050704)
Section __cfstring: 0x242a0 (addr 0x1004d8958 offset 5081432)
Section __objc_classlist: 0x1440 (addr 0x1004fcbf8 offset 5229560)
Section __objc_nlclslist: 0x18 (addr 0x1004fe038 offset 5234744)
Section __objc_catlist: 0xb0 (addr 0x1004fe050 offset 5234768)
Section __objc_nlcatlist: 0x18 (addr 0x1004fe100 offset 5234944)
Section __objc_protolist: 0x500 (addr 0x1004fe118 offset 5234968)
Section __objc_imageinfo: 0x8 (addr 0x1004fe618 offset 5236248)
Section __objc_const: 0xbad68 (addr 0x1004fe620 offset 5236256)
Section __objc_selrefs: 0xd228 (addr 0x1005b9388 offset 6001544)
Section __objc_protorefs: 0x18 (addr 0x1005c65b0 offset 6055344)
Section __objc_classrefs: 0x1750 (addr 0x1005c65c8 offset 6055368)
Section __objc_superrefs: 0xe30 (addr 0x1005c7d18 offset 6061336)
Section __objc_ivar: 0x5ce8 (addr 0x1005c8b48 offset 6064968)
Section __objc_data: 0x11278 (addr 0x1005ce830 offset 6088752)
Section __llvm_prf_cnts: 0x1b258 (addr 0x1005e0000 offset 6160384)
Section __llvm_prf_data: 0x458d0 (addr 0x1005fc000 offset 6275072)
Section __llvm_prf_names: 0x15407 (addr 0x1006418d0 offset 6559952)
Section __data: 0x4530 (addr 0x100656ce0 offset 6647008)
Section __llvm_prf_vnds: 0x0 (addr 0x10065b210 offset 6664720)
Section __bss: 0x2c38 (addr 0x10065b210 zerofill)
Section __common: 0x88 (addr 0x10065de48 zerofill)
total 0x18cbbf
Segment __LLVM_COV: 0x90000 (vmaddr 0x100660000 fileoff 6668288)
Section __llvm_covfun: 0x7f327 (addr 0x100660000 offset 6668288)
Section __llvm_covmap: 0xe0e4 (addr 0x1006df328 offset 7189288)
total 0x8d40b
Segment __LINKEDIT: 0x39c000 (vmaddr 0x1006f0000 fileoff 7258112)
total 0x100a8c000
这是一个纯 OC 的项目
Mach Header 简介:
文件头 | mach64 Header | 文件类型、大小等信息 |
加载命令 | Load Commands | 指示加载器如何加载二进制文件 |
文本段 | __TEXT | 类似PE的.text段 |
数据段 | __DATA | 类似PE的.data段 |
动态库加载信息 | Dynamic Loader Info | - |
入口函数 | Function Starts | - |
符号表 | Symbol Table | - |
动态库符号表 | Dynamic Symbol Table | - |
字符串表 | String Table | _ |
通过两个 load commands
:
-
LC_SYMTAB
: 当前Mach-O
中的符号表信息 -
LC_DYSYMTAB
: 描述动态链接器使用其他的Symbol Table
信息
用来描述 Symbol Table
的大小和位置,以及其他元数据
二进制文件加载进内存要执行的一些指令。
这里的指令主要负责我们 App 对应进程的创建和基本设置(分配虚拟内存,创建主线程,处理代码签名/加密的工作),然后对动态链接库(.dylib系统库和我们自己创建的动态库)进行库加载和符号解析的工作。
-
调用
fork
函数,创建一个process
-
在2017年就被引入至
iOS 11
,当时主要用来优化系统库。现在,在iOS 13
中它也将用于启动第三方APP,将完全替代dyld2
. -
dyld2
是纯粹的in-process
,也就是在程序进程内执行的,也就意味着只有当应用程序被启动的时候,dyld2
才能开始执行任务。 -
dyld3
则是部分out-of-process
,部分in-process
。图中,虚线之上的部分是out-of-process
的,在App下载安装和版本更新的时候会去执行,out-of-process
会做如下事情:- 分析
Mach-o Headers
- 分析依赖的动态库
- 查找需要
Rebase & Bind
之类的符号-
Rebase
: 修正内部(指向当前mach-o文件)的指针指向 -
Bind
: 修正外部指针指向
-
- 把上述结果写入缓存
这样,在应用启动的时候,就可以直接从缓存中读取数据,加快加载速度。
- 分析
-
-
调用
execve
或其衍生函数,在该进程上加载,执行我们的Mach-O
文件- 当我们调用时
execve
(程序加载器),内核实际上在执行以下操作
- 将文件加载到内存
- 开始分析
Mach-O
中的mach_header
,以确认它是有效的Mach-O
文件
- 当我们调用时
虽然macOS
系统使用了很多UNIX上的特性,但它并没有使用ELF作为系统的可执行文件格式,而是使用自家独创的Mach-O文件格式。
苹果一路走来支持的CPU及硬件平台变化较大, 从PowerPC平台
-> x86
-> ARM
、x86-64
为了解决软件在多个硬件平台上的兼容性问题,苹果开发了一个通用的二进制文件格式(Universal Binary)。 又称为胖二进制(Fat Binary),通用二进制文件中将多个支持不同CPU架构的二进制文件打包成一个文件,系统在加载运行该程序时,会根据通用二进制文件中提供的多个架构来与当前系统平台做匹配,运行适合当前系统的那个版本。
通用二进制的“通用”不止针对可以直接运行的可执行程序,系统中的动态库dylib、静态库.a文件以及框架等都可以是通用二进制文件,对它们也可以同样使用lipo命令来进行管理。