進程是特殊文件在內存中加載獲得的結果。那這種文件的格式必須是系統內核能夠理解的,系統內核才能正確解析。python
不一樣操做系統的可執行文件格式不一樣:git
可執行格式 | 魔數 | 用途 |
---|---|---|
PE32/PE32+ | MZ | Windows的可執行文件 |
ELF | \x7FELF | Linux和大部分UNIX的可執行文件和庫文件 |
腳本 | #! | 主要用於shell腳本,也有一些解釋器腳本使用這個格式。這是一種特殊的二進制文件格式,#! 後面指向真正的可執行文件(好比python),而腳本其它內容,都被當作輸入傳遞給這個命令。 |
通用二進制格式(胖二進制格式) | 0xcafebabe(小端) | 包含多種架構支持的Mach-O格式,iOS和OS X支持的格式 |
Mach-O | 0xfeedface(32位) 0xfeedfacf(64位) | iOS和OS x支持的格式 |
系統內核將文件讀入內存,而後尋找文件的頭簽名(魔數magic),根據magic就能夠判斷二進制文件的格式。程序員
其實PE/ELF/Mach-O這三種可執行文件格式都是COFF(Common file format)格式的變種。COFF的主要貢獻是目標文件裏面引入了「段」的機制,不一樣的目標文件能夠擁有不一樣數量和不一樣類型的「段」。github
接下來我將介紹通用二進制文件和Mach-O文件:shell
爲何有了Mach-O格式了,蘋果還搞通用二進制格式?由於不一樣CPU平臺支持的指令不一樣,好比arm64和x86,那咱們是否是能夠把arm64和x86對應的Mach-O格式打包在一塊兒,而後系統根據本身的CPU平臺,選擇合適的Mach-O。通用二進制格式就是多種架構的Mach-O文件「打包」在一塊兒,因此通用二進制格式,更多被叫作胖二進制格式。緩存
通用二進制格式定義在<mach-o/fat.h>中。bash
#define FAT_MAGIC 0xcafebabe
#define FAT_CIGAM 0xbebafeca /* NXSwapLong(FAT_MAGIC) */
struct fat_header {
uint32_t magic; /* FAT_MAGIC or FAT_MAGIC_64 */
uint32_t nfat_arch; /* number of structs that follow */
};
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 */
};
複製代碼
通用二進制文件開始是fat_header結構體,magic可讓系統內核讀取該文件時候知道是通用二進制文件;nfat_arch代表下面有多少個fat_arch結構體(也能夠說這個通用二進制文件包含多少個Mach-O)。架構
fat_arch結構體是描述Mach-O。cputype和cpusubtype說明Mach-O適用什麼平臺;offset(偏移)、size(大小)和align(頁對齊)能夠清楚描述Mach-O二進制位於通用二進制文件哪裏。ide
file 命令查看
$ file bq
bq: Mach-O universal binary with 2 architectures: [arm_v7:Mach-O executable arm_v7] [arm64]
bq (for architecture armv7): Mach-O executable arm_v7
bq (for architecture arm64): Mach-O 64-bit executable arm64
otool 命令查看fat_header信息
$ otool -f -V bq
Fat headers
fat_magic FAT_MAGIC
nfat_arch 2
architecture armv7
cputype CPU_TYPE_ARM
cpusubtype CPU_SUBTYPE_ARM_V7
capabilities 0x0
offset 16384
size 74952848
align 2^14 (16384)
architecture arm64
cputype CPU_TYPE_ARM64
cpusubtype CPU_SUBTYPE_ARM64_ALL
capabilities 0x0
offset 74973184
size 84135936
align 2^14 (16384)
lipo(脂肪) 能夠增、刪、提取胖二進制文件中的特定架構(Mach-O)
提取特定Mach-O
lipo bq -extract armv7 -o bq_v7
刪除特定Mach-O
lipo bq -remove armv7 -o bq_v7
瘦身爲Mach-O文件格式
lipo bq -thin armv7 -o bq_v7
複製代碼
從上面能夠知道,儘管通用二進制文件會佔用大量的磁盤空間,可是系統會挑選合適的Mach-O來執行,不相關的架構代碼不會佔用內存空間,且執行效率高了。函數
挑選合適的Mach-O的函數定義在<mach-o/arch.h>中,NXGetLocalArchInfo()函數得到主機的架構信息,NXFindBestFatArch()函數匹配最合適的Mach-O。
網上不少介紹Mach-O格式的文章,可是大篇幅都是介紹各類加載命令,讓剛接觸Mach-O的讀者一上來就懵逼了,覺得掌握Mach-O,就是記憶各類加載命令,讓學習Mach-O文件格式變得枯燥且困難。
讀者只需跟着我這系列文章,由淺入深,保你早日拿下Mach-O~~
Mach-O文件格式就是COFF(Common file format)格式的變種。而COFF引入了「段」的機制,不一樣的Mach-O文件能夠擁有不一樣數量和不一樣類型的「段」。Mach-O目標文件是源代碼編譯獲得的文件,那至少文件裏有機器指令、數據吧。其實除了這些以外,還有連接時候須要的一些信息,好比符號表、調試信息、字符串等。而後按照不一樣的信息,放在不一樣的「段」(segment)中。機器指令通常放在代碼段裏,全局變量和局部靜態變量通常放在數據段裏。
這裏簡單說下數據分段的好處,好比數據和機器指令分段:
從很早之前蘋果官網的這個老圖中,咱們知道了Mach-O文件由:Header、Load Commands、Data三部分組成。
文件最開始的Header是mach_header結構體,定義在<mach-o/loader.h>。
//後面默認都講64位操做系統的,老早就淘汰的古董機iPhone5s就是64位操做系統了。。。
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常取字段有:
#define MH_OBJECT 0x1 目標文件
#define MH_EXECUTE 0x2 可執行文件
#define MH_DYLIB 0x6 動態庫
#define MH_DYLINKER 0x7 動態鏈接器
#define MH_DSYM 0xa 存儲二進制文件符號信息,用於Debug分析
複製代碼
進程是特殊文件在內存中加載獲得的結果。那這種文件的格式必須是系統內核能夠理解的,系統內核才能正確解析。 --本文最開始
上面介紹了Mach-O有不一樣類型的「段」,且系統內核(或連接器)須要不一樣的加載方式來加載對應的段,而加載命令就是指導系統內核如何加載,因此有了不一樣的加載命令。
爲了講清楚Mach-O格式,我僅講一個最普通且有表明意義的加載命令:段加載命令(LC_SEGMENT_64),其它加載命令,後面篇章用到時候,再具體講解。
// 定義在<mach-o/loader.h>
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 */
};
複製代碼
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,後面會緊跟nsects個section。section的header結構體是同樣的。
真的要講清這個,須要理解虛擬內存。我這裏拋磚引玉,但願讀者在看Mach-O文件結構時候,也能想下爲何這麼設計。
其實從連接的角度來看,Mach-O文件是按照section來存儲文件的,segment只不過是把多個section打包放在一塊兒而已;可是從Mach-O文件裝載到內存的角度來看,Mach-O文件是按照segment(編譯時候,編譯器把相同權限的數據放在一塊兒,成爲segment)來存儲的,即便一個segment裏的內容小於1頁空間的內存,可是仍是會佔用一頁空間的內存,因此segment裏不只有filesize,也有vmsize,而section不須要有vmsize。
這樣作,是爲了節約內存,減小頁面內部碎片。
經過上面分析,最後給出Mach-O格式圖,若是你對這個格式圖有不理解地方,再回過頭看看上面對應地方的分析~