解析 MACH_O 文件
现在做iOS开发的挺多,了解一下在苹果平台上程序运行的原理
解析 MACH_O 文件
这篇文章描述了如何解析 Mach-O 文件并稍微解释了一下它的格式。这不是一份权威指南,不过当你不知从何开始时,它可能有些帮助。想了解更多信息,请考虑阅读官方文档和操作系统提供的头文件。
Macho-O 是什么
维基百科 的简单描述:
Mach-O 是 Mach object 文件格式的缩写,它是一种用于记录可执行文件、对象代码、共享库、动态加载代码和内存转储的文件格式。作为 a.out 格式的替代品,Mach-O 提供了更好的扩展性,并提升了符号表中信息的访问速度。
大多数基于 Mach 内核的操作系统都使用 Mach-O。NeXTSTEP、OS X 和 iOS 是使用这种格式作为本地可执行文件、库和对象代码的例子。
Mach-O 格式
Mach-O 没有类似于 XML、YAML、JSON 等诸如此类的特殊格式,它只是一个二进制字节流,被划分为了有意义的数据块。这些块包含元信息,比如,字节顺序、cpu 类型、块的大小,等等。
典型的 Mach-O 文件(对应的 官方文档 )包含三个区域:
- 头-包含该二进制文件的一般信息:字节顺序、(魔数)、cpu 类型、加载指令的数量等等。
- 加载指令-它是一张包含很多内容的表,内容包括区域的位置、符号表、动态符号表等。每个加载指令都包含一个元信息,比如指令类型、名称、在二进制文件中的位置等等。
- 数据-通常是对象文件中最大的部分。主要包含代码、数据,例如符号表,动态符号表等等。
这里是一个简化的图形表示︰
OS X 有两种类型的目标文件:Mach-O 文件和通用二进制文件,也叫作胖文件。它们之间的区别是:Mach-O 文件包含一种架构(i386、x86_64、arm64 等等)的对象代码,而胖文件可能包含若干包含不同架构(i386、x86_64、arm、arm64 等等)对象代码的对象文件。
胖文件的结构相当简单︰ 胖文件头以及后面的 Mach-O 文件:
解析 Mach-O 文件
OS X 没有提供 libmacho
或任何类似的工具,我们唯一拥有的是一组定义在 /usr/include/mach-o/*
中的 C 结构体,因此我们需要自己实现解析。它可能非常棘手,但也并不是非常困难。
内存描述
在我们开始解析前,让我们看看一个 Mach-O 文件的详细描述。简单起见,下面的对象文件是单个 i386 Mach-O 文件(而不是胖文件),它只包含两个段类型的数据条目。
仅需下面的结构体我们就可以描述该文件:
Objective-C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
struct mach_header {
uint32_t magic;
cpu_type_t cputype;
cpu_subtype_t cpusubtype;
uint32_t filetype;
uint32_t ncmds;
uint32_t sizeofcmds;
uint32_t flags;
};
struct segment_command {
uint32_t cmd;
uint32_t cmdsize;
char segname[16];
uint32_t vmaddr;
uint32_t vmsize;
uint32_t fileoff;
uint32_t filesize;
vm_prot_t maxprot;
vm_prot_t initprot;
uint32_t nsects;
uint32_t flags;
};
|
下面是内存映射的情况:
如果你想要从文件中读取特定的信息,你只需要一个正确的数据结构和偏移量。
解析
让我们来编写一个程序,它能读取 Mach-O 或 胖文件 并打印每个段的名称以及它编译的目标架构。
结束时,我们可能会有类似这样的东西︰
Objective-C
1
2
3
4
5
6
|
$ ./segname_dumper some_binary
i386
segname __PAGEZERO
segname __TEXT
segname __LINKEDIT
|
驱动
让我们从一个简单的“驱动”开始。
至少有两种可用的方式来解析此类文件︰ 加载文件内容到内存中并直接处理缓冲区 或打开一个文件在其中来回跳转。两种方法都有自己的优点和缺点,但这里我会选用第二种。此外,我假定没有人会用错误的方式使用该程序,因此我没有添加错误处理。
Objective-C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
#include <stdio.h>
#include <stdlib.h>
#include <mach-o/loader.h>
#include <mach-o/swap.h>
void dump_segments(FILE *obj_file);
int main(int argc, char *argv[]) {
const char *filename = argv[1];
FILE *obj_file = fopen(filename, "rb");
dump_segments(obj_file);
fclose(obj_file);
return 0;
}
void dump_segments(FILE *obj_file) {
// Driver
}
|
魔数、CPU、字节序
为了至少阅读对象文件的头,我们需要得到我们需要的所有信息︰ CPU 架构(32 位或 64 位) 和字节顺序。但首先我们需要取出一个魔数︰
Objective-C
1
2
3
4
5
6
7
8
9
10
|
uint32_t read_magic(FILE *obj_file, int offset) {
uint32_t magic;
fseek(obj_file, offset, SEEK_SET);
fread(&magic, sizeof(uint32_t), 1, obj_file);
return magic;
}
void dump_segments(FILE *obj_file) {
uint32_t magic = read_magic(obj_file, 0);
}
|
函数 read_magic 是非常直截了当的,但有一件事可能看起来很怪︰ fseek。问题是,每当有人读取文件,文件内部的偏移量都会发生改变。最好显式指定偏移量,以确保我们阅读到我们实际上想要读取的内容。此外,这个小技巧稍后也会有用。
描述 32 位和 64 位对象文件的结构体是不同的(例如︰ mach_header 和 mach_header_64),我们需要检查文件的体系结构来选择需要的结构体:
Objective-C
1
2
3
4
5
6
7
8
9
|
int is_magic_64(uint32_t magic) {
return magic == MH_MAGIC_64 || magic == MH_CIGAM_64;
}
void dump_segments(FILE *obj_file) {
uint32_t magic = read_magic(obj_file, 0);
int is_64 = is_magic_64(magic);
}
|
MH_MAGIC_64 和 MH_CIGAM_64 是系统提供的魔数。第二个看起来比第一个更多 magicly(译者注:原文如此,magic 对应的形容词是 magical,作者使用了magicly。)。解释如下。
由于历史的原因,不同的计算机可能使用不同的 字节顺序︰ 大端字节序 (从左到右) 和小端字节序 (从右至左)。魔数同样存储了这一信息︰ MH_CIGAM
和 MH_CIGAM_64
表示字节顺序不同于主机操作系统,因此所有字节都应颠倒︰
Objective-C
1
2
3
4
5
6
7
8
9
10
|
int should_swap_bytes(uint32_t magic) {
return magic == MH_CIGAM || magic == MH_CIGAM_64;
}
void dump_segments(FILE *obj_file) {
uint32_t magic = read_magic(obj_file, 0);
int is_64 = is_magic_64(magic);
int is_swap = should_swap_bytes(magic);
}
|
Mach-O 头
终于我们能够读取 mach_header 了
。我们先来介绍几个用于从文件中读取数据的常用函数。
Objective-C
1
2
3
4
5
6
7
|
void *load_bytes(FILE *obj_file, int offset, int size) {
void *buf = calloc(1, size);
fseek(obj_file, offset, SEEK_SET);
fread(buf, size, 1, obj_file);
return buf;
}
|
注意︰ 数据应当在使用后释放 !
Objective-C
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
|
void dump_mach_header(FILE *obj_file, int offset, int is_64, int is_swap) {
if (is_64) {
int header_size = sizeof(struct mach_header_64);
struct mach_header_64 *header = load_bytes(obj_file, offset, header_size);
if (is_swap) {
swap_mach_header_64(header, 0);
}
free(header);
} else {
int header_size = sizeof(struct mach_header);
struct mach_header *header = load_bytes(obj_file, offset, header_size);
if (is_swap) {
swap_mach_header(header, 0);
}
free(header);
}
free(buffer);
}
void dump_segments(FILE *obj_file) {
uint32_t magic = read_magic(obj_file, 0);
int is_64 = is_magic_64(magic);
int is_swap = should_swap_bytes(magic);
dump_mach_header(obj_file, 0, is_64, is_swap);
}
|
为了不搞砸驱动函数,我们在这里引入另一个函数 dump_mach_header
。下一步是读取所有段指令并打印它们的名字。问题是,mach-o 文件通常也包含其他指令。如果你还记得 segment_command
结构的第一个字段的是 uint32_t cmd;
,此字段表示指令的类型。下面是我们将使用的由系统提供的另一种结构体︰
Objective-C
1
2
3
4
5
|
struct load_command {
uint32_t cmd;
uint32_t cmdsize;
};
|
除了以上的所有信息 mach_header
还有许许多多加载命令,所以我们可以只是遍历并跳过我们不感兴趣的指令。此外,我们需要计算标头结束位置的偏移量。这里是 dump_mach_header
的最终版本︰
Objective-C
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
|
void dump_mach_header(FILE *obj_file, int offset, int is_64, int is_swap) {
uint32_t ncmds;
int load_commands_offset = offset;
if (is_64) {
int header_size = sizeof(struct mach_header_64);
struct mach_header_64 *header = load_bytes(obj_file, offset, header_size);
if (is_swap) {
swap_mach_header_64(header, 0);
}
ncmds = header->ncmds;
load_commands_offset += header_size;
free(header);
} else {
int header_size = sizeof(struct mach_header);
struct mach_header *header = load_bytes(obj_file, offset, header_size);
if (is_swap) {
swap_mach_header(header, 0);
}
ncmds = header->ncmds;
load_commands_offset += header_size;
free(header);
}
dump_segment_commands(obj_file, load_commands_offset, is_swap, ncmds);
}
|
段指令
是时候去打印所有的段名了:
Objective-C
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
34
|
void dump_segment_commands(FILE *obj_file, int offset, int is_swap, uint32_t ncmds) {
int actual_offset = offset;
for (int i = 0; i < ncmds; i++) {
struct load_command *cmd = load_bytes(obj_file, actual_offset, sizeof(struct load_command));
if (is_swap) {
swap_load_command(cmd, 0);
}
if (cmd->cmd == LC_SEGMENT_64) {
struct segment_command_64 *segment = load_bytes(obj_file, actual_offset, sizeof(struct segment_command_64));
if (is_swap) {
swap_segment_command_64(segment, 0);
}
printf("segname: %sn", segment->segname);
free(segment);
} else if (cmd->cmd == LC_SEGMENT) {
struct segment_command *segment = load_bytes(obj_file, actual_offset, sizeof(struct segment_command));
if (is_swap) {
swap_segment_command(segment, 0);
}
printf("segname: %sn", segment->segname);
free(segment);
}
actual_offset += cmd->cmdsize;
free(cmd);
}
}
|
这个函数不需要 is_64
参数,因为我们可以从 cmd
类型本身 (LC_SEGMENT
/LC_SEGMENT_64
)推断它。如果它不是段,我们只需跳过该命令并向前移动到下一个。
CPU 名
我想要展示的最后一件事是如何基于 mach_header
的 cputype
获得处理器的名称。我相信这不是最好的选择,但对于本文的示例来说,它是可以接受的︰
Objective-C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
struct _cpu_type_names {
cpu_type_t cputype;
const char *cpu_name;
};
static struct _cpu_type_names cpu_type_names[] = {
{ CPU_TYPE_I386, "i386" },
{ CPU_TYPE_X86_64, "x86_64" },
{ CPU_TYPE_ARM, "arm" },
{ CPU_TYPE_ARM64, "arm64" }
};
static const char *cpu_type_name(cpu_type_t cpu_type) {
static int cpu_type_names_size = sizeof(cpu_type_names) / sizeof(struct _cpu_type_names);
for (int i = 0; i < cpu_type_names_size; i++ ) {
if (cpu_type == cpu_type_names[i].cputype) {
return cpu_type_names[i].cpu_name;
}
}
return "unknown";
}
|
OS X 为大量的 CPU 提供了 CPU_TYPE_ *
,所以我们可以 “容易”地为特定的魔数关联一个字符串。为了打印 CPU 的名称,我们需要稍微修改 dump_mach_header
:
Objective-C
1
2
3
4
5
6
7
8
9
10
11
12
|
int header_size = sizeof(struct mach_header_64);
struct mach_header_64 *header = load_bytes(obj_file, offset, header_size);
if (is_swap) {
swap_mach_header_64(header, 0);
}
ncmds = header->ncmds;
load_commands_offset += header_size;
printf("%sn", cpu_type_name(header->cputype)); // <-
free(header);
|
胖对象
这篇文章已经包含大量的内容,所以我不会描述如何处理胖对象,但你可以在这里找到如何实现它︰ segment_dumper
接下来是什么
大概就是以上这些。
这里是一组可能有用的链接,如果你想要更深入地挖掘和了解更多关于 mach-o 的内容:
- OS X ABI Mach-O 文件格式参考 – 苹果官方文档
- MachOView – 是一个可视的 Mach-O 文件浏览器。它提供完整的浏览和就地编辑 Intel 和 ARM 二进制文件的解决方案。
- Mach-O 可执行行文件-来自 objc.io 的优秀文章。
- bitcode_retriever – 简单的 C 程序,从Mach-O 二进制文件检索 Bitcode。
- segment_dumper – 来自本文的源码
转载:http://ios.jobbole.com/89498/
解析 MACH_O 文件的更多相关文章
- Android 解析XML文件和生成XML文件
解析XML文件 public static void initXML(Context context) { //can't create in /data/media/0 because permis ...
- CSharpGL(9)解析OBJ文件并用CSharpGL渲染
CSharpGL(9)解析OBJ文件并用CSharpGL渲染 2016-08-13 由于CSharpGL一直在更新,现在这个教程已经不适用最新的代码了.CSharpGL源码中包含10多个独立的Demo ...
- Jsoup系列学习(2)-解析html文件
解析html文件 1.当我们通过发送http请求时,有时候返回结果是一个html格式字符串,你需要从一个网站获取和解析一个HTML文档,并查找其中的相关数据.你可以使用下面解决方法: 使用 Jsoup ...
- JAVA使用SAX解析XML文件
在我的另一篇文章(http://www.cnblogs.com/anivia/p/5849712.html)中,通过一个例子介绍了使用DOM来解析XML文件,那么本篇文章通过相同的XML文件介绍如何使 ...
- JAVA中使用DOM解析XML文件
XML是一种方便快捷高效的数据保存传输的格式,在JSON广泛使用之前,XML是服务器和客户端之间数据传输的主要方式.因此,需要使用各种方式,解析服务器传送过来的信息,以供使用者查看. JAVA作为一种 ...
- CSharpGL(5)解析3DS文件并用CSharpGL渲染
CSharpGL(5)解析3DS文件并用CSharpGL渲染 我曾经写过一个简单的*.3ds文件的解析器,但是只能解析最基本的顶点.索引信息,且此解析器是仿照别人的C++代码改写的,设计的也不好,不方 ...
- php解析.csv文件
public function actionImport() { //post请求过来的 $fileName = $_FILES['file']['name']; $fileTmpName = $_F ...
- java中采用dom4j解析xml文件
一.前言 在最近的开发中用到了dom4j来解析xml文件,以前听说过来解析xml文件的几种标准方式:但是从来的没有应用过来,所以可以在google中搜索dmo4j解析xml文件的方式,学习一下dom4 ...
- 使用XStream解析MXL文件用到的jar包---xpp3_min-1.1.3.4.O.jar和xstream-1.3.1.jar
使用XStream解析MXL文件用到的jar包---xpp3_min-1.1.3.4.O.jar和xstream-1.3.1.jar
随机推荐
- 表生成器@ TableGenerator
将当前主键的值单独保存到一个数据库的表中,主键的值每次都是从指定的表中查询来获得,这种生成主键的方式也是很常用的.这种方法生成主键的策略可以适用于任何的数据库,不必担心不同数据库不兼容造成的问题. 使 ...
- [CF733D]Kostya the Sculptor(贪心)
题目链接:http://codeforces.com/contest/733/problem/D 题意:给n个长方体,允许最多两个拼在一起,拼接的面必须长宽相等.问想获得最大的内切圆的长方体序号是多少 ...
- Beaglebone Black–GPIO 高低电平控制 LED 灯
上一篇,运用 Linux 的 sysfs,控制本机上的 LED 灯,usr0 至 usr3,这次用 GPIO 控制外部的电路,点亮 LED 灯. 这次的全部材料: BBB 一台 购买 BBB 自带的 ...
- [C和指针]第五部分
声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...
- poj 3335(半平面交)
链接:http://poj.org/problem?id=3335 //大牛们常说的测模板题 ------------------------------------------------- ...
- MemSQL Start[c]UP 2.0 - Round 1(无聊练手B题)
http://codeforces.com/contest/452/problem/B B. 4-point polyline time limit per test 2 seconds memo ...
- SQL Server 2005 中的同义词
From : http://blog.csdn.net/itblog/article/details/752881 =============创建同义词 可以为下列对象类型创建同义词: 程序集 (CL ...
- iOS - UIRefreshControl 刷新数据
前言 NS_CLASS_AVAILABLE_IOS(6_0) __TVOS_PROHIBITED @interface UIRefreshControl : UIControl 1.UIRefresh ...
- mysql概要(十四)索引
1.索引是对数据库数据建立目录加快了查询速度.索引分为哈希索引和二叉树索引 (大数据量转移,如果表中带有大量字段索引,进行数据导入时,建议先去掉索引导入数据再统一加入索引,减少索引计算量) 2.索引原 ...
- Android_相关路径
1. Android应用安装涉及到如下几个目录:system/app 系统自带的应用程序,无法删除.data/app 用户程序安装的目录,有删除权限.安装时把apk文件复制到此目录.da ...