Mach-O為Mach object文件格式的縮寫,它是一種用于可執(zhí)行文件、目標代碼、動態(tài)庫的文件格式,由多個源文件組成。作為a.out格式的替代,Mach-O提供了更強的擴展性。
常見文件:.a,.dylib,Framework,可執(zhí)行文件,dyld,.dSYM(release)
這里值得注意的是nib雖然用file命令查看是data文件,但是仍被構建成動態(tài)鏈接共享庫的。其本質就是偽nib,是一個.dylib文件。

C文件的編譯鏈接實踐
1、創(chuàng)建C文件,并編寫相關代碼,然后進行編譯,生成test.o的目標文件。

查看目標文件,得到一個為
Mach-O的object文件。
file test.o

2、鏈接一下,生成a.out可執(zhí)行文件。
鏈接器或鏈接編輯器是計算機實用程序,它負責接收由編譯器生成的一個或多個目標文件,并將它們組合成單個可執(zhí)行文件,庫文件或者另一個"對象"文件。
Clang test.o

生成為
test2的可執(zhí)行文件:
clang -o test2 test.o
通過執(zhí)行可執(zhí)行文件可得到打印結:
./a.out
//%只是一個結束符。
test===test===test===test===test%
探尋系統(tǒng)的dylib庫
通過命令:find /usr/lib -name "*.dylib"查看系統(tǒng)的動態(tài)庫,下面列出常見的有:
#libobjc:objc和runtime
/usr/lib/libobjc.A.dylib
/usr/lib/libSystem.dylib
libSystem中常見的庫有:
- libdispatch ( GCD )
- libsystem_c ( C語言庫 )
- libsystem_blocks ( Block )
- libcommonCrypto ( 加密庫,比如常用的 md5 函數 )
通過命令file /usr/lib/libobjc.dylib查看動態(tài)庫信息:

通過
cd /usr/lib進入dyld所在文件夾,file dyld查看其具體信息??芍?code>Mach-O文件,但不是可執(zhí)行文件,屬于dynamic linker,Mach-O中的獨立類型。
探尋dSYM文件。
將工程置于release模式下,Build。

顯示
dSYM包內容,通過命令可知它也是屬于Mach-O文件
淺談可執(zhí)行文件架構
通用二進制代碼有兩種基本類型。一種類型就是簡單提供兩種獨立的二進制代碼,一個用來對應x86架構,一個用來對應PowerPC架構。但是對于不熟悉代碼的普通軟件使用者來說,在購買和使用的時候,可能搞不清二者區(qū)別。另外一種類型就是只編寫一個架構的代碼,當另外一種處理環(huán)境時讓系統(tǒng)自動調用模擬器運行。這會導致運行速度下降,一般是作為“通用二進制”或者“特別連編二進制”出現之前暫時使用的折衷辦法。(參見Rosetta)
因為需要儲存多種代碼,通用二進制應用程序通常比單一平臺二進制的程序要大,但是由于兩種架構有共通的非執(zhí)行資源,所以并不會達到單一版本的兩倍之多。而且由于執(zhí)行中只調用一部分代碼,運行起來也不需要額外的內存。
目前,蘋果公司的Xcode是唯一一個可以編譯通用二進制代碼的GUI工具
$(ARCHS_STANDARD):代表armv7和arm64兩種架構。

lipo命令=>拆分可執(zhí)行文件

lipo命令=>合并可執(zhí)行文件

使用lipo -info 可以查看MachO文件包含的架構
$lipo -info MachO文件
使用lipo –thin 拆分某種架構
$lipo MachO文件 –thin 架構 –output 輸出文件路徑
使用lipo -create 合并多種架構
$lipo -create MachO1 MachO2 -output 輸出文件路徑
分析Mach-O的可執(zhí)行文件

分三大模塊:
Header
header包含該二進制文件的一般信息
字節(jié)順序、架構類型、加載指令的數量等。由系統(tǒng)內核負責讀取。
使得可以快速確認一些信息,比如當前文件用于32位還是64位,對應的處理器是什么、文件類型是什么
Load Commands
一張包含了很多內容的表,內容包括區(qū)域的位置、符號表、動態(tài)符號表等。
Data
通常是對象文件中最大的部分,包含了Segement的具體數據
通過otool查看可執(zhí)行文件的一些信息,根據提示命令獲取所需要的信息。
-f print the fat headers
-a print the archive header
-h print the mach header
-l print the load commands
-L print shared libraries used
-D print shared library id name
-t print the text section (disassemble with -v)
-p <routine name> start dissassemble from routine name
-s <segname> <sectname> print contents of se
查看Mach-O的源碼文件

