善恶众相

  • 首页

  • 分类

  • 归档

Mach-O文件格式与otool工具的使用

发表于 2015-06-19 更新于 2019-08-07 分类于 iOS
介绍MacOSX平台下Mach-O(Mach object)文件格式,类似于Windows平台下的PE文件格式或Linux平台下的ELF文件格式,了解Mach-O文件格式是深入理解Objective-C的runtime加载运行的机制的基础

Basic Structure

一个Mach-O文件包含三个主要的区域(region):

  • 在每一个Mach-O文件的开始都有一个header结构(header structure)来标示这是一个Mach-O文件。这个header同时还包含着其他一些信息:文件类型信息,目标架构信息和一写影响文件其他部分内容的flags标示。

  • 紧跟着header的是一系列大小可变的加载命令(load commands),用于标示文件的布局和联动特征。两个加载命令之间的信息一般有一下几种含义:

  • 文件在虚拟内存中的初始布局

  • 用于动态链接的符号列表(symbol talbe)的位置

  • 程序主线程初始的执行状态

  • 共享库的名称,这些库包括主执行文件导入的符号定义

  • load commands下面是一个或多个的数据段(segments)。每一个segment都包含零个或更多的sections。segemnt的每一个section包含特定类型的代码和数据。每一个segment定义了一片虚拟内存区域,也就是动态链接器将映射到的进程地址空间。segment和section的数量及其布局是由load commands和文件类型决定的。

    最后一个segment是链接编辑段(link edit segment),它与用户级挂钩。该segment包含链接编辑信息表(tables of link edit information),如:符号表(symbol table),字符表(string table)等等,动态加载器会根据这些表将可执行文件或Mach-O包和与他依赖的库进行链接。

当使用DWARF进行调试的时候,调试信息存储在image对应的dSYM文件中,并且被uuid_command 结构所定义。

每个Mach-O文件都包含着与一个架构相对应的代码和数据。头结构指定了被内核所确认的特定的结构,例如:在基于PowerPC架构的Mac计算机中的代码就不能在基于Intel架构的Mac计算机上运行。但是你可以利用Universal Binaries and 32-bit/64-bit PowerPC Binaries.中所定义的格式将多个Mack-O文件组合进一个二进制文件中。

注意:由包含一个以上架构的对象文件组成的二进制文件并不是Mach-O文件,它是指对多个Mach-O文件进行了压缩。

segment和section通常是通过名称进行访问的,segment的名称由两个底杠加上大写字母组成(如:TEXT);section的名称由两个底杠加上小写字母组成(如:text)。这些命名规则都是约定俗成的。

Header Structure

通过otool -h hw.out命令可以显示出Mach-O文件的Mach header信息,其结构如下:

1
2
3
4
5
localhost:binspect ronglei$ xcrun otool -h hw.out 
hw.out:
Mach header
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
0xfeedfacf 16777223 3 0x80 2 16 1296 0x00200085
1
2
3
4
5
6
7
8
9
10
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;
uint32_t reserved;
};
  • magic是一个常量,详见:/usr/include/mach-o/loader.h
1
2
3
4
5
6
/ Constant for the magic field of the mach_header (32-bit architectures) /
#define MH_MAGIC 0xfeedface / the mach magic number /
#define MH_CIGAM 0xcefaedfe / NXSwapInt(MH_MAGIC) /
/ Constant for the magic field of the mach_header_64 (64-bit architectures) /
#define MH_MAGIC_64 0xfeedfacf / the 64-bit mach magic number /
#define MH_CIGAM_64 0xcffaedfe / NXSwapInt(MH_MAGIC_64) /
  • cputypeCPU标示,代码执行所需的CPU的类型
  • cpusubtypemachine标示,详见:/usr/include/mach/machine.h
1
2
3
4
5
6
7
8
9
10
#define	CPU_TYPE_MC680x0    ((cpu_type_t) 6)
#define CPU_TYPE_X86 ((cpu_type_t) 7)
#define CPU_TYPE_I386 CPU_TYPE_X86 / compatibility /
#define CPU_TYPE_X86_64 (CPU_TYPE_X86 | CPU_ARCH_ABI64)
......
#define CPU_SUBTYPE_X86_ALL ((cpu_subtype_t)3)
#define CPU_SUBTYPE_X86_64_ALL ((cpu_subtype_t)3)
#define CPU_SUBTYPE_X86_ARCH1 ((cpu_subtype_t)4)
#define CPU_SUBTYPE_X86_64_H ((cpu_subtype_t)8) / Haswell feature subset /
......
  • filetype标示文件类型,参见:/usr/include/mach-o/loader.h
