原文地址

写在之前

之前工作中对Mach-O文件有一定的接触, 原本早就想写一篇文章分享一下,但是奈何只是不够深入, 总怕分析的有问题误导读者。

最近又在阅读深入解析Mac OS X 与iOS 操作系统,借着这个机会记录下自己的学习成果, 并结合之前的经验, 加上一些实例让读者更好的理解。
毕竟对于程序员来说 大部分人对抽象的概念的感觉就是 听说过很多原理, 依然不知道大佬说的是什么

Mac OS 与 iOS 支持的文件类型

Unix-Like系列的操作系统, 可以通过命令 chmod +x 给予文件可执行权限, 但是这不代表这个文件具有可执行权限, 实际上 Apple家的操作系统只支持三种文件格式。

  1. #!开头的脚本文件
  2. 通用二进制文件
  3. Mach-O格式文件

但是实际上 以#!开头的脚本文件其实是shell解释器找到后面指定的脚本解释器来执行的, 而通用二进制文件其实是多个架构的Mach-O文件的打包体。
通用二进制文件其实有个更加形象化的名字fat binary
那么操作系统如何知道你打开的文件是何种类型的?
其实是通过这些文件头的固定数字来区分的, 对于这些固定数字通常叫做 Magic Number(魔数).

对于fat binary的魔数是 0xcafebabe(小端)0xbebafeca大端
对于Mach-O的魔数是 0xfeedface(32位) 0xfeedfacf(64位)

多说无益~~上代码

我们以/usr/bin/perl为例 (这是一个fat binary)
$ file /usr/bin/perl
/usr/bin/perl: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [i386:Mach-O executable i386]
/usr/bin/perl (for architecture x86_64):    Mach-O 64-bit executable x86_64
/usr/bin/perl (for architecture i386):  Mach-O executable i386
$ otool -vh /usr/bin/perl
Mach headermagic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
MH_MAGIC_64  X86_64        ALL LIB64     EXECUTE    17       1800   NOUNDEFS DYLDLINK TWOLEVEL PIE

不过可能你觉得拿着系统的命令来看感觉不那么真实, 那么cat命令我们都用过吧,来看下

/usr/include/mach-o/fat.h路径下有关于fat binary文件的头文件定义

struct fat_header {uint32_t    magic;      /* FAT_MAGIC or FAT_MAGIC_64 */uint32_t    nfat_arch;  /* 包含的架构数 */
};struct fat_arch {cpu_type_t  cputype;    /* cpu类型 */cpu_subtype_t   cpusubtype; /* 机器标示符  */uint32_t    offset;     /* 当前架构在这个文件中的便宜量 */uint32_t    size;       /* 当前架构在文件中的长度*/uint32_t    align;      /* 对齐方式 */
};

不知道大家还记得不记得之前使用windows的时候有System32和64之分, 那是因为在windows操作系统中不同架构的可执行文件是分开存放的。

苹果在某次WWDC大会声称自己优雅的将多个架构合并在了一个文件中。引来果粉一阵鼓掌。
其实fat binary文件的真正布局非常简单。

以/usr/bin/perl为例

Apple的实现只是将不同架构的文件并排放在一起,然后在文件头部添加不同架构的描述信息, 然后再加载当前架构的Mach-O文件 丢弃掉其他架构的部分即可。实在是简单粗暴~~

Mach-O文件结构

Unix标准了一个可移植的二进制格式ELF但是苹果并没有实现它而是维护了一套NeXTSTEP的遗物 Mach-Object简称Mach-O
但是这并不是说苹果不遵守POSXI规范,这个规范通常说的是源码级别的跨平台性,对于二进制则不强制要求。

下面是一个官方提供的图片。

Mach-0 Header

先来介绍Mach-O的Header(只介绍64位)信息。
相关头文件定义在/usr/include/mach-o/loader.h里面。如果需要使用只需要加载<mach-O/loader.h>

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;   /* 文件类型 */uint32_t    ncmds;      /* load commadns的个数 */uint32_t    sizeofcmds; /* load commands的总大小 */uint32_t    flags;      /* 动态连接器标志*/uint32_t    reserved;   /* 保留*/
};/* Constant for the magic field of the mach_header_64 (64-bit architectures) */
#define MH_MAGIC_64 0xfeedfacf /* 小端 */
#define MH_CIGAM_64 0xcffaedfe /* 大端 */

