Mach O - ShenYj/ShenYj.github.io GitHub Wiki

Mach-O

.

Mach-O简介

Mach-O (Mach Object) 是 macOSiOSiPad OS 存储程序和库的文件格式。
对应系统通过应用二进制接口(application binary interface,缩写为 ABI)来运行该格式的文件。

Mach-O 格式用来替代 BSD 系统的 a.out 格式。
Mach-O 文件格式保存了在 编译过程和链接过程中产生的机器代码和数据,从而为静态链接和动态 链接的代码提供了单一文件格式。

Mach-O = Mach Header(文件配置) + Load Commands + Raw segment data 二进制代码 (bss段、data段、text段)

  • Mach Header: 文件类型,目标架构类型等
    • Mach-O 文件也分为多种类型, 比如可执行文件、静态库文件/目标文件等,可以参考 XNU 源码查看具体类型
    • 使用 otool-h 参数可以查看 Mach Header 信息
  • Load Commands: 描述文件在虚拟内存中的逻辑结构、布局
    • 如常提到的内存五大区(text代码段、data数据段、bss段)这些段(即 Segment)就是通过 Load Commands 来描述的,有哪些段,每个段内有哪些信息
    • Segments(segment commands): 指定操作系统应该将 Segments 加载到内存中的什么位置,以及为该 Segments 分配的字节数。还指定文件中的哪些字节属于该 Segments,以及文件包含多少 sections. 始终是4096字节4 KB的倍数,其中4096字节是最小大小。
    • Section: 所有 Sections 都在每个 segment 之后一个接一个的描述。 Sections里面定义其名称,在内存中的地址,大小,文件中 section 数据的偏移量和 segment 名称。

      段之前始终是4096字节或4 KB的倍数,其中 4096字节是最小大小
      现在段是 16 KB的倍数,在macOS_x86_64上是16 KB,在 iOS 上是32KB

    • 使用 otool-l 参数可以查看 Load Commands 信息
  • Raw segment data: 在 Load Commands 中定义的 Segment 的原始数据
    • Load Commands 像是一个索引、目录,具体的数据在这里

