什么是Mach-O?
Mach-O(Mach Object)
是macOS、iOS、iPadOS
存储程序和库(动态库,静态库)的文件格式。对应系统通过应用二进制接口(application binary interface
,缩写为ABI
)来运行该格式的文件。ABI
说明了Mach-O
的文件格式
Mach-O
格式用来替代BSD
系统的a.out
格式。Mach-O
文件格式保存了在编译过程和链接过程中产生的机器代码和数据,从而为静态链接和动态链接的代码提供了单一文件格式。
APP 是怎么跑起来的?
Application run的过程,其实就是加载我们application folder里面的可执行文件,那这个过程是怎样的呢?
- 调用
fork
函数,创建一个process
- 调用
execve
或其衍生函数,在该进程上加载,执行我们的Mach-O
文件 当我们调用时execve
(程序加载器),内核实际上在执行以下操作: - 将文件加载到内存
- 开始分析
Mach-O
中的mach_header
,以确认它是有效的Mach-O
文件
分析Mach-O
使用如下命令
objdump -macho -private-headers {可执行文件路径}
可以看到
private headers
里有很多load command
. 可以理解Mach-O
文件就是配置文件() + 二进制代码
,Mach-O
中都是二进制. 我们的程序为什么入口是main
? 是因为在Mach-O
文件中指定了程序入口是main
,
这个
LC_MAIN
是告诉动态链接器(dyld
)去加载可执行文件的入口,并不一定是main
, 可以自己指定.
为了理解Mach-O
本质上就是一个二进制文件,Mach-O
本质上是可读可写的,在网上找了一些资料去分析Mach-O
内容详见
Mach-O
中的数据是按照一定的规则排列的,最前面是mach_header_64
的结构体,后面依次存放的是load_command
编译与链接
当我们编译生成目标文件会有一个.o
文件生成,那么这个.o
文件是什么呢?为什么要生成一个.o
文件?编译过程到底发生了什么事情?在此推荐一本书程序员的自我修养
.
编译其实就是将我们所写的代码放到对应的配置文件里面. 比如对于一个符号,是全局的还是本地的,是外部的还是内部的?比如NSLog
的调用,NSLog
其实是一个外部符号. 根据这些符号的特性,将这些符号在编译的过程中进行分类. 当然我们编译的时候,肯定不止一个.o
文件,简单来说,链接的本质就是多个目标文件组合成一个文件.
查看符号的命令
Symbol Table
:就是用来保存符号。String Table
:就是用来保存符号的名称。Indirect Symbol Table
:间接符号表。保存使用的外部符号。更准确一点就是使 用的外部动态库的符号。是Symbol Table
的子集。
tty
命令:输出当前terminal
的标识.
在
Build Phase
中配置run script
如上,当我们编译的时候,就会输出hello mmm
到terminal
中.
怎样在我们编译之后,直接执行一个shell
脚本呢?
我们在xconfig
中定义的变量,也可以在build phases
中的run script
使用.
当然我们也可以build 通过之后,直接在terminal
中执行nm -pa {可执行文件路径}
我们可以看到,输出了符号表的内容,但是把一些我们没必要关心的
Debug
符号也包含了,当然后面我们可以通过strip
命令剥离Debug
符号.
我们可以看下build的过程
我们发现
run script
是在编译完整,sign
之前去run
的.
strip 命令
在build setting
中可以设置strip
strip
命令可以剥离符号表,符号占用的控件还是挺大的. 但是strip
命令是在sign
之前,run script
之后.
我们查看下ld
命令有什么 option
可以剥离Debug
符号
因此我们可以在
xconfig
中配置other link flag
如下:
OTHER_LDFLAGS = -Xlinker -S
可以剥离掉Debug
符号,再次执行nm -pa {可执行文件路径}
命令, 可以得到如下结果
上述输出结果,是我们的目标文件中,除了
Debug
符号的所有符号.
只有了解了符号,后续才能知道怎么对动态库进行瘦身.
对库瘦身
- 编译选项
-O1
-Oz
之类的 dead code strip
死代码剥离 (链接的过程中)strip
命令进行剥离符号(修改mach-o
)