iOS中Mach-O概览

希望通过本文来记录对于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(可执行文件)。

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

静态库 动态库

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的类型定义如下,

C++ 复制代码
#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命令
arduino 复制代码
// 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文件结构

项目 内容
结构图
Header 包含Mach-O文件的基本信息,例如文件类型,支持的CPU架构类型,加载指令的数量,所占内存大小等
Load Command 不同数据段segment的加载命令,指导加载器加载数据
Data 在Load Command中定义的Segment的原始数据。

四、查看Mach-O的方式

xml 复制代码
otool -l <file>:显示 Mach-O 文件的加载命令信息。
otool -t <file>:显示 Mach-O 文件的文本节信息。
otool -L <file>:显示 Mach-O 文件的依赖库信息。
使用 man otool 命令查看 otool 的帮助文档
  • lipo命令
scss 复制代码
lipo -info 文件 // 查看架构信息
lipo <file> -thin 目标架构 -output 输出文件 // 导出某种架构
lipo <file1> <file2> -output 输出文件 // 合并多个架构
  • objdump命令
css 复制代码
objdump --macho --private-headers <file>

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

还是可以从loader.h文件中找源码定义。

1. Header

源码中结构

arduino 复制代码
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的头部信息

css 复制代码
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数据,主结构是:

arduino 复制代码
struct load_command {
    uint32_t cmd; /* type of load command */
    uint32_t cmdsize; /* total size of command in bytes */
};

可以通过MachOView来查看有哪些段命令:

项目 内容
加载命令
LC_SEGMENT_64 segment段加载指令
LC_DYLD_INF0_0NLY 加载动态链接库信息(重定向地址、弱引用绑定、懒加载绑定、开放函数等的偏移值等信息)
... ...
2.1 其中LC_SEGMENT_64命令的结构
arduino 复制代码
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 的头文件。

arduino 复制代码
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中的. 代码段和数据段的各个节分别代表什么可以通过这篇linkmap文章来了解 通过LinkMap来了解Mach-O

六、MachO里面各部分占用大小

我们还可以通过size命令行查看一个Mach-O大小信息:

arduino 复制代码
// 假设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

相关推荐
2402_857589368 小时前
Spring Boot编程训练系统:实战开发技巧
数据库·spring boot·性能优化
爱搞技术的猫猫9 小时前
实现API接口的自动化
大数据·运维·数据库·性能优化·自动化·产品经理·1024程序员节
EterNity_TiMe_14 小时前
【论文复现】STM32设计的物联网智能鱼缸
stm32·单片机·嵌入式硬件·物联网·学习·性能优化
saturday-yh16 小时前
性能优化、安全
前端·面试·性能优化
青云交1 天前
大数据新视界 -- 大数据大厂之 Impala 性能优化:基于数据特征的存储格式选择(上)(19/30)
大数据·性能优化·金融数据·impala·存储格式选择·数据特征·社交媒体数据
数据智能老司机1 天前
Rust原子和锁——Rust 并发基础
性能优化·rust·编程语言
A5rZ1 天前
CTF-RE 从0到N: windows反调试-获取Process Environment Block(PEB)信息来检测调试
逆向
2401_857026231 天前
Spring Boot编程训练系统:性能优化实践
spring boot·后端·性能优化
数据智能老司机2 天前
Rust中的异步编程——创建我们自己的Fiber
性能优化·rust·编程语言
青云交2 天前
大数据新视界 -- 大数据大厂之 Impala 性能优化:为企业决策加速的核心力量(下)(14/30)
大数据·性能优化·impala·查询优化·企业决策·数据整合·系统融合