[TOC]
簡介
MachO文件是mac平臺上一類文件的簡稱,它的類型有以下種類,可以在#import <mach-o/loader.h>文件中找到
#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 */
列舉一些常見的類型
| 文件類型 | 含義 |
|---|---|
| MH_OBJECT | 目標(biāo)文件,平時.o結(jié)尾的文件 |
| MH_EXECUTE | 可執(zhí)行文件,我們平時編譯后的包中的執(zhí)行文件 |
| MH_DYLIB | 一些動態(tài)庫,該文件夾下很多/usr/lib/xxx.dylib |
| MH_DSYM | 符號文件,編譯成功后XXX.app.dSYM |
一、MachO的分類
這里準(zhǔn)備了一些macho文件,分別通過MachOView工具來看看,如果你的MachOView會崩潰,點擊下載這個

分別用MachOView打開如下




二、MachO的組成

每個Macho文件都會有個Header對這個Macho進(jìn)行整體描述,這個header根據(jù)你打包的選擇的架構(gòu)又分為Fat Header 和 Mach Header,先介紹下如何生成這2個文件類型

Architectures和valid Architectures 的交集就是最后打的包的架構(gòu),Architectures后面是標(biāo)準(zhǔn)的架構(gòu),我把項目設(shè)置為iOS8選擇真機,將build改成release,會編譯出一開始的展示的MochO_arm_fat文件放到MachOView中如圖

然后選擇模擬器將build改成debug ,build會出現(xiàn)MochO_x86,結(jié)果如圖

2.1 fat_header
2.1.1 fat_header 結(jié)構(gòu)
struct fat_header {
uint32_t magic; /* FAT_MAGIC or FAT_MAGIC_64 */
uint32_t nfat_arch; /* number of structs that follow */
};
-
magic: 描述文件類型 值分為2組分別為FAT_CIGAM(0xbebafeca)、FAT_MAGIC(0xcafebabe)和FAT_CIGAM_64(0xbfbafeca)、FAT_MAGIC_64(0xcafebabf),值也就是大小端的區(qū)別; -
nfat_arch:描述當(dāng)前fat有多少個架構(gòu)。
2.1.2 fat_arch 結(jié)構(gòu)
2.1.1 說到fat_header的nfat_arch會描述有多少個架構(gòu),其實架構(gòu)的類型就是fat_arch類型的,結(jié)構(gòu)如下
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 */
};
-
cputype:說明CPU的類型一般有CPU_TYPE_X86、CPU_TYPE_X86_64、CPU_TYPE_ARM64、CPU_TYPE_ARM; -
cpusubtype: 對cpu類型的具體劃分一般有CPU_SUBTYPE_I386_ALL、CPU_SUBTYPE_X86_ALL、CPU_SUBTYPE_ARM_V7、CPU_SUBTYPE_ARM_V7S; -
offset: 當(dāng)前架構(gòu)的偏移量; -
size:當(dāng)前架構(gòu)的大?。?/li> -
align:對齊大小。
通過MachOView來看下MochO_arm_fat的fat_header

也可以用otool查看
otool -f /Users/fangshufeng/Desktop/thirdPart/macho/MochO/MochO/exccute/
MochO_arm_fat
Fat headers
fat_magic 0xcafebabe
nfat_arch 2
architecture 0
cputype 12
cpusubtype 9
capabilities 0x0
offset 16384
size 112048
align 2^14 (16384)
architecture 1
cputype 16777228
cpusubtype 0
capabilities 0x0
offset 131072
size 108624
align 2^14 (16384)
上面可以看到
架構(gòu)architecture 0的偏移地址是16384,也就是16進(jìn)制的0x4000;
架構(gòu)architecture 1的偏移地址是131072,也就是16進(jìn)制的0x20000;
我們來看下是否正確


