上一篇說到編譯產(chǎn)生了目標文件.o,我們知道不同的操作系統(tǒng),可執(zhí)行文件是不同的,系統(tǒng)能夠理解這個特殊文件,才能加載到內(nèi)存,創(chuàng)建出進程.
Mach-O是Mach object的縮寫愈腾,雖然windows,linux,unix,mac os/ios他們的可執(zhí)行文件雖然有著不同的文件,但是他們都來是來自一種叫做COFF(Common file format)的格式,是它的變種版本,特點是不同的文件有用不同的"段".,是Mac os以及 iOS上用來存儲程序的一類文件.Mach-O目標文件是源代碼編譯得到的文件,包含機器指令,數(shù)據(jù),符號表,調(diào)試信息,字符串等,然后按照不同的信息,放在不同的“段”(segment)中;比如指令一般放在代碼段里,變量一般放在數(shù)據(jù)段里.
除了.o,還有像可執(zhí)行文件,framework,.a,.out等文件也都是mach-o.
一.Mach-O的結(jié)構(gòu)
主要分為三個部分,Mach Header鞠眉、Load Command唯卖、Data.
可以使用MachOView查看mach-o文件
下載地址
下載源碼
從EXTERNAL_HEADERS/mach-o/loader.h中的定義可以了解mach-o的一些基本內(nèi)容.
1.Mach Header
首先是對文件類型的定義
#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 */
MH_OBJECT : 經(jīng)過編譯和靜態(tài)鏈接(也可以沒有這個過程)的.o文件,以及靜態(tài)鏈接庫.
MH_EXECUTE : 可執(zhí)行文件
MH_DYLIB : 動態(tài)鏈接庫
MH_DYLINKER : 動態(tài)鏈接器
MH_BUNDLE : bundle資源文件
MH_DYLIB_STUB : 靜態(tài)鏈接庫
MH_DSYM : 符號表和調(diào)試信息文件
還是在EXTERNAL_HEADERS/mach-o/loader.h中,可以找到header的定義,
/*
* 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 */
};
/* Constant for the magic field of the mach_header_64 (64-bit architectures) */
#define MH_MAGIC_64 0xfeedfacf /* the 64-bit mach magic number */
#define MH_CIGAM_64 0xcffaedfe /* NXSwapInt(MH_MAGIC_64) */
magic是mach-o文件的識別符,比如MH_MAGIC,MH_MAGIC_64,MH_CIGAM,MH_CIGAM_64,除此之外還有通用二進制的FAT_MAGIC和FAT_CIGAM后面會說到.
cputype和 cpusubtype是 cpu架構(gòu)和細分.
filetype是文件類型,也就是上面那些宏定義,MH_OBJECT,MH_EXECUTE等等.
ncmds加載命令的數(shù)量
sizeofcmds加載命令的數(shù)據(jù)大小
flags標識位
reserved保留字段,沒有固定的值
可以使用命令查看header
otool -v -h main
輸出
main:
Mach header
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
MH_MAGIC_64 ARM64 ALL 0x00 EXECUTE 21 1784 NOUNDEFS DYLDLINK TWOLEVEL PIE
或者用MachOView查看
可以看到這個main,是64位,是個可執(zhí)行文件MH_EXECUTE.
2.Load Commands
加載指令,也在EXTERNAL_HEADERS/mach-o/loader.h中定,和Header一樣,也有一個結(jié)構(gòu)體
struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */
};
只有兩個屬性,命令類型,和命令的字節(jié)長度.
cmd是一批宏定義,以LC開頭,有五六十個.
主要有這些:
LC_SEGMENT_64 將文件中的段映射到進程地址空間中
LC_DYLD_INFO_ONLY 加載動態(tài)鏈接庫信息(重定向地址、弱引用綁定、懶加載綁定腔召、開放函數(shù)等的偏移值信息)
LC_SYMTAB 載入符號表地址
LC_DYSYMTAB 載入動態(tài)符號表地址
LC_LOAD_DYLINKER 加載動態(tài)鏈接器
LC_UUID 唯一標識,crash解析中也會用到弥臼,檢查dysm文件和crash文件是否匹配
LC_VERSION_MIN_MACOSX / LC_VERSION_MIN_IPHONEOS 二進制文件支持的最底操作系統(tǒng)版本
LC_SOURCE_VERSION 構(gòu)建二進制文件使用的源代碼版本
LC_MAIN 設置程序主線程的入口地址和棧大小
LC_ENCRYPTION_INFO_64 獲取加密信息
LC_LOAD_DYLIB 加載額外的動態(tài)庫
LC_FUNCTION_STARTS 函數(shù)起始地址表
LC_DATA_IN_CODE 定義在代碼段(__text)內(nèi)的非指令表
LC_CODE_SIGNATURE 應用的簽名信息
不過load_command這個結(jié)構(gòu)體似乎不怎么使用,使用的是其他更具體的定義,每種加載命令都有對應的結(jié)構(gòu)體
比如 dylib_command, symtab_command等等.他們也都有cmd和cmdsize.
struct dylib_command {
uint32_t cmd; /* LC_ID_DYLIB, LC_LOAD_{,WEAK_}DYLIB,
LC_REEXPORT_DYLIB */
uint32_t cmdsize; /* includes pathname string */
struct dylib dylib; /* the library identification */
};
struct symtab_command {
uint32_t cmd; /* LC_SYMTAB */
uint32_t cmdsize; /* sizeof(struct symtab_command) */
uint32_t symoff; /* symbol table offset */
uint32_t nsyms; /* number of symbol table entries */
uint32_t stroff; /* string table offset */
uint32_t strsize; /* string table size in bytes */
};
另外還有segment command負責描述segment里的section
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或者LC_SEGMENT_64了.
而segment_command就指定了后面數(shù)據(jù)段的布局.
vmaddr是內(nèi)存地址,
vmsize占用內(nèi)存的大小,
fileoff是這個segment在mach-o文件的數(shù)據(jù)開始位置.也叫做偏移.
filesize是這個segment包含數(shù)據(jù)的大小.也叫做段.
也就是從fileoff(也叫做偏移)取filesize字節(jié)的數(shù)據(jù)宴咧,放到內(nèi)存的vmaddr處的vmsize字節(jié).
3.Data
數(shù)據(jù)段主要由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 */
};
前面是segment name比如__TEXT, __DATA;
后面是section name比如__text,__cstring等.
__text 機器碼
__cstring C 語言字符串
__const 初始化的常量
__objc runtime支持
__data 初始化的變量
__bss 未初始化的靜態(tài)變量
__stubs 跳轉(zhuǎn)表,重定向到 lazy 和 non-lazy 符號的 section
__stubs_helper lazy 動態(tài)綁定符號的輔助函數(shù)
__objc_methname OC 方法名
__objc_methtype OC 方法類型
__objc_classname OC 類名
__swift5_proto swift協(xié)議
__got 全局偏移表
__la_symbol_ptr lazy binding的指針表径缅,表中的指針一開始都指向__stub_helper
__cfstring 工程中使用的Core Foundation字符串(CFStringRefs)
__objc_classlist OC 類列表
__objc_protolist OC protocol列表
__objc_imginfo OC 鏡像信息
__const 沒有初始化過的常量
__objc_selfrefs OC 引用的SEL列表
__objc_protorefs OC 引用的protocol列表
__objc_superrefs OC 引用的父類列表
__objc_ivar OC ivar信息
__objc_data class信息
__bss BSS掺栅,存放 未初始化的全局變量,就是常說的靜態(tài)內(nèi)存分配
__data 初始化的可變數(shù)據(jù)
...等等
-
Assembly 匯編代碼
在這里存儲的是編譯main.o過程中的生成的匯編指令.
匯編代碼
機器碼
__TEST__text存儲的機器碼
-
Symbol Table 符號表
在編譯那篇說到過,clang在詞法分析這一步中,把代碼拆分成一個個token,這其中變量名,方法名,類名等等這些被叫做符號,符號表存儲了符號在字符串表中的位置,以及類型,地址,描述等等.
符號表 -
String Table 字符串表
存儲變量名,方法名,類名,協(xié)議,結(jié)構(gòu)體等等符號
字符串表 -
Dynamic Symbol Table 動態(tài)符號表
存儲的是動態(tài)庫函數(shù)位于符號表的偏移信息
動態(tài)符號表 -
Lazy Symbol Pointers 懶加載符號表
懶加載是指在程序運行時需要訪問這些符號的時候再去綁定,一般是動態(tài)庫里的符號.
懶加載符號表 -
Non Lazy Symbol Pointers 非懶加載符號表
與懶加載符號表相反,這些符號也是來自程序依賴的動態(tài)庫,不同的是會在程序一加載就綁定好.
非懶加載符號表 -
Symbol Stubs 符號樁
與Lazy Symbol Pointers相對應的,使用外部符號會先在符號樁查找,然后對應到懶加載符號表.
符號樁
二.符號
1.符號
在load command中有兩個和符號表相關(guān),LC_SYMTAB和LC_DYSYMTAB,用來描述symbol table和dynamic symbol table的元數(shù)據(jù),包括位置,長度等等
這是前面提到的符號表加載命令
struct symtab_command {
uint32_t cmd; /*固定為 LC_SYMTAB */
uint32_t cmdsize; /* 這個結(jié)構(gòu)體的大小 */
uint32_t symoff; /* 符號表的偏移 */
uint32_t nsyms; /* 符號數(shù)量 */
uint32_t stroff; /* 字符串表的便宜 */
uint32_t strsize; /* 字符串表的大小 */
};
這是EXTERNAL_HEADERS/mach-o/nlist.h中符號的定義
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) */
};
n_type字段是一個8位十六進制復合字段纳猪,其中bit[0:1]表示是外部符號氧卧,bit[5:8]表調(diào)試符號,bit[4:5]表示私有 external 符號氏堤,bit[1:4]是符號類型沙绝,有 N_UNDF 未定義搏明、N_ABS 絕對地址、N_SECT 本地符號闪檬、N_PBUD 預綁定符號星著、N_INDR 同名符號幾種類型.
具體定義如下
#define N_UNDF 0x0 // 未定義
#define N_ABS 0x2 // 絕對地址
#define N_SECT 0xe // 本地符號
#define N_PBUD 0xc // 預定義符號
#define N_INDR 0xa // 同名符號
#define N_STAB 0xe0 // 調(diào)試符號
#define N_PEXT 0x10 // 私有 external 符號
#define N_TYPE 0x0e // 類型位的掩碼
#define N_EXT 0x01 // external 符號
2.符號的類型
分別使用命令和MachOView查看符號表
objdump --macho --syms main.o
輸出
main.o:
SYMBOL TABLE:
0000000000000000 l F __TEXT,__text ltmp0
00000000000000a0 l O __DATA,__objc_classrefs _OBJC_CLASSLIST_REFERENCES_$_
00000000000000d0 l O __DATA,__objc_selrefs _OBJC_SELECTOR_REFERENCES_
00000000000000a8 l O __TEXT,__cstring l_.str
00000000000000d8 l O __DATA,__cfstring l__unnamed_cfstring_
00000000000000a0 l O __DATA,__objc_classrefs ltmp1
00000000000000a8 l O __TEXT,__cstring ltmp2
00000000000000bc l O __TEXT,__objc_methname ltmp3
00000000000000bc l O __TEXT,__objc_methname l_OBJC_METH_VAR_NAME_
00000000000000d0 l O __DATA,__objc_selrefs ltmp4
00000000000000b1 l O __TEXT,__cstring l_.str.1
00000000000000d8 l O __DATA,__cfstring ltmp5
00000000000000f8 l O __DATA,__objc_imageinfo ltmp6
0000000000000100 l O __LD,__compact_unwind ltmp7
0000000000000000 g F __TEXT,__text _main
0000000000000000 *UND* _NSLog
0000000000000000 *UND* _OBJC_CLASS_$_NSString
0000000000000000 *UND* ___CFConstantStringClassReference
0000000000000000 *UND* _objc_alloc
0000000000000000 *UND* _objc_autoreleasePoolPop
0000000000000000 *UND* _objc_autoreleasePoolPush
0000000000000000 *UND* _objc_msgSend
其中l(wèi)表示本地符號,g表示全局符號,下面幾個沒有l(wèi)或者的g的,這個命令無法分類,需要其他命令
其中根據(jù)功能又分為O(data),F(文件),f(file),d(debug),UDN(未定義)等.
- 查看外部符號
objdump --macho --indirect-symbols main.o
輸出是空的,因為下面那幾個都是動態(tài)鏈接庫里的,而main.o只進行了靜態(tài)鏈接
objdump --macho --indirect-symbols main
查看可執(zhí)行文件main的,就有輸出了
main:
Indirect symbols for (__TEXT,__stubs) 5 entries
address index name
0x0000000100003f54 2 _NSLog
0x0000000100003f60 5 _objc_alloc
0x0000000100003f6c 6 _objc_autoreleasePoolPop
0x0000000100003f78 7 _objc_autoreleasePoolPush
0x0000000100003f84 8 _objc_msgSend
Indirect symbols for (__DATA_CONST,__got) 5 entries
address index name
0x0000000100004000 2 _NSLog
0x0000000100004008 5 _objc_alloc
0x0000000100004010 6 _objc_autoreleasePoolPop
0x0000000100004018 7 _objc_autoreleasePoolPush
0x0000000100004020 8 _objc_msgSend
- 查看導出符號
導出不是動詞,導出符號就是用來提供給外部訪問的符號
objdump --macho --exports-trie main
輸出
main:
Exports trie:
0x100000000 __mh_execute_header
0x100003EB4 _main
三.通用二進制
1.Fat Binary
Fat Binary本身是一個mach-o,不過它還包含了多個mach-o.
用MachOView打開一個framework,比如這個Bugly,看起來是這樣的
首先它叫做Fat Binary,胖二進制,也就是通用二進制
Fat Header的定義在EXTERNAL_HEADERS/mach-o/fat.h中
#define FAT_MAGIC 0xcafebabe
#define FAT_CIGAM 0xbebafeca /* NXSwapLong(FAT_MAGIC) */
struct fat_header {
uint32_t magic; /* FAT_MAGIC */
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 */
};
magic是FAT_MAGIC或者FAT_CIGAM,nfat_arch表示有幾個fat_arch,從上面那張圖可以看到,這個header有五個fat_arch,表示了對五種架構(gòu)的支持,對應了五個Static Library,也就是靜態(tài)庫.
每個靜態(tài)庫都對應Start,Symtab Header(符號表描述),symbol table(符號表),string table(字符串表),object header(目標文件描述)和一個目標文件.o;
.o也是一整個mach-o,magic是MH_MAGIC,并且文件類型是目標文件.
2.拆分和組合
通用二進制文件一般用于庫的概念,讓一個庫能夠兼容多個硬件架構(gòu),對于開發(fā)和測試來說是很方便的,但是實際運行的時候,對于一種硬件架構(gòu)的設備,就沒必要在硬盤里存儲其他架構(gòu)的文件.如果是上傳商店,蘋果會為我們做這件事.
- 查看
lipo -info Bugly
輸出
Architectures in the fat file: Bugly are: armv7 armv7s i386 x86_64 arm64
- 提取
lipo -output 取個名字 -extract 架構(gòu) 文件
lipo -output Bugly-x86_64 -extract x86_64 Bugly
- 移除
lipo -output Bugly-noarmv7 -remove armv7 Bugly
輸出一個Bugly-noarmv7文件
查看
lipo -info Bugly-noarmv7
Architectures in the fat file: Bugly-noarmv7 are: armv7s i386 x86_64 arm64
- 合并
lipo -output NewBugly -create Bugly-noarmv7 Bugly-armv7
lipo -info NewBugly
Architectures in the fat file: NewBugly are: armv7s i386 armv7 x86_64 arm64
上面的命令都可以使用相對路徑
上面這些命令都不會影響源文件,必須添加-output.