Post

App 的本质和 Mach-O 文件

进程

App 的本质是一个可执行程序,是一段计算机代码和数据的集合。从操作系统的角度来看,App 的本质是一个进程。进程是计算机中正在运行的程序的实例。在操作系统中,进程是操作系统分配资源和调度执行的基本单位。每个进程都有自己的内存空间、寄存器集合、文件句柄、网络连接等资源,它们可以独立地运行和被管理。

进程是操作系统中最基本的资源分配和调度单位。操作系统通过进程控制块 PCB(Process Control Block) 来管理进程,PCB 包含了进程的状态、进程 ID、进程优先级、内存使用情况、文件句柄等信息。当操作系统需要切换到另一个进程时,它会保存当前进程的上下文,然后加载另一个进程的上下文,从而实现进程之间的切换。

macOS 中,PCB 被称为 procproc 结构体macOS 内核中非常重要的数据结构,用于描述进程在内核中的状态和信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct proc {
    LIST_ENTRY(proc) p_list;                /* List of all processes. */

    void *          XNU_PTRAUTH_SIGNED_PTR("proc.task") task;       /* corresponding task (static)*/
    struct  proc *  XNU_PTRAUTH_SIGNED_PTR("proc.p_pptr") p_pptr;   /* Pointer to parent process.(LL) */
    pid_t           p_ppid;                 /* process's parent pid number */
    pid_t           p_original_ppid;        /* process's original parent pid number, doesn't change if reparented */
    pid_t           p_pgrpid;               /* process group id of the process (LL)*/
    uid_t           p_uid;
    gid_t           p_gid;
    uid_t           p_ruid;
    gid_t           p_rgid;
    uid_t           p_svuid;
    gid_t           p_svgid;
    uint64_t        p_uniqueid;             /* process unique ID - incremented on fork/spawn/vfork, remains same across exec. */
    uint64_t        p_puniqueid;            /* parent's unique ID - set on fork/spawn/vfork, doesn't change if reparented. */

    lck_mtx_t       p_mlock;                /* mutex lock for proc */
    pid_t           p_pid;                  /* Process identifier. (static)*/
    char            p_stat;                 /* S* process status. (PL)*/
    char            p_shutdownstate;
    char            p_kdebug;               /* P_KDEBUG eq (CC)*/
    char            p_btrace;               /* P_BTRACE eq (CC)*/
    /* 以下其他字段已省略 */
};

proc 包含了大量的字段和指针,用于描述进程的各种属性和资源使用情况,例如进程状态(p_stat)、进程 ID(p_pid)、进程名称(p_comm)、进程优先级(p_priority)、进程内存使用情况(p_vmspace)、文件描述符表(p_fd)、线程列表(p_threadlist)等。

Mach-O 文件

在 App 加载到内存成为进程之前,macOS 上的可执行文件是 Mach-O 文件。Mach-O 文件包含了可执行代码、数据、符号表、动态链接信息等多个部分,是 macOS 中应用程序和库文件的基本格式。

Mach-O 文件的格式可以分为文件头(Header)、加载命令(Load commands)和数据区(Raw segment data)三个部分。

Mach-O Mach-O file

包含多个 CPU 架构的 Mach-O 文件被称为 Fat Binary。可以通过 file 命令查看 Mach-O 文件的 CPU 架构。

1
2
3
4
$ file /System/Applications/Calculator.app/Contents/MacOS/Calculator
/System/Applications/Calculator.app/Contents/MacOS/Calculator: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [arm64e:Mach-O 64-bit executable arm64e]
/System/Applications/Calculator.app/Contents/MacOS/Calculator (for architecture x86_64):	Mach-O 64-bit executable x86_64
/System/Applications/Calculator.app/Contents/MacOS/Calculator (for architecture arm64e):	Mach-O 64-bit executable arm64e

Fat Binary 对应的 fat_header 在操作系统中的数据结构定义是 fat_header

1
2
3
4
5
6
7
8
9
10
11
12
struct fat_header {
	uint32_t	magic;		/* FAT_MAGIC */
	uint32_t	nfat_arch;	/* number of structs that follow */
};
// Fat Binary 包含了多个 fat_arch 组成的 Mach-O 文件
struct fat_arch {
	cpu_type_t	cputype;	/* cpu specifier (int) */
	cpu_subtype_t	cpusubtype;	/* machine specifier (int) */
	uint32_t	offset;		/* file offset to this object file */
	uint32_t	size;		/* size of this object file */
	uint32_t	align;		/* alignment as a power of 2 */
};

