最近學(xué)習(xí)了一下 Mach-O ,這里做個筆記記錄确虱,整理思路,加深理解。
原文出處 Valar Morghulis 的博客
本文修改了原文幾處錯誤。
概述
第一章 描述了 Mach-O 文件的基本結(jié)構(gòu)幌羞;
第二章 概述了符號,分析了符號表(symbol table)竟稳。
第三章 探尋動態(tài)鏈接属桦。
第四章 分析fishhook。
第五章 分析BeeHive他爸。
第六章 App啟動時間聂宾。
結(jié)構(gòu)分析
關(guān)于 Mach-O 的文件格式,在網(wǎng)上常痴矬裕看到如下這張圖亏吝,出自官方文檔《OS X ABI Mach-O File Format Reference》:
通過這張圖,可以看到盏混,從布局上,Mach-O 文件分為三個部分:Header惜论、Load Commands许赃、Data。但這張圖過于簡略馆类,信息不完善混聊,可能會讓人困惑。先簡單分析 Header 和 Load Commands 的結(jié)構(gòu)吧乾巧!
Header 的結(jié)構(gòu)
32位和64位分別對應(yīng)了不同的結(jié)構(gòu)句喜,但大同小異预愤,這里以64位為例。
mach-o/loader.h 的struct mach_header
定義了 Header 的結(jié)構(gòu):
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 */
};
從圖中可以看出咳胃,Header的結(jié)構(gòu)是固定的植康,size固定為32 bytes(64位架構(gòu))
每個字段的意思,如下:
magic展懈,系統(tǒng)內(nèi)核用來判斷是否是 mach-o 格式销睁。
// magic
#define MH_MAGIC 0xfeedface /* 32位大端字節(jié)序*/
#define MH_CIGAM 0xcefaedfe /* 32位小端字節(jié)序 */
#define MH_MAGIC_64 0xfeedfacf /* 64位大端字節(jié)序 */
#define MH_CIGAM_64 0xcffaedfe /* 64位小端字節(jié)序 */
filetype,描述了二進(jìn)制文件的類型存崖,包括了十來個有效值冻记,常打交道的包括:
#define MH_OBJECT 0x1 // 中間目標(biāo)文件,例如.o文件
#define MH_EXECUTE 0x2 // 可執(zhí)行文件
#define MH_DYLIB 0x6 // 動態(tài)鏈接庫
#define MH_DYLINKER 0x7 // 動態(tài)鏈接器
flags 是雜項来惧,通常它包含的信息用于為動態(tài)鏈接器服務(wù)冗栗,告訴后者如何工作。
Load Commands 的結(jié)構(gòu)
Load Commands 可以被看作是一個 command 列表供搀,緊貼著 Header隅居,所以它的 file offset 是固定的:0x20(上面說的header的size為32 bytes,即0x20)趁曼。一共有哪些 load commands 呢军浆?Load commands 由內(nèi)核定義,不同版本的 command 數(shù)量不同挡闰,本文所參考的內(nèi)核乒融,一共定義了 50+ load commands,它們的 type 是以LC_為前綴常量摄悯,譬如 LC_SEGMENT赞季、LC_SYMTAB 等。
每個 command 都有獨立的結(jié)構(gòu)奢驯,但所有 command 結(jié)構(gòu)的前兩個字段是固定的:
struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */
};
第一個字段指定了類型申钩,第二個字段指定了大小,確保它能被正確解析瘪阁。
這里只講其中的一個 load command 撒遣,LC_SEGMENT_64,因為它和 segment管跺、section 有關(guān)义黎;命令格式如下:
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 */
};
它描述了文件映射的兩大問題:從哪里來(fileoff
、filesize
)豁跑、到哪里去(vmaddr
廉涕、vmsize
);它還告訴了內(nèi)核該區(qū)域的名字(segname
,即 segment name)狐蜕,以及該區(qū)域包含了幾個 section(nsects
)宠纯,以及該區(qū)域的保護(hù)級別(initprot
、maxprot
)层释。
- 每一個 segment 的 VP (Virtual Page) 都根據(jù)
initprot
進(jìn)行初始化婆瓜,initprot 指定了如何通過 讀/寫/可執(zhí)行 初始化頁面的保護(hù)級別;segment 的保護(hù)設(shè)置可以動態(tài)改變湃累,但是不能超過 maxprot 中指定的值(在 iOS 中勃救,+x(可執(zhí)行) 和+w(可寫) 是互斥的);initprot治力、maxprot 的值均用八進(jìn)制表示(1=r蒙秒,2=w,4=x) - flags 是雜項標(biāo)志位
- vmsize 并不等于 filesize宵统,對于 4KB 大小的 VP晕讲,vmsize 是 4K 的倍數(shù);換句話說马澈,vmsize 一般大于 segment 的實際大小
對于 segment 而言瓢省,有了這些信息,其結(jié)構(gòu)其實就足夠清晰了痊班,但是如何知道其中各個 sections 的具體位置和 size 呢勤婚?
對于 LC_SEGMENT_64 而言,如果其nsects
字段大于 0涤伐,其命令后面還會緊接著掛載nsects
個描述 section 的信息馒胆,這些信息是結(jié)構(gòu)體section_64
的列表,section_64
結(jié)構(gòu)體定義如下:
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 */
};
結(jié)構(gòu)體section_64
可以看做 section header凝果,它描述了對應(yīng) section 的具體位置祝迂,以及要被映射的目標(biāo)虛擬地址。
回頭再看segment_command_64
的cmdsize
字段器净,它的數(shù)值是segment_command_64
的 size 大小型雳,加上緊接在 command 后面的所有section_64
結(jié)構(gòu)體的大小。
舉個例子山害,如果 segment 含有 2 個 section纠俭,那么對應(yīng)的 segment_command_64
的 cmdsize 值為:
72(segment_command_64本身大小) + 2 * 80(section_64的大欣嘶拧) = 232 bytes
這里應(yīng)該明白 segment 和 section :Mach-O 本沒有 segment柑晒,有了 LC_SEGMENT_64,于是有了 segment眷射。
segment 是一段連續(xù)的內(nèi)存地址,一般來說擁有相同的讀、寫妖碉、執(zhí)行權(quán)限涌庭,segment都是頁對齊的。section 不是頁對齊的欧宜。
更多Load Commond作用及相關(guān)定義坐榆,可以參考 聊聊 Mach-O 文件格式 。
Data的結(jié)構(gòu)
和 Header冗茸、Load Commands 不同席镀,Mach-O 對 Data 區(qū)域沒有任何公共的結(jié)構(gòu)上的定義。它里面盛裝的字節(jié)本來沒有意義夏漱,有了 LC_SEGMENT_64 以及其他的 load commands豪诲,一切才開始有了意義。
Mach-O 的結(jié)構(gòu)
結(jié)合上面的內(nèi)容挂绰,通過一個具體的 case屎篱,綜述一下 Mach-O 的結(jié)構(gòu)。寫一個簡單的 C 文件如下:
#include <stdio.h>
int main(void) {
printf("Hello, world!\n");
return 0;
}
執(zhí)行g(shù)cc main.c葵蒂,得到可執(zhí)行文件 a.out交播,使用 MachOView 工具查看,得到如下結(jié)構(gòu):
注意左右標(biāo)紅的部分践付,可以得到的信息:
- 一共包括三個 segment:__TEXT秦士、__DATA、__LINKEDIT
- segment 的內(nèi)容范圍并非一定在 Data 區(qū)內(nèi)(譬如 __TEXT segment)
- 并非每一個 segment 都由 section 組成(譬如 __LINKEDIT segment)
為啥 __TEXT 的地址范圍從 0 開始而非從 _text 這個 section 開始呢永高?《OS X ABI Mach-O File Format Reference》是這么說的:
The header and load commands are considered part of the first segment of the file for paging purposes. In an executable file, this generally means that the headers and load commands live at the start of the __TEXT segment because that is the first segment that contains data.
一個典型的 Mach-O 結(jié)構(gòu)圖的更清晰描述應(yīng)該是這個樣子:
總結(jié)
這篇文章主要說明了 Mach-O 文件的結(jié)構(gòu)隧土,以及三種結(jié)構(gòu)體mach_header_64
、segment_command_64
乏梁、section_64
的結(jié)構(gòu)及其意義:
//解釋 Header
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 */
};
//解釋 segment
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 */
};
//解釋 section
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 */
};