主要內(nèi)容:
- 理解可執(zhí)行文件
- 理解
Mach-O
文件 -
Mach-O
文件結(jié)構(gòu) Mach Header
Load Commands
Data
- 理解大小端模式
- 理解通用二進(jìn)制文件
一、理解可執(zhí)行文件
1.可執(zhí)行文件
-
進(jìn)程
,其實(shí)就是可執(zhí)行文件
在內(nèi)存中加載得到的結(jié)果; -
可執(zhí)行文件
必須是操作系統(tǒng)可理解的格式,而且不同系統(tǒng)的可執(zhí)行文件
的格式也是不同的;
2.不同平臺的可執(zhí)行文件
-
Linux:ELF
文件 -
Windows
:PE32/PE32+
文件 -
OS和iOS
:Mach-O(Mach Object)
文件
二、理解Mach-O文件
作為iOS
,iPadOS
、macOS
平臺的可執(zhí)行文件格式,Mach-O
文件涉及App啟動運(yùn)行、bitcode
分析、 crash
符號化等諸多多個功能:
1. Mach-O文件
-
Mach-O
文件是iOS
,iPadOS
、macOS
平臺的可執(zhí)行文件格式。對應(yīng)系統(tǒng)通過應(yīng)用二進(jìn)制接口(application binary interface
,縮寫為ABI
)來運(yùn)行該格式的文件; -
Mach-O
格式用來替代BSD
系統(tǒng)中的a.out
格式,保存了在編譯和鏈接過程中產(chǎn)生的機(jī)器代碼和數(shù)據(jù)
,從而為靜態(tài)鏈接和動態(tài)鏈接的代碼提供單一文件格式。 -
Mach-O
提供了更強(qiáng)的擴(kuò)展性,以及更快的符號表信息訪問速度;
2.Mach-O格式的常見文件類型
-
Executable
:可執(zhí)行文件(.out
.o
); -
Dylib
:動態(tài)鏈接庫; -
Bundle
:不能被鏈接,只能在運(yùn)行時(shí)使用dlopen()
加載; -
Image
:包含Executable
、Dylib
和Bundle
; -
Framework
:包含Dylib
、資源文件和頭文件的文件夾;
三、Mach-O文件結(jié)構(gòu)
1.查看Mach-O的兩種方法
- 使用
MachOView
軟件,可直接查看MachO
文件的結(jié)構(gòu); - 使用終端命令
objdump
;
2.查看Mach-O文件結(jié)構(gòu)
使用MachOView
查看Mach-O
,效果如下:
Mach-O
文件中包含三個主要的部分:
-
Header
:頭部,描述CPU
類型、文件類型、加載命令的條數(shù)大小等信息; -
Load Commands
:加載命令,其條數(shù)和大小已經(jīng)在header
中被提供; -
Data
:數(shù)據(jù)段;
其他的信息還有:
-
Dynamic Loader Info
:動態(tài)庫加載信息 -
Function Starts
:入口函數(shù) -
Symbol Table
:符號表 -
Dynamic Symbol Table
: 動態(tài)庫符號表 -
String Table
:字符串表
四、Mach Header(可執(zhí)行文件頭)
1.功能總結(jié)
-
Header
是鏈接器加載時(shí)最先讀取的內(nèi)容,因?yàn)樗鼪Q定了一些基礎(chǔ)架構(gòu)
、系統(tǒng)類型
等信息; -
Header
包含整個Mach-O
文件的關(guān)鍵信息,如CPU類型
、文件類型
、加載命令的條數(shù)大小
等信息,使得系統(tǒng)能夠迅速定位Mach-O
文件的運(yùn)行環(huán)境; -
Header
針對32
位和64
位架構(gòu)的CPU
,分別對應(yīng)mach_header
和mach_header_64
的結(jié)構(gòu)體;
2.源碼分析
Header
被定義在loader.h
文件中,具體代碼如下:
struct mach_header_64 {
uint32_t magic; // 32位或者64位,系統(tǒng)內(nèi)核用來判斷是否是mach-o格式
cpu_type_t cputype; // CPU架構(gòu)類型,比如ARM
cpu_subtype_t cpusubtype; // CPU的具體類型,例如arm64、armv7
uint32_t filetype; // mach-o文件類型, 可執(zhí)行文件、目標(biāo)文件或者靜態(tài)庫和動態(tài)庫
uint32_t ncmds; // LoadCommands加載命令的條數(shù)(加載命令緊跟header之后)
uint32_t sizeofcmds; // 全部LoadCommands加載命令的大小
uint32_t flags; // 標(biāo)志位標(biāo)識二進(jìn)制文件支持的功能,主要是和系統(tǒng)加載、鏈接有關(guān)
uint32_t reserved; // 保留字段(相比于32位多出的字段)
};
由于可執(zhí)行文件
、目標(biāo)文件
或者靜態(tài)庫
和動態(tài)庫
等都是Mach-O
格式,所以才需要filetype
來說明。常用的文件類型有以下幾種:
#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分析*/
3.MachOView演示
五、分析Load Commands
1.功能總結(jié)
-
Load Commands
是加載命令的列表,用于描述Data
在二進(jìn)制文件和虛擬內(nèi)存中的布局信息; -
Load Commands
記錄了很多信息,例如動態(tài)鏈接器的位置、程序的入口、依賴庫的信息、代碼的位置、符號表的位置等; -
Load commands
由內(nèi)核定義,不同版本的command
數(shù)量不同,其條數(shù)和大小記錄在header
中; -
Load commands
的type
是以LC_
為前綴常量,譬如LC_SEGMENT
、LC_SYMTAB
等;
2..代碼分析
Load Command
被定義在loader.h
文件中,具體代碼如下:
struct load_command {
uint32_t cmd; /* 加載命令的類型 */
uint32_t cmdsize; /* 加載命令的大小 */
};
每個Load Command
都有獨(dú)立的結(jié)構(gòu),但是所有結(jié)構(gòu)的前兩個字段是固定的。比如LC_SEGMENT_64
,這是一個讀取segment
、section
有關(guān)命令,具體代碼如下:
struct segment_command_64 { /* for 64-bit architectures */
uint32_t cmd; // 表示加載命令類型
uint32_t cmdsize; // 表示加載命令大小(還包括了緊跟其后的nsects個section的大小)
char segname[16]; // 16個字節(jié)的段名字
uint64_t vmaddr; // 段的虛擬內(nèi)存起始地址
uint64_t vmsize; // 段的虛擬內(nèi)存大小
uint64_t fileoff; // 段在文件中的偏移量
uint64_t filesize; // 段在文件中的大小
vm_prot_t maxprot; // 段頁面所需要的最高內(nèi)存保護(hù)(4 = r,2 = w,1 = x)
vm_prot_t initprot; // 段頁面初始的內(nèi)存保護(hù)
uint32_t nsects; // 段中section數(shù)量
uint32_t flags; // 標(biāo)志位
};
六、Data
1.功能總結(jié)
-
Data
中存儲了實(shí)際的數(shù)據(jù)與代碼,主要包含方法、符號表、動態(tài)符號表、動態(tài)庫加載信息(重定向、符號綁定等)等; -
Data
中的排布完全按照Load Command
中的描述; -
Data
由Segment
(段)和Section
(節(jié))的方式來組成,通常,Data
擁有多個segment
,每個segment
可以有零到多個section
節(jié); - 不同的
segment
都有一段虛擬地址
映射到進(jìn)程的地址空間;
幾乎所有的Mach-O
文件都包含3
個segment
-
__TEXT:代碼段,只讀可執(zhí)行,存儲
函數(shù)的二進(jìn)制代碼(__text)
,常量字符串(__cstring)
,OC的類/方法名
等信息 -
__DATA:數(shù)據(jù)段, 可讀可寫,存儲
OC的字符串(__cfstring)
,以及運(yùn)行時(shí)的元數(shù)據(jù):class/protocol/method
,以及全局變量,靜態(tài)變量等; -
__LINKEDIT:只讀,存儲啟動
App
需要的信息,如bind & rebase 的地址
、函數(shù)的名稱和地址等信息;
2.源碼分析
在Data
區(qū)中,Section
占了很大的比例,而且在Mach-O
中集中體現(xiàn)在__TEXT
和__DATA
兩段里。
Section
被定義在loader.h
文件中,具體代碼如下:
struct section_64 { /* for 64-bit architectures */
char sectname[16]; // 當(dāng)前section的名稱
char segname[16]; // section所在的segment名稱
uint64_t addr; // 內(nèi)存中起始位置
uint64_t size; // section大小
uint32_t offset; // section的文件偏移
uint32_t align; // 字節(jié)大小對齊
uint32_t reloff; // 重定位入口的文件偏移
uint32_t nreloc; // 重定位入口數(shù)量
uint32_t flags; // 標(biāo)志,section的類型和屬性
uint32_t reserved1; // 保留(用于偏移量或索引)
uint32_t reserved2; // 保留(用于count或sizeof)
uint32_t reserved3; // 保留
};
七、理解大小端模式
分析Mach-O文
件時(shí),經(jīng)常會看到內(nèi)存地址相關(guān)的內(nèi)容,這里就涉及到了大小端模式的概念;
- 小端模式:數(shù)據(jù)的低字節(jié),保存在內(nèi)存的低地址;
- 大端模式:數(shù)據(jù)的低字節(jié),保存在內(nèi)存的高地址;
iOS
設(shè)備的處理器是基于ARM
架構(gòu)的,默認(rèn)是采用小端模式(低字節(jié)放低位)讀取數(shù)據(jù)的,而網(wǎng)絡(luò)和藍(lán)牙傳輸數(shù)據(jù)通常是用的大端模式(低字節(jié)放高位):
下面以unsigned int value = 0x12345678
為例,分別看看在兩種字節(jié)序下其存儲情況,我們可以用unsigned char buf[4]
來表示value
Little-Endian: 低地址存放低位,如下:
低地址 ------------------> 高地址
0x78 | 0x56 | 0x34 | 0x12
Big-Endian: 低地址存放高位,如下:
低地址 -----------------> 高地址
0x12 | 0x34 | 0x56 | 0x78
內(nèi)存地址 | 小端模式存放內(nèi)容 | 大端模式存放內(nèi)容 |
---|---|---|
0x4000 | 0x78 | 0x12 |
0x4001 | 0x56 | 0x34 |
0x4002 | 0x34 | 0x56 |
0x4003 | 0x12 | 0x78 |
八、理解通用二進(jìn)制文件
1.基本概念
- 通用二進(jìn)制文件的存儲結(jié)構(gòu),是將多種架構(gòu)的
Mach-O
文件打包在一起,CPU
在讀取該二進(jìn)制文件時(shí)可以自動檢測并選用合適的架構(gòu); - 通用二進(jìn)制文件會同時(shí)存儲多種架構(gòu),所以比單一架構(gòu)的二進(jìn)制文件大很多,會占用大量的磁盤空間。但由于系統(tǒng)運(yùn)行時(shí)會自動選擇最合適的,不相關(guān)的架構(gòu)代碼,不會占用內(nèi)存空間,所以執(zhí)行效率提高了;
- 通用二進(jìn)制格式也被稱為胖二進(jìn)制格式;
2.通用二進(jìn)制格式分析
通用二進(jìn)制格式的定義在<mach-o/fat.h>
中:
- 下載xnu后,依次在
xnu -> EXTERNAL_HEADERS ->mach-o
中找到該文件。 - 通用二進(jìn)制文件有兩個重要結(jié)構(gòu)體:
fat_header
、fat_arch
;
兩個結(jié)構(gòu)體的定義如下:
/*
- magic:可以讓系統(tǒng)內(nèi)核讀取該文件時(shí)知道是通用二進(jìn)制文件
- nfat_arch:表明下面有多個fat_arch結(jié)構(gòu)體,即通用二進(jìn)制文件包含多少個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二進(jìn)制位于通用二進(jìn)制文件的位置
*/
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 */
};