期望经过本文来记载关于iOS开发对Mach-O需求有的根本了解。

苹果推出Mach-O的背景:

  1. 过渡至根据 Mach 内核的操作体系:苹果于 2001 年推出了 macOS(其时称为 Mac OS X)操作体系,该操作体系采用了根据 Mach 内核的架构。为了习惯新的操作体系架构,苹果需求引进一种新的文件格局来支撑该架构,用于可履行文件、静态库和动态库的存储和交互。
  2. 提高功能和可扩展性:Mach-O 文件格局相关于旧的方针文件格局(如 a.out 格局)具有更好的功能和可扩展性。它采用了愈加紧凑和高效的数据结构,使得应用程序的加载、链接和履行更高效。这在日益复杂的应用程序和需求下变得尤为重要。
  3. 支撑 Objective-C 和 Cocoa 结构:苹果广泛采用 Objective-C 编程语言和 Cocoa 结构来开发 macOS 和 iOS 上的应用程序。Mach-O 文件格局与 Objective-C 运转时和 Cocoa 结构的集成严密相关,使得开发者可以更好地运用这些技能进行应用程序开发。
  4. 跨渠道支撑和移植性:Mach-O 文件格局不仅用于 macOS 和 iOS,还可以支撑其他根据 Darwin 内核的操作体系,如 tvOS 和 watchOS。这种共同的文件格局使得开发者可以更方便地在不同的苹果渠道上同享和移植代码,提高开发功率和代码复用性。
  5. 操作体系集成:Mach-O 文件格局与苹果操作体系的内核(XNU)严密集成。苹果控制了 Mach-O 格局的标准和解析器,然后使得操作体系和应用程序可以更严密地进行交互和整合。

一、认识Mach-O

Xcode工程中,咱们可以看到编译设置里边有一个Mach-O type, 可以看到主工程的格局是Executable(可履行文件)。

iOS中Mach-O概览

而在组件化工程里,有一些本地或私有库咱们可能会在podspec中声明s.static_framework = true,这样就会是静态库;三方库没有这个声明默认是动态库。

静态库 动态库
iOS中Mach-O概览
iOS中Mach-O概览

Mach-OMach Object 的缩写,它是Mac/iOS 中用于存储程序、库的标准格局。作为 a.out 格局的代替,Mach-O 供给了更强的扩展性,并提升了符号表中信息的拜访速度。

类型 代表文件
Executable(可履行文件) xxx.app/xxx、推送扩展
Dynamic Library(动态库文件) .dylib(一般是体系动态库)和xxx.framework/xxx (三方动态库)
Bundle 一种特定结构的文件夹,可以包括可履行文件、动态库、静态库和各种资源文件,以及配置文件等,一般作为插件或扩展。需经过dlopen加载。
Static Library 静态库文件(.a文件,是多个.o文件的集合),如pod库声明s.static_framework = true,产物是静态结构
Relocatable Object File 方针文件(.o文件,编译源代码得到的中心文件)

二、Mach-O的类型

1. 有哪些类型

咱们可以在Xcodeshift + command + o,输入loader.h,可在文件中检查到Mach-O的类型界说如下,

#define	MH_OBJECT	0x1		/* relocatable object file */
#define	MH_EXECUTE	0x2		/* demand paged executable file */
#define	MH_FVMLIB	0x3		/* fixed VM shared library file */
#define	MH_CORE		0x4		/* core file */
#define	MH_PRELOAD	0x5		/* preloaded executable file */
#define	MH_DYLIB	0x6		/* dynamically bound shared library */
#define	MH_DYLINKER	0x7		/* dynamic link editor */
#define	MH_BUNDLE	0x8		/* dynamically bound bundle file */
#define	MH_DYLIB_STUB	0x9		/* shared library stub for static linking only, no section contents */
#define	MH_DSYM		0xa		/* companion file with only debug  sections */
#define	MH_KEXT_BUNDLE	0xb		/* x86_64 kexts */
#define MH_FILESET	0xc		/* a file composed of other Mach-Os to be run in the same userspace sharing a single linkedit. */
#define	MH_GPU_EXECUTE	0xd		/* gpu program */
#define	MH_GPU_DYLIB	0xe		/* gpu support functions */

2. 常见的Mach-O类型

MH_OBJECT:方针文件即 .o 文件 以及静态库文件即 .a 文件(多个.o文件兼并在一起);

MH_EXECUTE:可履行文件,即App编译运转后生成的可履行文件,在/Products途径下;

MH_DYLIB:动态库文件,即.dylib文件 或许 .framework文件;

MH_DYLINKER:/usr/lib/dyld途径下的dyld文件;

MH_DSYM:Xcode打包后生成的符号表文件,即.dSYM文件;

3. 怎么检查mach-o文件类型

找到咱们的APP后可经过指令行检查类型。

  • 经过file指令