正好是要的值
2.2 mac_header
對于不是fat的Macho文件一開始的內(nèi)容就是mac_header
2.2.1 結(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 */
};
magic、cputype、cpusubtype:同上;filetype:Macho文件的類型,也就是文章一開始列舉的類型;ncmds:接下來load commands的數(shù)量,后面會介紹;sizeofcmds:接下來load commands的大小,后面會介紹;-
flags:文件的表示信息,值如下:/* Constants for the flags field of the mach_header */ #define MH_NOUNDEFS 0x1 /* the object file has no undefinedreferences */ #define MH_INCRLINK 0x2 /* the object file is the output of an incremental link against a base file and can't be link edited again */ #define MH_DYLDLINK 0x4 /* the object file is input for the dynamic linker and can't be staticly link edited again */ #define MH_BINDATLOAD 0x8 /* the object file's undefined references are bound by the dynamic linker when loaded. */ #define MH_PREBOUND 0x10 /* the file has its dynamic undefined references prebound. */ #define MH_SPLIT_SEGS 0x20 /* the file has its read-only and read-write segments split */ #define MH_LAZY_INIT 0x40 /* the shared library init routine is
截圖如下

同樣使用otool也是可以的
? MochO otool -h exccute/MochO_x86
Mach header
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
0xfeedfacf 16777223 3 0x00 2 21 3176 0x00200085
? MochO
上面顯示該文件的類型為MH_EXECUTE,Load Commands的數(shù)量為21個,數(shù)一下確實是21個。

也就是說mach_header更多的是對load Commands的描述
2.3 load Commands
load Commands是由很多的LC_Type組成的,而LC_Type有很多種,可在文件loader.h文件中查看,這邊就列出前幾種
/* Constants for the cmd field of all load commands, the type */
#define LC_SEGMENT 0x1 /* segment of this file to be mapped */
#define LC_SYMTAB 0x2 /* link-edit stab symbol table info */
#define LC_SYMSEG 0x3 /* link-edit gdb symbol table info (obsolete) */
[...]
而每個LC_Type都會有一個頭部load_command結(jié)構(gòu)如下
struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */
};
是的就是對segment的描述
-
cmd: 當(dāng)前l(fā)oad command的類型; -
cmdsize:load command的大小。
也就是下面這張圖

2.3.1 LC_SEGMENT
為了方便管理,程序在內(nèi)存中是分段管理的,先來看看LC_Type其中一種LC_SEGMENT的結(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 */
};
-
cmd和cmdsize: 就是上面的load_command類型; -
segname:就是當(dāng)前segment的名稱 -
vmaddr:在虛擬內(nèi)存中的地址,這個很重要的,以后會介紹到 -
vmsize:在虛擬內(nèi)存中所占用的大??; -
fileoff:在文件中的偏移量; -
filesize:在文件中的大小,注意和vmaddr、vmsize區(qū)別 -
maxprot:表示頁面所需要的最高內(nèi)存保護; -
initprot:表示頁面初始的內(nèi)存保護; -
nsects: 當(dāng)前segment有多少個sections -
flags:表示段的標(biāo)志信息。
常見的LC_SEGMENT有以下幾種
#define SEG_PAGEZERO "__PAGEZERO"
#define SEG_TEXT "__TEXT" /* the tradition UNIX text segment */
#define SEG_DATA "__DATA" /* the tradition UNIX data segment */
2.3.1.1 __PAGEZERO
這是一個不可讀、不可寫、不可執(zhí)行的空間,能夠在空指針訪問時拋出異常。這個段的大小,32位上是 0x4000,64位上0000000100000000也就是 4G,4GB 并不是文件的真實大小,但是規(guī)定了進(jìn)程地址空間的前 4GB 被映射為 不可執(zhí)行、不可寫和不可讀,是從0(也是NULL指針的位置)開始的,這就是為什么當(dāng)讀寫一個 NULL 指針或更小的值時會得到一個 EXC_BAD_ACCESS 錯誤。
內(nèi)容如下

2.3.1.2 __TEXT
這是程序的代碼段,該段是可讀可執(zhí)行,但是不可寫。常見的section如下

2.3.1.3 __DATA
數(shù)據(jù)段,包含了可讀寫數(shù)據(jù)。常見的section如下

