Mach-O文件
Mach-O格式全稱為Mach Object文件格式的縮寫
Mach-O文件類型分類:
1.Executable:應(yīng)用可執(zhí)行的二進(jìn)制文件,如.m/.h文件經(jīng)過編譯后會生成對應(yīng)的Mach-O文件
2.Dylib Library:動態(tài)鏈接庫
3.Static Library:靜態(tài)鏈接庫
4.Bundle:不能被鏈接 Dylib,只能在運(yùn)行使用dlopen()加載
5.Relocatable Object File:可重定向文件類型
Mach-O文件結(jié)構(gòu)
參考蘋果官方文檔,Mach-O文件結(jié)構(gòu)由Header,Load Commands,Data三部分組成
Header
作用:快速確認(rèn)Mach-O文件的基本信息,如運(yùn)行環(huán)境,Load Commands概述。
數(shù)據(jù)結(jié)構(gòu):
32位架構(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 */
};
64位架構(gòu)
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
:確定Mach-O文件運(yùn)行框架,如64位/32位
cpu
:CPU類型,如arm
cpusubtype
:對應(yīng)CPU類型的具體型號
filetype
:文件類型
ncmds
:加載命令條數(shù)
sizeofcmds
:所有加載命令的大小
flags
:保留字段
reserved
:標(biāo)志位
案例說明:
使用MachOView工具查看Mach-O文件,如下圖:
Mach-O文件運(yùn)行基本環(huán)境:
- cpu:x86,64位
- 文件類型:可執(zhí)行二進(jìn)制文件
- LoadCommand:15個LoadCommand,占用大小位1360
Load commands
作用:
Load Commands
加載指令,告訴加載器如何處理二進(jìn)制數(shù)據(jù),處理對方分別為內(nèi)核,動態(tài)鏈接器等。加載指令緊跟在Header后的加載命令區(qū)。Load Commands
加載指令個數(shù)及大小在Header中定義( commands 的大小總和即為 Header->sizeofcmds 字段,共有 Header->ncmds 條加載命令)。
數(shù)據(jù)結(jié)構(gòu):
通用結(jié)構(gòu)
struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */
};
- cmd:指令類型
- cmdsize: 指令長度
Command以LC開發(fā),不同的加載指令有不同的專有結(jié)構(gòu),但必包含cmd,cmdsize兩個字段,用來定義指令類型以及指令長度
指令類型:
LC_SEGMENT/LC_SEGMENNT_64
- 作用:將對應(yīng)段中的數(shù)據(jù)加載并映射到進(jìn)程的內(nèi)存空間
- 結(jié)構(gòu):
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 (固定)
cmdsize
:指令長度
segname
:段名,如_PAGEZERO(保留數(shù)據(jù)空間,4g),_TEXT(代碼數(shù)據(jù)),_DATA(函數(shù)指針),_LINKDIT(鏈接數(shù)據(jù))
vmaddr
:段的虛擬內(nèi)存起始地址
vmsize
:段的虛擬內(nèi)存大小
fileoff
:段在文件中的偏移量
filesize
:段在文件中的大小
-
MachOView實(shí)例:
image.png
段數(shù)據(jù)加載并映射到內(nèi)存過程:從fileo ff處加載file size大小到虛擬內(nèi)存vmaddr處,并占用虛擬內(nèi)存大小為vmsize,一般情況下段名_TEXT,_DATA的file size=vmsize;段名_LINKDIT的file size<vmsize(動態(tài)鏈接申請的內(nèi)存控件要大于文件大小)
LC_SEGMENT_TEXT
- 作用:代碼段,其中_stub_helper用于關(guān)聯(lián)函數(shù)bind/rebind
LC_SEGMENT_DATA
- 作用:可讀/可寫的數(shù)據(jù)段,函數(shù)指針,其中_la_symbol_ptr動態(tài)函數(shù)個數(shù),及相對動態(tài)符號表的偏移量
LC_SEGMENT_LINKEDIT
- 作用:動態(tài)鏈接加載指令,支持動態(tài)鏈接dyld,該段長度覆蓋
符號表等數(shù)據(jù)
(計算鏈接時程序的基址),符號表,動態(tài)符號表,字符串表段中定義的offset偏移量
都是基于_LINKEDIT的vm_add
- 結(jié)構(gòu):與LC_SEGMENT一致
-
MachOView實(shí)例:
image.png
LC_SYMTAB
- 作用:符號表信息,解析函數(shù)名
- 結(jié)構(gòu):
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
:符號表偏移量,如一個函數(shù)對應(yīng)一個符號
nsyms
:符號表中表個數(shù)
stroff
:字符串表偏移量,連續(xù)的內(nèi)存空間用來存儲函數(shù)名
strsize
:字符串表的大小
LC_DYSYMTAB
- 作用:動態(tài)符號表信息,地址值為動態(tài)函數(shù)相對符號表的索引,_la_symbol_ptr對應(yīng)的cmd可以換算出第一個動態(tài)函數(shù)對應(yīng)動態(tài)符號表的初始地址,其次存儲是
連續(xù)
,結(jié)構(gòu)長度固定
的,可以通過遍歷獲取所有動態(tài)函數(shù)
的對應(yīng)的符號表索引
- 結(jié)構(gòu):
struct symtab_command {
uint32_t cmd; /* LC_DYSYMTAB */
uint32_t cmdsize; /* sizeof(struct dysymtab_command) */
uint32_t indirectsymoff; /* file offset to the indirect symbol table */
uint32_t nindirectsyms; /* number of indirect symbol table entries */
.....
}
- 主要字段說明:
indirectsymoff
:動態(tài)符號表偏移量
nindirectsyms
:動態(tài)符號表中表個數(shù)
ASLR隨機(jī)地址控件__
ASLR:Address space layout randomization,將可執(zhí)行程序隨機(jī)裝載到內(nèi)存中,這里的隨機(jī)只是偏移,而不是打亂,具體做法就是通過內(nèi)核將 Mach-O的段“平移”某個隨機(jī)系數(shù)。slide 正是ASLR引入的偏移
加載指令相互關(guān)系
作用
- LC_SEGMENT(LC_LINKEDIT):換算程序基址
- LC_SYMBOL:符號表(symoff偏移地址,nsyms符號個數(shù)),字符串表(stroff偏移地址,strsize大小)
- LC_DYSYMBOL:動態(tài)符號表(indirectsymoff偏移地址,nindirectsyms符號個數(shù))
- ASLR:加載到進(jìn)程的內(nèi)存偏移量
地址換算
//鏈接時程序基址
uintptr_t linkedit_base = linkedit_segment->vmadd - linkedit_segment->fileoff + (unintptr)slide
//符號表地址 = 基址 + 符號表偏移量
nlist_t *symbol = (nlist_t *)(linkedit_base + symtab_cmd-> symoff)
//字符串表地址 = 基址 +字符串表偏移量
char *strtab = (char *)(linkedit_base + symtab_cmd->stroff)
//動態(tài)符號表地址 = 基址 + 動態(tài)符號表偏移量
uint32_t *indirect_symtab = (uint32_32 *)(linkedit_base + dysymtab_cmd->indirectsymoff)
補(bǔ)充
- LC_DYSYMTAB(動態(tài)符號表)是LC_SYMTAB(符號表)的子集,LC_DYSYMTAB存儲動態(tài)符號對應(yīng)在LC_SYMTAB的序號
- LC_SEGMENT(_TEXT):存儲函數(shù)代碼,stub_help取得綁定信息(即函數(shù)地址與符號的對應(yīng)關(guān)系)
- LC_SEGMENT(_DATA):函數(shù)地址存放在LC_SEGMENT(_DATA)內(nèi)的la_symbol_ptr,第二次調(diào)用,直接通過la_symbol_ptr找到函數(shù)地址,不在需要繁瑣的獲取函數(shù)地址過程。
- la_symbol_ptr對應(yīng)section中的
reserved1
字段指明相對應(yīng)的indirect symbol table
起始的index
程序運(yùn)行時,動態(tài)鏈接的函數(shù)a地址記錄在LC_SEGMENT(_DATA )的la_symbol_ptr中。初始化時,程序只知道函數(shù)a的符號名而不知道函數(shù)的實(shí)現(xiàn)地址;首次調(diào)用,程序通過LC_SEGMENT(_TEXT)的stub_help獲取綁定信息;dyld_stub_binder來更新la_symbol_ptr中的符號實(shí)現(xiàn)地址;再次調(diào)用,直接通過la_symbol_ptr獲取函數(shù)實(shí)現(xiàn)
補(bǔ)充:
動態(tài)函數(shù)調(diào)用過程
程序初始化:
函數(shù)符號名:已存在,并以nlist結(jié)構(gòu)存儲,但nlist->n_value=0
(函數(shù)地址沒有值)
函數(shù)實(shí)現(xiàn)地址:已存在,存放在mach-o文件cmd加載指令(SegmentName=_DATA,SectionName=__la_symbol_ptr)
函數(shù)符號名與實(shí)現(xiàn)地址關(guān)聯(lián)(未關(guān)聯(lián)):即補(bǔ)全nlist信息
程序運(yùn)行:
函數(shù)首次調(diào)用:
函數(shù)符號名與實(shí)現(xiàn)地址進(jìn)行關(guān)聯(lián),nlist->n_value
賦值函數(shù)地址
函數(shù)再次調(diào)用:
通過關(guān)聯(lián)信息,通過函數(shù)符號直接獲得函數(shù)實(shí)現(xiàn)地址
關(guān)聯(lián)過程如下:
介紹關(guān)聯(lián)過程前,簡單介紹幾個基礎(chǔ)知識
MACH-O的加載指令(__DATA,__la_symbol_ptr)
作用:存儲了對應(yīng)MACH-O文件所有的動態(tài)函數(shù)實(shí)現(xiàn)地址,且以連續(xù)內(nèi)存地址存儲
字段解析:
Szie:指令大小,可通過換算動態(tài)函數(shù)個數(shù)
,如Szie/Szieof(void*)
Reserved1:相對動態(tài)符號表dlsymbol_Tab偏移量
,用來換算第一個動態(tài)函數(shù)對應(yīng)動符號表
的地址Indirect Symbol Table
作用:連續(xù)的存儲動態(tài)函數(shù)對應(yīng)符號表的索引
地址尋址:第一個動態(tài)符號尋址
= 動態(tài)符號基址(vm_add+slide) + Reserved1
地址值:對應(yīng)符號Symbol的索引值index
Symbol Table
作用:以nlist結(jié)構(gòu)連續(xù)存儲函數(shù)符號與地址的關(guān)聯(lián)關(guān)系,nlist包含函數(shù)實(shí)現(xiàn)地址,字符串偏移地址(計算函數(shù)名)
地址尋址:符號表基址(vm_add+slide)+ index
地址值:nlist值String Table
作用:存儲MACH-O文件所有的函數(shù)名,char*,每個函數(shù)名以\0
分隔
地址尋址:字符串表基址(vm_add+slide)+ 偏移量(nlist-> n_un->n_str)
地址值:函數(shù)名
關(guān)聯(lián)過程
1.遍歷Mach-O文件下的所有LoadCommand,尋址目標(biāo)cmd,搜索條件(SegmetName=__DATA,SectionName=__la_symbol_ptr)
,目標(biāo)cmd存儲了動態(tài)函數(shù)個數(shù),及第一個動態(tài)函數(shù)相對動態(tài)符號表偏移量
2.動態(tài)函數(shù)個數(shù)及動態(tài)符號表偏移量:動態(tài)函數(shù)個數(shù)
=cmd->Szie/Szieof(void*);
動態(tài)符號表偏移量
=cmd->reserved1
3.動態(tài)函數(shù)符號表尋址:第一個動態(tài)符號地址=動態(tài)符號表基址_LC_ DYSYMTAB->vm_add+slide+ reserved1
;由于動態(tài)符號表連續(xù)存儲動態(tài)函數(shù)符號,可遍歷所有動態(tài)函數(shù)符號地址;地址值存儲對應(yīng)動態(tài)函數(shù)的符號表索引
4.關(guān)聯(lián)建立,補(bǔ)全nlist結(jié)構(gòu)體: 函數(shù)地址 + reserved1
--->動態(tài)符號表尋址
--->符號表index
--->nlist信息補(bǔ)全
Data
section對應(yīng)SEGMENTD的DATA數(shù)據(jù)
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) */
};
字段解釋
sectname
:比如_text、stubs
segname
:該section所屬的segment,比如__TEXT
addr
: 該section在內(nèi)存的起始位置
size
: 該section的大小
offset
: 該section的文件偏移
align
:字節(jié)大小對齊
reloff
:重定位入口的文件偏移
nreloc
: 需要重定位的入口數(shù)量
flags
:包含section的type和attributes