Mach-O与连接器的一个作用
Mach-o到底是什么?
Mach-O(Mach Object)是macOS、iOS、iPadOS存储序和库的文件格式。对应系统通过引用二进制接口(application binary interface,所写为ABI)来运行该格式的文件。
Mach-O格式用来替代BSD系统的a.out格式。Mach-O文件格式保存了在编译过程的连接过重产生的机器代码和数据,从而为静态连接和动态连接的代码提供了单一文件格式。
Mach-O 是iOS系统不同运行时期,可执行文件类型的统称。它是一种用于,可执行文件、目标代码、动态库、内核转储的文件格式。
Mach -O 的三种类型:Executable 、Dylib 、bundle
- 思考一个问题当我们项目点击运行那一刻发生了什么系统到底做了什么?
首先它去加载 ipa包,然后去找到 ipa包里的一个可执行文件黑不溜秋的。
其可执行文件的调用过程:
- 1.调用
fork
函数,创建一个process
- 2.调用
execve
或其衍生函数,在该进程上加载,执行我们的Mach-O
文件
当我们调用时execve
(程序加载器),内核实际上在执行: - 1.将文件加载到内存
- 2.开始分析
Mach-O
中的mach_header
,以确认它是有效的Mach-O
文件
对于上面的我们先不了解过深我们先看它到底是怎样一种格式我们理解
通过下面命令
objdump --macho --private-headers / 黑不溜秋的地址
可以看到里面他有 mach header
有多个 load command
有多个section
,那他们之间的关系是怎样的呢?我们先看下面每个字段的含义。
Mach header :指定文件的目标体系结构,自我描述信息
- magic: ,系统加载器通过该字段快速,判断当前文件
- cputype: 标识cpu架构,比如ARM, X86,i386等等,该字段确保系统可以将合适的二进制文件在当前架构下运行。
- cpusubtype: 具体的cpu类型,区分不同版本的处理器,如arm64,armv7
- filetype: 说明该mach-o文件类型(可执行文件,库文件,核心转储文件,内核扩展,DYSM文件,动态库)。
- ncmds: loadCommands数量,每个LoadCommands代表了一种Segment的加载方式 .
- sizeofcmmds:所有Load commands的总字节大小
- flags:标识二进制文件支持的功能,主要与系统的加载、连接有关
- reserved:保留的字段。
Load commands: 在虚拟内存中指定文件的逻辑结构和文件的布局。类似书的目录
- load commands 跟在
mach_header
之后。所有命令的总大小由mach_header中sizeofcmmds字段给出。 - load command必须有前两个字段 cmd 和 cmdsize. cmd字段以该命令类型的常量填充。每个命令类型都有专门追对它的结构。cmdsize字段是特定加载命令结构的字节大小加跟随它的任何一部分,这是加载命令(即节结构、字符串等)的须是4字节的倍数,对于64位架构必须是8字节的倍数(这些永远是任何加载命令的最大对齐)。填充的字节必须为0.
- 目标文件中的所有表也必须遵循这些规则,以便文件可以进行内容映射。否则,这些表的指针在某些机器上无法正常工作或根本无法真长工作。所有padding归零对象
Raw segment data: 包含在加载命令中定义的段的原始数据。主要包含代码、数据、符号表等
因为mach -O里面全部都是二进制的,我们就可以理解为 配置文件+二进制代码,比如说 为什么我们程序的启动都是main?就是因为mach- O指定了它的入口函数 在dyld解析的时候会根据这个字段来找到入口函数
通过下面命令 找到 LC_MAIN
objdump --macho --private-headers / 黑不溜秋的地址 | ag 'LC_MAIN' -A 3
总结
- 比如通过黄色区域 _TEXT load Command 就可以找到其_TEXT代码具体的执行二进制的位置。
- 通过 _DATA Load Command 就可以 找到 _DATA 具体的执行位置
- load Command 就向一本书的目录. 可以通过目录 来定位 具体的章节
此图更能形象的描述出 Mach-O 的结构,整个 Mach-O 均为二进制文件,它就向一本书,Mach header 就像 书的前言 自描述着,load commands 就像书的目录 Data 就向是目录 具体的内容。
编译链接过程
对于上面的描述有些生硬,我来实操一下
看如上代码只编译一个mian.m 然后我们通过命令去查看编译出来的可执行文件代码 段
objdump --macho -d /黑不溜秋的地址。
- 可以看到NSLog变成了一条指令 callq 这个地址
那在编译的时候实际干了点什么事情呢?
编译
- 再编译过程把能变成汇编的尽量变成汇编变成机器码
- 不能变成汇编的给其进行一个归类。
比如上面的NSLog 可以callq一个地址,向其他的变量就要进行归类 比如说 数据,那应该放在数据段,其他的放在其他地方,比如变成目标文件的时候像NSlog在其他的mach-O 中,需要链接的时候才能确定一些东西那怎么办?所以就是,将不能变成机器码的符号进行一个归类
下面我们看一下当前的重定位符号表 我们先将 main.m 用clang编译器成 中间代码.o
$ clang -x objective-c \
> -target x86_64-apple-macos10.15 \
> -fobjc-arc \
> -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
> -c main.m -o main.o
- -x objective-c :指定编译语言
- -target x86_64-apple-macos10.15 :指定编译架构
- -fobjc-arc : 指定编译 是arc
- -isysroot 比如<Foundation/Foundation.h> 系统sdk在哪
- 输出。
编译出中间代码.o之后 我们在来看一下 当前.o 的重定位符号表
objdump --macho --reloc .o地址
$ objdump --macho --reloc /Users/lh/Desktop/LHObject/symbol/symbol/main.o
/Users/lh/Desktop/LHObject/symbol/symbol/main.o:
Relocation information (__TEXT,__text) 4 entries
address pcrel length extern type scattered symbolnum/value
00000033 True long True BRANCH False _NSLog
00000029 True long True SIGNED False _static_init_value
0000001f True long True SIGNED4 False _static_uninit_value
0000000b True long False SIGNED False 5 (__DATA,__cfstring)
Relocation information (__DATA,__cfstring) 2 entries
address pcrel length extern type scattered symbolnum/value
00000010 False quad False UNSIGND False 4 (__TEXT,__cstring)
00000000 False quad True UNSIGND False ___CFConstantStringClassReference
Relocation information (__LD,__compact_unwind) 1 entries
address pcrel length extern type scattered symbolnum/value
00000000 False quad False UNSIGND False 1 (__TEXT,__text)
对应上面的我们看一下都代表什么意思
- 从这里我们就能明白,重定位符号表里 的保存的是当前文件你用到的符号,使用了的在这里,没有使用的不在这里。
总结:
程序编译过程 就是将.m 变成.o 的过程。里面做了通过词法分析语法分析展开宏啊清除注释啊,能变成机器码的尽量变成机器码。不能变成对符号进行归类,对于当前.m文件用到的符号,放在重定向符号表里。
链接:
当程序被编译成一个个中间代码.o, 接下来就需要进行一个链接过程,处理编译情况。 把多个目标文件合并到一起,之后 也就意味着,重定位符号表,包括 其他符号,就被合并到一张表中,最后生成我们的可执行文件 exec
Symbol Table
- symbol Table:用来保存符号
- StringTable: 用来保存符号的名称
- indrect Symbol Table:间接符号表,保存使用外部符号,更准确一点就是使用的外部动态库的符号,是symbolTable的子集(例如: NSLog)
看到这里在回顾一下上面mach -O
- 一样通过load Command 来找到 符号表的一个具体的位置。
总结:
- 链接过程就是在处理目标文件符号的过程。