1
2
3
4
5
6
#define	MH_OBJECT  0x1 / relocatable object file /
#define MH_EXECUTE 0x2 / demand paged 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 /
......
  • ncmds和sizeofcmds分别标示load commands的数量和所有load commands命令的size

Load Commands

Load Commands不仅定义了文件的逻辑结构,还定义了文件在虚拟内存中的布局。他们是Mach-O文件的核心从上面输出的mac header信息中我看看到我们的Mach-O文件总共有16个load commads:

在终端通过otool -l hw.out 命令可以查看Mach-O文件中所有Load Commands。

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
localhost:binspect ronglei$ xcrun otool -l hw.out 
hw.out:
Load command 0
cmd LC_SEGMENT_64
cmdsize 72
segname __PAGEZERO
vmaddr 0x0000000000000000
vmsize 0x0000000100000000
fileoff 0
filesize 0
maxprot 0x00000000
initprot 0x00000000
nsects 0
flags 0x0
Load command 1
cmd LC_SEGMENT_64
cmdsize 552
segname __TEXT
vmaddr 0x0000000100000000
vmsize 0x0000000000001000
fileoff 0
filesize 4096
maxprot 0x00000007
initprot 0x00000005
nsects 6
flags 0x0
Section
sectname __text
segname __TEXT
addr 0x0000000100000f40
size 0x000000000000002a
offset 3904
align 2^4 (16)
reloff 0
nreloc 0
flags 0x80000400
reserved1 0
reserved2 0
Section
sectname __stubs
segname __TEXT
addr 0x0000000100000f6a
size 0x0000000000000006
offset 3946
align 2^1 (2)
reloff 0
nreloc 0
flags 0x80000408
reserved1 0 (index into indirect symbol table)
reserved2 6 (size of stubs)
Section
sectname __stub_helper
segname __TEXT
addr 0x0000000100000f70
size 0x000000000000001a
offset 3952
align 2^2 (4)
reloff 0
nreloc 0
flags 0x80000400
reserved1 0
reserved2 0
Section
sectname __cstring
segname __TEXT
addr 0x0000000100000f8a
size 0x000000000000000e
offset 3978
align 2^0 (1)
reloff 0
nreloc 0
flags 0x00000002
reserved1 0
reserved2 0
Section
sectname __unwind_info
segname __TEXT
addr 0x0000000100000f98
size 0x0000000000000048
offset 3992
align 2^2 (4)
reloff 0
nreloc 0
flags 0x00000000
reserved1 0
reserved2 0
Section
sectname __eh_frame
segname __TEXT
addr 0x0000000100000fe0
size 0x0000000000000018
offset 4064
align 2^3 (8)
reloff 0
nreloc 0
flags 0x00000000
reserved1 0
reserved2 0
Load command 2
cmd LC_SEGMENT_64
cmdsize 232
segname __DATA
vmaddr 0x0000000100001000
vmsize 0x0000000000001000
fileoff 4096
filesize 4096
maxprot 0x00000007
initprot 0x00000003
nsects 2
flags 0x0
Section
sectname __nl_symbol_ptr
segname __DATA
addr 0x0000000100001000
size 0x0000000000000010
offset 4096
align 2^3 (8)
reloff 0
nreloc 0
flags 0x00000006
reserved1 1 (index into indirect symbol table)
reserved2 0
Section
sectname __la_symbol_ptr
segname __DATA
addr 0x0000000100001010
size 0x0000000000000008
offset 4112
align 2^3 (8)
reloff 0
nreloc 0
flags 0x00000007
reserved1 3 (index into indirect symbol table)
reserved2 0
Load command 3
cmd LC_SEGMENT_64
cmdsize 72
segname __LINKEDIT
vmaddr 0x0000000100002000
vmsize 0x0000000000001000
fileoff 8192
filesize 304
maxprot 0x00000007
initprot 0x00000001
nsects 0
flags 0x0
Load command 4
cmd LC_DYLD_INFO_ONLY
cmdsize 48
rebase_off 8192
rebase_size 8
bind_off 8200
bind_size 24
weak_bind_off 0
weak_bind_size 0
lazy_bind_off 8224
lazy_bind_size 16
export_off 8240
export_size 48
Load command 5
cmd LC_SYMTAB
cmdsize 24
symoff 8360
nsyms 4
stroff 8440
strsize 56
Load command 6
cmd LC_DYSYMTAB
cmdsize 80
ilocalsym 0
nlocalsym 0
iextdefsym 0
nextdefsym 2
iundefsym 2
nundefsym 2
tocoff 0
ntoc 0
modtaboff 0
nmodtab 0
extrefsymoff 0
nextrefsyms 0
indirectsymoff 8424
nindirectsyms 4
extreloff 0
nextrel 0
locreloff 0
nlocrel 0
Load command 7
cmd LC_LOAD_DYLINKER
cmdsize 32
name /usr/lib/dyld (offset 12)
Load command 8
cmd LC_UUID
cmdsize 24
uuid ED7FAB6F-8A35-3C1A-9825-A92D7E42B142
Load command 9
cmd LC_VERSION_MIN_MACOSX
cmdsize 16
version 10.10
sdk 10.10
Load command 10
cmd LC_SOURCE_VERSION
cmdsize 16
version 0.0
Load command 11
cmd LC_MAIN
cmdsize 24
entryoff 3904
stacksize 0
Load command 12
cmd LC_LOAD_DYLIB
cmdsize 56
name /usr/lib/libSystem.B.dylib (offset 24)
time stamp 2 Thu Jan 1 08:00:02 1970
current version 1213.0.0
compatibility version 1.0.0
Load command 13
cmd LC_FUNCTION_STARTS
cmdsize 16
dataoff 8288
datasize 8
Load command 14
cmd LC_DATA_IN_CODE
cmdsize 16
dataoff 8296
datasize 0
Load command 15
cmd LC_DYLIB_CODE_SIGN_DRS
cmdsize 16
dataoff 8296
datasize 64
  • LC_SEGMENT_64:(Load command 0-3)定义了一个(64-bit)的segments及其中的sections在文件加载时如何映射到内存中。
  • LC_SYMTAB:(Load command 5)定义了文件的符号表(stabs style)和字符表。链接器在链接文件的时候会用到,调试器也可以将它们将符号表映射到与它们相对应的源码文件中,以便于调试问题所在。
  • LC_DYSYMTAB:(Load command 6)为动态链接器提供了额外的信息,用以如何将符号写进符号表中。
  • LC_DYLD_INFO_ONLY:(Load command 4)定义了一些额外的与动态链接压缩信息,包含信号的元数据和动态绑定其它事情的操作码。“_ONLY”后缀意味着这条load command需要在程序中运行,因此那些不懂这条load command的旧的链接器将无法运行。
  • LC_LOAD_DYLINKER:(Load command 7)加载动态链接器,(name /usr/lib/dyld)
  • LC_LOAD_DYLIB:(Load command 12)加载动态链接共享库。如:“/usr/lib/libSystem.B.dylib”一个用标准C语言函数库。每一个动态库都会被动态链接器加载进内存,它们内部包含一张符号名称与地址链接在一起的符号表。
  • LC_MAIN:(Load command 11)指定了程序main()函数的入口地址。
  • LC_UUID:(Load command 8)提供了一个随机的唯一标示符(UUID),通常由静态连接器生成。
  • LC_VERSION_MIN_MAXOSX:(Load command 9)该二进制文件所支持的最低OS X系统版本。
  • LC_SOURCE_VERSION:(Load command 10)用于build二进制文件的源码版本。
  • LC_FUNTION_STARTS:(Load command 13)定义了一张包含所有函数开始位置的表。
  • LC_DATA_IN_CODE:(Load command 14)定义了code segemnt中的一张非指令表。
  • LC_DYLIB_CODE_SIGN_DRS:(Load command 15)为动态链接库定义了代码签名。

