可执行文件Mach-O的结构

在Mach-O文件的开头,有一个header结构标记这个文件的类型为Mach-O文件,并且这个结构里面包含了一些其它的信息:如target architecture和其它文件解析的选项等。
header结构后面紧接着是一串关于layout和链接文件特征的加载命令,除了一些其他消息外,这些加载命令定义了:

  1. 在虚拟内存中的初始化布局文件
  2. symbol 表的地址($可是使用nm(1)工具去查看里面的内容)
  3. 程序主线程的初始执行状态
  4. 包含主要可执行的imported symbols的共享库

在这些加载命令之后,所有的Mach-O文件包含了一个或多个segments的数据,每一个segment都包含一个或多个的sections,每个section包含一些独特类型的代码或数据。每一个segment定义了虚拟内存的一个区域,这个区域被动态连接器映射到程序的内存地址。segment和section的实际数量和布局是被上面的加载命令和文件类型确定产生的。
在用户级别的完全链接的Mach-O文件,最后一个segment是__LINKEDIT段,这个段包含了链接编译信息的表,如symbol 表、string表等,他们被用于动态加载器对可执行文件或Mach-O bundle和他们所依赖的库进行链接

头结构和加载命令

一个Mach-O文件包含了一个结构的代码和数据,一个Mach-O文件头结构定义了能让内核去查看的targer architecture,例如基于PowerOC电脑的代码不能再基于Intel的电脑上执行,可以使用下面命令查看头结构
xcrun otool -v -h a.out

可使用下面命令查看加载命令
xcun otool -v -l a.out

DATA

Segment

一个segment定义了一些Mach-O文件的数据、地址和内存保护属性,这些数据在动态链接器加载程序时被映射到了虚拟内存中。因此,segments通常都是虚拟内存页对齐的,一个segment包含了0或多个sections。
segments在运行时需要的内存比他们在编译时更多,而且他能定义比他们实际在硬盘空间大小中更多的内存空间。例如,链接器产生给PowerPC的可执行文件PAGEZERO segment在虚拟内存中的大小是一页,但是在硬盘中大小为0,因为 PAGEZERO 没有数据,所以它没有必要在可执行文件中占有任何空间。
为了压缩,中间对象文件()包含一个segment。这个segment没有名字,它包含了在最终对象文件中所有最终会分到不同segment的sections数据。这些数据结构中定义了一个section在segment中的名字和地方,然后静态链接会把每个section在最后对象文件中放到不同的segment中去。
为了规范,segment应该在虚拟内存页面边界上对齐,4096个字节。为了计算segment的大小,把每段的大小给加起来,然后以这个总大小在虚拟内存中取整4096字节的倍数。
头结构和加载命令在可执行文件中因为分页的原因。被当成是第一个segment的部分。
下列这些是标准OS X的开发工具可能在可执行文件中包含的segments,其中包括:

  1. 静态连接器创建了PAGEZERO segment作为可执行文件的第一段,这个segment在虚拟内存中位置为0x00000000并且没有确保是内存对齐的,因此造成访问NULL指针(一个会导致马上崩溃的通用c程序错误)。这个segment在现在的程序结构中充满了一整页的虚拟内存,即4096个字节,因为在PAGEZERO segment中没有数据,所以他在文件中没有占有空间(在segment command中文件大小为0)。
  2. __TEXT segment包含了可执行代码和其他只读的数据,为了允许内核把它直接从可执行文件映射到可共享内存,静态连接器设置了这一段的虚拟内存不允许执行写操作。当这段命令映射到内存中时,他能够被所有对这段内容感星期的进程访问(这个最开始被用作frameworks,bundles和共享的库,但是它也可能在OS X中同时运行多份同样的可执行复制文件)。只读的属性也意味着__TEXT segment的数据永远不需要重新写回硬盘中。当内核需要释放物理内存时,它只需要废弃掉那些__TEXT的内存页并且当需要的时候重新从硬盘中加载。
  3. __DATA segment包含了可写数据,静态链接器设置这段的虚拟内存允许读和写操作。因为它是可写的,一个框架或其他共享库的__DATA 段被每个链接到该库的进程复制一份。因为__DATA段的内存页被设置为可写可读的,内核会标记他们为在写的时候会被服务,因此当一个进程写了__DATA段的其中一页时,进程会收到那一页的复制数据。
  4. __OBJC段包含了在Objective-C语言运行时支持库的数据
  5. __IMPORT段包含了在可执行文件中的没有被定义的符号集合和非lazy的指向符号的指针。
  6. __LINKEDIT段包含了动态链接器的原始数据,如符号,字符串和重定位的表的入口。

Section

TEXT和\DATA段可能包含了一系列的标准sections。__OBJC segment包含了一系列的是Objective-C编译器私有的段。需要知道的是静态链接器和文件分析工具会使用section的类型和数据去定义它们如何处理section。

__TEXT segment

text section 有可执行的机器码,编译器只会把可执行的代码放到这一个section,没有其它任何形式的表或者数据。
\
cstring section c的字符串常量,静态链接器会在构造最终的程序时把c的字符串常量合并(删除冗余的常量)
__picsymbol_stub 独立位置的间接符号集合Position-Independent Code” in Mach-O Programming Topics
__symbol_stub 间接符号集合
__const 初始化的常量变量,编译器会把所有声明为const的不能重定位的数据放到这里。(编译器会把未初始化的常量变量放到__zero-filled section中
__literal4 编译器会把单精度的浮点数放到这个section
__literal8 编译器会把双精度的浮点数放到这个section

__DATA segment

data 初始化的可变变量,如可写的c字符串和数据数组等。
\
la_symbol_ptr lazy的符号指针,间接引用了被import到不同文件的功能函数
__nl_symbol_ptr 非lazy的符号指针,间接引用了被import到不同文件的数据项(See “Position-Independent Code” in Mach-O Programming Topics for more information.)
__dyld 动态链接器的的占位符段,好像然而并没什么用
__const 初始化的可重定位的常量变量
__module_init_func module的初始化函数,c++编译器会把静态构造器放到这里。
__bss 未初始化的静态变量,如static int i
__common 未初始化的被import的符号定义,如被定义在全局范围中的int i;

__IMPORT segment

jump_table 在动态库中调用函数的符号集合
\
pointers 非lazy的符号指针,这些用于直接引用被import到不同文件的功能函数

可以使用xcrun size -x -l -m a.out命令查看有哪些数据段

Data Types

详情查看 OS X ABI Mach-O File Format Reference