1平斩、虛擬內(nèi)存 & ASLR
在早期計(jì)算機(jī)中數(shù)據(jù)是直接通過物理地址訪問的
,這就造成了下面兩個(gè)問題
- 1家卖、內(nèi)存不夠用
- 2前普、數(shù)據(jù)安全問題
內(nèi)存不夠 --- > 虛擬內(nèi)存
虛擬內(nèi)存就是通過創(chuàng)建一張物理地址和虛擬地址的映射表
來管理內(nèi)存,提高了CPU利用率酥艳,使多個(gè)進(jìn)程可以同時(shí)/按需加載
- 在iOS中摊溶,每個(gè)進(jìn)程都有獨(dú)立的
虛擬內(nèi)存
,存放物理內(nèi)存中充石,其地址是從0開始的
莫换,大小固定4G
,每個(gè)虛擬內(nèi)存又會按頁劃分骤铃,每頁16K
拉岁,以頁為單位加載
,每個(gè)進(jìn)程是相互獨(dú)立的惰爬,保證進(jìn)程間的數(shù)據(jù)安全 - 當(dāng)一個(gè)進(jìn)程只有部分功能使用時(shí)喊暖,系統(tǒng)會自動將使用部分加載到物理內(nèi)存中
- CPU在進(jìn)行數(shù)據(jù)訪問時(shí),會先訪問虛擬內(nèi)存撕瞧,通過虛擬內(nèi)存和物理內(nèi)存的映射關(guān)系陵叽,去對應(yīng)的物理內(nèi)存中查找狞尔,在CPU上有專門處理映射的硬件
- 當(dāng)CPU訪問數(shù)據(jù)時(shí),如果虛擬內(nèi)存中的數(shù)據(jù)沒有物理內(nèi)存綁定巩掺,會發(fā)生
缺頁異常(pagefault)
偏序,會阻塞當(dāng)前進(jìn)程
,直到數(shù)據(jù)加載到物理內(nèi)存中胖替,虛擬內(nèi)存和物理內(nèi)存綁定 - 當(dāng)內(nèi)存條中的內(nèi)存全部用完后研儒,系統(tǒng)會將新的數(shù)據(jù)
覆蓋
到很久沒使用的內(nèi)存上
數(shù)據(jù)安全 --- > ASLR
因?yàn)樘摂M內(nèi)存的起始地址(0x000000)和大小(4G)是固定的,這就意味著我們的數(shù)據(jù)地址也是固定的
独令,所以在iOS4.3引進(jìn)了ASLR
ASLR:英文全稱Address Space Layout Randomization端朵,也叫地址空間配置隨機(jī)加載
,是一種針對緩沖區(qū)溢出
的安全保護(hù)技術(shù)
燃箭。通過對堆冲呢、棧、共享庫映射等線性區(qū)布局的隨機(jī)化
遍膜,增強(qiáng)了攻擊者找到目標(biāo)物理內(nèi)存的難度碗硬,防止攻擊者直接定位攻擊代碼位置,達(dá)到阻止溢出攻擊的目的的一種技術(shù)瓢颅。
由于ASLR的存在恩尾,導(dǎo)致可執(zhí)行文件和動態(tài)鏈接庫在虛擬內(nèi)存中的地址每次都是不固定的,所以需要在編譯時(shí)來修復(fù)鏡像中的資源指針挽懦,來指向正確的地址翰意。即正確的內(nèi)存地址 = ASLR地址 + 偏移值
2、Mach-O
Mach-O
文件是Mach Object
文件格式的縮寫信柿,它是用于可執(zhí)行文件冀偶、動態(tài)庫、目標(biāo)代碼的文件格式渔嚷。作為a.out格式的替代进鸠,Mach-O格式提供了更強(qiáng)的擴(kuò)展性,以及更快的符號表信息訪問速度
我們可以通過工具MachOView
來查看Mach-O文件具體信息
對于OS X 和iOS來說形病,Mach-O是其可執(zhí)行文件的格式
客年,主要包括以下幾種文件類型
-
Executable
:可執(zhí)行文件 -
Dylib
:動態(tài)鏈接庫 -
Bundle
:無法被鏈接的動態(tài)庫,只能在運(yùn)行時(shí)使用dlopen加載 -
Image
:指的是Executable漠吻、Dylib和Bundle的一種 -
Framework
:包含Dylib量瓜、資源文件和頭文件的集合
Mach-O文件格式
一個(gè)完整的Mach-O文件主要分為三大部分:
-
Header
:Mach-O的cpu架構(gòu),文件類型以及加載命令等信息 -
Load Comands
:文件中數(shù)據(jù)的具體組織結(jié)構(gòu)途乃,不同數(shù)據(jù)使用不同的加載命令 -
Data
:數(shù)據(jù)中的每個(gè)段(Segment)的數(shù)據(jù)保存在這里绍傲,每個(gè)段有一個(gè)或多個(gè)部分,存放具體的數(shù)據(jù)耍共、代碼烫饼、符號表猎塞、動態(tài)符號表等
Header
Header
中包含了整個(gè)Mach-O文件中的關(guān)鍵信息
,可以使CPU快速知道整個(gè)Mach-O的基本信息枫弟,決定了一些基礎(chǔ)架構(gòu)邢享、系統(tǒng)類型鹏往、指令條數(shù)等信息淡诗。針對32位和64位架構(gòu)的cpu,分別使用了mach_header
和mach_header_64
結(jié)構(gòu)體來描述Mach-O頭部伊履,mach_header_64
結(jié)構(gòu)體相比于mach_header
只是多了一個(gè)reserved
保留字段
/*
- magic:0xfeedface(32位) 0xfeedfacf(64位)韩容,系統(tǒng)內(nèi)核用來判斷是否是mach-o格式
- cputype:CPU類型,比如ARM
- cpusubtype:CPU的具體類型唐瀑,例如arm64群凶、armv7
- filetype:由于可執(zhí)行文件、目標(biāo)文件哄辣、靜態(tài)庫和動態(tài)庫等都是mach-o格式请梢,所以需要filetype來說明mach-o文件是屬于哪種文件
- ncmds:sizeofcmds:LoadCommands加載命令的條數(shù)(加載命令緊跟header之后)
- sizeofcmds:LoadCommands加載命令的大小
- flags:標(biāo)志位標(biāo)識二進(jìn)制文件支持的功能,主要是和系統(tǒng)加載力穗、鏈接有關(guān)
- reserved:保留字段
*/
struct mach_header_64 {
uint32_t magic; /* mach magic number identifier */
cpu_type_t cputype; /* cpu specifier */
cpu_subtype_t cpusubtype; /* machine specifier */
uint32_t filetype; /* type of file */
uint32_t ncmds; /* number of load commands */
uint32_t sizeofcmds; /* the size of all the load commands */
uint32_t flags; /* flags */
uint32_t reserved; /* reserved */
};
filetype
主要記錄Mach-O的文件類型毅弧,常用的有以下幾種
#define MH_OBJECT 0x1 /* 目標(biāo)文件*/
#define MH_EXECUTE 0x2 /* 可執(zhí)行文件*/
#define MH_DYLIB 0x6 /* 動態(tài)庫*/
#define MH_DYLINKER 0x7 /* 動態(tài)鏈接器*/
#define MH_DSYM 0xa /* 存儲二進(jìn)制文件符號信息,用于debug分析*/
Load Commands
Load Commands
主要用于加載指令
当窗,例如動態(tài)鏈接器的位置够坐、程序的入口、依賴庫的信息崖面、代碼的位置元咙、符號表的位置
等等。其大小和數(shù)目在Header中已確定巫员,在Mach.h中的定義如下
/*
load_command用于加載指令
- cmd 加載命令的類型
- cmdsize 加載命令的大小
*/
struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */
};
其中LC_SEGMENT_64
的類型segment_command_64
定義如下
/*
segment_command 段加載命令
- cmd:表示加載命令類型庶香,
- cmdsize:表示加載命令大小(還包括了緊跟其后的nsects個(gè)section的大屑蚴丁)
- segname:16個(gè)字節(jié)的段名字
- vmaddr:段的虛擬內(nèi)存起始地址
- vmsize:段的虛擬內(nèi)存大小
- fileoff:段在文件中的偏移量
- filesize:段在文件中的大小
- maxprot:段頁面所需要的最高內(nèi)存保護(hù)(4 = r赶掖,2 = w,1 = x)
- initprot:段頁面初始的內(nèi)存保護(hù)
- nsects:段中section數(shù)量
- flags:其他雜項(xiàng)標(biāo)志位
- 從fileoff(偏移)處财异,取filesize字節(jié)的二進(jìn)制數(shù)據(jù)倘零,放到內(nèi)存的vmaddr處的vmsize字節(jié)。(fileoff處到filesize字節(jié)的二進(jìn)制數(shù)據(jù)戳寸,就是“段”)
- 每一個(gè)段的權(quán)限相同(或者說呈驶,編譯時(shí)候,編譯器把相同權(quán)限的數(shù)據(jù)放在一起疫鹊,成為段)袖瞻,其權(quán)限根據(jù)initprot初始化司致。initprot指定了如何通過讀/寫/執(zhí)行位初始化頁面的保護(hù)級別
- 段的保護(hù)設(shè)置可以動態(tài)改變,但是不能超過maxprot中指定的值(在iOS中聋迎,+x和+w是互斥的)
*/
struct segment_command_64 { /* for 64-bit architectures */
uint32_t cmd; /* LC_SEGMENT_64 */
uint32_t cmdsize; /* includes sizeof section_64 structs */
char segname[16]; /* segment name */
uint64_t vmaddr; /* memory address of this segment */
uint64_t vmsize; /* memory size of this segment */
uint64_t fileoff; /* file offset of this segment */
uint64_t filesize; /* amount to map from the file */
vm_prot_t maxprot; /* maximum VM protection */
vm_prot_t initprot; /* initial VM protection */
uint32_t nsects; /* number of sections in segment */
uint32_t flags; /* flags */
};
Data
Data
主要用于存儲具體的只讀脂矫、可讀寫代碼
,例如方法霉晕、符號表庭再、字符表、代碼數(shù)據(jù)牺堰、重定向/符號綁定等拄轻。其中大多數(shù)的Mach-O文件均包含以下三個(gè)段:
-
__TEXT
:代碼段:只讀,包括函數(shù)和只讀字符串 -
__DATA
:數(shù)據(jù)段:讀寫伟葫,可讀寫的全局變量等 -
__LINKEDIT
:包含方法和變量的元數(shù)據(jù)(位置恨搓、偏移量),代碼簽名等信息
在Data
區(qū)中筏养,section
占很大比例斧抱,Section在Mach.h中是以結(jié)構(gòu)體section_64
(在arm64架構(gòu)下)表示,其定義如下
/*
Section節(jié)在MachO中集中體現(xiàn)在TEXT和DATA兩段里.
- sectname:當(dāng)前section的名稱
- segname:section所在的segment名稱
- addr:內(nèi)存中起始位置
- size:section大小
- offset:section的文件偏移
- align:字節(jié)大小對齊
- reloff:重定位入口的文件偏移
- nreloc:重定位入口數(shù)量
- flags:標(biāo)志渐溶,section的類型和屬性
- reserved1:保留(用于偏移量或索引)
- reserved2:保留(用于count或sizeof)
- reserved3:保留
*/
struct section_64 { /* for 64-bit architectures */
char sectname[16]; /* name of this section */
char segname[16]; /* segment this section goes in */
uint64_t addr; /* memory address of this section */
uint64_t size; /* size in bytes of this section */
uint32_t offset; /* file offset of this section */
uint32_t align; /* section alignment (power of 2) */
uint32_t reloff; /* file offset of relocation entries */
uint32_t nreloc; /* number of relocation entries */
uint32_t flags; /* flags (section type and attributes)*/
uint32_t reserved1; /* reserved (for offset or index) */
uint32_t reserved2; /* reserved (for count or sizeof) */
uint32_t reserved3; /* reserved */
};
3辉浦、優(yōu)化建議
啟動的過程一般是指點(diǎn)擊App圖標(biāo)
開始到AppDelegate 的didFinishLaunching
-
冷啟動
:內(nèi)存中不包含App相關(guān)數(shù)據(jù),一般可以通過重啟手機(jī)來實(shí)現(xiàn)冷啟動 -
熱啟動
:殺掉App進(jìn)程后掌猛,數(shù)據(jù)依然存在內(nèi)存中時(shí)啟動
啟動優(yōu)化一般指在冷啟動情況
下主要分為兩部分:
-
main函數(shù)開始之前
(pre-main): 操作系統(tǒng)加載App可執(zhí)行文件到內(nèi)存盏浙,執(zhí)行一系列的加載和鏈接操作,也就是dyld加載過程
-
main函數(shù)開始
:從main函數(shù)開始到AppDelegate 的didFinishLaunching
方法執(zhí)行為止荔茬,加載渲染第一個(gè)界面
main函數(shù)之前
在之前的文章已經(jīng)了解過dyld加載流程
我們可以通過Edit Scheme -> Run -> Arguments ->Environment Variables->點(diǎn)擊+添加環(huán)境變量DYLD_PRINT_STATISTICS == 1
上圖中pre-main階段總共用時(shí)1.7s
dylib loading
:動態(tài)庫加載耗時(shí)-
rebase/binding
:偏移修正和符號綁定-
rebase
:每個(gè)app的二進(jìn)制文件內(nèi)部的方法废膘、函數(shù)調(diào)用,都有一個(gè)偏移地址
,每次app啟動慕蔚,系統(tǒng)會分配一個(gè)ALSR
新啼,例如韩脑,二進(jìn)制文件中有一個(gè) test方法,偏移值是0x0001,而隨機(jī)分配的ASLR是0x1f00虐沥,如果想訪問test方法灵份,其內(nèi)存地址(即真實(shí)地址)變?yōu)?ASLR+偏移值 = 運(yùn)行時(shí)確定的內(nèi)存地址(即0x1f00+0x0001 = 0x1f01) -
binding
:綁定就是給符號賦值的過程
扬跋。方法脆栋、函數(shù)在編譯時(shí)期會創(chuàng)建一個(gè)對應(yīng)的符號
指向一個(gè)隨機(jī)地址,在運(yùn)行時(shí)(磁盤加載到內(nèi)存中鸠匀,是一個(gè)鏡像文件)蕉斜,會將真正的內(nèi)存地址和符號進(jìn)行綁定
(即dyld
將內(nèi)存地址和符號綁定,也稱動態(tài)庫符號綁定
)
-
Objc setup
:oc類注冊的耗時(shí),類越多宅此,耗時(shí)越長机错,swift
沒有這個(gè)initializer
:執(zhí)行l(wèi)oad和構(gòu)造函數(shù)的耗時(shí)
優(yōu)化
少用外部動態(tài)庫
,蘋果官方建議自定義動態(tài)庫數(shù)量不要超過6個(gè)
父腕,如果超過需要進(jìn)行動態(tài)庫合并減少OC類使用
將不必須在
+load
方法中做的事情延遲到+initialize
中弱匪,盡量不要用C++虛函數(shù)
如果是
swift
,盡量使用struct
main函數(shù)開始后
減少啟動初始化的流程
:能懶加載的懶加載璧亮,能延遲的延遲萧诫,能放后臺初始化的放后臺,盡量不要占用主線程的啟動時(shí)間優(yōu)化代碼邏輯杜顺,去除非必要的代碼邏輯
啟動階段能使用
多線程來初始化
的财搁,就使用多線程主UI框架盡量使用
純代碼構(gòu)建
刪除廢棄的類和方法