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文件格式和程序从加载到执行过程_第1张图片

简单浏览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,都能很容易理解。

你可能感兴趣的:(ios,mac)