可以看到,macOS 系统的计算器程序 Mach-O 文件,是包含了 x86_64arm64e 两种 CPU 架构的 Fat Binary

iOS 系统自 iOS 11.0 版本以后,不再支持 armv7armv7s 等架构,只支持 arm64 架构。所以仅支持 iOS 11.0 以后版本的项目,打包产物的 Fat Binary 只有 arm64 架构的 Mach-O 文件。这也是 Xcode 14.0 取消了 bitcode 的原因,因为不再需要编译成 bitcode 中间产物,且 bitcode 转译会消耗 AppStore 的服务器资源

文件头(haeder)

Mach-O 文件头包含了文件类型、CPU 类型、加载命令数量等信息。Mach-O 文件支持多种文件类型,包括可执行文件、动态链接库、框架等。CPU 类型指定了可执行文件适用的 CPU 架构,例如 x86x86_64armv7arm64 等。加载命令数量指定了文件中包含的加载命令数量。

otool 命令是 macOSiOS 等操作系统上的一个工具,用于查看可执行文件、动态库和框架等二进制文件的信息。它可以用来查看二进制文件的头部信息、节表、符号表、动态链接信息等。

1
2
3
4
5
$ otool -h /System/Applications/Calculator.app/Contents/MacOS/Calculator
/System/Applications/Calculator.app/Contents/MacOS/Calculator:
Mach header
      magic  cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
 0xfeedfacf 16777228          2  0x80           2    29       4208 0x00200085

除了 otools 命令,还可以使用 MachOView 工具通过图形化页面来查看 Mach-O 文件

1
brew install machoview

MachOView MachOView

Fat Binary 中,每个架构都有一个 header 文件头,被称为 mach_header。64 位架构的 mach_header 会多一个保留字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/*
 * 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) */

加载命令(Load commands)

Mach-O 文件中的加载命令(Load Command)用于描述可执行文件的各个段的属性和位置等信息,操作系统会根据这些信息将可执行文件加载到内存中。每个 Load Command 描述了一个特定的段或区域。常见的 Load Command 包括:

  • LC_SEGMENTLC_SEGMENT_64:描述可执行代码和数据的段信息;
  • LC_SYMTABLC_DYSYMTAB:描述符号表和动态符号表信息;
  • LC_LOAD_DYLIBLC_LOAD_WEAK_DYLIB:描述动态链接库的信息;
  • LC_MAIN:描述程序入口点的信息。

load_command

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

数据区(Raw segment data)

Mach-O 文件的数据区包含了多个段(Segment),每个段包含了不同类型的数据。常见的段包括 __TEXT__DATA__LINKEDIT 等。其中,__TEXT 段包含了代码和只读数据,__DATA 段包含了全局变量和静态变量等数据,__LINKEDIT 段包含了符号表和重定位信息等。

segment

1
2
3
4
5
6
7
8
9
10
11
12
13
struct segment_command_64 { /* for 64-bit architectures */
	uint32_t	cmd;		/* LC_SEGMENT_64 */
	uint32_t	cmdsize;	/* includes sizeof section_64 structs */
	char		segname[16];	/* segment name */
	uint64_t	vmaddr;		/* memory address of this segment */
	uint64_t	vmsize;		/* memory size of this segment */
	uint64_t	fileoff;	/* file offset of this segment */
	uint64_t	filesize;	/* amount to map from the file */
	int32_t		maxprot;	/* maximum VM protection */
	int32_t		initprot;	/* initial VM protection */
	uint32_t	nsects;		/* number of sections in segment */
	uint32_t	flags;		/* flags */
};

Mach-O 文件中,每个 Segment 包含一个或多个 section,每个 section 包含了一组相关的数据或代码。例如,在一个可执行文件中,常见的 Segment 包括 __TEXT、__DATA__LINKEDIT 等,每个 Segment 包含了多个 section,例如 __TEXT 包含了 __text__cstring__stub 等多个 section

SectionMach-O 文件中的一个子单元,它是 Segment 中的一个子段,包含了一组相关的数据或代码。每个 Section 都有一个名称和一个类型,例如 __text__data__cstring 等。在 Mach-O 文件中,Section 的名称和类型通常与编译器和链接器相关,不同的编译器和链接器可能会使用不同的名称和类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 */
};

参考资料

  1. Mach-O Programming Topics
  2. osx-abi-macho-file-format-reference
  3. 深入剖析Macho
  4. ChatGPT
This post is licensed under CC BY 4.0 by the author.