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(可执行文件)。

可执行文件.png

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

静态库 动态库
假设本地库Home模块.png
AFNetworking.png

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. 有哪些类型

我们可以在Xcode.app查看到Mach-O的类型定义如下,路径为:
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/mach-o/loader.h

#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文件结构

项目 内容
结构图
Mach-O文件结构.png
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>

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

源码头文件地址:
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/mach-o/loader.h

1. Header

源码中结构

struct mach_header_64 {
    uint32_t    magic;      /* mach magic number identifier 确定是64位还是32位 */
    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来查看有哪些段命令.

项目 内容
加载命令
加载命令段.png
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.1 每个命令包含的内容

我们在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中的.

段数据部分

代码段和数据段的各个节分别代表什么可以通过这篇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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,874评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,151评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,270评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,137评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,116评论 5 370
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,935评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,261评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,895评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,342评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,854评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,978评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,609评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,181评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,182评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,402评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,376评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,677评论 2 344

推荐阅读更多精彩内容