1. 程式人生 > >Mach-O文件格式和程序從載入到運行過程

Mach-O文件格式和程序從載入到運行過程

height star 也會 linked trail dylib 建立 helper cmd

>
之前深入了解過。過去了一年多的時間。如今花些時間好好總結下,畢竟好記性不如爛筆頭。

其次另一個目的,對於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。該段數據信息為一個指針
結合數據段2的數據,獲得一個重定向符號指針[0x100001010 -> _printf]

綁定數據 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文件格式和程序從載入到運行過程