Mach-O
Mach-O
是Mach Object的缩写,是Mac、iOS上用于存储程序、库的标准格式。属于Mach-O
格式的文件类型有以下(具体可以在XNU
源码中看到Mach-O
的详细定义,详细地址)
-
MH_OBJECT
-
目标文件(.O)
-
静态库文件(.a),静态库其实就是多个
.O
的合集
-
-
MH_EXECUTE
:可执行文件 -
MH_DYLIB
:动态库(.dylib、.framework) -
MH_DYLINKER
:动态链接编辑器(/usr/lib/dyld) -
MH_DSYM
:存储着二进制符号信息的文件(.dSYM/Contents/Resources/DWARF/xx),常用于分析APP的崩溃信息
同样也可以在Xcode
中查看target
的Mach-O类型
Mach-O的基本结构
一个
Mach-O
文件包含3个主要区域
- Header:文件类型,目标架构类型等信息
- Load Commands:描述文件在虚拟内存中的逻辑结构和布局
- Raw Segment Data:在
Load Commands
中定义的Segment
的原始数据
查看Mach-O的结构
- 命令行工具:
file
、otool
、lipo -info
- GUI工具:
MachOView
使用objdump --macho -p [文件路径]
命令查看mach-Header
同样可以使用otool -h [文件路径]
命令查看,不过这时候有些字段的意思并不是可以直接阅读的
magic number
对于otool
命令查看的magic
中包含的意思可通过此表对照
FEEDFACE | CEFAEDFE | FEEDFACF | CFFAEDFE | |
---|---|---|---|---|
IsLittleEndian(小端模式) | YES | NO | YES | NO |
Is64Bits | NO | NO | YES | YES |
MachO Type | MH_MAGIC | MH_CIGAM | MH_MAGIC_64 | MH_CIGAM_64 |
查看__TEXT
使用命令objdump -d [文件路径]
查看代码段,指令具体含义如下
Tips:想查看objdump
命令具体含义及使用参数可使用man objdump
编译链接的过程做了以下操作:
- 代码汇编化:将代码转变为汇编指令
- 符号进行归类,例如
NSLOG
这类的外部符号归入重定位符号表
- 多个目标文件进行合并,同样把多张符号表也进行合并为一张表,最后生成可执行文件
- 那么链接的过程就是处理目标文件符号的过程
同样可以通过命令objdump --reloc [文件路径]
来查看重定位符号表
的信息
对照信息归类如下:
符号
全局符号、本地符号
定义一些变量如下
使用命令objdump --macho --syms [文件路径]
查看其符号表
上面定义的变量都可以在里面找到,分为两类,前面带g
和l
标识的,意思为本地符号
和全局符号
,变量前加了static
来修饰的变为了本地符号
,其余默认均为全局符号
。 本地符号和全局符号的区别本质在于符号的可见性,如果加上__attribute__((visibility("hidden")))
来修饰default_x
变量,发现它会变为本地符号
visibility
属性,控制文件导出符号,限制符号导出性
- 使用
default
定义的符号将被导出 - 使用
hidden
定义的符号将不被导出
对变量可见性进行修改:
修改之后符号表:
全局符号和本地符号的演示
创建一个framework
和一个测试工程,并在framework
中定义一个并未在其头文件中声明的方法,那么在测试工程中去调用这个方法会如何呢?
结果是成功调用了framework
中的方法
如果在方法前加static
修饰,编译时期便会报错:
那么如果在测试项目中写一个同方法名的方法再去调用,会调用framework
中的方法还是会调用本项目的方法还是会报错呢?
结果是成功调用了本项目的方法,这是因为命名空间的问题
。
二级命名空间和一级命名空间(two_levelnamespace & flat_namespace)
链接器默认采用二级命名空间,即除了会记录符号名称,还会记录符号是属于哪一个MachO
的,比如会记载_NSLog
来自Foundation
。
链接器对于二级命名空间的注解
所以在上面的举例中,会直接调用到本项目的方法而不会报错。
导入符号&导出符号
我们经常使用的NSLog
是属于Foundation
库的,那么对于我们使用到它的文件或者项目来说,NSLog
就是一个导入符号
,同样对于Foundation
来说它是一个导出符号
,那么它是要出现在项目中的间接符号表
里,这里通过objdump --macho --indirect-symbols [文件路径]
进行查看
所以基本可以说全局符号
就是导出符号
,这里会有一个问题,如果你有制作一个动态库,那么在不经任何处理的情况下,是不是你所有的全局符号都会被导出?
Objc类是默认是导出符号
我们在项目里随意增加一个Objc
的类,进行编译后来通过命令objdump --macho --exports-trie [文件路径]
查看导出符号表
增加前:
增加后:
可以看出Objc
的类在未经任何处理的情况下,默认是添加到导出符号表里的,但是可以通过链接器的一些参数设置来把它变为不导出。
OTHER_LDFLAGS=$(inherited) -Xlinker -unexported_symbol -Xlinker _OBJC_CLASS_$_TestObject
这样设置就可以不导出
弱符号(weak symbol)
弱符号
可以分为:弱定义符号
和弱引用符号
,下面就分别来介绍
弱定义符号(weak definition symbol)
弱定义符号
表示此符号是弱定义符号
,如果静态链接器或者动态链接器为此符号找到一个另一个非弱定义
,则弱定义会被忽略。只能将合并部分中的符号标记为弱定义。
先来看这样一个例子
很显然这样编译后会报错,也是一个经典错误:重复定义的符号错误
但是如果这时候对这个符号定义为弱定义符号
结果就不会再次报错,而且会调用main
中的函数实现
Tips 告诉编译器将符号文件信息输出到某个文件中OTHER_LDFLAGS=$(inherited) -Xlinker -S -Xlinker -map -Xlinker [输出文件路径]
弱引用符号(weak reference symbol)
弱引用符号
:表示此未定义符号是弱引用。如果动态链接器找不到该符号的定义,则将其设置为0
。链接器会将此符号设置为弱链接
符号。 使用weak_import
来修饰,并且未在任何地方作出weak_import_func
函数的定义
在main
函数中直接调用
运行程序发现报错,指出并未找到该符号
这时候只需要告诉链接器,这个符号属于动态链接,运行的时候会自己找它的符号,如果没找到则置为0
。 OTHER_LDFLAGS=$(inherited) -Xlinker -U -Xlinker _weak_import_func
这样做对于动态库来说会更加的灵活。例如链接器指令中可以将整个库设置为弱引用
符号的重新导出
从前面的分析可以知道_NSLog
符号是Foundation
的导出符号,那么在我们程序中,它会存在于间接符号表
中
给链接器设置一些参数可以对_NSLog
这样的符号重新导出,使用命令OTHER_LDFLAGS=$(inherited) -Xlinker -alias -Xlinker _NSLog -Xlinker HD_NSLog
,相当于给_NSLog
起了一个别名HD_NSLog
可以看到文档里标注不但可以给单个符号起别名,同样可以传入文件以应对多个符号起别名的情况。
那么现在可以通过命令objdump --macho --exports-trie
查看导出符号表
或者通过命令nm -m [文件路径] | grep "HD"
这样重新导出的符号可以不直接对某个动态库引用而直接调用我们这边重新导出的符号即可。
swift 符号
前面一直分析的都是Objc
的符号,现在来简单看一下Swift
的符号
使用命令objdump --macho --syms ${文件路径} | grep 'SwiftClassSymbol'
同时也查看一下导出符号表
进行以下修改
再次查看符号表信息
之前全局符号基本变成了本地符号,也验证了Swift
是静态语言,很多东西在编译的时候就已经确定了。
补充
在之前查看符号表的过程中经常可以看到l
、g
等等这样的标记,现在来总结归类以下
按功能区分:
Type | 说明 |
---|---|
f | File |
F | Function |
O | Data |
d | Debug |
*ABS* | Absolute |
*COM* | Commen |
*UND* | ? |
按符号种类划分:
①:小写代表local symbol
Symbol Type | 说明 |
---|---|
U | undefined(未定义) |
A | Absolute(绝对符号) |
text section symbol(__Text.__text) | |
data section symbol(__ Data.__ data) | |
bss section symbol(__ Data.__ bss) | |
C | common symbol(只出现在MH_OBJECT 类型的MachO 文件中) |
- | debugger symbol table |
除了上面所述的,存放在其他section 的内容,例如未初始化的全局变量存放在(__ Data.__ common)中 |
|
I | indirect symbol(符号信息相同,代表同一符号) |
u | 动态共享库中小写u表示一个未定义引用对同一库中另一个模块中私有外部符号 |