前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Mach-O文件结构

Mach-O文件结构

作者头像
梧雨北辰
发布2021-11-24 15:23:35
1.3K0
发布2021-11-24 15:23:35
举报
文章被收录于专栏:梧雨北辰的开发录

主要内容:

  1. 理解可执行文件
  2. 理解Mach-O文件
  3. Mach-O文件结构
  4. Mach Header
  5. Load Commands
  6. Data
  7. 理解大小端模式
  8. 理解通用二进制文件
一、理解可执行文件
1.可执行文件
  1. 进程,其实就是可执行文件在内存中加载得到的结果;
  2. 可执行文件必须是操作系统可理解的格式,而且不同系统的可执行文件的格式也是不同的;
2.不同平台的可执行文件
  • Linux:ELF文件
  • WindowsPE32/PE32+文件
  • OS和iOSMach-O(Mach Object)文件
二、理解Mach-O文件

作为iOSiPadOSmacOS平台的可执行文件格式,Mach-O文件涉及App启动运行、bitcode分析、 crash符号化等诸多多个功能:

1. Mach-O文件
  1. Mach-O文件是iOSiPadOSmacOS平台的可执行文件格式。对应系统通过应用二进制接口(application binary interface,缩写为ABI)来运行该格式的文件;
  2. Mach-O格式用来替代BSD系统中的a.out格式,保存了在编译和链接过程中产生的机器代码和数据,从而为静态链接和动态链接的代码提供单一文件格式。
  3. Mach-O提供了更强的扩展性,以及更快的符号表信息访问速度;
2.Mach-O格式的常见文件类型
  1. Executable:可执行文件(.out .o);
  2. Dylib:动态链接库;
  3. Bundle:不能被链接,只能在运行时使用dlopen()加载;
  4. Image:包含ExecutableDylibBundle
  5. Framework:包含Dylib、资源文件和头文件的文件夹;
三、Mach-O文件结构
1.查看Mach-O的两种方法
  1. 使用MachOView软件,可直接查看MachO文件的结构;
  2. 使用终端命令objdump
2.查看Mach-O文件结构

使用MachOView查看Mach-O,效果如下:

Mach-O文件中包含三个主要的部分:

  1. Header:头部,描述CPU类型、文件类型、加载命令的条数大小等信息;
  2. Load Commands:加载命令,其条数和大小已经在header中被提供;
  3. Data:数据段;

其他的信息还有:

  1. Dynamic Loader Info:动态库加载信息
  2. Function Starts:入口函数
  3. Symbol Table:符号表
  4. Dynamic Symbol Table: 动态库符号表
  5. String Table:字符串表
四、Mach Header(可执行文件头)
1.功能总结
  1. Header是链接器加载时最先读取的内容,因为它决定了一些基础架构系统类型等信息;
  2. Header包含整个Mach-O文件的关键信息,如CPU类型文件类型加载命令的条数大小等信息,使得系统能够迅速定位Mach-O文件的运行环境;
  3. Header针对32位和64位架构的CPU,分别对应mach_headermach_header_64的结构体;
2.源码分析

Header被定义在loader.h文件中,具体代码如下:

代码语言:javascript
复制
struct mach_header_64 {
    uint32_t    magic;          // 32位或者64位,系统内核用来判断是否是mach-o格式
    cpu_type_t  cputype;        // CPU架构类型,比如ARM
    cpu_subtype_t   cpusubtype; // CPU的具体类型,例如arm64、armv7
    uint32_t    filetype;       // mach-o文件类型, 可执行文件、目标文件或者静态库和动态库
    uint32_t    ncmds;          // LoadCommands加载命令的条数(加载命令紧跟header之后)
    uint32_t    sizeofcmds;     // 全部LoadCommands加载命令的大小
    uint32_t    flags;          // 标志位标识二进制文件支持的功能,主要是和系统加载、链接有关
    uint32_t    reserved;       // 保留字段(相比于32位多出的字段)
    };

