虛擬內存 & 物理內存
早期的數據訪問是直接通過物理地址訪問
的,這種方式有以下兩個問題:
1、內存不夠用
2、內存數據的安全問題
內存不夠用的方案:虛擬內存
針對問題1,我們在進程和物理內存之間增加一個中間層
,這個中間層就是所謂的虛擬內存
,主要用于解決當多個進程同時存在時,對物理內存的管理。提高了CPU的利用率,使多個進程可以同時、按需加載
。所以虛擬內存其本質就是一張虛擬地址和物理地址對應關系的映射表
每個進程都有一個獨立的
虛擬內存
,其地址都是從0開始
,大小是4G固定的,每個虛擬內存又會劃分為一個一個的頁
(頁的大小在iOS中是16K,其他的是4K
),每次加載都是以頁為單位加載的,進程間是無法互相訪問的,保證了進程間數據的安全性。一個進程中,只有部分功能是活躍的,所以只需要
將進程中活躍的部分放入物理內存
,避免物理內存的浪費當CPU需要訪問數據時,首先是訪問虛擬內存,然后通過虛擬內存去尋址,即可以理解為在表中找對應的物理地址,然后對相應的物理地址進行訪問
如果在訪問時,虛擬地址的內容未加載到物理內存,會發生
缺頁異常(pagefault)
,將當前進程阻塞掉,此時需要先將數據載入到物理內存,然后再尋址,進行讀取。這樣就避免了內存浪費
如下圖所示,虛擬內存與物理內存間的關系
內存數據的安全問題:ASLR技術
在上面解釋的虛擬內存中,我們提到了虛擬內存的起始地址與大小都是固定的,這意味著,當我們訪問時,其數據的地址也是固定的,這會導致我們的數據非常容易被破解,為了解決這個問題,所以蘋果為了解決這個問題,在iOS4.3開始引入了ASLR
技術。
ASLR的概念:(Address Space Layout Randomization ) 地址空間配置隨機加載
,是一種針對緩沖區溢出
的安全保護技術
,通過對堆、棧、共享庫映射等線性區布局的隨機化,通過增加攻擊者預測目的地址的難度,防止攻擊者直接定位攻擊代碼位置,達到阻止溢出攻擊的目的的一種技術。
其目的的通過利用隨機方式配置數據地址空間
,使某些敏感數據(例如APP登錄注冊、支付相關代碼)配置到一個惡意程序無法事先獲知的地址,令攻擊者難以進行攻擊。
由于ASLR的存在,導致可執行文件和動態鏈接庫在虛擬內存中的加載地址每次啟動都不固定
,所以需要在編譯時來修復鏡像中的資源指針,來指向正確的地址。即正確的內存地址 = ASLR地址 + 偏移值
可執行文件
不同的操作系統,其可執行文件的格式也不同。系統內核將可執行文件讀取到內存,然后根據可執行文件的頭簽名(magic
魔數)判斷二進制文件的格式
其中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
結構體。兩個結構體的定義如下:
/*
- magic:可以讓系統內核讀取該文件時知道是通用二進制文件
- nfat_arch:表明下面有多個fat_arch結構體,即通用二進制文件包含多少個Mach-O
*/
struct fat_header {
uint32_t magic; /* FAT_MAGIC */
uint32_t nfat_arch; /* number of structs that follow */
};
/*
fat_arch是描述Mach-O
- cputype 和 cpusubtype:說明Mach-O適用的平臺
- offset(偏移)、size(大小)、align(頁對齊)描述了Mach-O二進制位于通用二進制文件的位置
*/
struct fat_arch {
cpu_type_t cputype; /* cpu specifier (int) */
cpu_subtype_t cpusubtype; /* machine specifier (int) */
uint32_t offset; /* file offset to this object file */
uint32_t size; /* size of this object file */
uint32_t align; /* alignment as a power of 2 */
};
所以,綜上所述,
通用二進制文件是蘋果公司提出的一種新的二進制文件的存儲結構,可以
同時存儲多種架構的二進制指令
,使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是其可執行文件的格式,主要包括以下幾種文件類型
-
Executable
:可執行文件 -
Dylib
:動態鏈接庫 -
Bundle
:無法被鏈接的動態庫,只能在運行時使用dlopen加載 -
Image
:指的是Executable、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
保留字段,
/*
- magic:0xfeedface(32位) 0xfeedfacf(64位),系統內核用來判斷是否是mach-o格式
- cputype:CPU類型,比如ARM
- cpusubtype:CPU的具體類型,例如arm64、armv7
- filetype:由于可執行文件、目標文件、靜態庫和動態庫等都是mach-o格式,所以需要filetype來說明mach-o文件是屬于哪種文件
- ncmds:sizeofcmds:LoadCommands加載命令的條數(加載命令緊跟header之后)
- sizeofcmds:LoadCommands加載命令的大小
- flags:標志位標識二進制文件支持的功能,主要是和系統加載、鏈接有關
- 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 /* 目標文件*/
#define MH_EXECUTE 0x2 /* 可執行文件*/
#define MH_DYLIB 0x6 /* 動態庫*/
#define MH_DYLINKER 0x7 /* 動態鏈接器*/
#define MH_DSYM 0xa /* 存儲二進制文件符號信息,用于debug分析*/
相對應的,Header在MachOView
中的展示如下
Load Commands
在Mach-O文件中,Load Commands
主要是用于加載指令
,其大小和數目在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 */
};
我們在MachOView
中查看Load Commands,其中記錄了很多信息,例如動態鏈接器的位置、程序的入口、依賴庫的信息、代碼的位置、符號表的位置
等等,如下所示
其中LC_SEGMENT_64
的類型segment_command_64
定義如下
/*
segment_command 段加載命令
- cmd:表示加載命令類型,
- cmdsize:表示加載命令大小(還包括了緊跟其后的nsects個section的大小)
- segname:16個字節的段名字
- vmaddr:段的虛擬內存起始地址
- vmsize:段的虛擬內存大小
- fileoff:段在文件中的偏移量
- filesize:段在文件中的大小
- maxprot:段頁面所需要的最高內存保護(4 = r,2 = w,1 = x)
- initprot:段頁面初始的內存保護
- nsects:段中section數量
- flags:其他雜項標志位
- 從fileoff(偏移)處,取filesize字節的二進制數據,放到內存的vmaddr處的vmsize字節。(fileoff處到filesize字節的二進制數據,就是“段”)
- 每一個段的權限相同(或者說,編譯時候,編譯器把相同權限的數據放在一起,成為段),其權限根據initprot初始化。initprot指定了如何通過讀/寫/執行位初始化頁面的保護級別
- 段的保護設置可以動態改變,但是不能超過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
Load Commands后就是Data
區域,這個區域存儲了具體的只讀、可讀寫代碼
,例如方法、符號表、字符表、代碼數據、連接器所需的數據(重定向、符號綁定等)。主要是存儲具體的數據。其中大多數的Mach-O文件均包含以下三個段:
-
__TEXT 代碼段
:只讀,包括函數,和只讀的字符串 -
__DATA 數據段
:讀寫,包括可讀寫的全局變量等 -
__LINKEDIT
: __LINKEDIT包含了方法和變量的元數據(位置,偏移量),以及代碼簽名等信息。
在Data
區中,Section
占了很大的比例,Section
在Mach.h
中是以結構體section_64
(在arm64架構下)表示,其定義如下
/*
Section節在MachO中集中體現在TEXT和DATA兩段里.
- sectname:當前section的名稱
- segname:section所在的segment名稱
- addr:內存中起始位置
- size:section大小
- offset:section的文件偏移
- align:字節大小對齊
- reloff:重定位入口的文件偏移
- nreloc:重定位入口數量
- flags:標志,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 */
};
Section
在MachOView
中可以看出,主要集中體現在TEXT
和DATA
兩段里,如下所示
其中常見的section,主要有以下一些
section - __TEXT | 說明 |
---|---|
__TEXT.__text |
主程序代碼 |
__TEXT.__cstring |
C語言字符串 |
__TEXT.__const |
const 關鍵字修飾的常量 |
__TEXT.__stubs |
用于 Stub 的占位代碼,很多地方稱之為樁代碼 |
__TEXT.__stubs_helper |
當 Stub 無法找到真正的符號地址后的最終指向 |
__TEXT.__objc_methname |
Objective-C 方法名稱 |
__TEXT.__objc_methtype |
Objective-C 方法類型 |
__TEXT.__objc_classname |
Objective-C 類名稱 |
section - __DATA | 說明 |
---|---|
__DATA.__data |
初始化過的可變數據 |
__DATA.__la_symbol_ptr |
lazy binding 的指針表,表中的指針一開始都指向 __stub_helper |
__DATA.nl_symbol_ptr |
非 lazy binding 的指針表,每個表項中的指針都指向一個在裝載過程中,被動態鏈機器搜索完成的符號 |
__DATA.__const |
沒有初始化過的常量 |
__DATA.__cfstring |
程序中使用的 Core Foundation 字符串(CFStringRefs) |
__DATA.__bss |
BSS,存放為初始化的全局變量,即常說的靜態內存分配 |
__DATA.__common |
沒有初始化過的符號聲明 |
__DATA.__objc_classlist |
Objective-C 類列表 |
__DATA.__objc_protolist |
Objective-C 原型 |
__DATA.__objc_imginfo |
Objective-C 鏡像信息 |
__DATA.__objc_selfrefs |
Objective-C self 引用 |
__DATA.__objc_protorefs |
Objective-C 原型引用 |
__DATA.__objc_superrefs |
Objective-C 超類引用 |
所以,綜上所述,Mach-O的格式圖示,如下所示