iOS高级强化--002:ABI Mach-O
Mach-O
(Mach Object
)是macOS
、iOS
、iPadOS
存储程序和库的⽂件格式。对应系统通过应⽤⼆进制接⼝(application binary interface
,缩写为ABI
)来运⾏该格式的⽂件。
Mach-O
格式⽤来替代BSD
系统的a.out
格式。Mach-O
⽂件格式保存了在编译过程和链接过程中产⽣的机器代码和数据,从⽽为静态链接和动态链接的代码提供了单⼀⽂件格式。
苹果很多文件都采用Mach-O
格式,最常见的就是可执行文件和动态库。当然,还有.o
的目标文件、.a
和.framework
的静态库以及动态连接器dyld
等等。
项目运行时,
IPA
包里的可执行文件会被加载
可执⾏⽂件调⽤过程:
- 调⽤
fork
函数,创建⼀个process
(进程)- 调⽤
execve
或其衍⽣函数,在该进程上加载,执⾏我们的Mach-O
⽂件当调⽤
execve
(程序加载器)时,内核实际上在执⾏以下操作:
- 将⽂件加载到内存
- 开始分析
Mach-O
中的mach_header
,以确认它是有效的Mach-O
⽂件
Mach-O
文件结构
Header
包含该二进制文件的一般信息
- 字节顺序、架构类型、加载指令的数量等
- 使得可以快速确认一些信息,比如当前文件用于
32位
还是64位
,对应的处理器是什么、文件类型是什么
Load commands
一张包含很多内容的表
- 内容包括区域的位置、符号表、动态符号表等
Data
通常是对象文件中最大的部分
- 包含
Segement
的具体数据
Header
包含Mach Header
、Segment
、Sections
三种类型
Header
内的Sections
描述了对应的⼆进制信息Mach Header
属于Header
的⼀部分,它包含了整个⽂件的信息和Segment
信息
Segments
(segment commands
):指定操作系统应该将Segments
加载到内存中的什么位置,以及为该Segments
分配的字节数。还指定⽂件中的哪些字节属于该Segments
,以及⽂件包含多少sections
。之前段始终是4096字节
或4KB
的倍数,其中4096字节
是最小大小。现在段是16KB
的倍数,在macOS_x86_64
上是16KB
,在iOS
上是32KB
。Segments
名称的约定是使⽤全⼤写字⺟,后跟双下划线(例如__TEXT
)Section
:所有sections
都在每个segment
之后⼀个接⼀个地描述。sections
⾥⾯定义其名称,在内存中的地址、⼤⼩、⽂件中section
数据的偏移量和segment
名称。Section
的名称约定是使⽤全⼩写字⺟,再加上双下划线(例如__text
)
Header
的最开始是Magic Number
,表示这是一个Mach-O
文件
使用
objdump --macho -private-header 【Mach-O路径】
命令,查看Mach Header
信息
也可以使用
otool -h 【Mach-O路径】
命令,这种方式打印的更像是原始数据,开发者难以阅读
Mach-O
中Load Commands
和Data
是分开的
Load Commands
:二进制⽂件加载进内存要执⾏的⼀些指令
这⾥的指令主要在负责APP
对应进程的创建和基本设置(分配虚拟内
存,创建主线程,处理代码签名/加密的⼯作),然后对动态链接库(.dylib
系统库和自定义的动态库)进⾏库加载和符号解析的⼯作Data
:由多个⼆进制组成,逐一排列。包含__TEXT代码
、__DATA代码
、符号表
,存储实际的代码和数据
__TEXT段
:只读区域,包含可执⾏代码和常量数据
__DATA段
:读/写,包含初始化和未初始化数据和⼀些动态链接专属数据
- 例如:
Load Command __TEXT
中,记录了代码起始位置和大小等配置信息,dyld
根据这些配置信息读取__TEXT代码段
的实际代码Mach-O
中都是二进制数据,根据结构体内存对齐规则,数据会按照指定方式在二进制中进行排列,dyld
按照相同方式逐一读取Load Command
Mach-O
中保存了执行过程中的所有信息:
Load Command __LINKEDIT
:动态连接器需要的信息
Load Command LC_LOAD_DYLINKER
:链接器的位置
Load Command LC_UUID
:Mach-O
的唯一标识符
Load Command LC_BUILD_VERSION
:Mach-O
的编译信息
Load Command LC_MAIN
:入口函数,默认为main
,也可以修改
Load Command LC_LOAD_DYLIB
:加载动态库的信息
使用
objdump
命令,查看Mach-O
文件objdump --macho --private-headers 【Mach-O路径】
查看
Load Commands
中入口函数的配置(LC_MAIN
)objdump --macho --private-headers 【Mach-O路径】 | grep 'LC_MAIN' -A 3
__TEXT代码段
的格式:以
main
函数为例,在x86
架构下通过clang
编译出来的并不是汇编代码,而是机器指令,这些机器指令对应每一条汇编代码都是固定不变的
使用
objdump --macho -d 【Mach-O路径】
命令,查看__TEXT代码段
信息
- 在
__TEXT段
,__text section
中,main
函数以100003fa0
地址开始,至100003fb5
地址结束。中间列是机器码,最后列的是汇编代码- 汇编代码转换机器码在底层有类似字典的映射关系;汇编是给开发者提供的,而机器只需要读取最原始的机器码即可
通过代码读取
Mach-O
文件
machoinfo
是一个读取Mach-O
文件的项目,通过main
函数接收参数
编译后生成
Mach-O
文件
打开终端,进入
Mach-O
所在目录,使用./machoinfo
作为命令执行./machoinfo 【将要读取的Mach-O文件路径】
对
machoinfo
项目进行调试打开
Edit Scheme...
配置默认参数
main
函数设置断点并运行项目,可以接收到配置的默认参数
通过
lldb
可以查看结果
总结
-
Mach-O
本质是一个二进制文件,数据按照指定方式在二进制中进行排列,dyld
按照相同方式进行读取 -
Mach-O
文件包含了文件配置 + 二进制代码
-
Mach-O
文件可读可写
,Mach-O
文件在苹果签名前、签名后均可修改,但签名后修改需要进行重签名