OC底层原理32:启动优化(一)基本概念
前言
早期的 数据访问
是直接通过 物理地址
访问 物理内存
,而物理内存是有 固定大小的
,这种方式有以下两个问题:
- 内存不够用
- 内存数据的安全问题
内存不够用的解决方案:虚拟内存
针对问题1,我们在 进程和物理内存
之间增加一个 中间层
,这个中间层就是所谓的 虚拟内存
,主要用于解决当多个进程同时存在时,对物理内存的管理,提高了CPU的利用率,使多个进程可以同时、按需加载
,所以虚拟内存其本质就是 一张虚拟地址和物理地址对应关系的映射表
每个进程
都有一个独立的虚拟内存
,其地址都是从0开始
,到最后结束,大小是4G
固定的,每个虚拟内存又划分为一个一个的页
,所有页放在一起组成页表
,统称虚拟内存分页管理
,页的大小在iOS中是16K,Linux、MacOS等是4K
,每次加载都是以页
为单位加载的,进程间是无法互相访问的,保证了进程间数据的安全性。一个进程中,只有部分功能是活跃的,所以只需要
将进程中活跃的部分放入物理内存
,避免物理内存的浪费当
CPU
需要访问数据
时,首先是访问虚拟内存
,然后通过虚拟内存地址寻址
,这个虚拟地址在被送到内存条之前先转换为物理地址,这个转换的过程叫地址翻译(需要CPU的硬件MMU和操作系统配合)
,即可以理解为在虚拟地址和物理地址对应表
中找到对应的物理地址
,然后对相应的物理地址进行访问如果在访问时,虚拟地址的内容未加载到物理内存,会发生
缺页异常(pagefault)
,将当前进程阻塞掉,此时操作系统需要先将数据载入到物理内存中,这个过程很快用户感知不到(每页16K),然后再寻址,进行读取。这样就避免了内存浪费。如果在访问时,物理内存满了,操作系统会将
新数据将旧数据覆盖
如下图所示,虚拟内存与物理内存
间的关系:
内存数据安全问题的解决方案:ASLR技术
在上面解释的虚拟内存中,我们提到了 虚拟内存的起始地址与大小都是固定的
,这意味着,当我们访问时,其数据的地址也是固定的,这会导致我们的数据非常容易被破解,为了解决这个问题,所以苹果为了解决这个问题,在iOS4.3开始引入了 ASLR
技术。
ASLR
的概念:(Address Space Layout Randomization)地址空间配置随机加载
,是一种 针对缓冲区溢出
的 安全保护技术
,通过对 堆、栈、共享库映射等线性区布局的随机化
,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的的一种技术。
其目的通过 利用随机方式配置数据地址空间
,使某些敏感数据(例如App登录注册、支付相关代码)配置到一个恶意程序无法事先获知的地址,令攻击者难以进行攻击
由于 ASLR
的存在,导致 可执行文件和动态链接库
在虚拟内存中的 记载地址每次启动都不固定
,所以需要在编译时来修复镜像中的资源指针,来指向正确的地址,即 正确的内存地址 = ASLR地址 + 偏移值
可执行文件
不同的操作系统,其可执行文件的格式也不同,系统内核
将 可执行文件
读取到 内存
,然后根据可执行文件的 头签名(magic魔数)
判断二进制文件的格式。
可执行文件 | 魔数 | 用途 |
---|---|---|
PE32/PE32+ | MZ | Windows的可执行文件 |
ELF | \x7FFLF | Linux和大部分UNIX的可执行文件和库文件 |
脚本 | #! | 主要用于shell脚本,也有一些解释器脚本使用这个格式,这是一种特殊的二进制文件格式,#!后面指向真正的可执行文件(比如python),而脚本其他内容,都被当做输入传递给这个命令 |
通用二进制格式(胖二进制格式) | Oxcafebabe(小端) | 包含多种架构支持的Mach-O格式,iOS和OS X支持的格式 |
Mach-O | 0xfeedface(32位) 0xfeedfacf(64位) | iOS和OS X支持的格式 |
其中 PE、ELF、Mach-O
这三种可执行文件格式都是 COFF(Command file format)
格式的变种,COFF的主要贡献是目标文件里面 引入了段的机制
,不同的目标文件可以拥有不同数量和不同类型的 段
、
通用二进制文件
因为不同的 CPU
平台支持的指令不同,比如 arm64
和 x86
,苹果中的通用二进制格式就是 将多种架构的Mach-O文件打包在一起
,然后系统根据自己的CPU平台,选择合适的 Mach-O
,所以 通用二进制格式
又被称为 胖二进制格式
,如下图所示:
通用二进制格式的定义在 <Mach-O/fat.h>中
,可以下载xnu ,然后根据 xnu ->EXTERNAL_HEADERS ->mach-o
中找到该文件,通用二进制文件开始的 Fat Header
是 fat_header
结构体,而 Fat Archs
是表示通用二进制文件中有多少个 Mach-O
,单个 Mach-O
的描述是通过 fat_arch
结构体。两个结构体的定义如下:
1 | /* |
所以,综上所述
通用二进制文件是苹果公司提出的一种新的二进制文件的存储结构,可以
同时存储多种架构的二进制指令
,使CPU在读取该二进制文件时可以自动检测并选用合适的架构,以最理想的方式进行读取。由于通用二进制文件会同时存储多种架构,所以比单一架构的二进制文件大很多,会占用大量的磁盘空间,但由于系统会自动选择最合适的,不相关的架构代码不会占用内存空间,且
执行效率高
了还可以通过指令来进行Mach-O的合并与拆分
- 查看当前
Mach-O
的架构:lipo -info MachO文件
- 合并:
lipo -create MachO1 MachO2 -output 输出文件路径
- 拆分:
lipo MachO文件 –thin 架构 –output 输出文件路径
- 查看当前
Mach-O文件
Mach-O
文件是 Mach Object
文件格式的缩写,它是用于可执行文件、动态库、目标代码的文件格式。作为 a.out
格式的替代,Mach-O
格式提供了更强的扩展性,以及更快的符号表信息访问速度
熟悉Mach-O文件格式,有助于更好的理解苹果底层的运行机制,更好的掌握 dyld
加载 Mach-O
的步骤。
查看Mach-O文件
如果想要查看具体的 Mach-O
文件信息,可以通过以下 两种
方式,推荐
使用 第二种
方式,更直观:
- 【方式一】
otool
终端命令:otool -l Mach-O文件名
- 【方式二】
MachOView
工具(推荐):将Mach-O可执行文件拖动到MachOView
工具打开:
Mach-O文件格式
对于OS X 和 iOS来说,Mach-O
是其 可执行文件的格式
,主要包括以下几种文件类型
Excutable
:可执行文件Dylib
:动态链接库Bundle
:无法被链接的动态库,只能在运行时使用dlopen加载Image
:指的是Excutable、Dylib和Bundle的一种Framework
:包含Dylib、资源文件和头文件的集合
下面图示是Mach-O镜像文件格式:
以上是Mach-O文件的格式,一个完整的Mach-O文件主要分为三大部分:
Header Mach-O头部
:主要是Mach-O的cpu架构,文件类型以及加载命令等信息Load Commands 加载命令
:描述了文件中数据的具体组织结构,不同的数据类型使用不同的加载命令表示Data 数据
:数据中的每个段(segment)的数据都保存在这里,段的概念与ELF文件中段的概念类似。每个段都有一个或多个部分,它们放置了具体的数据与代码,主要包含代码,数据,例如符号表,动态符号表等等
Header
Mach-O的 Header
包含了 整个Mach-O文件的关键信息
,使得CPU能快速知道Mac-O的基本信息,其在 Mach.h
(路径同前文的fat.h一致)针对 32
位和 64
位架构的cpu,分别使用了mach_header
和 mach_header_64
结构体来 描述Mach-O头部
。mach_header
是连接器加载时最先读取的内容,决定了一些基础架构、系统类型、指令条数等信息,这里查看64位架构的mach_header_64
结构体定义,相比于32位架构的 mach_header
,只是多了一个 reserved
保留字段
1 | /* |
其中filetype
主要记录Mach-O的文件类型,常用的有以下几种:
1 | #define MH_OBJECT 0x1 /* 目标文件*/ |
相对应的,Header在MachOView中的展示如下:
Load Commands
在Mach-O文件中,Load Commands
主要是用于 加载指令
,其大小和数目在Header中已经被提供,其在Mach.h中的定义如下:
1 | /* |
我们在MachOView
中查看Load Commands
,其中记录了很多信息,例如动态链接器的位置、程序的入口、依赖库的信息、代码的位置、符号表的位置
等等,如下所示:
其中LC_SEGMENT_64
的类型segment_command_64
定义如下:
1 | /* |
Data
Load Commands
后就是 Data
区域,这个区域 存储了具体的只读、可读写代码
,例如方法、符号表、字符表、代码数据、连接器所需的数据(重定向、符号绑定等)。主要是存储具体的数据。其中大多数的Mach-O文件均包含以下三个段:
__TEXT 代码段
:只读,包括函数,和只读的字符串__DATA 数据段
:读写,包括可读写的全局变量等__LINKEDIT
: __LINKEDIT包含了方法和变量的元数据(位置,偏移量),以及代码签名等信息。
在 Data
区中,Section
占了很大的比例,Section
在 Mach.h
中是以结构体section_64
(在arm64架构下)表示,其定义如下:
1 | /* |
Section
在 MachOView
中可以看出,主要集中体现在 TEXT
和 DATA
两段里,如下所示:
其中常见的section,主要有以下一些:
所以,综上所述,Mach-O的格式如下:
- Post title:OC底层原理32:启动优化(一)基本概念
- Post author:张建
- Create time:2021-05-04 14:44:11
- Post link:https://redefine.ohevan.com/2021/05/04/OC底层原理/OC底层原理32:启动优化(一)基本概念/
- Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.