注: Mach-O文件不仅仅是可执行文件, 也包括目标文件(.o) 动态库, Bundle插件等。
标志位
flag 标记了一些dyld加载 执行 中可配置的信息。
关于Mach-O文件的魔数信息,有兴趣的读者可以按照之前的方式亲自动手尝试一下

Mach-O Load commands

Mach-O文件中最重要的元信息就是 load Commands,加载命令紧跟在文件头信息之后。

//   [_mach_header_|___load_commands___||___load_commands___||____other____|]struct load_command {uint32_t cmd;       /*  load command的类型 */uint32_t cmdsize;   /*  command 的长度 */
};

LC_SEGMENT

对于加载命令是LC_SEGMENT的命令指定了内核如何设置新运行的进程的内存空间
对应的头文件也在<mach-o/loader.h>

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;     /* 当前segment加载的虚拟内存起始地址 */uint64_t    vmsize;     /* 当前segment加载的虚拟内存地址占用的长度  */uint64_t    fileoff;    /* segment在文件中的偏移 */uint64_t    filesize;   /* segment在文件中的长度 */vm_prot_t   maxprot;    /* 最大的保护级别 */vm_prot_t   initprot;   /* 初始化的保护级别 */uint32_t    nsects;     /* 包含sections的个数  */uint32_t    flags;      /* 标志位 */
};

由于有了LC_SEGMENT命令。对于每一个Segment,将文件中偏移量为fileOff长度为filesize的文件内容加载到虚拟地址为vmaddr的位置,长度为vmsize, 页面的权限通过initprot来初始化(比如设定读/写/执行, 段的保护级别可以动态设置最大不超过maxprot

常见的Segment有以下几个

  1. __TEXT 代码段
  2. __PAGEZERO 空指针陷阱
  3. __DATA 数据段
  4. __LINKEDIT 包含需要被动态链接器使用的信息,包括符号表、字符串表、重定位项表等。
  5. __OBJC(现已经被合并到__DATA部分)包含会被Objective Runtime使用到的一些数据。

当然读者如果有兴趣查看其他所有的loadcommands可以去loader.h头文件定义去查看,也可以实际操练一下
如 使用otool 查看某些mach-O文件的所有load_commands

otool -l /bin/ls

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 */
};

对于__TEXT, __DATA下面, 又有细分的各种Section,常见的如

名称 作用
TEXT.text 只有可执行的机器码
TEXT.cstring 硬编码去重后的C字符串
TEXT.const 初始化过的常量
DATA.data 初始化过的可变的数据
DATA.bss 没有初始化的静态变量
DATA.common 没有初始化过的符号声明
DATA.objc_clasname oc类名称
DATA.objc_classlist 类列表
DATA.objc_protocollist 协议列表

···
其他的就不一一列举,建议读者亲自动手试一试, 会发现很多有价值的东西

了解这些有什么用?

相信看了这些内容, 你已经大致知道Mach-O文件的物理布局, 那么我们知道了这个文件格式能用来做什么呢?
理解了这个可以用来做下面一些东西:

  1. 依赖解耦
  2. 元信息获取
  3. 调试代码
  4. CI工具插件检测
  5. 逆向

相关一些示例放在下篇文章讲解。

作者:南栀倾寒
链接:https://www.jianshu.com/p/d43a8279a1c2
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

