Mach-O文件格式和程序從載入到運行過程
>
之前深入了解過。過去了一年多的時間。如今花些時間好好總結下,畢竟好記性不如爛筆頭。
其次另一個目的,對於mach-o文件結構。關於動態載入信息那個數據區中,命令含義沒有深刻掰扯清除,希望有同學能夠指點下。
摘要:對於mach-o是Mac和iOS能夠運行文件的格式。進程就是系統依據該格式將運行文件載入到內存後得到的結果。系統通過解析文件,建立依賴(動態庫),初始化運行時環境,才幹真正開始運行該App(進程)
Start from Hello World
通過分析以下這個最熟悉的可運行文件。來好好總結和了解下Mach-O這樣的文件格式,而且也總結下系統在運行可運行文件幾個過程:
+ 解析文件
+ 依賴建立
+ 初始化運行環境
+ 運行進程
代碼1.0
#include <stdio.h>
int main( int argc, char *argv[])
{
printf( "Hello World!\n" );
return 0;
}
瀏覽可運行文件格式
小工具介紹下:爛蘋果MachOView。通過改工具能夠直接review mach-o可運行文件的幾個基本的組成部分:
編譯下main.c文件(使用gcc -g main.c)
簡單看下可運行文件格式:
簡單瀏覽mach-o可運行文件,詳細能夠分為幾個部分
- 文件頭 mach64 Header
- 載入命令 Load Commands
- 文本段 __TEXT
- 數據段 __TEXT
- 動態庫載入信息 Dynamic Loader Info
- 入口函數 Function Starts
- 符號表 Symbol Table
- 動態庫符號表 Dynamic Symbol Table
- 字符串表 String Table
詳細介紹下各個區域的作用。以及載入時系統是怎樣使用該可運行文件。
Mach Header - 可運行文件文件頭
使用下mac的otool工具
代碼3.0
yingfang:mach-o文件結構-src fangying$ otool -h a.out
a.out:
Mach header
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
0xfeedfacf 16777223 3 0x80 2 15 1200 0x00200085
上面是mach-o標準的文件頭格式。其相關的數據結構在
/*
* 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 | 魔數,系統載入器通過改字段高速,推斷該文件是用於32位or64位。 32位-0xfeedface 64位-0xfeedfacf |
demo中magic值為0xfeedfacf。表明該文件支持64位 |
cputype | CPU類型以及子類型字段。該字段確保系統能夠將適合的二進制文件在當前架構下運行 | demo值為0x1000007, 依據#define CPU_TYPE_X86_64 (CPU_TYPE_X86 |
cpusubtype | CPU指定子類型。對於inter。arm。powerpc等CPU架構,其都有各個階段和等級的CPU芯片。該字段就是詳細描寫敘述其支持CPU子類型 | 對於Demo,能夠查看 #define CPU_SUBTYPE_X86_ALL ((cpu_subtype_t)3) #define CPU_SUBTYPE_X86_64_ALL ((cpu_subtype_t)3) #define CPU_SUBTYPE_X86_ARCH1 ((cpu_subtype_t)4) #define CPU_SUBTYPE_X86_64_H ((cpu_subtype_t)8) |
filetype | 說明該mach-o文件類型(可運行文件,庫文件。核心轉儲文件。內核擴展,DYSM文件,動態庫)。詳細能夠看 | demo中該值為2,表示該文件為二進制文件 |
ncmds | 說明載入命令條數 | demo中表示該文件載入命令條數為15,通過machoview工具。看到也是15條載入命令 |
sizeofcmds | 表示載入命令大小 | demo表示該文件載入命令大小為1200字節 |
flags | 標誌位,該字段用位表示二進制文件支持的功能,主要是和系統載入,鏈接相關。詳細能夠看 | demo中值為0x00200085,分別表示三個功能: 1. MH_PRELOAD 2. MH_TWOLEVEL 動態庫載入二級名稱空間 3. MH_PIE 對可運行文件類型啟用地址空間隨機布局化 |
reserved | 保留字段 |
系統解釋,先解釋文件頭,獲得文件支持位數(64位 or 32位),獲得CPU類型。獲得文件類型。獲得載入命令條數和大小,獲得文件標識。
Load Commands - 載入命令
Mach-O文件包括非常詳細的載入指令,這些指令非常清晰地指示載入器怎樣設置而且載入二進制數據。Load Commands緊緊跟著二進制文件頭。
先使用工具review demo中二進制文件Load Commands
也能夠使用otool命令讀取二進制文件載入命令:(之前分析bitcode是否支持,也是通過otool -l進行分析的)
代碼4.0
yingfang:mach-o文件結構-src fangying$ otool -l a.out
a.out:
Load command 0
cmd LC_SEGMENT_64
cmdsize 72
segname __PAGEZERO
vmaddr 0x0000000000000000
vmsize 0x0000000100000000
fileoff 0
filesize 0
maxprot 0x00000000
initprot 0x00000000
nsects 0
flags 0x0
Load command 1
cmd LC_SEGMENT_64
cmdsize 472
segname __TEXT
vmaddr 0x0000000100000000
vmsize 0x0000000000001000
fileoff 0
filesize 4096
maxprot 0x00000007
initprot 0x00000005
nsects 5
flags 0x0
Section
sectname __text
segname __TEXT
addr 0x0000000100000f50
size 0x0000000000000034
offset 3920
align 2^4 (16)
reloff 0
nreloc 0
flags 0x80000400
reserved1 0
reserved2 0
Section
sectname __stubs
segname __TEXT
addr 0x0000000100000f84
size 0x0000000000000006
offset 3972
align 2^1 (2)
reloff 0
nreloc 0
flags 0x80000408
reserved1 0 (index into indirect symbol table)
reserved2 6 (size of stubs)
Section
sectname __stub_helper
segname __TEXT
addr 0x0000000100000f8c
size 0x000000000000001a
offset 3980
align 2^2 (4)
reloff 0
nreloc 0
flags 0x80000400
reserved1 0
reserved2 0
Section
sectname __cstring
segname __TEXT
addr 0x0000000100000fa6
size 0x000000000000000e
offset 4006
align 2^0 (1)
reloff 0
nreloc 0
flags 0x00000002
reserved1 0
reserved2 0
Section
sectname __unwind_info
segname __TEXT
addr 0x0000000100000fb4
size 0x0000000000000048
offset 4020
align 2^2 (4)
reloff 0
nreloc 0
flags 0x00000000
reserved1 0
reserved2 0
Load command 2
cmd LC_SEGMENT_64
cmdsize 232
segname __DATA
vmaddr 0x0000000100001000
vmsize 0x0000000000001000
fileoff 4096
filesize 4096
maxprot 0x00000007
initprot 0x00000003
nsects 2
flags 0x0
Section
sectname __nl_symbol_ptr
segname __DATA
addr 0x0000000100001000
size 0x0000000000000010
offset 4096
align 2^3 (8)
reloff 0
nreloc 0
flags 0x00000006
reserved1 1 (index into indirect symbol table)
reserved2 0
Section
sectname __la_symbol_ptr
segname __DATA
addr 0x0000000100001010
size 0x0000000000000008
offset 4112
align 2^3 (8)
reloff 0
nreloc 0
flags 0x00000007
reserved1 3 (index into indirect symbol table)
reserved2 0
Load command 3
cmd LC_SEGMENT_64
cmdsize 72
segname __LINKEDIT
vmaddr 0x0000000100002000
vmsize 0x0000000000001000
fileoff 8192
filesize 552
maxprot 0x00000007
initprot 0x00000001
nsects 0
flags 0x0
Load command 4
cmd LC_DYLD_INFO_ONLY
cmdsize 48
rebase_off 8192
rebase_size 8
bind_off 8200
bind_size 24
weak_bind_off 0
weak_bind_size 0
lazy_bind_off 8224
lazy_bind_size 16
export_off 8240
export_size 48
Load command 5
cmd LC_SYMTAB
cmdsize 24
symoff 8296
nsyms 12
stroff 8504
strsize 240
Load command 6
cmd LC_DYSYMTAB
cmdsize 80
ilocalsym 0
nlocalsym 8
iextdefsym 8
nextdefsym 2
iundefsym 10
nundefsym 2
tocoff 0
ntoc 0
modtaboff 0
nmodtab 0
extrefsymoff 0
nextrefsyms 0
indirectsymoff 8488
nindirectsyms 4
extreloff 0
nextrel 0
locreloff 0
nlocrel 0
Load command 7
cmd LC_LOAD_DYLINKER
cmdsize 32
name /usr/lib/dyld (offset 12)
Load command 8
cmd LC_UUID
cmdsize 24
uuid A36ECE81-1426-3D1F-928C-62F6CC590A5D
Load command 9
cmd LC_VERSION_MIN_MACOSX
cmdsize 16
version 10.11
sdk 10.11
Load command 10
cmd LC_SOURCE_VERSION
cmdsize 16
version 0.0
Load command 11
cmd LC_MAIN
cmdsize 24
entryoff 3920
stacksize 0
Load command 12
cmd LC_LOAD_DYLIB
cmdsize 56
name /usr/lib/libSystem.B.dylib (offset 24)
time stamp 2 Thu Jan 1 08:00:02 1970
current version 1225.1.1
compatibility version 1.0.0
Load command 13
cmd LC_FUNCTION_STARTS
cmdsize 16
dataoff 8288
datasize 8
Load command 14
cmd LC_DATA_IN_CODE
cmdsize 16
dataoff 8296
datasize 0
從otool展現的載入命令,我們能夠分析基本的載入命令(段命令,區命令。段命令中會分為幾個區命令)
詳細能夠看看bsd/kern/mach_loader.c
segname cmd | 說明 | 舉例 |
---|---|---|
LC_SEGMENT_64 | 將文件裏(32位或64位)的段映射到進程地址空間中 | |
LC_DYLD_INFO_ONLY | ||
LC_SYMTAB | 符號表地址 | |
LC_DYSYMTAB | 動態符號表地址 | |
LC_LOAD_DYLINKER | 使用何種動態載入庫 | demo中表明使用/usr/lib/dyld |
LC_UUID | 文件的唯一標識,crash解析中也會有該僅僅。去確定dysm文件和crash文件是匹配的 | |
LC_VERSION_MIN_MACOSX | 二進制文件要求的最低操作系統版本號 | demo二進制版本號。支持最低os版本號為10.11 |
LC_SOURCE_VERSION | 構建該二進制文件使用的源碼版本號 | |
LC_MAIN | 設置程序主線程的入口地址和棧大小 | demo二進制的entryoff位OXF50,正式__TEXT段偏移地址,然後看看0XF50的匯編代碼,正式熟悉的main函數調用地址。詳細請看以下匯編代碼 |
LC_LOAD_DYLIB | 載入額外的動態庫,細致看這個命令格式,動態庫地址和名,當前版本號號,兼容版本號號。該設計比較合理。假設對於動態庫有版本號管理能力 | |
LC_FUNCTION_STARTS | 函數起始地址表,怎樣使用呢? | |
LC_DATA_IN_CODE | /* table of non-instructions in __text */ 不是非常理解 |
代碼4.1
yingfang:mach-o文件結構-src fangying$ otool -vt a.out
a.out:
(__TEXT,__text) section
_main:
0000000100000f50 pushq %rbp
0000000100000f51 movq %rsp, %rbp
0000000100000f54 subq $0x20, %rsp
0000000100000f58 leaq 0x47(%rip), %rax
0000000100000f5f movl $0x0, -0x4(%rbp)
0000000100000f66 movl %edi, -0x8(%rbp)
0000000100000f69 movq %rsi, -0x10(%rbp)
0000000100000f6d movq %rax, %rdi
0000000100000f70 movb $0x0, %al
0000000100000f72 callq 0x100000f84
0000000100000f77 xorl %ecx, %ecx
0000000100000f79 movl %eax, -0x14(%rbp)
0000000100000f7c movl %ecx, %eax
0000000100000f7e addq $0x20, %rsp
0000000100000f82 popq %rbp
0000000100000f83 retq
section cmd | 說明 | 舉例 |
---|---|---|
__text | 主程序代碼 | |
__stubs | 用於動態庫鏈接的樁 | |
__stub_helper | 用於動態庫鏈接的樁 | |
__cstring | 常亮字符串符號表描寫敘述信息,通過該區信息,能夠獲得常亮字符串符號表地址 | |
__unwind_info | 這裏字段不是太理解啥意思。希望大家指點下 |
動態庫連接器–動態庫鏈接信息
總結了mach-o文件的兩個最重要的部分,那麽動態庫依據載入命令怎樣動態鏈接到內存中的呢?以下總結這個動態過程。
- 系統通過載入命令。獲得動態載入器的地址/usr/lib/dyly(其解決的問題是。把代碼段__TEXT中和動態庫相關內容進行關聯,比方代碼中怎樣調用到哪個動態庫的相關代碼段的偏移地址)
(dyly是用戶態進程。這個是開源的,不屬於kern內核的部分)感興趣能夠看看這裏
總結下:在載入命令中。和動態庫和鏈接相關命令有例如以下幾個:
- 段命令
- LC_DYLD_INFO_ONLY
- LC_LOAD_DYLIB
- LC_LOAD_DYLINKER
- LC_SYMTAB
- LC_DYSYMTAB
- 區命令
- __stubs
- __stubs_helper
在進行動態鏈接器工作前,要先解析相關工作環境參數
- LC_LOAD_DYLINKER 獲得動態載入器地址
- LC_LOAD_DYLIB 二進制文件依賴動態庫信息
能夠使用otool工具。讀取該部分信息
代碼5.0
yingfang:mach-o文件結構-src fangying$ otool -L a.out
a.out:
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1225.1.1)
- LC_DYLD_INFO_ONLY 動態庫信息,依據該命令是真正動態庫綁定,地址重定向重要的信息。
結合之前otool -l的信息中的command 4命令,以下struct就是該命令相應的數據結構:
代碼5.1
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;
uint32_t rebase_size;
uint32_t bind_off;
uint32_t bind_size;
uint32_t weak_bind_off;
uint32_t weak_bind_size;
uint32_t lazy_bind_off;
uint32_t lazy_bind_size;
uint32_t export_off;
uint32_t export_size;
};
依據該載入命令的字段偏移,系統能夠得到壓縮動態數據信息區(dymanic load info)。
依據上述數據,dymanic load info數據區,主要包括了5種數據:
(以下的一些內容,我的理解可能不是太正確。希望和大家一起討論)dyld_info_command詳細定義地址
dymanic load info | 說明 | 舉例 |
---|---|---|
重定向數據 rebase | demo中該段數據位 11 22 10 51 | 11: 高位0x10表示設置馬上數類型,低位0x01表示馬上數類型為指針 22: 表示REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB + 2 重定向到數據段2。 結合上面的信息。就是重定向到數據段2。該段數據信息為一個指針 |
綁定數據 bind | 在demo中進行動態綁定依賴的dyld的函數 | U dyld_stub_binder |
弱綁定數據 weak bind | 用於弱綁定動態庫,就像weak_framework一樣 | |
懶綁定數據 lazy bind | 對於須要從動態庫載入的函數符號 | demo中有兩個: U _printf U _scanf |
export數據 | 用於對外開放的函數 | demo中僅僅有兩個 0000000100000000 T __mh_execute_header 0000000100000f50 T _main |
對於相關的綁定函數查找。能夠使用nm命令
代碼5.2
yingfang:mach-o文件結構-src fangying$ nm a.out
0000000100000000 T __mh_execute_header
0000000100000f50 T _main
U _printf
U dyld_stub_binder
>
標註:dymanic load info數據是以命令碼(命令碼就是一個字節碼)的形式,傳遞詳細內容。
高四位表示真正命令名。低四位表示一個馬上數。
00表示該類型命令結束
能夠使用dylyinfo獲得這部分信息讀取
代碼5.3
yingfang:mach-o文件結構-src fangying$ xcrun dyldinfo -opcodes a.out
rebase opcodes:
0x0000 REBASE_OPCODE_SET_TYPE_IMM(1)
0x0001 REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB(2, 0x00000010)
0x0003 REBASE_OPCODE_DO_REBASE_IMM_TIMES(1)
0x0004 REBASE_OPCODE_DONE()
binding opcodes:
0x0000 BIND_OPCODE_SET_DYLIB_ORDINAL_IMM(1)
0x0001 BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM(0x00, dyld_stub_binder)
0x0013 BIND_OPCODE_SET_TYPE_IMM(1)
0x0014 BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB(0x02, 0x00000000)
0x0016 BIND_OPCODE_DO_BIND()
0x0017 BIND_OPCODE_DONE
no compressed weak binding info
lazy binding opcodes:
0x0000 BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB(0x02, 0x00000010)
0x0002 BIND_OPCODE_SET_DYLIB_ORDINAL_IMM(1)
0x0003 BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM(0x00, _printf)
0x000C BIND_OPCODE_DO_BIND()
0x000D BIND_OPCODE_DONE
0x000E BIND_OPCODE_DONE
0x000F BIND_OPCODE_DONE
動態庫鏈接器運行結果
上面總結,動態鏈接器相關信息(載入命令信息,載入命令偏移地址相關信息)。
如今總結下動態鏈接器運行的結果是什麽?怎樣使用相關動態鏈接信息,完畢相關過程的?
從字面上,之前我理解,鏈接器就是把文本段原來動態庫函數相關地址,用真實的動態庫地址替換。生成一個真實的完整的可運行文本。比方demo中printf是其他動態庫的,可是其真實地址對於可運行文件是不知道,僅僅有這個運行文件運行時,通過動態鏈接器完好其原來文本段地址。
在總結之前兩個問題前。先總結下__stubs(樁)的區概念:該區存放的是二進制文件裏沒有定義符號的占位符。編譯器生成代碼時會創建對符號樁區的調用。鏈接器在運行時解決對樁的這些調用。鏈接器解決方式是在被調用的地址處。放置一條JMP指令。JMP指令將控制權轉交給真實的函數體。
回想下nm命令結果:U表示沒有定義的符號
yingfang:mach-o文件結構-src fangying$ nm a.out
0000000100000000 T __mh_execute_header
0000000100000f50 T _main
U _printf
U dyld_stub_binder
看下demo中main函數匯編代碼:
- demo中真實的print函數調用,編譯器,編譯為callq 0x100000f84 ## symbol stub for: _printf
- 匯編代碼中。callq 0x100000f84 . 註意0x100000f84,這個地址是 __TEXT段的__stubs區的地址。換句話說,就是JMP到__stubs(樁區)– 段1 section位__stubs的起始地址
- 0x100000f84地址。是一段匯編指令 0x100000f84: jmpq *0x86(%rip) # 0x100001010
- 0x100001010地址,是指向(代碼7.2)數據段__la_symbol_ptr區。因為這個數據都符號指針,查看下該地址指向的數據區域(代碼7.3)
- 0x100001010地址指針。指向地址為4294971292(數據段都在高位。所以相比__TEXT地址,這個地址是正常的)
- 4294971292地址,運行匯編代碼請看(代碼7.4) 0x100000fa1: jmpq 0x100000f8c。請註意 0x100000f8c就是__TEXT段section __stub_helper區的起始地址。該地址是運行匯編代碼詳細請看(代碼7.5)0x100000f95: jmpq *0x65(%rip) # 0x100001000
- 0x100001000地址,恰好是__DATA段, __nl_symbol_ptr區的事實上地址,該地址指向的數據值位為 0x100001000: 0x0000000000000000 0x0000000000000000 (代碼7.6)
>
總結:
+ __stubs區和__stub_helper區是幫助動態鏈接器找到指定數據段__nl_symbol_ptr區,二進制文件用0x0000000000000000進行占位,在運行時。系統依據dynamic loader info信息,把占位符換為調用dylib的dyld_stub_binder函數的匯編指令。
+ 當第一次調用完動態庫中的符號後,動態鏈接器會依據dynamic loader info信息,把數據段__la_symbol_ptr指向正在的符號地址。而不是指向_nl_symbol_ptr區
代碼7.0
yingfang:mach-o文件結構-src fangying$ otool -p _main -tV a1.out
a1.out:
(__TEXT,__text) section
_main:
0000000100000f50 pushq %rbp
0000000100000f51 movq %rsp, %rbp
0000000100000f54 subq $0x20, %rsp
0000000100000f58 leaq 0x47(%rip), %rax ## literal pool for: "Hello World!\n"
0000000100000f5f movl $0x0, -0x4(%rbp)
0000000100000f66 movl %edi, -0x8(%rbp)
0000000100000f69 movq %rsi, -0x10(%rbp)
0000000100000f6d movq %rax, %rdi
0000000100000f70 movb $0x0, %al
0000000100000f72 callq 0x100000f84 ## symbol stub for: _printf
0000000100000f77 xorl %ecx, %ecx
0000000100000f79 movl %eax, -0x14(%rbp)
0000000100000f7c movl %ecx, %eax
0000000100000f7e addq $0x20, %rsp
0000000100000f82 popq %rbp
0000000100000f83 retq
代碼7.1
Section
sectname __stubs
segname __TEXT
addr 0x0000000100000f84
size 0x0000000000000006
offset 3972
align 2^1 (2)
reloff 0
nreloc 0
flags 0x80000408
reserved1 0 (index into indirect symbol table)
reserved2 6 (size of stubs)
Section
sectname __stub_helper
segname __TEXT
addr 0x0000000100000f8c
size 0x000000000000001a
offset 3980
align 2^2 (4)
reloff 0
nreloc 0
flags 0x80000400
reserved1 0
reserved2 0
代碼7.2
yingfang:mach-o文件結構-src fangying$ xcrun dyldinfo -lazy_bind a1.out
lazy binding information (from lazy_bind part of dyld info):
segment section address index dylib symbol
__DATA __la_symbol_ptr 0x100001010 0x0000 libSystem _printf
代碼7.3
(gdb) x/2g 0x100001010
0x100001010: 4294971292
代碼7.4
(gdb) x/3i 4294971292
0x100000f9c: pushq $0x0
0x100000fa1: jmpq 0x100000f8c
代碼7.5
(gdb) x/3i 0x100000f8c
0x100000f8c: lea 0x75(%rip),%r11 # 0x100001008
0x100000f93: push %r11
0x100000f95: jmpq *0x65(%rip) # 0x100001000
代碼7.6
(gdb) x/3g 0x100001000
0x100001000: 0x0000000000000000 0x0000000000000000
0x100001010: 0x0000000100000f9c
可運行文件運行過程
依據上面總結。該段總結下可運行文件運行過程。
我總結例如以下:
- 解析mach-o文件
- 設置運行環境參數
- 文本段VM映射參數
- 載入命令
- 動態庫信息
- 符號表地址信息
- 動態符號表地址信息
- 常亮字符串表地址信息
- 動態庫載入信息
- 符號函數地址
- 依賴動態庫信息
- 動態鏈接器地址信息
- 依據動態庫載入信息,把樁占位符,填寫為指定調用_nl_symbol_ptr的匯編指令
- 依據LC_MAIN的entry point調用指定entry offset偏移地址
- 運行entry offset相關二進制(邏輯是依照匯編指令,進行運行)
- 第一次運行到動態庫函數時,進行一次懶載入動態綁定。而且動態鏈接器自己主動改動_la_symbol_ptr區的地址。指向動態庫相應符號的地址
- 第二次運行到動態庫函數時,直接jmp到指定的符號地址
>
註意:系統非常多動態庫都是共同擁有的,所以XOS做了共享庫緩存優化,僅僅要有相關進程使用過相關動態庫,在另一進程,動態鏈接器在填樁時。直接會把樁_la_symbol_ptr區的地址,指向動態庫相應符號的地址。
詳細看系統怎樣載入文件。動態鏈接文件,以及進入入口函數,能夠這裏
parse_machfile(
struct vnode *vp,
vm_map_t map,
thread_t thread,
struct mach_header *header,
off_t file_offset,
off_t macho_size,
int depth,
int64_t aslr_offset,
int64_t dyld_aslr_offset,
load_result_t *result
)
mach-o格式和載入方式 – 能夠做啥呢
基礎非常重要。mach-o格式理解和載入運行邏輯總結。能夠幫助我們正確認識到mac os和ios app可運行文件啟動過程。
基於該內容。能夠做的事情列一下
- category沖突分析
- 非OC函數switch
- bitcode分析
- 包支持架構分析
- 常量字符串分析,比方資源為無使用情況分析
- crash符號化
- 符號模塊查找
- 學習經典的數據格式,對於設計相關數據協議,網絡協議等等。也有非常好啟示和參考價值
反過來:假設二進制包越來越大。進程啟動速度也會越來越慢。因為讀取。解析文件格式會比較慢。
通過對於mach-o文件的分析,對於__TEXT系統都是僅僅讀區。程序代碼邏輯都在該區。可是也發現因為為了保持代碼棧形式的運行。系統對於動態庫的函數,不是直接調用動態庫的函數地址,而是先調用到數據段的_nl_symbol_ptr區。通過_nl_symbol_ptr的信息調用到__DATA段_la_symbol_ptr區,因為__DATA內存區。用戶態能夠去改動。因此我們能夠利用該特性去替換相關函數調用。詳細代碼我也在基於fishhook代碼總結中。
了解mach-o詳細的格式和載入,動態鏈接過程原理。比方mac上使用DYLD_INSERT_LIBRARIES進行代碼註入。或者使用fishhook進行c函數hook。都能非常easy理解。
Mach-O文件格式和程序從載入到運行過程