由于可执行文件目标文件或者静态库动态库等都是Mach-O格式,所以才需要filetype来说明。常用的文件类型有以下几种:

代码语言:javascript
复制
#define MH_OBJECT   0x1     /* 目标文件*/
#define MH_EXECUTE  0x2     /* 可执行文件*/
#define MH_DYLIB    0x6     /* 动态库*/
#define MH_DYLINKER 0x7     /* 动态链接器*/
#define MH_DSYM     0xa     /* 存储二进制文件符号信息,用于debug分析*/
3.MachOView演示
五、分析Load Commands
1.功能总结
  1. Load Commands是加载命令的列表,用于描述Data在二进制文件和虚拟内存中的布局信息;
  2. Load Commands记录了很多信息,例如动态链接器的位置、程序的入口、依赖库的信息、代码的位置、符号表的位置等;
  3. Load commands由内核定义,不同版本的command数量不同,其条数和大小记录在header中;
  4. Load commandstype是以LC_为前缀常量,譬如LC_SEGMENTLC_SYMTAB等;
2..代码分析

Load Command被定义在loader.h文件中,具体代码如下:

代码语言:javascript
复制
struct load_command {
    uint32_t cmd;       /* 加载命令的类型 */
    uint32_t cmdsize;   /* 加载命令的大小 */
};

每个Load Command都有独立的结构,但是所有结构的前两个字段是固定的。比如LC_SEGMENT_64,这是一个读取segmentsection有关命令,具体代码如下:

代码语言:javascript
复制
struct segment_command_64 { /* for 64-bit architectures */
    uint32_t    cmd;          // 表示加载命令类型
    uint32_t    cmdsize;      // 表示加载命令大小(还包括了紧跟其后的nsects个section的大小)
    char        segname[16];  // 16个字节的段名字
    uint64_t    vmaddr;       // 段的虚拟内存起始地址
    uint64_t    vmsize;       // 段的虚拟内存大小
    uint64_t    fileoff;      // 段在文件中的偏移量
    uint64_t    filesize;     // 段在文件中的大小
    vm_prot_t   maxprot;      // 段页面所需要的最高内存保护(4 = r,2 = w,1 = x)
    vm_prot_t   initprot;     // 段页面初始的内存保护
    uint32_t    nsects;       // 段中section数量
    uint32_t    flags;        // 标志位
};
六、Data
1.功能总结
  1. Data中存储了实际的数据与代码,主要包含方法、符号表、动态符号表、动态库加载信息(重定向、符号绑定等)等;
  2. Data中的排布完全按照Load Command中的描述;
  3. DataSegment(段)和 Section (节)的方式来组成,通常,Data拥有多个segment,每个segment可以有零到多个section节;
  4. 不同的segment都有一段虚拟地址映射到进程的地址空间;

几乎所有的Mach-O文件都包含3segment

  1. __TEXT:代码段,只读可执行,存储函数的二进制代码(__text)常量字符串(__cstring)OC的类/方法名等信息
  2. __DATA:数据段, 可读可写,存储OC的字符串(__cfstring),以及运行时的元数据:class/protocol/method,以及全局变量,静态变量等;
  3. __LINKEDIT:只读,存储启动App需要的信息,如 bind & rebase 的地址、函数的名称和地址等信息;
2.源码分析

Data区中,Section占了很大的比例,而且在Mach-O中集中体现在__TEXT__DATA两段里。

Section被定义在loader.h文件中,具体代码如下:

代码语言:javascript
复制
struct section_64 { /* for 64-bit architectures */
    char        sectname[16];   // 当前section的名称
    char        segname[16];    // section所在的segment名称
    uint64_t    addr;       // 内存中起始位置
    uint64_t    size;       // section大小
    uint32_t    offset;     // section的文件偏移
    uint32_t    align;    // 字节大小对齐
    uint32_t    reloff;     // 重定位入口的文件偏移
    uint32_t    nreloc;   // 重定位入口数量
    uint32_t    flags;      // 标志,section的类型和属性
    uint32_t    reserved1;  // 保留(用于偏移量或索引)
    uint32_t    reserved2;  // 保留(用于count或sizeof)
    uint32_t    reserved3;  // 保留
};
七、理解大小端模式

分析Mach-O文件时,经常会看到内存地址相关的内容,这里就涉及到了大小端模式的概念;

  1. 小端模式:数据的低字节,保存在内存的低地址;
  2. 大端模式:数据的低字节,保存在内存的高地址;

iOS设备的处理器是基于ARM架构的,默认是采用小端模式(低字节放低位)读取数据的,而网络和蓝牙传输数据通常是用的大端模式(低字节放高位):

下面以unsigned int value = 0x12345678为例,分别看看在两种字节序下其存储情况,我们可以用unsigned char buf[4]来表示value

代码语言:javascript
复制
Little-Endian: 低地址存放低位,如下:
低地址 ------------------> 高地址
0x78  |  0x56  |  0x34  |  0x12

Big-Endian: 低地址存放高位,如下:
低地址 -----------------> 高地址
0x12  |  0x34  |  0x56  |  0x78

内存地址

小端模式存放内容

大端模式存放内容

0x4000

0x78

0x12

0x4001

0x56

0x34

0x4002

0x34

0x56

0x4003

0x12

0x78

八、理解通用二进制文件
1.基本概念
  1. 通用二进制文件的存储结构,是将多种架构的Mach-O文件打包在一起,CPU在读取该二进制文件时可以自动检测并选用合适的架构;
  2. 通用二进制文件会同时存储多种架构,所以比单一架构的二进制文件大很多,会占用大量的磁盘空间。但由于系统运行时会自动选择最合适的,不相关的架构代码,不会占用内存空间,所以执行效率提高了;
  3. 通用二进制格式也被称为胖二进制格式;
2.通用二进制格式分析

通用二进制格式的定义在<mach-o/fat.h>中:

  1. 下载xnu后,依次在 xnu -> EXTERNAL_HEADERS ->mach-o中找到该文件。
  2. 通用二进制文件有两个重要结构体:fat_headerfat_arch

两个结构体的定义如下:

代码语言:javascript
复制
/*
 - magic:可以让系统内核读取该文件时知道是通用二进制文件
 - nfat_arch:表明下面有多个fat_arch结构体,即通用二进制文件包含多少个Mach-O
 */
struct fat_header {
    uint32_t    magic;      /* FAT_MAGIC */
    uint32_t    nfat_arch;  /* number of structs that follow */
};

/*
 fat_arch是描述Mach-O
 - cputype 和 cpusubtype:说明Mach-O适用的平台
 - offset(偏移)、size(大小)、align(页对齐)描述了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 */
};
参考链接
  1. xnu
  2. Mach-O官方源码
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/10/29 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、理解可执行文件
    • 1.可执行文件
      • 2.不同平台的可执行文件
      • 二、理解Mach-O文件
        • 1. Mach-O文件
          • 2.Mach-O格式的常见文件类型
          • 三、Mach-O文件结构
            • 1.查看Mach-O的两种方法
              • 2.查看Mach-O文件结构
              • 四、Mach Header(可执行文件头)
                • 1.功能总结
                  • 2.源码分析
                    • 3.MachOView演示
                    • 五、分析Load Commands
                      • 1.功能总结
                        • 2..代码分析
                        • 六、Data
                          • 1.功能总结
                            • 2.源码分析
                            • 七、理解大小端模式
                            • 八、理解通用二进制文件
                              • 1.基本概念
                                • 2.通用二进制格式分析
                                • 参考链接
                                相关产品与服务
                                腾讯云代码分析
                                腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
                                领券
                                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档