Segments and Sections

segment 在Mach-O文件中定义了一定范围的字节数,在动态链接器加载程序的时候这一特定范围的字节数将被映射到对应的虚拟内存中。
出于内存分页的考虑,头结构和加载命令通常会成为第一个segment 的一部分。在一个可执行文件中,这通常意味着头结构和加载命令会被放置到__TEXT segment 的开头,因为它才是第一个包含数据的segment。

注意:内容为空的section需要被放置在segment的末尾,否则标准工具将无法对Mach-O文件正确地进行操作。

我们可以通过size指令打印出Mach-O文件在虚拟内存中的布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
localhost:binspect ronglei$ xcrun size -x -l -m hw_clang.out 
Segment __PAGEZERO: 0x100000000 (vmaddr 0x0 fileoff 0)
Segment __TEXT: 0x1000 (vmaddr 0x100000000 fileoff 0)
Section __text: 0x2a (addr 0x100000f40 offset 3904)
Section __stubs: 0x6 (addr 0x100000f6a offset 3946)
Section __stub_helper: 0x1a (addr 0x100000f70 offset 3952)
Section __cstring: 0xe (addr 0x100000f8a offset 3978)
Section __unwind_info: 0x48 (addr 0x100000f98 offset 3992)
Section __eh_frame: 0x18 (addr 0x100000fe0 offset 4064)
total 0xb8
Segment __DATA: 0x1000 (vmaddr 0x100001000 fileoff 4096)
Section __nl_symbol_ptr: 0x10 (addr 0x100001000 offset 4096)
Section __la_symbol_ptr: 0x8 (addr 0x100001010 offset 4112)
total 0x18
Segment __LINKEDIT: 0x1000 (vmaddr 0x100002000 fileoff 8192)
total 0x100003000

