MachO与符号那点事

Mach-O

Mach-O是Mach Object的缩写,是Mac、iOS上用于存储程序、库的标准格式。属于Mach-O格式的文件类型有以下(具体可以在XNU源码中看到Mach-O的详细定义,详细地址)

image.png

  • 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类型

image.png

Mach-O的基本结构

官方描述

image.png 一个Mach-O文件包含3个主要区域

  • Header:文件类型,目标架构类型等信息
  • Load Commands:描述文件在虚拟内存中的逻辑结构和布局
  • Raw Segment Data:在Load Commands中定义的Segment的原始数据

查看Mach-O的结构

  • 命令行工具:fileotoollipo -info
  • GUI工具: MachOView

使用objdump --macho -p [文件路径]命令查看mach-Header

image.png

同样可以使用otool -h [文件路径]命令查看,不过这时候有些字段的意思并不是可以直接阅读的

image.png

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

image.png

image.png

编译链接的过程做了以下操作:

  • 代码汇编化:将代码转变为汇编指令
  • 符号进行归类,例如NSLOG这类的外部符号归入重定位符号表
  • 多个目标文件进行合并,同样把多张符号表也进行合并为一张表,最后生成可执行文件
  • 那么链接的过程就是处理目标文件符号的过程

同样可以通过命令objdump --reloc [文件路径]来查看重定位符号表的信息

image.png

对照信息归类如下:

image.png

符号

全局符号、本地符号

定义一些变量如下

image.png

使用命令objdump --macho --syms [文件路径]查看其符号表

image.png

上面定义的变量都可以在里面找到,分为两类,前面带gl标识的,意思为本地符号全局符号,变量前加了static来修饰的变为了本地符号,其余默认均为全局符号。 本地符号和全局符号的区别本质在于符号的可见性,如果加上__attribute__((visibility("hidden")))来修饰default_x变量,发现它会变为本地符号

visibility属性,控制文件导出符号,限制符号导出性

  • 使用default定义的符号将被导出
  • 使用hidden定义的符号将不被导出

对变量可见性进行修改: image.png

修改之后符号表:

image.png

全局符号和本地符号的演示

创建一个framework和一个测试工程,并在framework中定义一个并未在其头文件中声明的方法,那么在测试工程中去调用这个方法会如何呢?

image.png

image.png

结果是成功调用了framework中的方法

image.png

如果在方法前加static修饰,编译时期便会报错:

image.png

那么如果在测试项目中写一个同方法名的方法再去调用,会调用framework中的方法还是会调用本项目的方法还是会报错呢?

image.png

结果是成功调用了本项目的方法,这是因为命名空间的问题

二级命名空间和一级命名空间(two_levelnamespace & flat_namespace)

链接器默认采用二级命名空间,即除了会记录符号名称,还会记录符号是属于哪一个MachO的,比如会记载_NSLog来自Foundation

链接器对于二级命名空间的注解

image.png

所以在上面的举例中,会直接调用到本项目的方法而不会报错。

导入符号&导出符号

我们经常使用的NSLog是属于Foundation库的,那么对于我们使用到它的文件或者项目来说,NSLog就是一个导入符号,同样对于Foundation来说它是一个导出符号,那么它是要出现在项目中的间接符号表里,这里通过objdump --macho --indirect-symbols [文件路径]进行查看

image.png

所以基本可以说全局符号就是导出符号,这里会有一个问题,如果你有制作一个动态库,那么在不经任何处理的情况下,是不是你所有的全局符号都会被导出?

Objc类是默认是导出符号

我们在项目里随意增加一个Objc的类,进行编译后来通过命令objdump --macho --exports-trie [文件路径]查看导出符号表 增加前:

image.png

增加后:

image.png

可以看出Objc的类在未经任何处理的情况下,默认是添加到导出符号表里的,但是可以通过链接器的一些参数设置来把它变为不导出。