2.3.2 LC_DYLD_INFO_ONLY
LC_DYLD_INFO_ONLY和LC_DYLD_INFO是同一個結(jié)構(gòu)
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; /* file offset to rebase info */
uint32_t rebase_size; /* size of rebase info */
uint32_t bind_off; /* file offset to binding info */
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; /* file offset to lazy binding info */
uint32_t export_size; /* size of lazy binding infs */
};
這個command是dyld在將二進(jìn)制文件裝載到內(nèi)存鏈接的時候使用的
- 前面2個不介紹了,
rebase:由于Macho被加載到內(nèi)存的時候首地址不是固定的,是隨機分配的,針對這個做修正的; -
bind:在鏈接的時候?qū)σ恍┓栠M(jìn)行綁定的,比如我們用到了UIKIT框架的api,但是二進(jìn)制中又沒有這個符號,此刻就是做這個對應(yīng)的工作; -
lazy_bind:就是一開始不必要立即綁定,后面用到的時候再綁定。
內(nèi)容如下

可以通過偏移量找到對應(yīng)的地方

2.3.3 LC_SYMTAB
這里面記錄著所有的符號信息
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 */
};
-
symoff:符號表的偏移量; -
nsyms:符號表的元素的數(shù)量; -
stroff:符號的字符串的偏移量; -
strsize:所占的字節(jié)數(shù)。

2.3.3.1 查看Symbol Table
上面說到symbol的偏移量為21568也就是0x0000 5440
選擇如下

看到下圖

Symbol Table裝著都是結(jié)構(gòu)nlist_64或者nlist可以see <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_strx: 在String Table中的索引值; -
n_type: 可選的值有N_STAB、N_PEXT、N_TYPE、N_EXT; -
n_sect:section的類型,要么就是NO_SECT; -
n_desc: -
n_value: 符號對應(yīng)的地址
這里以AppDelegate的符號_OBJC_CLASS_$_AppDelegate來演示

根據(jù)圖得出以下信息:
-
n_sect顯示位于__DATA,__objct_data; -
value顯示地址為0x100003F48。
跳到對應(yīng)的地址看到確實是我們要找的:

具體的數(shù)據(jù)如圖

讀出以下信息:
-
_OBJC_CLASS_$_AppDelegate的isa是OBJC_METACLASS$_AppDelegate; - 父類是
UIResponder - 此時的緩存是空
- 緩存的數(shù)量為0
- 當(dāng)前類相關(guān)的信息在地址
0x100003DC0;
到這是不是覺得特別熟悉呢,我們把AppDelegate的代碼用c++看下
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc AppDelegate.m
可以看到
extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_AppDelegate __attribute__ ((used, section ("__DATA,__objc_data"))) = {
0, // &OBJC_METACLASS_$_AppDelegate,
0, // &OBJC_CLASS_$_UIResponder,
0, // (void *)&_objc_empty_cache,
0, // unused, was (void *)&_objc_empty_vtable,
&_OBJC_CLASS_RO_$_AppDelegate,
};
static void OBJC_CLASS_SETUP_$_AppDelegate(void ) {
[...這里刪除了元類的信息]
OBJC_CLASS_$_AppDelegate.isa = &OBJC_METACLASS_$_AppDelegate;
OBJC_CLASS_$_AppDelegate.superclass = &OBJC_CLASS_$_UIResponder;
OBJC_CLASS_$_AppDelegate.cache = &_objc_empty_cache;
}
和我們的工具看到的不謀而合
我們繼續(xù)跳到0x100003DC0看下

確實看到了我們要的信息,而右邊的又是什么呢,由剛才的C++代碼可以知道0x100003DC0就是_OBJC_CLASS_RO_$_AppDelegate的地址
看下_class_ro_t的結(jié)構(gòu)
struct _class_ro_t {
unsigned int flags;
unsigned int instanceStart;
unsigned int instanceSize;
const unsigned char *ivarLayout;
const char *name;
const struct _method_list_t *baseMethods;
const struct _objc_protocol_list *baseProtocols;
const struct _ivar_list_t *ivars;
const unsigned char *weakIvarLayout;
const struct _prop_list_t *properties;
};
這個結(jié)構(gòu)也即是我們截圖的內(nèi)容真好匹配
static struct _class_ro_t _OBJC_CLASS_RO_$_AppDelegate __attribute__ ((used, section ("__DATA,__objc_const"))) = {
0,
__OFFSETOFIVAR__(struct AppDelegate, _window),
sizeof(struct AppDelegate_IMPL),
0,
"AppDelegate",
(const struct _method_list_t *)&_OBJC_$_INSTANCE_METHODS_AppDelegate,
(const struct _objc_protocol_list *)&_OBJC_CLASS_PROTOCOLS_$_AppDelegate,
(const struct _ivar_list_t *)&_OBJC_$_INSTANCE_VARIABLES_AppDelegate,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_AppDelegate,
};
所以到這里我們可以知道該類的所有信息了,比如我們想看看它得方法列表,由截圖可以知道地址為0x0000000100003C60