在上一节的otool -l hw.out 命令输出中我们看到,segment/section结构被定义在’LC_SEGMENT_64’加载命令中。下面然我们来看一下有关它们的一下详细信息:

  • PAGEZERO :作为可执行文件的第一个segment,该segment被放置在虚拟内存开始的位置权限为可读写。PAGEZERO segment在被映射入内存后(在当前的架构对于基于Intel和PowerPC的Mac计算机而言,该空间大小为4096字节用十六进制表示为0x1000)占据虚拟内存一页的空间,而在存储在磁盘上市,__PAGESIZE segment不不占据任何空间,所以他加载进内存中所需要的空间要比存储在磁盘上的空间更大。顺便说一句该segment是隐藏恶意代码的好地方。

  • TEXT:包含可执行代码和只读数据。为了使得内核方便的将该段直接从可执行文件中映射到共享内存中,静态链接器设置该段在虚拟内存的权限为只读。当该段被映射到内存的时候,它被所有对它感兴趣的进程所共享(这正是framework,bundle和shared library的主要用途,但是它可以在同一个执行程序中运行多个拷贝),只读属性同时意味着由该segment组成的内存页不需要再被写回到磁盘中。当内核需要释放物理内存时,它只需简单地释放一页或更多页的TEXT,然后在需要的时候重新从磁盘将他们读取到内存。

  • __text:存放可执行机械代码的section;

  • __stubs:间接地符号存根。代表着lazy引用和no-lazy引用做要跳转的地址值。

  • __stubs_helper:为lazy加载符号提供帮助。

  • __cstring:存储只读的C语言格式字符串(如:“hello, wold!0”)的section,链接器会对该section进行去重的处理。

  • unwind_info:栈展开信息的压缩格式。该section是链接器对‘eh_frame’section里面的信息进行压缩产生的。

  • __eh_frame:一个标准的用于异常处理的
    section,它负责为DWARF格式提供栈展开信息。

  • DATA:一个包含可读写数据的segment。静态链接器设置这段虚拟内存的属性为可读写。因为它是可写读写的,共享库的DATA段会被拷贝到每一个与这个库链接的进程中。当可读写__DATA段被映射到虚拟内存页的时候,内核将他们标记为写时拷贝(copy-on-write);因此当进程向某页写入数据时,该进程将会接受一个该页的私有拷贝。

  • __nl_symbol_ptr:指向非延迟导入符号(non-lazy imported symbols)的表。

  • __la_symbol_ptr:指向延迟导入符号(lazy imported symbols)的表。

  • __OBJ:一个包含用于Ojbectiv-C语言动态运行时库数据的segment。
  • __IMPORT:一个只有在IA-32架构的计算机中才会产生的segment。
  • __LINKEDIT:段包含会被动态链接器使用的原生数据,如:符号,字符串和重定位表入口。

Reference

OS X ABI Mach-O File Format Reference

THE NITTY GRITTY OF “HELLO WORLD” ON OS X

Mach-O Executables

Dynamic Linking On OS X

otool

iOS程序main函数之前发生了什么

AFNetworking2.0源码分析(原创)
OC运行时源码概览
  • 文章目录
  • 站点概览
Zrongl

Zrongl

23 日志
3 分类
GitHub E-Mail
  1. 1. Basic Structure
  2. 2. Header Structure
  3. 3. Load Commands
  4. 4. Segments and Sections
  5. 5. Reference
© 2019 Zrongl
不争无尤
|
主题 – NexT.Mist v7.3.0
0%