- 定義的
Mach-O文件:
#define MH_OBJECT 0x1 /* relocatable object file */
#define MH_EXECUTE 0x2 /* demand paged executable file */
#define MH_FVMLIB 0x3 /* fixed VM shared library file */
#define MH_CORE 0x4 /* core file */
#define MH_PRELOAD 0x5 /* preloaded executable file */
#define MH_DYLIB 0x6 /* dynamically bound shared library */
#define MH_DYLINKER 0x7 /* dynamic link editor */
#define MH_BUNDLE 0x8 /* dynamically bound bundle file */
#define MH_DYLIB_STUB 0x9 /* shared library stub for static */
/* linking only, no section contents */
#define MH_DSYM 0xa /* companion file with only debug */
/* sections */
#define MH_KEXT_BUNDLE 0xb /* x86_64 kexts */
- 定義的
Header
struct mach_header_64 {
uint32_t magic; //魔數:快速定位屬于64位還是32位
cpu_type_t cputype; //cpu 類型
cpu_subtype_t cpusubtype; //cpu的具體類型
uint32_t filetype; //文件類型,比如可執(zhí)行文件。
uint32_t ncmds; //load commands 的數量
uint32_t sizeofcmds; //load command 的大小
uint32_t flags; //表示二進制文件所支持的一些功能,和系統(tǒng)加載有關系。描述文件在編譯、鏈接等過程中的信息,示例中的 MH_NOUNDEFS 表示文件中不存在未定義的符號,MH_DYLDLINK 表示文件要交由 DYLD 進一步處理,MH_TWOLEVEL 表示文件使用兩級命名空間,MH_PIE 表示啟用地址空間布局隨機化。
uint32_t reserved; //比32位多一個保留字段。
};
通過MachOView查看文件
1、映射:告訴對方二進制文件會映射到手機內存中會占用多大,下一塊是誰。

_PAGEZERO:這個字段在文件中不存在的,在虛擬內存中是一塊區(qū)域,不具備訪問權限,專門來處理空指針。
VM Address:虛擬內存地址,4個G,根據CPU架構而來。
File Offset:文件偏移地址。
2、LC_DYLD_ INFO_ONLY:動態(tài)鏈接的相關信息。
它的 ONLY 后綴表明這是程序運行所必須的,如果鏈接器不支持,那么加載過程就會終止。
struct dyld_info_command {
uint32_t cmd; /* LC_DYLD_INFO or LC_DYLD_INFO_ONLY */
uint32_t cmdsize; /* sizeof(struct dyld_info_command) */
uint32_t rebase_off; //重定向的偏移值。(ASLR)
uint32_t rebase_size; //重定向的大小。
uint32_t bind_off; //綁定。可執(zhí)行文件讀到內存中會綁定一些數據。weak綁定,lazy綁定。
uint32_t bind_size; /* size of binding info */
uint32_t weak_bind_off; /* file offset to weak binding info */
uint32_t weak_bind_size; /* size of weak binding info */
uint32_t lazy_bind_off; /* file offset to lazy binding info */
uint32_t lazy_bind_size; /* size of lazy binding infs */
uint32_t export_off; //對外開發(fā)的函數。
uint32_t export_size; /* size of lazy binding infs */
};
3、LC_SYMTAB和LC_DYSYMTAB,符號表地址和動態(tài)符號表地址。
記錄了程序的符號表以及字符串表的偏移量及大小,符號表中記錄了程序用到的函數以及全局變量的信息
/*
* Format of a symbol table entry of a Mach-O file for 32-bit architectures.
*/
struct nlist {
union {
#ifndef __LP64__
char *n_name; /* for use when in-core */
#endif
uint32_t n_strx; /* index into the string table */
} n_un;
uint8_t n_type; /* type flag, see below */
uint8_t n_sect; /* section number or NO_SECT */
int16_t n_desc; /* see <mach-o/stab.h> */
uint32_t n_value; /* value of this symbol (or stab offset) */
};
/*
* This is the symbol table entry structure for 64-bit architectures.
*/
struct nlist_64 {
union {
uint32_t n_strx; /* index into the string table */
} n_un;
uint8_t n_type; /* type flag, see below */
uint8_t n_sect; /* section number or NO_SECT */
uint16_t n_desc; /* see <mach-o/stab.h> */
uint64_t n_value; /* value of this symbol (or stab offset) */
};
數據結構中相關字段的含義都可以在 nlist.h 中找到,這里值得一說的是 n_un 字段,它用來記錄符號的名字,但為什么是 uint32_t 類型呢?又為什么在注釋中標明是 string table 的 索引呢?
這是因為在程序中,字符串的長度是不固定的,所以會將其放在 string table 中,然后存儲它在 string table 中的偏移。如果其他部分想要引用某個字符串,那么他首先需要找到 string table 的起始地址,然后根據偏移量找到相應字符串的起始位置并向后讀取字符,直到遇見 \0 才會停止讀取過程,最后返回讀到的字符串。
這也是 LC_SYMTAB 額外記錄 string table 地址的原因,string table 通常用于記錄 section 名、符號名等信息。
4、LC_LOAD_DYLINKER:動態(tài)連接器:dyld,在iPhone手機下的usr/lib/dyld來加載的。調用這個方法之前都是系統(tǒng)內核進行調用的。
5、LC_UUID:靜態(tài)鏈接器為其生成的文件所提供的唯一標識符
6、LC_MAIN:指定 main 函數的地址
7、LC_LOAD_DYLIB:加載一些依賴庫和三方庫的地址。
補充:
-
DATA段有懶加載表和非懶加載表。 -
symbol:用于綁定的時候會用到這里的信息。 -
Mach-O文件被內核加載,被DYLD讀取。