// 1.检查APP的可履行文件
file xxx.app/xxx
输出: Mach-O 64-bit executable arm64
// 2.检查pod三方库的machO
file xxx.app/Frameworks/AFNetworking.framework/AFNetworking
输出:Mach-O 64-bit dynamically linked shared library arm64

三、Mach-O文件结构

项目 内容
结构图
iOS中Mach-O概览
Header 包括Mach-O文件的根本信息,例如文件类型,支撑的CPU架构类型,加载指令的数量,所占内存巨细等
Load Command 不同数据段segment的加载指令,辅导加载器加载数据
Data 在Load Command中界说的Segment的原始数据。

四、检查Mach-O的方式

otool -l <file>:显现 Mach-O 文件的加载指令信息。
otool -t <file>:显现 Mach-O 文件的文本节信息。
otool -L <file>:显现 Mach-O 文件的依靠库信息。
运用 man otool 指令检查 otool 的帮助文档
  • lipo指令
lipo -info 文件 // 检查架构信息
lipo <file> -thin 方针架构 -output 输出文件 // 导出某种架构
lipo <file1> <file2> -output 输出文件 // 兼并多个架构
  • objdump指令
objdump --macho --private-headers <file>

五、文件结构中各部分内容细节

还是可以从loader.h文件中找源码界说。

1. Header

源码中结构

struct mach_header_64 {
    uint32_t magic; /* mach magic number identifier */
    int32_t cputype; /* cpu specifier */
    int32_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 */
};

经过otool指令检查一个Mach-O的头部信息

otool -hv xxx
Mach header
   magic  cputype  cpusubtype  caps  filetype ncmds sizeofcmds  flags
MH_MAGIC_64 ARM64        ALL  0x00    EXECUTE   138      13384 NOUNDEFS DYLDLINK TWOLEVEL WEAK_DEFINES BINDS_TO_WEAK PIE

2. Load Commands

加载指令段,这部分的作用是实质便是确定怎么加载段segment数据,主结构是:

struct load_command {
    uint32_t cmd; /* type of load command */
    uint32_t cmdsize; /* total size of command in bytes */
};

可以经过MachOView来检查有哪些段指令:

项目 内容
加载指令
iOS中Mach-O概览
LC_SEGMENT_64 segment段加载指令
LC_DYLD_INF0_0NLY 加载动态链接库信息(重定向地址、弱引用绑定、懒加载绑定、敞开函数等的偏移值等信息)
2.1 其中LC_SEGMENT_64指令的结构
struct segment_command { /* for 32-bit architectures */
    uint32_t    cmd;        /* LC_SEGMENT  加载指令的类型*/
    uint32_t    cmdsize;    /* includes sizeof section structs  加载指令的所占内存巨细*/
    char        segname[16];    /* segment name */
    uint32_t    vmaddr;     /* memory address of this segment 段Segment的虚拟内存地址*/
    uint32_t    vmsize;     /* memory size of this segment 段Segment的虚拟内存巨细*/
    uint32_t    fileoff;    /* file offset of this segment 段Segment的在文件中的偏移量*/
    uint32_t    filesize;   /* amount to map from the file 段Segment在文件中所占的内存巨细*/
    vm_prot_t   maxprot;    /* maximum VM protection 表明页面所需求的最高内存维护*/
    vm_prot_t   initprot;   /* initial VM protection 表明页面初始的内存维护*/
    uint32_t    nsects;     /* number of sections in segment 段Segment包括节区sections的数量*/
    uint32_t    flags;      /* flags 表明段的标志信息*/
}
  • 结构体中segname是加载方针段Segment的称号,常见的段segment有四个(可以从上图中看到)

    __PAGEZERO : 在可履行文件有的,动态库里没有,这个段开端地址为0(NULL指针指向的方位),是一个不行读、不行写、不行履行的空间,可以在空指针拜访时抛出异常。

    __TEXT:代码段,里边首要是存放代码的,该段是可读可履行,可是不行写;

    __DATA:数据段,里边首要是存放数据,该段是可读可写,但不行履行;

    __LINKEDIT:用于存放签名信息,该段是只可读,不行写不行履行;

2.2 每个指令包括的内容

咱们在MachOView展开LC_SEGMENT_64(__TEXT)LC_SEGMENT_64(__DATA),可以看到许多的section header. 这个是数据段和代码段的各个section 的头文件。

struct section_64 { /* for 64-bit architectures */
    char sectname[16]; /* name of this section */
    char segname[16]; /* segment this section goes in */
    uint64_t addr; /* memory address of this section */
    uint64_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) */
    uint32_t reserved3; /* reserved */
};

sectname:section的称号,常见的section有_text、stubs等等;

segname :当时section所隶属的Segment,例如__TEXT(代码段);

addr : section在内存的开端方位;

size: section所占内存巨细;

offset: section在文件中的偏移量;

align:字节巨细对齐,2的align次方;

reloff:重定位进口的文件偏移;

nreloc: 需求重定位的进口数量;

flags:包括section的type和attributes;

3. Data部分

