1、MachO文件的概念
Mach-O
其實是Mach Object
文件格式的縮寫,是 mac 以及 iOS 上可執(zhí)行文件的格式捆蜀, 類似于 windows
上的 PE
格式 (Portable Executable
), linux
上的 elf
格式 (Executable and Linking Format
) 。常?的 .o
,.a
.dylib
Framework
踱阿,dyld
.dsym
。
2钦铁、整體結(jié)構(gòu)如下圖
2.1软舌、Header(頭部)
Header
表明該文件是 Mach-O 格式,指定目標(biāo)架構(gòu)牛曹,還有一些其他的文件屬性信 息佛点,文件頭信息影響后續(xù)的文件結(jié)構(gòu)安排。
與 Mach-O 文件格式有關(guān)的結(jié)構(gòu)體定義都可以從xnu源碼的loader.h
中找到黎比。
32位和64位的mach_header的數(shù)據(jù)結(jié)構(gòu)如下
/*
* The 32-bit mach header appears at the very beginning of the object file for
* 32-bit architectures.
*/
struct mach_header {
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 */
};
/* Constant for the magic field of the mach_header (32-bit architectures) */
#define MH_MAGIC 0xfeedface /* the mach magic number */
#define MH_CIGAM 0xcefaedfe /* NXSwapInt(MH_MAGIC) */
/*
* The 64-bit mach header appears at the very beginning of object files for
* 64-bit architectures.
*/
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 */
};
-
magic
魔數(shù)(特征數(shù)字)超营,用來標(biāo)記當(dāng)前設(shè)備是大端序還是小端序。 -
cputype
標(biāo)識 CPU 的架構(gòu)阅虫,例如 ARM演闭、ARM64、X86_64 等书妻。 -
cpusubtype
標(biāo)識 CPU 的具體類型船响,區(qū)分不同版本的處理器躬拢。 -
filetype
由于 Mach-O 支持多種類型文件,所以此處引入了 filetype 字段來標(biāo)明(.o
见间,.a
.dylib
Framework
聊闯,dyld
.dsym
等) -
ncmds
Mach-O 文件中加載命令(load commands)的條數(shù)。 -
sizeofcmds
Mach-O文件中加載命令(load commands)的總大小米诉。 -
flags
標(biāo)識著 Mach-O 文件的一些重要信息(可以loader.h
中查看結(jié)構(gòu)菱蔬,其中MH_PIE
啟用ASLR) -
reserved
64位預(yù)留字段
2.2、Load commands
Load commands
是一張包含很多內(nèi)容的表史侣。內(nèi)容包括區(qū)域的位置拴泌、符號表、動態(tài)符號表 等惊橱。用于告訴loader
如何設(shè)置并加載二進制數(shù)據(jù),對系統(tǒng)內(nèi)核加載器和動態(tài)鏈接器(dyld
)起指導(dǎo)作用蚪腐。
load_command
的數(shù)據(jù)結(jié)構(gòu)
struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */
};
前 4 個字節(jié)表示類型,不同類型的 load command 作用不一樣税朴,緊跟其后的 4 個字節(jié)表示該 load command 的大小.
可以通過MachOView
軟件來查看類容(類型)
-
LC_SEGMENT_64
將文件中(32位或64位)的段映射到進程地 址空間中 -
LC_DYLD_INFO_ONLY
動態(tài)鏈接相關(guān)信息 -
LC_SYMTAB
符號地址 -
LC_DYSYMTAB
動態(tài)符號表地址 -
LC_LOAD_DYLINKER
dyld加載 -
LC_UUID
文件的UUID -
LC_VERSION_MIN_MACOSX
支持最低的操作系統(tǒng)版本 -
LC_SOURCE_VERSION
源代碼版本 -
LC_MAIN
設(shè)置程序主線程的入口地址和棧大小 -
LC_LOAD_DYLIB
依賴庫的路徑回季,包含三方庫 -
LC_FUNCTION_STARTS
函數(shù)起始地址表 -
LC_CODE_SIGNATURE
代碼簽名 -
LC_ENCRYPTION_INFO
和LC_ENCRYPTION_INFO_64
:加密信息,如果是從App Store上下載的應(yīng)用正林,外面被加了一層殼泡一,對應(yīng)的加密標(biāo)記(Crypt ID)不為0,如果不是App Store上下載的應(yīng)用(例如PP助手上)觅廓,或這個已經(jīng)被脫過殼的鼻忠,加密標(biāo)記(Crypt ID)便為0
LC_SEGMENT(段)
LC_SEGMENT_64
和LC_SEGMENT
(32位系統(tǒng))是加載的主要命令,翻譯成中文叫做“段”杈绸,它負(fù)責(zé)指導(dǎo)內(nèi)核來設(shè)置進程的內(nèi)存空間帖蔓,說白了,只要是這個類型的 load command蝇棉,系統(tǒng)會將其指示的內(nèi)容全部加載到指定的虛擬內(nèi)存地址上來讨阻。
MachOView看到的內(nèi)容
通過
mach-o/loader.h
找到數(shù)據(jù)結(jié)構(gòu)
struct segment_command { /* for 32-bit architectures */
uint32_t cmd; /* LC_SEGMENT */
uint32_t cmdsize; /* includes sizeof section structs */
char segname[16]; /* segment name */
uint32_t vmaddr; /* memory address of this segment */
uint32_t vmsize; /* memory size of this segment */
uint32_t fileoff; /* file offset of this segment */
uint32_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 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 */
};
-
cmd
該加載命令類型,為LC_SEGMENT_64(64位)或LC_SEGMENT(32位) -
cmdsize
該加載命令大小篡殷,包括segement下session結(jié)構(gòu)所占大小 -
segname[16]
segment 名字 -
vmaddr
為當(dāng)前segment分配的虛擬內(nèi)存地址 -
vmsize
為當(dāng)前segment分配的虛擬內(nèi)存大小 -
fileoff
當(dāng)前segment在 Mach-O 文件中的偏移量 -
filesize
當(dāng)前segment在 Mach-O 文件中占用的字節(jié) -
maxprot
segment所在頁所需要的最高內(nèi)存保護 -
initprot
segment所在頁原始內(nèi)存保護 -
nsects
segment中section數(shù)量 -
flags
標(biāo)識符
大致可以這么理解:系統(tǒng) Mach-O 從fileoff
處加載filesie
大小的內(nèi)容到虛擬內(nèi)存vmaddr
處钝吮,大小為vmsize
,segment
頁權(quán)限initport
進行初始化板辽,這些權(quán)限可以被修改奇瘦,但是不能超過maxprot
的值。通俗來說就是劲弦,fileoff
和filesie
指導(dǎo)和說明了內(nèi)容從哪里來耳标,vmaddr
和vmsize
指導(dǎo)和說明了文件到哪里去。需要留意邑跪,對某些segment
來說次坡,vmsize
可能會大于 filesize
呼猪,如__DATA、__LINKEDIT砸琅。
segname
分類(用下劃線和大寫字母組成)
-
__PAGEZERO
:靜態(tài)鏈接器創(chuàng)建了__PAGEZERO命名的段作為可執(zhí)行文件的第一個段宋距,該段在文件中所占大小為0,在 32 位系統(tǒng)上症脂,加載到虛擬內(nèi)存中是 0x4000谚赎,也就是 16kb阱飘,在 64 位系統(tǒng)上到千,加載到虛擬未存中是 0x100000000摩桶,也就是 4GB谒所。是一個不可讀、不可寫唬格、不可執(zhí)行的空間扳还,能夠在空指針訪問時拋出異常土浸。 -
__TEXT
:代碼段橙凳,里面包含了可執(zhí)行代碼和其他一些只讀數(shù)據(jù)蕾殴,該段是可讀、可執(zhí)行岛啸,但是不可寫。 -
__DATA
:數(shù)據(jù)段茴肥,里面主要是存放將會被更改的數(shù)據(jù)坚踩,該段是可讀、可寫瓤狐,但不可執(zhí)行瞬铸。 -
__LINKEDIT
:包含需要被動態(tài)鏈接器使用的符號和其他表,包括符號表础锐、字符串表等嗓节,可讀,但不可寫不可執(zhí)行皆警。
segname
下的section
segname
為__TEXT
和__DATA
兩個segment
可以進一步分解為section
拦宣。之所以按照segment
-> section
的結(jié)構(gòu)組織方式,是因為在同一個segment
下的section
信姓,可以控制相同的權(quán)限鸵隧,也可以不完全按照Page的大小進行內(nèi)存對其,節(jié)省內(nèi)存的空間意推。而segment
對外整體暴露豆瘫,在程序載入階段映射成一個完整的虛擬內(nèi)存,更好的做到內(nèi)存對齊菊值。
從mach-o/loader.h
文件中可以找到section
的數(shù)據(jù)結(jié)構(gòu)
struct section { /* for 32-bit architectures */
char sectname[16]; /* name of this section */
char segname[16]; /* segment this section goes in */
uint32_t addr; /* memory address of this section */
uint32_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) */
};
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 */
};
-
sectname
:section名字 -
segname
:section所在的segment名稱 -
addr
:section所在的內(nèi)存地址 -
size
:section的大小 -
offset
:section所在的文件偏移 -
align
:section的內(nèi)存對齊邊界 (2 的次冪) -
reloff
:重定位信息的文件偏移 -
nreloc
:重定位條目的數(shù)目 -
flags
:標(biāo)志屬性 -
reserved
:保留字段
使用 MachOView 進行查看OC工程的macho文件
各個section作用如下:
-
__TEXT.__text
:主程序代碼 -
__TEXT.__stubs
外驱、__TEXT.__stub_helper
:用于幫助動態(tài)鏈接器綁定符號 -
__TEXT.__const
:const關(guān)鍵字修飾的常量 -
__TEXT.__objc_methodname
:OC方法名 -
__TEXT.__cstring
:只讀的C語言字符串 -
__TEXT.__objc_classname
:OC類名 -
__TEXT.__objc_methtype
:OC方法類型(方法簽名) -
__TEXT.__gcc_except_tab
育灸、__ustring
、__unwind_info
:GCC編譯器自動生成昵宇,用于確定異常發(fā)生是棧所對應(yīng)的信息(包括棧指針描扯、返回地址及寄存器信息等) -
__DATA.__got
:全局非懶綁定符號指針表 -
__DATA.__la_symbol_ptr
:懶綁定符號指針表 -
__DATA.__mod_init_func
:C++類的構(gòu)造函數(shù) -
__DATA.__const
:未初始化過的常量 -
__DATA.__cfstring
:Core Foundation字符串 -
__DATA.__objc_classlist
:OC類列表 -
__DATA.__objc_nlclslist
:實現(xiàn)+load方法的 OC 類列表 -
__DATA.__catlist
:OC 分類(Category)列表 -
__DATA.__protolist
:OC 協(xié)議(Protocol)列表 -
__DATA.__imageinfo
:鏡像信息,可用它區(qū)別 OC 1.0與2.0 -
__DATA.__const
:OC 初始化過的常量 -
__DATA.__selrefs
:OC 選擇器(SEL)引用列表 -
__DATA.__protorefs
:OC 協(xié)議引用列表 -
__DATA.__classrefs
:OC 類引用列表 -
__DATA.__superrefs
:OC 超類(即父類)引用列表 -
__DATA.__ivar
:OC 類的實例變量 -
__DATA.__objc_data
:OC 初始化過的變量 -
__DATA.__data
:實際初始化數(shù)據(jù)段 -
__DATA.__common
:未初始化過的符號申明 -
__DATA.__bss
:未初始化的全局變量
使用 MachOView 進行查看Swift工程的macho文件
在Swift的macho文件中趟薄,types是4字節(jié)存儲信息绽诚,存儲的是相對地址
注意:swift5的macho會多出幾個section(目前知道的就是下面幾個,后面了解再補充)
__swift5_types
:存儲的是Class杭煎、Struct恩够、Enum的描述(Descriptor)偏移信息
__swift5_fieldmd
:存儲的是屬性描述(屬性信息)的偏移信息
__swift5_reflstr
:存儲的是屬性名稱
__swift5_protos
:存儲的是代理的描述
2.3、Data
Data
區(qū)主要就是負(fù)責(zé)代碼和數(shù)據(jù)記錄的羡铲。Mach-O 是以 Segment
這種結(jié)構(gòu)來組織數(shù)據(jù) 的蜂桶,一個 Segment 可以包含 0 個或多個 Section。根據(jù) Segment 是映射的哪一個 Load Command也切,Segment 中 section 就可以被解讀為是是代碼扑媚,常量或者一些其他的數(shù)據(jù)類 型。在裝載在內(nèi)存中時雷恃,也是根據(jù) Segment 做內(nèi)存映射的疆股。