otool

  • 部分参数说明

    -f print the fat headers
    -a print the archive header
    -h print the mach header
    -l print the load commands
    -L print shared libraries used
    -D print shared library id name
    -t print the text section (disassemble with -v)
    -x print all text sections (disassemble with -v)
    -p <routine name>  start dissassemble from routine name
    -s <segname> <sectname> print contents of section
    -d print the data section
    -o print the Objective-C segment
    -r print the relocation entries
    -S print the table of contents of a library (obsolete)
    -T print the table of contents of a dynamic shared library (obsolete)
    -M print the module table of a dynamic shared library (obsolete)
    -R print the reference table of a dynamic shared library (obsolete)
    -I print the indirect symbol table
    -H print the two-level hints table (obsolete)
    -G print the data in code table
    -v print verbosely (symbolically) when possible
    -V print disassembled operands symbolically
    -c print argument strings of a core file
    -X print no leading addresses or headers
    -m don't use archive(member) syntax
    -B force Thumb disassembly (ARM objects only)
    -q use llvm's disassembler (the default)
    -Q use otool(1)'s disassembler
    -mcpu=arg use `arg' as the cpu for disassembly
    -j print opcode bytes
    -P print the info plist section as strings
    -C print linker optimization hints

除了通过 otool 在终端内查看外,还可以借助 MachOView 工具查看

还可以使用 size -l -m -x 来查看 Mach-O 的内存分布

Segment __PAGEZERO: 0x100000000 (zero fill)  (vmaddr 0x0 fileoff 0)
Segment __TEXT: 0x48000 (vmaddr 0x100000000 fileoff 0)
    Section __text: 0x38670 (addr 0x100004c60 offset 19552)
    Section __stubs: 0x7fe (addr 0x10003d2d0 offset 250576)
    Section __swift5_typeref: 0x1217 (addr 0x10003dace offset 252622)
    Section __cstring: 0x272a (addr 0x10003ecf0 offset 257264)
    Section __const: 0x2480 (addr 0x100041420 offset 267296)
    Section __swift5_reflstr: 0x57a (addr 0x1000438a0 offset 276640)
    Section __swift5_fieldmd: 0x798 (addr 0x100043e1c offset 278044)
    Section __swift5_types: 0x114 (addr 0x1000445b4 offset 279988)
    Section __objc_methname: 0x1e07 (addr 0x1000446c8 offset 280264)
    Section __swift5_capture: 0x580 (addr 0x1000464d0 offset 287952)
    Section __swift5_proto: 0x124 (addr 0x100046a50 offset 289360)
    Section __swift5_assocty: 0x1d0 (addr 0x100046b74 offset 289652)
    Section __swift5_builtin: 0x1cc (addr 0x100046d44 offset 290116)
    Section __swift5_protos: 0x20 (addr 0x100046f10 offset 290576)
    Section __swift5_entry: 0x4 (addr 0x100046f30 offset 290608)
    Section __entitlements: 0x114 (addr 0x100046f34 offset 290612)
    Section __unwind_info: 0xd08 (addr 0x100047048 offset 290888)
    Section __eh_frame: 0x2b0 (addr 0x100047d50 offset 294224)
    total 0x4338c
Segment __DATA_CONST: 0x4000 (vmaddr 0x100048000 fileoff 294912)
    Section __got: 0x1120 (addr 0x100048000 offset 294912)
    Section __const: 0x1610 (addr 0x100049120 offset 299296)
    Section __objc_classlist: 0xb0 (addr 0x10004a730 offset 304944)
    Section __objc_catlist: 0x28 (addr 0x10004a7e0 offset 305120)
    Section __objc_protolist: 0x50 (addr 0x10004a808 offset 305160)
    Section __objc_imageinfo: 0x8 (addr 0x10004a858 offset 305240)
    total 0x2860
Segment __DATA: 0x8000 (vmaddr 0x10004c000 fileoff 311296)
    Section __objc_const: 0x1670 (addr 0x10004c000 offset 311296)
    Section __objc_selrefs: 0x5a0 (addr 0x10004d670 offset 317040)
    Section __objc_protorefs: 0x50 (addr 0x10004dc10 offset 318480)
    Section __objc_classrefs: 0x148 (addr 0x10004dc60 offset 318560)
    Section __objc_data: 0x2610 (addr 0x10004dda8 offset 318888)
    Section __data: 0xd30 (addr 0x1000503b8 offset 328632)
    Section __bss: 0x19c0 (addr 0x1000510f0 zerofill)
    Section __common: 0x1c0 (addr 0x100052ab0 zerofill)
    total 0x6c68
Segment __LINKEDIT: 0xa0000 (vmaddr 0x100054000 fileoff 344064)
total 0x1000f4000

上面的是一个 Swift 为主的项目,也包含少量 OC 代码

Segment __PAGEZERO: 0x100000000 (zero fill)  (vmaddr 0x0 fileoff 0)
Segment __TEXT: 0x4d0000 (vmaddr 0x100000000 fileoff 0)
    Section __text: 0x440430 (addr 0x1000036d0 offset 14032)
    Section __stubs: 0x984 (addr 0x100443b00 offset 4471552)
    Section __stub_helper: 0xfd8 (addr 0x100444484 offset 4473988)
    Section __gcc_except_tab: 0x78fc (addr 0x10044545c offset 4478044)
    Section __objc_methname: 0x43dd2 (addr 0x10044cd58 offset 4509016)
    Section __cstring: 0x2100b (addr 0x100490b30 offset 4786992)
    Section __ustring: 0xa870 (addr 0x1004b1b3c offset 4922172)
    Section __objc_classname: 0x452c (addr 0x1004bc3ac offset 4965292)
    Section __objc_methtype: 0x827c (addr 0x1004c08d8 offset 4983000)
    Section __const: 0x1350 (addr 0x1004c8b60 offset 5016416)
    Section __entitlements: 0x1b7 (addr 0x1004c9eb0 offset 5021360)
    Section __unwind_info: 0x39fc (addr 0x1004ca068 offset 5021800)
    Section __eh_frame: 0x2598 (addr 0x1004cda68 offset 5036648)
    total 0x4cc918
Segment __DATA: 0x190000 (vmaddr 0x1004d0000 fileoff 5046272)
    Section __nl_symbol_ptr: 0x8 (addr 0x1004d0000 offset 5046272)
    Section __got: 0x478 (addr 0x1004d0008 offset 5046280)
    Section __la_symbol_ptr: 0xcb0 (addr 0x1004d0480 offset 5047424)
    Section __mod_init_func: 0x18 (addr 0x1004d1130 offset 5050672)
    Section __const: 0x7808 (addr 0x1004d1150 offset 5050704)
    Section __cfstring: 0x242a0 (addr 0x1004d8958 offset 5081432)
    Section __objc_classlist: 0x1440 (addr 0x1004fcbf8 offset 5229560)
    Section __objc_nlclslist: 0x18 (addr 0x1004fe038 offset 5234744)
    Section __objc_catlist: 0xb0 (addr 0x1004fe050 offset 5234768)
    Section __objc_nlcatlist: 0x18 (addr 0x1004fe100 offset 5234944)
    Section __objc_protolist: 0x500 (addr 0x1004fe118 offset 5234968)
    Section __objc_imageinfo: 0x8 (addr 0x1004fe618 offset 5236248)
    Section __objc_const: 0xbad68 (addr 0x1004fe620 offset 5236256)
    Section __objc_selrefs: 0xd228 (addr 0x1005b9388 offset 6001544)
    Section __objc_protorefs: 0x18 (addr 0x1005c65b0 offset 6055344)
    Section __objc_classrefs: 0x1750 (addr 0x1005c65c8 offset 6055368)
    Section __objc_superrefs: 0xe30 (addr 0x1005c7d18 offset 6061336)
    Section __objc_ivar: 0x5ce8 (addr 0x1005c8b48 offset 6064968)
    Section __objc_data: 0x11278 (addr 0x1005ce830 offset 6088752)
    Section __llvm_prf_cnts: 0x1b258 (addr 0x1005e0000 offset 6160384)
    Section __llvm_prf_data: 0x458d0 (addr 0x1005fc000 offset 6275072)
    Section __llvm_prf_names: 0x15407 (addr 0x1006418d0 offset 6559952)
    Section __data: 0x4530 (addr 0x100656ce0 offset 6647008)
    Section __llvm_prf_vnds: 0x0 (addr 0x10065b210 offset 6664720)
    Section __bss: 0x2c38 (addr 0x10065b210 zerofill)
    Section __common: 0x88 (addr 0x10065de48 zerofill)
    total 0x18cbbf
Segment __LLVM_COV: 0x90000 (vmaddr 0x100660000 fileoff 6668288)
    Section __llvm_covfun: 0x7f327 (addr 0x100660000 offset 6668288)
    Section __llvm_covmap: 0xe0e4 (addr 0x1006df328 offset 7189288)
    total 0x8d40b
Segment __LINKEDIT: 0x39c000 (vmaddr 0x1006f0000 fileoff 7258112)
total 0x100a8c000

这是一个纯 OC 的项目

Mach Header 简介:

.

文件头 mach64 Header 文件类型、大小等信息
加载命令 Load Commands 指示加载器如何加载二进制文件
文本段 __TEXT 类似PE的.text段
数据段 __DATA 类似PE的.data段
动态库加载信息 Dynamic Loader Info -
入口函数 Function Starts -
符号表 Symbol Table -
动态库符号表 Dynamic Symbol Table -
字符串表 String Table _

通过两个 load commands:

  • LC_SYMTAB: 当前 Mach-O中的符号表信息
  • LC_DYSYMTAB: 描述动态链接器使用其他的 Symbol Table信息

用来描述 Symbol Table的大小和位置,以及其他元数据

Load Commands

二进制文件加载进内存要执行的一些指令。

这里的指令主要负责我们 App 对应进程的创建和基本设置(分配虚拟内存,创建主线程,处理代码签名/加密的工作),然后对动态链接库(.dylib系统库和我们自己创建的动态库)进行库加载和符号解析的工作。

可执行文件调用过程

  1. 调用fork函数,创建一个 process

    .

    • 在2017年就被引入至 iOS 11,当时主要用来优化系统库。现在,在 iOS 13 中它也将用于启动第三方APP,将完全替代 dyld2.

    • dyld2 是纯粹的 in-process,也就是在程序进程内执行的,也就意味着只有当应用程序被启动的时候,dyld2 才能开始执行任务。

    • dyld3 则是部分 out-of-process,部分 in-process。图中,虚线之上的部分是 out-of-process 的,在App下载安装和版本更新的时候会去执行,out-of-process 会做如下事情:

      • 分析 Mach-o Headers
      • 分析依赖的动态库
      • 查找需要 Rebase & Bind 之类的符号
        • Rebase: 修正内部(指向当前mach-o文件)的指针指向
        • Bind: 修正外部指针指向
      • 把上述结果写入缓存

      这样,在应用启动的时候,就可以直接从缓存中读取数据,加快加载速度。

  2. 调用 execve 或其衍生函数,在该进程上加载,执行我们的 Mach-O 文件

    • 当我们调用时 execve(程序加载器),内核实际上在执行以下操作
    1. 将文件加载到内存
    2. 开始分析 Mach-O 中的 mach_header,以确认它是有效的 Mach-O 文件

扩展

虽然macOS系统使用了很多UNIX上的特性,但它并没有使用ELF作为系统的可执行文件格式,而是使用自家独创的Mach-O文件格式。

苹果一路走来支持的CPU及硬件平台变化较大, 从PowerPC平台 -> x86 -> ARMx86-64

为了解决软件在多个硬件平台上的兼容性问题,苹果开发了一个通用的二进制文件格式(Universal Binary)。 又称为胖二进制(Fat Binary),通用二进制文件中将多个支持不同CPU架构的二进制文件打包成一个文件,系统在加载运行该程序时,会根据通用二进制文件中提供的多个架构来与当前系统平台做匹配,运行适合当前系统的那个版本。

通用二进制的“通用”不止针对可以直接运行的可执行程序,系统中的动态库dylib、静态库.a文件以及框架等都可以是通用二进制文件,对它们也可以同样使用lipo命令来进行管理。

符号与符号表 iOS Memory Deep Dive Mach-O Programming Topics

⚠️ **GitHub.com Fallback** ⚠️