每一個item對應(yīng)的就是_objc_method
struct _objc_method {
struct objc_selector * _cmd;
const char *method_type;
void *_imp;
};
比如我們現(xiàn)在想到拿到方法- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions的名稱,看圖可以知道方法的字符串地址為0000000100001C90

果然找到了,其它的信息可以自己嘗試去找找。更多關(guān)于類方面的知識可以看下我之前的oc主題相關(guān)的文章,現(xiàn)在文章已經(jīng)很長了,不說這個了。
2.3.3.2 查看String Table

2.3.4 LC_DYSYMTAB
這里記錄著所有的動態(tài)鏈接時需要的符號信息

同樣我們找到00005C10

還有很多沒有截取了,比如這些_NSFullUserName這些在鏈接的時候回去動態(tài)解析這些符號表
這個Indirect Symbols包含了所有和動態(tài)庫相關(guān)的符號,包括__DATA,__la_symbol_ptr、__DATA,__nl_symbol_ptr、__DATA,__got,這個表有以下用處:
- 通過這個表的
Symbol可以找到在符號表Symbol Table的位置,從而在字符串表String Table中找到名稱; - 通過這個表的
Indirect Address可以在__DATA,__la_symbol_ptr、__DATA,__nl_symbol_ptr、__DATA,__got中找到方法的地址
fishook就用到了這個,后面我會單獨來介紹這個庫的實現(xiàn)原理
2.3.5 LC_MAIN
指定了main函數(shù)的入口地址

加載到內(nèi)存后增加頭部地址就是函數(shù)的真正地址了

2.3.6 LC_LOAN_DYLIB
描述了一些動態(tài)庫相關(guān)的信息

struct dylib {
union lc_str name; /* library's path name */
uint32_t timestamp; /* library's build time stamp */
uint32_t current_version; /* library's current version number */
uint32_t compatibility_version; /* library's compatibility vers number*/
};
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 */
};
2.3.7 LC_RPATH
Runpath的簡寫
程序運行鏈接路徑

xcode中可以看到

2.3.8 LC_FUNCTION_STARTS
方法是從哪里開始的


和解析出來的順序也是一致的

2.3.9 LC_CODE_SINGATURE
簽名相關(guān)的信息

找到地方0x67E0

關(guān)于簽名的后面打算單獨寫一篇
2.4 section
結(jié)構(gòu)如下
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 */
};
這里只列舉了section64位的,section可以自己在#include <mach-o/loader.h>查看
-
sectname:當(dāng)前section的名字; -
segname:位于哪個segment; -
addr:當(dāng)前section在內(nèi)存中的地址; -
size:當(dāng)前的section所占的內(nèi)存大??; -
offset:當(dāng)前section的偏移量; -
reloff: 抱歉暫時沒找到實際的用處,不做解釋,以免誤人子弟; -
nreloc:這個就是表示上面reloff的數(shù)量; -
flags: 這個是當(dāng)前section的標(biāo)志位,包括sectionType和sectionAttribute,一個section可以有多個屬性,但是只能有一個類型,這個很好理解了,可以通過位運算分別獲取類型和屬性,(section->flags & SECTION_TYPE、section->flags & SECTION_ATTRIBUTES -
reserved1:這是個保留字段,它可以表示偏移量也可以用來表示索引,一般用來表示Indirect Symbol Index也就是間接索引表的位置,你可以在__got、__subs等中可以查看; -
reserved3:也是個保留字段,一般表示數(shù)量的,比如在__subssection中就表示subs的個數(shù); -
reserved3:這個真是個保留字段了,暫時沒什么用處
隨意截取一個section看下結(jié)構(gòu)吧

本篇完。