Data部分首要放的是__Text段和__DATA段的数据,根据不同功能分为不同的节(section)。 段数据的头部信息是存放在Load Commands中的.

iOS中Mach-O概览
代码段和数据段的各个节分别代表什么可以经过这篇linkmap文章来了解 经过LinkMap来了解Mach-O

六、MachO里边各部分占用巨细

咱们还可以经过size指令行检查一个Mach-O巨细信息:

// 假定app工程名xxx,
// -l 参数可以显现方针文件的完整节(section)和段(segment)信息
// -m 参数用于指定方针文件的格局
size -l -m xxx.app/xxx 
输出文件信息包括四个部分:
Segment __PAGEZERO
Segment __TEXT
Segment __DATA
Segment __LINKEDIT
详细的信息可以打印你APP的对照看

接下来看看每个段的打印信息详细是什么意思。

  • Segment __PAGEZERO: 4294967296 (zero fill) (vmaddr 0x0 fileoff 0) 该段信息指的是一个名为 __PAGEZERO 的段(segment),其巨细为 4GB,对应的是零填充的内存。__PAGEZERO 段的虚拟内存地址(vmaddr)为 0x0,文件偏移量(fileoff)为 0。
  • Segment __TEXT: 43319296 (vmaddr 0x100000000 fileoff 0). 该段信息指的是一个名为 __TEXT 的段(segment),其巨细为 约等于 41.31 MB。__TEXT 段的虚拟内存地址(vmaddr)为 0x100000000,文件偏移量(fileoff)为 0。虚拟内存地址指示了段在程序运转时被加载到内存的方位,而文件偏移量指示了段在方针文件中的方位。
  • Segment __DATA: 5849088 (vmaddr 0x102950000 fileoff 43319296) 该段信息指的是一个名为 __DATA 的段(segment),其巨细约等于 5.572 MB。__DATA 段的虚拟内存地址(vmaddr)为 0x102950000,文件偏移量(fileoff)为 43319296。
  • Segment __LINKEDIT: 2736128 (vmaddr 0x102ee4000 fileoff 48840704) 该段信息指的是一个名为 __LINKEDIT 的段(segment),其巨细约等于 2.61 MB。__LINKEDIT 段的虚拟内存地址(vmaddr)为 0x102ee4000,文件偏移量(fileoff)为 48840704。

1. __PAGEZERO

在 Mach-O 文件格局中,__PAGEZERO 段用于标识虚拟内存空间的开端方位,并指示该段之前的内存区域应该被清零填充。__PAGEZERO(又称为 Page Zero)是一个特殊的节(Section)称号,它在 Mach-O(Mach Object)文件中界说了虚拟地址空间的第一个页。它没有任何实践的可履行代码或数据,仅作为一种占位符存在,用于确保虚拟内存空间的连续性和维护。

一些首要的特点和作用如下:

  • 安全维护:__PAGEZERO 节的存在是为了供给一种安全机制,用于检测和避免针对软件缝隙的攻击。经过将可履行文件或可加载文件的第一个页设置为没有权限的页,可以避免非法拜访者运用指向第一个页的指针进行缝隙运用。

  • 空节:__PAGEZERO 节自身不包括实践的代码或数据。它的巨细一般为0字节,所以在履行时不会占用任何实践内存。

  • 地址空间布局:__PAGEZERO 节一般位于可履行文件或可加载文件的开端方位,即位于虚拟地址空间的最低部分。它的存在确保了后续节的虚拟地址是从一个清晰界说的方位开端的。

2.__TEXT

在 Mach-O 文件格局中,__TEXT 段包括了可履行程序的实践代码和只读数据。它是二进制文件中的一个重要段,存储了程序的代码段和只读数据段。

3.__DATA

在 Mach-O 文件格局中,__DATA 段存储了可履行程序的静态变量和全局变量等数据。它包括了程序在运转时的可写数据段,即存储程序在运转过程中产生的数据的空间。

4. __LINKEDIT

在 Mach-O 文件格局中,__LINKEDIT 段存储与链接器相关的信息,比方符号表、重定位信息等。链接器首要担任将不同的方针文件兼并成可履行文件,__LINKEDIT 段存储与该过程相关的一些信息,因此该段也被称为链接器的信息段。

详细来说,__LINKEDIT 段包括以下内容:

  • 符号表(Symbol Table):用于存储程序中界说和引用的各种符号(变量、函数、类等)的信息,链接器经过符号表进行符号解析和重定位等操作。
  • 字符串表(String Table):存储符号表中的字符串,用于标识符号的称号。
  • 动态符号表(Dynamic Symbol Table):包括一些在运转时动态加载的符号信息。
  • 重定位表(Relocation Table):存储在链接过程中需求进行地址重定位的部分,包括指令中需求修改的地址和重定位类型等信息。 __LINKEDIT 段的存在使得链接器可以在程序运转时解析和重定位符号,然后正确地衔接和加载各种模块。

相关资料: Mach-O入门理解 Mach-O iOS逆向06 — Mach-O