理解Mach-O文件格式(1)相关推荐

  1. 深入理解Java Class文件格式

    首先, 让我们回顾一下关于class文件格式的之前两篇博客的主要内容. 在 深入理解Java Class文件格式(一) 中, 讲解了class文件在整个java体系结构中的位置和作用, 讲解了clas ...

  2. 深入理解 JVM Class文件格式(九)

    经过前八篇关于class文件的博客, 关于class文件格式的内容也基本上讲完了. 本文是关于class文件格式的最后一篇. 在这篇博客中, 将会讲解关于方法的几个属性. 理解这篇博客的内容, 对于理 ...

  3. 深入理解 JVM Class文件格式(八)

    在本专栏的第一篇文章 深入理解Java虚拟机到底是什么 中, 我们主要讲解了什么是虚拟机, 这篇博客是对JVM的一个概述. 在随后的几篇文章中,一直在讲解class文件格式. 在今天这篇博客中, 将会 ...

  4. 深入理解 JVM Class文件格式(七)

    本专栏列前面的一系列博客, 对Class文件中的一部分数据项进行了介绍. 本文将会继续介绍class文件中未讲解的信息. 先回顾一下上面一篇文章. 在上一篇博客中, 我们介绍了: this_class ...

  5. 深入理解 JVM Class文件格式(六)

    经过前几篇文章, 终于将常量池介绍完了, 之所以花这么大的功夫介绍常量池, 是因为对于理解class文件格式,常量池是必须要了解的, 因为class文件中其他地方,大量引用了常量池中的数据项. 对于还 ...

  6. 深入理解 JVM Class文件格式(五)

    (8) CONSTANT_Class_info 常量池中的一个CONSTANT_Class_info, 可以看做是CONSTANT_Class数据类型的一个实例. 他是对类或者接口的符号引用. 它描述 ...

  7. 深入理解 JVM Class文件格式(三)

    ** JVM常量池中各数据项类型详解 ** 关于常量池的大概内容, 已经在 深入理解 JVM Class文件格式(一) 中讲解过了, 这篇文章中还介绍了常量池中的11种数据类型. 本文的任务是详细讲解 ...

  8. java class教程_深入理解Java Class文件格式(七)

    本专栏列前面的一系列博客, 对Class文件中的一部分数据项进行了介绍. 本文将会继续介绍class文件中未讲解的信息. 先回顾一下上面一篇文章. 在上一篇博客中, 我们介绍了: this_class ...

  9. java怎么编程class,深入理解Java Class文件格式(一)

    Class文件在Java体系结构中的位置和作用 在上一篇博客中, 大致讲解了Java虚拟机的体系结构和执行原理. 本篇博客主要讲解能够被JVM识别, 加载并执行的class文件的格式. 对于理解JVM ...

  10. 深入理解 WIN32 PE 文件格式

    原文地址:http://www.cppblog.com/shaoxie1986/articles/126142.html 译自:An In-Depth Look into the Win32 Port ...

最新文章

  1. 【廖雪峰python入门笔记】if语句
  2. FineUI小技巧(4)关闭窗体那些事
  3. (2)shiro角色资源权限
  4. 如何安装rpm包?掌握rpm包管理工具就够了
  5. Dubbo设置超时时间
  6. 【转】Yahoo!团队:网站性能优化的35条黄金守则
  7. 如何在GraphPad Prism中使用非线性回归拟合模型?
  8. 多用as少用强制类型转换
  9. 计算机网络基础常考简答题,计算机网络基础知识简答题
  10. js动态产生对象push进数组,发现数组所有元素(element or object)一样
  11. C++--第6课 - 专题一经典问题解析
  12. [图文详解]图像处理中的高斯模糊
  13. 现在单片机编程语言是c吗,单片机编程用什么语言 哪个适合新手
  14. 地铁票务管理系统_地铁票务管理是干什么
  15. SVN主干合并到分支
  16. 计算机网络故障的排除,计算机网络故障诊断与排除
  17. 简单内存泄漏检测方法 解决 Detected memory leaks! 问题
  18. Cheat Engine(CE)的下载和安装指南以及相关教程
  19. 程序员跳槽时,如何正确做好职业规划?
  20. 好久能旅游?新加坡,一座惬意的城市

热门文章

  1. Elasticsearch Java虚拟机配置详解
  2. 当耐克用上了AI、AR技术,你的鞋也要放飞自我了?Just Do It !
  3. 设置foxmail通过ccproxy代理收发邮件
  4. 通过CCproxy配置内网linux服务器
  5. 2022常见软件测试面试题
  6. 【HTML】-- 用户注册表单
  7. AdminLTE2的模态框(弹出框)
  8. CleanMyMac X4.12.2免费版MAC电脑系统磁盘优化工具
  9. vue解决mintui中使用MessageBox弹窗拦截,移动端多次点击手机的物理返回键,选择确定后页面返回不正确问题
  10. 百度地图的一些踩坑 marker网络图片不显示