OTHER_LDFLAGS=$(inherited) -Xlinker -unexported_symbol -Xlinker _OBJC_CLASS_$_TestObject

这样设置就可以不导出

image.png

弱符号(weak symbol)

弱符号可以分为:弱定义符号弱引用符号,下面就分别来介绍

弱定义符号(weak definition symbol)

弱定义符号表示此符号是弱定义符号,如果静态链接器或者动态链接器为此符号找到一个另一个非弱定义,则弱定义会被忽略。只能将合并部分中的符号标记为弱定义。

先来看这样一个例子

image.png

image.png

image.png

很显然这样编译后会报错,也是一个经典错误:重复定义的符号错误

image.png

但是如果这时候对这个符号定义为弱定义符号

image.png

结果就不会再次报错,而且会调用main中的函数实现

image.png

Tips 告诉编译器将符号文件信息输出到某个文件中OTHER_LDFLAGS=$(inherited) -Xlinker -S -Xlinker -map -Xlinker [输出文件路径]

image.png

弱引用符号(weak reference symbol)

弱引用符号:表示此未定义符号是弱引用。如果动态链接器找不到该符号的定义,则将其设置为0。链接器会将此符号设置为弱链接符号。 使用weak_import来修饰,并且未在任何地方作出weak_import_func函数的定义

image.png

main函数中直接调用

image.png

运行程序发现报错,指出并未找到该符号

image.png

这时候只需要告诉链接器,这个符号属于动态链接,运行的时候会自己找它的符号,如果没找到则置为0OTHER_LDFLAGS=$(inherited) -Xlinker -U -Xlinker _weak_import_func

image.png

image.png

这样做对于动态库来说会更加的灵活。例如链接器指令中可以将整个库设置为弱引用

image.png

符号的重新导出

从前面的分析可以知道_NSLog符号是Foundation的导出符号,那么在我们程序中,它会存在于间接符号表

image.png

给链接器设置一些参数可以对_NSLog这样的符号重新导出,使用命令OTHER_LDFLAGS=$(inherited) -Xlinker -alias -Xlinker _NSLog -Xlinker HD_NSLog,相当于给_NSLog起了一个别名HD_NSLog

image.png

可以看到文档里标注不但可以给单个符号起别名,同样可以传入文件以应对多个符号起别名的情况。

image.png

那么现在可以通过命令objdump --macho --exports-trie查看导出符号表

image.png

或者通过命令nm -m [文件路径] | grep "HD"

image.png

这样重新导出的符号可以不直接对某个动态库引用而直接调用我们这边重新导出的符号即可。

swift 符号

前面一直分析的都是Objc的符号,现在来简单看一下Swift的符号

image.png

使用命令objdump --macho --syms ${文件路径} | grep 'SwiftClassSymbol'

image.png

同时也查看一下导出符号表

image.png

进行以下修改

image.png

再次查看符号表信息

image.png

image.png

之前全局符号基本变成了本地符号,也验证了Swift是静态语言,很多东西在编译的时候就已经确定了。

补充

在之前查看符号表的过程中经常可以看到lg等等这样的标记,现在来总结归类以下

按功能区分:

Type 说明
f File
F Function
O Data
d Debug
*ABS* Absolute
*COM* Commen
*UND* ?

按符号种类划分:

①:小写代表local symbol

Symbol Type 说明
U undefined(未定义)
A Absolute(绝对符号)
T T_① text section symbol(__Text.__text)
D D_① data section symbol(__ Data.__ data)
B B_① bss section symbol(__ Data.__ bss)
C common symbol(只出现在MH_OBJECT类型的MachO文件中)
- debugger symbol table
S S_① 除了上面所述的,存放在其他section的内容,例如未初始化的全局变量存放在(__ Data.__ common)中
I indirect symbol(符号信息相同,代表同一符号)
u 动态共享库中小写u表示一个未定义引用对同一库中另一个模块中私有外部符号

猜你喜欢

转载自juejin.im/post/7033342599586906126