一、什么是Mach-O文件?
Mach-O是 Mach Object文件格式的縮寫,是 mac以及 iOS上可執(zhí)行文件的格式,對應(yīng)系統(tǒng)通過應(yīng)用二進(jìn)制接口(application binary interface,縮寫為ABI)來運(yùn)行該格式的的文件。Mach-O文件對應(yīng)有多種格式:
- 目標(biāo)文件
.o - 庫文件:
.a靜態(tài)庫文件.dylib動態(tài)庫文件.framework文件,自己創(chuàng)建的為靜態(tài)庫文件 - 可執(zhí)行文件
-
dyld動態(tài)鏈接器將依賴的動態(tài)庫加載到內(nèi)存 -
.dsym符號表
Mach-O格式用來替代BSD系統(tǒng)的a.out格式。Mach-O文件格式保存了在編譯過程和鏈接過程中產(chǎn)生的機(jī)器代碼和數(shù)據(jù),從而為靜態(tài)鏈接和動態(tài)鏈接的代碼提供了單一文件格式。
clang單文件編譯
在Xcode中我們可以直接創(chuàng)建.c文件,通過終端clang命令來對.c文件進(jìn)行編譯或生成可執(zhí)行文件,下面看一下clang怎樣使用的。
- 1、創(chuàng)建一個(gè)main.c文件如下:
#include <stdio.h>
int main(){
printf("打?。篽ello\n");
return 0;
}
- 2、編譯文件
clang -c main.c
會生成main.o文件,該文件即為mach-o文件,通過命令file main.o查看文件信息如下
main.o: Mach-O 64-bit object x86_64
是一個(gè)object類型的文件稱為目標(biāo)文件,并不是可執(zhí)行文件
-
3、生成可執(zhí)行文件
- 命令
clang main.o會生成a.out文件,即可執(zhí)行文件,通過ls查看 - 命令
clang -o main main.o也會生成可執(zhí)行文件main - 命令
clang -o main main.c直接根據(jù)源文件生成可執(zhí)行文件main - 命令
size -x -l -m main查看文件信息,如下:
image - 命令
4、執(zhí)行文件
./a.out 或 ./main

多個(gè)文件是如何編譯的呢?
開發(fā)中根據(jù)不同功能模塊我們會分很多文件來實(shí)現(xiàn),在clang中是可以對多個(gè)文件進(jìn)行一次性打包,生成一個(gè)可執(zhí)行文件。如下:
- 1、新建一個(gè)功能文件
people.c
#include <stdio.h>
void sleep(){
printf("正在睡覺\n");
}
- 2、在
main.c中聲明sleep方法并調(diào)用
void sleep();//聲明方法
int main(){
printf("打?。篽ello\n");
sleep();//調(diào)用方法
return 0;
}
- 3、編譯為可執(zhí)行文件
clang -o main main.c people.c
- 4、執(zhí)行可執(zhí)行文件
./main, 運(yùn)行結(jié)果如下:
image
二、通用二進(jìn)制文件(Universal binary)
在iOS中不同手機(jī)對應(yīng)著可能不同的架構(gòu),如arm64、armv7、armv7s。為了兼容不同架構(gòu)的手機(jī),蘋果推出了通用二進(jìn)制文件,包含了應(yīng)用程序常用的這些架構(gòu),因此通用二進(jìn)制文件,比單一架構(gòu)二進(jìn)制文件要大很多。
架構(gòu)選擇

通過以上配置真實(shí)編譯出來的是包含arm64、armv7架構(gòu)
通用二進(jìn)制文件在哪呢?
在xxx.app中的xxx黑色文件即是通用二進(jìn)制文件,右鍵xxx.app顯示包內(nèi)容即可獲得。
lipo命令
通過lipo命令可以查看、拆分及合并以上提出的架構(gòu),在做靜態(tài)庫時(shí)也會使用,來合并真機(jī)下和模擬器下的靜態(tài)庫,以適應(yīng)不同的調(diào)試環(huán)境。
從LoginApp.app中我獲取可執(zhí)行文件LoginApp
- 1、查看架構(gòu)信息:
lipo -info LoginApp
// 終端輸出結(jié)果如下
Architectures in the fat file: LoginApp are: armv7 arm64
- 2、拆分armv7、arm64架構(gòu)
lipo LoginApp -thin armv7 -output LoginApp_armv7
lipo LoginApp -thin arm64 -output LoginApp_arm64
// 終端輸出結(jié)果如下
Non-fat file: LoginApp_armv7 is architecture: armv7
Non-fat file: LoginApp_arm64 is architecture: arm64
- 3、合并架構(gòu)
lipo -create LoginApp_armv7 LoginApp_arm64 -output LoginApp_ALL
-
產(chǎn)生的可執(zhí)行文件如圖:
image
三、Mach-O文件結(jié)構(gòu)
官方圖解:

文件分為三個(gè)部分:
-
Header:包含Mach-O文件的基本信息,字節(jié)順序、架構(gòu)類型、加載指令的數(shù)量等 -
Load commands:包含區(qū)域位置、符號表、動態(tài)符號表,加載Mach-O文件時(shí)使用這里的數(shù)據(jù)確定內(nèi)存分布 -
Data:數(shù)據(jù)段segment,包含具體代碼、常量、類、方法等,有多個(gè)segment,每個(gè)segment有0到多個(gè)section,每個(gè)段有一個(gè)虛擬地址映射到進(jìn)程的地址空間
直接使用 MachOView打開LoginApp可執(zhí)行文件,如下:


結(jié)構(gòu)體對齊
1、查看Macho headers
// 查看 Header,包括 Load commands
objdump --macho --private-headers LoginApp
otool -l ${MACHO_PATH}
// 只查看 Header
objdump --macho --private-header LoginApp
otool -h ${MACHO_PATH}


在 Mach-O中定義了程序的入口main函數(shù)(定義在LC_MAIN的Load command命令),告訴動態(tài)鏈接器去加載程序的入口
// grep代表搜索, -A 5:向下輸出5行
objdump --macho --private-headers LoginApp | grep "MAIN" -A 5

- 以上打印的兩段分別是
armv7、arm64架構(gòu)下的header信息 在objc4源碼loader.h文件中有mach_header的結(jié)構(gòu)體定義,如下:
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 */
};
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ù),確定是64位還是32位 -
cputype:cpu類型 -
cpusubtype:cpu子類型 -
filetype:Mach-O支持多種文件類型,使用filetype來標(biāo)注具體文件類型 -
ncmds:加載命令的數(shù)量 -
sizeofcmds:命令區(qū)域(load commands)總的字節(jié)大小 -
flags:標(biāo)識二進(jìn)制文件所支持的功能,主要與系統(tǒng)的加載、鏈接有關(guān)
2、Load commands
Header之后是load commands段為加載命令段,在header結(jié)構(gòu)體中有對加載命令段相關(guān)信息的描述,用于解析加載命令。在objc4源碼loader.h中,有對loadcommand的定義:
struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */
};
-
cmd:命令類型,針對不同架構(gòu)有不同的結(jié)構(gòu)(32位、64位) -
cmdsize:命令所占字節(jié)大?。?2位size必須為4字節(jié)的倍數(shù),64位size必須為8字節(jié)的倍數(shù))在文件中有兩個(gè)結(jié)構(gòu)體segment_command和segment_command_64針對不同架構(gòu)的結(jié)構(gòu)體,內(nèi)部設(shè)置字段相同。以segment_command_64為例:
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:表示這好似一個(gè)段加載命令,需要將它加載到對應(yīng)的進(jìn)程空間上 -
LC_LOAD_DYLIB:這是一個(gè)需要動態(tài)加載的鏈接庫,它使用dylib_command結(jié)構(gòu)體表示 -
LC_MAIN:記錄了可執(zhí)行文件的主函數(shù)main()的位置,它使用entry_point_command結(jié)構(gòu)體表示 -
LC_CODE_SIGNATURE:代碼簽名的加載命令,描述了Mach-O的代碼簽名信息,它屬于鏈接信息,使用linkedit_data_command結(jié)構(gòu)體表示 -
cmdsize:加載命令所占內(nèi)存大小 -
segname:存放16字節(jié)大小的段名字,當(dāng)前是__PAGEZERO。 -
vmaddr:段的虛擬內(nèi)存起始地址 -
vmsize:段的虛擬內(nèi)存大小 -
fileoff:段在文件中偏移量 -
filesize:段在文件大小 -
maxprot:段頁面所需要的最高內(nèi)存保護(hù)(4=r,2=w,1=x) -
initprot:段頁面初始的內(nèi)存保護(hù) -
nsects:段中包含section的數(shù)量 -
flags:其他雜項(xiàng)標(biāo)志位
以上加載命令含義如下:
-
LC_SEGMENT_64:將文件中的段映射到進(jìn)程地址空間中 -
LC_DYLD_INFO_ONLY:動態(tài)鏈接相關(guān)信息 -
LC_SYMTAB:符號表信息,位置、偏移、數(shù)據(jù)個(gè)數(shù),供dyld使用 -
LC_DYSYMTAB:動態(tài)符號表信息,供dyld使用 -
LC_LOAD_DYLINKER:鏈接器信息,記錄使用那些鏈接器完成內(nèi)核后序的加載工作 -
LC_UUID:Mach-O文件的唯一標(biāo)識 -
LC_VERSION_MIN_MACOSX:支持最低操作系統(tǒng)版本 -
LC_SOURCE_VERSION:源代碼的版本號 -
LC_MAIN:設(shè)置主線程的入口 -
LC_LOAD_DYLIB:依賴庫信息,dyld通過該命令去加載依賴庫 -
LC_FUNCTION_STARTS:函數(shù)的起始地址表 -
LC_CODE_SIGNATURE:代碼簽名
3、Data
Data區(qū)域由Segment段和Section節(jié)組成:
segment主要有__TEXT和__DATA組成-
__text:是主程序代碼-
__stubs、__stub_helper:是動態(tài)鏈接的樁 -
__cstring:程序中c語言字符串 -
__const:常量
-
-
__DATA含義:-
Section64(__TEXT,__objc_methname):OC類名 -
Section64(__DATA,__objc_classlist):OC類列表 -
Section64(__DATA,__objc_protollist):OC原型列表 -
Section64(__DATA,__objc_imageinfo):OC鏡像信息 -
Section64(__DATA,__objc_selfrefs):OC類自引用 -
Section64(__DATA,__objc_superrefs):OC類超類的引用 -
Section64(__DATA,__ivar):OC類成員變量
-
總結(jié)
- 查看
mach-header:objdump --macho -private-header ${MACHO_PATH}otool -h ${MACHO_PATH}
- 查看
__TEXT:objdump --macho -d ${MACHO_PATH} - 查看符號表:
objdump --macho --syms ${MACHO_PATH} - 查看導(dǎo)出符號表:
objdump --macho --exports-trie ${MACHO_PATH} - 查看間接符號表:
objdump --macho --indirect-symbols ${MACHO_PATH}


