概述
本文档描述了Mach-O文件格式的结构,它被用来存储程序和库到硬盘中,作为Mac OS X程序的二进制接口(ABI).想要了解xcode工具和Mach-O文件怎么工作以及底层任务调试的,需要了解这些信息
Mach-O文件格式提供中间物(构建过程中产生的)和最终存储有机器码和数据的文件。它被设计成一个灵活的BSD a.out的替代品。这种文件被编译器和静态链接器使用,并在运行时包含静态链接的可执行代码。随着Mac OS X目标的发展,动态链接的特性也被添加进来,从而为静态链接和动态链接的代码形成了单一的文件格式。
基本结构
一个Mach-O文件包含三个主要区域(如图1所示)
在每个Mach-O文件的开始部分都有一个Header结构,这个Header标志这个文件是Mach-O文件。这个头部也包含了其他基础文件类型信息,标明目标体系结构,包含指定选项的标志,这些标志会影响对文件剩余部分的解释。
在header之后是一系列大小可变的加载命令,它们指定文件的布局和链接特征。在其他信息中,load命令可以指定:
- 文件在虚拟内存中的初始化布局
- 符号表的位置(被用来动态链接使用)
- 程序主线程的初始执行状态
- 包含主可执行文件导入符号定义的共享库的名称
在加载指令之后,所有的Mach-O文件都包含一个或多个段的数据。每个段有零个或多个section,段的每个section都包含特定类型的代码或数据。每个段定义了虚拟内存区域,被连接器用来映射到进程的地址空间中,段和section的确切数量和布局由load命令和文件类型指定。
在用户级完全链接的Mach-O文件中,最后一个段是链接编辑段。此段包含链接编辑信息的表,如符号表、字符串表等,动态加载程序使用这些信息将可执行文件或Mach-O包链接到其附属库。
Mach-O文件中的各种表按编号引用section(节)。节编号从1(不是0)开始,并跨越段边界。因此,在文件中第一个段可能包含1、2节,第二个段可能包含3、4节。
当使用Stabs调试格式时,符号表还包含调试信息。使用DWARF时,调试信息存储在图像对应的dSYM文件中,该文件由uuid_command(第20页)结构指定。
注释: stabs取名于symbol table strings,因为开始的时候,调试信息是以字符串的形式存储在Unix的a.out目标文件的符号表中。 stabs以字符串的形式编码程序的信息。最开始的时候,stabs很简单,但是后来变得越来越复杂,难解,而且不一致。此外,stabs没有形成标准,文档也不够详细。Sun Microsystem基于stabs作了大量扩展;GCC在对SUn的扩展进行反向工程的过程中,作了其它的扩展。stabs仍然被广泛使用。
DWARF已经被广泛使用,包括GCC和LLVM。DWARF也是基于嵌套结构存储调试信息。
DWARF源于Unix System V Release 4中的C编译器以及sdb调试器。1989年的文档形成了DWARF 1。1900发布了DWARF 2的一个draft标准。随后,因为Motorola一个项目的失败,支持团队被解散。随后,DWARF 2的扩展泛滥,就有了各种各样的实现,没能形成最终标准。直到2006年发布的最终标准DWARF 3. 2010年发布了DWARF 4.
头部结构和加载命令
一个Mach-O文件包含了一个架构的代码和数据。Mach-O文件的头结构指定了目标架构,这使得内核能够确保,例如,为基于powerpc的Macintosh计算机设计的代码不会在基于intel的Macintosh计算机上执行。
你可以将多个Mach-O文件组合成一个二进制文件,用 “Universal Binaries and 32-bit/64-bit PowerPC Binaries” 相应格式描述即可。
包含多个体系结构的目标文件的二进制文件不是Mach-O文件。他们存档一个或多个Mach-O文件。
段和节通常按名称访问。按照惯例,段的命名是使用所有大写字母加上两个下划线(例如,用字母组合);section的命名应该使用所有小写字母加上两个下划线(例如,用字母组合)。这个命名约定是标准的,尽管对于工具的正确操作不是必需的。
段(segment)
段定义了Mach-O文件中的一个字节范围,以及地址和内存保护属性,当动态链接器加载应用程序时,这些字节被映射到虚拟内存中。因此,段总是与虚拟内存页对齐。一个段包含零个或多个节。
运行时比构建时需要更多内存的段可以指定比实际磁盘上更大的内存大小。PowerPC可执行文件的链接器生成的__PAGEZERO段的虚拟内存大小为一页,而在磁盘上的大小为0。它不需要占用可执行文件中的任何空间。
note: 段的末尾必须用大小为0的节填充,否则标准工具将没办法成功操作Mach-O文件。
为了紧凑性,中间对象文件只包含一个段。这段没有名字,在最终的对象文件中,它包含了不同段所定义的sections。定义了一个section(page 23)的数据结构包含了段中节的名称,静态链接器将每个节放在最终的目标文件中。
为了更好的性能,段应该和虚拟内存页的边界对齐,对于PowerPC和x86处理器每个虚拟内存页大写是4096b。累加每个section的大小,然后对结果取整作为下个虚拟内存页的分界线(4096b或者4kb).用这种算法,一个段最小是4kb,接下来会以4kb作为增量。
出于分页的目的,头部和加载命令会作为第一个段的一部分。在一个可执行文件中,通常这个意思是头部和加载命令__TEXT开始的位置,因为这是第一个包含数据的段。__PAGEZERO段在硬盘上面没有数据,所以一般会忽略它。
这些是标准的Mac OS X开发工具(包含在Xcode工具CD中)可能包含在Mac OS X可执行文件中的片段:
- 静态连接器会创建一个
__PAGEZERO
段作为可执行文件的第一个段。这个段位于虚拟内存的0位置,而且没有分配任何保护权限,它们的组合会导致对NULL
的访问,这是一种常见的C编程错误,会立即崩溃。__PAGEZERO
段对于现在的架构就是一页虚拟内存页的大小(对于基于Intel和Power-PC内核的Mac计算机,一般是4096字节或者十六进制0x1000).因为__PAGEZERO
段没有数据,在文件中没有占用任何空间(段命令中的文件大小是0) -
__TEXT
段包含了可执行代码和只读数据。允许内核直接从可执行文件映射到共享内存中,静态连接器设置这个段虚拟内存的权限为不允许写入。当段被映射到内存中时,它可以在所有对其内容感兴趣的进程之间共享。(这个主要用在frameworks,bundles,和共享库,但是可以在Mac OS X中运行同一可执行文件的多个副本,这也适用于这种情况。)只读属性也意味着这些内存也组成的__TEXT
段是不可以会写到磁盘中的。当内核需要释放物理内存的时候,他可以放弃一个或多个__TEXT
,如果下次有需要,就从磁盘重新读取他们 -
__DATA
段包含的是可写数据。静态连接器设置这段虚拟内存的权限为可读可写。因为是可写的,因为它是可写的,所以框架或其他共享库的数据段在逻辑上被复制到与该库链接的每个进程。当组成__DATA
段的内存页可读可写时,内核将它们标记为“写时复制”;因此当一个进程写入这些页的其中一页时,该进程会收到当前进程私有的当前页备份。 -
__OBJC
段包含了OC语言runtime支持库所用到的数据。 -
__IMPORT
段包含了符号桩和指向可执行文件中未定义的符号的非懒加载指针。这个段仅在IA-32架构的目标可执行文件中生成。 -
__LINKEDIT
段包含了动态连接器所用到的原始数据,例如符号、字符串、和重定位表记录
Sections(节)
__TEXT
和__DATA
段包含了许多标准section,列于表1、表2、和表3。__OBJC
段包含了许多OC编译器私有section。请注意,静态链接器和文件分析工具使用节类型和属性(而不是节名称)来确定它们应该如何处理节。section名称、类型和属性将在section数据类型的描述(第23页)中进一步说明。
表1 __TEXT
段的节
段 和 节名称 | 内容 |
---|---|
__TEXT, __text | 可执行机器码。编译器通常只放可执行代码,没有其他类型的表和数据 |
__TEXT, __cstring | C字符串常量。c字符串就是一个以'\0'结尾的非空的字节序列。静态连接器在构建最终结果时会合并c常量字符串值,移除重复的 |
__TEXT,__picsymbol_stub | 位置无关的间接符号桩。有关更多信息,请参阅Mach-O编程主题中的“动态代码生成”。 |
__TEXT,__symbol_stub | 简介符号桩,有关更多信息,请参阅Mach-O编程主题中的“动态代码生成” |
__TEXT, __const | 初始化的常量变量。编译器会在这个section中放置用const 修饰的不可重定位数据 |
__TEXT, __litera14 | 4字节的字面量。编译器在本节中放置单精度浮点常量。在构建最终文件时,静态链接器合并这些值,删除重复项。对于某些架构,编译器使用立即加载指令比添加到本节更有效。 |
__TEXT, __litera18 | 8字节的字面量。编译器在本节中放置双精度浮点常量。在构建最终文件时,静态链接器合并这些值,删除重复项。对于某些架构,编译器使用立即加载指令比添加到本节更有效。 |
表2 __DATA
段中的节
段 和 节名称 | 内容 |
---|---|
__DATA, __data | 初始化的可变变量,例如可写的c字符串和数据数组 |
__DATA, __la_symbol_ptr | 懒加载符号指针,它间接引用了来自不同文件的重要函数。了解更多信息,参阅Mach-O编程主题中的“生成动态代码” |
__DATA, __nl_symbol_ptr | 非懒加载符号指针,它间接引用了来自不同文件的数据项 。了解更多信息,参阅Mach-O编程主题中的“生成动态代码”。 |
__DATA, __dyld | 动态连接器使用的占位符部分 |
__DATA, __const | 初始化的可重定位的常量变量 |
__DATA, __mod_init_func | 模块初始化函数。C++编译器放置静态构造函数的地方 |
__DATA, __mod_term_func | 模块终止函数 |
__DATA, __bss | 未初始化的静态变量的数据 (比如,static int i; ) |
__DATA, __common | 导入的未初始化符号定义(比如:int i; )位于全局范围内的(声明在函数之外的) |
表3 __IMPORT
段的节
段 和 节名称 | 内容 |
---|---|
__IMPORT, __jump_table | 动态库中函数调用的存根。 |
__IMPORT,__pointers | 非懒加载符号指针,它直接引用从不同文件导入的函数。 |
注意:编译器或任何创建Mach-O文件的工具都可以定义额外的节名。这些额外的名称没有出现在表1中。
数据类型
这里介绍的是构成Mach-O文件的数据类型。除了fat_header(第56页)和fat_arch(第56页)以外,所有Mach-O数据结构中的整数类型的值都是使用主机CPU的字节排序方案写入的,它们都是按大端字节顺序写入的。
头部数据结构
mach_header
指定文件的一般属性。出现在以32位体系结构为目标的目标文件的开头。声明在/usr/include/mach-o/loader.h
,也可以看mach_header_64
(第14页)
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;
};
字段
magic
:包含了一个整数值,标志这个文件是32位的Mach-O的文件。如果文件打算在与运行编译器的计算机的字节序相同的CPU上使用,请使用常量MH_MAGIC。 当目标计算机的字节排序方案与主机CPU相反时,可以使用常量MH_CIGAM。
cputype
:这个整数值指示了你准备在什么架构上运行此文件。适当的值有以下几种:
-
CPU_TYPE_POWERPC
目标是基于PowerPC的Mac电脑 -
CPU_TYPE_I386
目标是基于Intel的Mac电脑
cpusubtype
指定CPU的精确模型的整数。在Mac OS X内核支持的所有PowerPC或x86处理器上运行,这个应该设置为CPU_SUBTYPE_POWERPC_ALL
或者CPU_SUBTYPE_I386_ALL
filetype
该整数值标明了文件的用途和对齐方式。这个字段有效的值包括:
-
MH_OBJECT
文件类型是用于中间目标文件的格式。它包含了一个段中所有的节格式很紧凑。编译器和汇编器通常会为每个源码文件创建一个MH_OBJECT
文件。按照惯例,这个文件名字的后缀格式是.o
。 -
MH_EXECUTE
文件类型是标准可执行程序使用的格式。 -
MH_BUNDLE
文件类型通常由运行时加载的代码使用(通常称为bundle或插件)。按照惯例,这种格式的文件扩展名是.bundle
。 -
MH_DYLIB
文件类型用于动态共享库。它包含一些额外的表来支持多个模块。按照惯例,这种格式的文件扩展名是dylib
,除了框架的主共享库外,它通常是不会有文件扩展名的。 -
HM_PRELOAD
文件类型是一种可执行格式,用于Mac OS X内核没有加载的特殊用途程序,例如刻录到可编程ROM芯片中的程序。不要将此文件类型与MH_PREBOUND
标志混淆,MH_PREBOUND
标志是静态链接器在头结构中设置的标志,用于标记预绑定图像。 -
MH_CORE
文件类型用于储存核心文件,这通常是在程序崩溃时创建的。核心文件存储进程崩溃时的整个地址空间。稍后,您可以在核心文件上运行gdb来找出为什么会发生崩溃。 -
MH_DYLINKER
文件类型用于动态连接器共享库。这是dyld
文件类型。 -
MH_DSYM
文件类型指定存储对应二进制文件的符号信息的文件。
ncmds
:在头部结构后面指示了加载命令的数量
sizeofcmds
:在header结构后面指示这加载命令所占用的字节数量
flags
:包含了一组位标志,指示着Mach-O文件格式某些可选特性的状态。这些是你可以用来操作该字段的掩码:
-
MH_NOUNDEFS
--该对象文件在构建时不包含未定义的引用。 -
MH_INCRLINK
--该对象文件是相对于基类文件增量连接的输出而且不能被重复连接。 -
MH_DYLDLINK
--该对象文件是动态连接器的输入,不能被再次静态连接。 -
MH_TWOLEVEL
--该镜像用二级命名空间绑定。 -
MH_BINDATLOAD
--当文件被加载时,动态拦截器应该绑定未定义的引用。 -
MH_PREBOUND
--文件的未定义引用是预绑定的。 -
MH_PREBINDABLE
--文件未定义的引用需要预绑定 -
MH_NOFIXPREBINDING
--动态链接器不会将此可执行文件通知预绑定代理。 -
MH_ALLMODSBOUND
--指示此二进制文件绑定到其附属库的所有两级名称空间模块。仅当MH_PREBINDABLE和MH_TWOLEVEL被设置时使用。 -
MH_CANONICAL
--通过从文件中取消预绑定-清除预绑定信息,此文件已被规范化。有关详细信息,请参阅redo_prebinding手册页。 -
MH_SPLIT_SEGS
--该文件将只读段和只写段分开 -
MH_FORCE_FLAT
--可执行文件强制所有映像使用平面名称空间绑定。 -
MH_SUBSECTIONS_VIA_SYMBOLS
--对象文件的各个部分可以划分为单独的块。如果其他代码不使用这些块,那么它们就是死区。有关详细信息,请参阅Xcode用户指南中的“链接”。 -
MH_NOMULTIDEFS
--这把伞保证在它的子图像中没有符号的多重定义。因此,总是可以使用两级名称空间提示。
加载命令数据结构
加载命令位于对象文件中头部后面,它指定了文件的逻辑结构以及在虚拟内存中的布局。每个加载命令都以一个指定命令类型和命令数据大小的字段开始。
load_command
包含所有加载命令通用的字段。
struct load_command
{
uint32_t cmd;
uint32_t cmdsize;
};
字段
cmd
:一个整数,代表加载命令的类型。表4列出了有效的加载命令类型。
cmdsize
:这个字段指定了记载命令数据结构总共的字节大小。根据load命令不同的类型,每个load命令结构包含一组不同的数据,所以每组数据大小可能不同。在32位架构中,这组数据的大小总是4的倍数,在64位架构中是8的倍数。如果load命令数据不能被4或8整除(这取决于目标体系结构是32位还是64位),则在末尾添加包含0的字节,直到它被整除为止。
讨论
表四列出了有效的加载命令类型,以及每种类型完整数据的连接。
表四 Mach-O加载命令
命令 | 数据结构 | 用途 |
---|---|---|
LC_UUID | uuid_command(page 20) | 指定图像或其对应的dSYM文件的128位UUID |
LC_SEGMENT | segment_command | 加载此文件时,定义映射到进程地址空间中所需的文件段。而且每个段中包含了所有的节 |
LC_SYMTAB | symtab_command | 指定了文件的符号表。静态链接器和动态连接器连接文件的时候都需要用到这些信息,还可以通过调试器将符号映射到生成符号的原始源代码文件。 |
LC_DYSYMTAB | dysymtab_command | 指定了动态连接器用到的附带符号表信息 |
LC_THREAD LC_UNIXTHREAD | thread_command | 对于可执行文件,LC_UNIXTHREAD 命令定义了进程主线程的线程状态。LC_THREAD 和LC_UNIXTHREAD 一样,但是LC_THREAD 不会引起内核分配堆栈 |
LC_LOAD_DYLIB | dylib_command | 定义此文件链接的动态共享库的名称。 |
LC_ID_DYLIB | dylib_command | 定义了动态共享库安装名称 |
LC_PREBOUND_DYLIB | prebound_dylib_command | 对于此可执行文件链接预绑定的共享库,指定使用的共享库中的模块。 |
LC_LOAD_DYLINKER | dylinker_command | 指定内核执行加载文件所需的动态连接器 |
LC_ID_DYLINKER | dylinker_command | 标志这个文件可以作为动态连接器 |
LC_ROUTINES | routines_command | 包含共享库初始化例行程序的地址(由链接器的-init 选项指定)。 |
LC_ROUTINES_64 | routines_command_64 | 包含共享库64位初始化例行程序的地址(由链接器的-init 选项指定)。 |
LC_TWOLEVEL_HINTS | twolevel_hints_command | 包含两级命名空间查询提示表。 |
LC_SUB_FRAMEWORK | sub_framework_command | 将此文件标识为伞形框架的子框架的实现。伞形框架的名称存储在字符串参数中。(伞形框架可以包含多个子框架,苹果不推荐这样使用) |
LC_SUB_UMBRELLA | sub_umbrella_command | 指定此文件作为伞框架的子伞 |
LC_SUB_LIBRARY | sub_library_command | 标志这个文件可以作为伞框架的一个字库的实现。请注意,Apple尚未为子库定义受支持的位置。 |
LC_SUB_CLIENT | sub_client_command | 子框架可以明确地允许另一个框架或包链接到它,方法是包含一个LC_SUB_CLIENT load命令,该命令包含框架的名称或包的客户端名称。 |
uuid_command
指定镜像或者与之相匹配的dSYM文件的128位通用唯一标识符
struct uuid_command
{
uint32_t cmd;
uint32_t cmdsize;
uint8_t uuid[16];
};
字段
cmd
为此结构设置LC_UUID。
cmdsize
设置sizeof(uuid_command)
uuid
128位唯一标识符
segment_command
指定组成段的32位Mach-O文件中的字节范围。这些字节通过加载器映射到程序的地址空间中。声明在/usr/include/mach-o/loader.h
。
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;
};
字段
cmd
所有加载命令的结构中都有此字段。设置LC_SEGMENT
对于次结构体。
cmdsize
所有加载命令结构体中都有此字段。对于这个结构,将这个字段设置为sizeof(segment_command)
加上后面所有section数据结构的大小(sizeof(segment_command + (sizeof(section) * segment->nsect))
。
segname
一个用C字符串作为名字的段。这个字段值可以是任意顺序的ASCII字符,Apple定义的段名以两个下划线开头,并由大写字母组成(如在__TEXT
和__DATA
中)。该字段的长度固定为16字节。
vmaddr
指向段在虚拟内存地址中开始的位置。
vmsize
指示此段占用的虚拟内存字节数。参见下面的filesize
大小说明。
fileoff
指示数据文件被映射到虚拟内存中,相对于开始位置的偏移量。
filesize
指定了段在磁盘上面占用的字节数量。对于这些段,它们在运行时要求的内存要比构建时多,vmsize
要比filesize
大。比如,对于MH_EXECUTABLE
文件连接器生成的__PAGEZERO
段分配的虚拟内存大小是0x1000但是文件大小是0。因为__PAGEZERO
段中没有数据,这里是不会为它分配任何内存的,直到运行时。而且,静态连接器经常分配未初始化的数据在__DATA
段后面;通过这些例子,vmsize
要大于filesize
。加载程序保证这种类型的任何内存都是用零初始化的。
maxprot
表示此段被允许的且受保护的最大虚拟内存。
initprot
表示此段受保护的初始化虚拟内存。
nsects
表示此加载命令后面节数据结构的数量。
flags
定义了一组标志,此段的加载会受到这组标志的影响:
-
SG_HIGHVM
--该段的文件内容为虚拟内存空间的高位部分;较低的部分是零填充(用于核心文件中的堆栈); -
SG_NORELOC
--这一段没有任何东西被移动到它里面也没有任何东西被移动到它里面。它可以安全更换,无需搬迁。
segment_command_64
表示由段组成的Mach-O文件的字节范围。这些字节被映射到加载器加载程序的地址空间。
struct segment_command_64
{
uint32_t cmd;
uint32_t cmdsize;
char segname[16];
uint64_t vmaddr;
uint64_t vmsize;
uint64_t fileoff;
uint64_t filesize;
vm_prot_t maxprot;
vm_prot_t initprot;
uint32_t nsects;
uint32_t flags;
};
section_64
定义64位节使用的元素。直接跟在一个segment_command_64
数据结构后面的一组section_64
数据结构,segment_command_64
结构中,nsects
是这组数据的准确数量。
struct section_64
{
char sectname[16];
char segname[16];
uint64_t addr;
uint64_t size;
uint32_t offset;
uint32_t align;
uint32_t reloff;
uint32_t nreloc;
uint32_t flags;
uint32_t reserved1;
uint32_t reserved2;
};
字段
secname
一个字符串,指定了这个节的名字。这个字段的值可以是任意序列的ASCII字符,苹果定义的名字由两个下划线开始,并且由小写字母组成(比如__text
和__data
)。这个字段的长度固定为16字节。
segname
表示包含此节的段的字符串名字。为了紧凑性,中间对象文件--类型是MH_OBJECT
只包含一个段,并且把所有的节都放在这个一个段中。静态连接器在构建最终产品时,会将每个节放到指定的段中(任意类型文件不仅是MH_OBJECT
)
addr
一个整数表示了节的虚拟内存地址。
size
一个整数标志此节占用的虚拟内存的字节大小
offset
一个整数表示此节在文件中的偏移。
align
一个整数表示节的字节对齐方式。这个整数是2的幂数;比如,具有8字节对齐的节的对齐值为3
reloff
指定此部分的第一个重定位项的文件偏移量的整数。
nreloc
一个整数,用于指定位于本节的重定位项的数目。
flags
这个整数被分为两部分。最低有效8位包含节类型,最多的有效24位包含了一组标记,这些标记表示节的其他属性。这些类型和标记主要是静态连接器和文件分析工具在使用,比如otool
,决定了要怎么修改和显示这些节。下面是有效类型:
-
S_REGULAR
--这个节没有特殊的类型。标准工具会创建这个类型的__TEXT
的__text
节。 -
S_ZEROFILL
--按需填充零的节--当这个section第一次读取或者写入的会后,每页都会自动的被包含0的字节填充。 -
S_CSTRING_LITERALS
--这个节仅包含C的常量字符串。标准工具会创建此类型的__TEXT
段__cstring
section。 -
S_4BYTE_LITEBALS
--这个节仅包含4字节长的常量值。标准工具会创建此类型的__TEXT
的__literal4
节。 -
S_8BYTE_LITERALS
--这个节仅包含8字节长的常量值。标准工具会创建此类型的__TEXT
的__literal8
节。 -
S_LITERAL_POINTERS
--这个section仅包含常量值的指针。 -
S_NON_LASY_SYMBOL_POINTERS
--这个section仅包含非懒加载符号指针。标准工具会创建此类型的__DATA
段的__nl_symbol_ptrs
节 -
S_SYMBOL_STUBS
--这个section包含了符号桩(存根),标准工具会创建此类型的__TEXT
的__symbol_stub
节和__TEXT
的__picsymbol_stub
节。详情请看Mach-O编程主题中的“动态代码生成”。 -
S_MOD_INIT_FUNC_POINTERS
--这个section包含了模块初始化函数的指针。标准工具会此类型的__DATA
的__mod_init_func
节。 -
S_MOD_TERM_FUNC_POINTERS
--这个section包含了模块终止函数的指针。标准工具会创建此类型的__DATA
的__mod_term_func
节。 -
S_COALESCED
--本节包含由静态链接器(可能还有动态链接器)合并的符号。多个文件可以包含同一符号的合并定义,而不会导致多个定义符号错误。 -
S_GB_ZEROFILL
--这是一个零填充随需应变的section。他可能大于4GB。这个section一定要被放在仅包含零填充section的段中。如果你将一个零填充section放到一个非零填充的段中,会导致那些section用31位偏移访问不到。这个结果源于一个事实,即一个零填充的部分的大小可以大于4 GB(在32位地址空间中)。正如结果一样,静态连接器是不能构建输出文件的。
讨论
在Mach-O文件中的每个section都包含类型和一组属性标记。在中间对象文件中,这个类型和属性决定了静态连接器怎么将section拷贝到最终产品中。对象文件分析工具(例如otool)用类型和属性决定怎么读取和现实这些section。有些section类型和属性是动态连接器用到的。
这些是符号类型和属性的重要静态链接变体:
- Regular sections.在常规部分中,中间对象文件中只能存在外部符号的一个定义。如果发现任何重复的外部符号定义,静态链接器将返回一个错误。
- Coalesced sections.在最终产品中,静态连接器在每个合并section符号定义中只有一个实例。为了支持复杂的语言(比如C++的vtables和RTTI)编译器会为每个中间对象文件创建一个特别的符号定义。然后,静态连接器和动态连接器将多个定义减少到程序用的单个定义。
- Coalesced sections with weak definitions弱引用符号定义可能仅显示在合并section中。当用静态连接器查找一个符号的多个定义时,它将忽略一些合并符号定义中被设置为弱定义。如果这里没有非弱定义,第一个弱引用定义将被替换。这样设计是为了支持C++模板;它允许一个明确的模板实例去复写模糊的另一个模板。C++编译器会将明确的定义放到工整的section中,而且会把模糊的定义放到合并section中,标记为若定义。用弱定义构建的中间目标文件(以及静态存档库)只能在Mac OS X v10.2及更高版本的静态链接器中使用。如果最终产品不会被用到macOS更好版本中,就不会包含若定义。
section_64
定义了64位section用到的元素。是一个section_64
数据结构的数组,直接放在segment_command_64
数据结构后面,这个数组的数量由segment_command_64
机构中的nsects
字段决定。
struct section_64
{
char sectname[16];
char segname[16];
uint64_t addr;
uint64_t size;
uint32_t offset;
uint32_t align;
uint32_t reloff;
uint32_t nreloc;
uint32_t flags;
uint32_t reserved1;
uint32_t reserved2;
};
字段
sectname
一个字符串指定了section的名称。这个字段值可以是任意序列的ASCII字符,然而苹果规定section名字的开始是两个下划线,后面由小写字母组成。这个字段最多16个字节。
segname
一个字符串指定了最终包含此section的段的名字。为了紧凑性,类型为MH_OBJECT
的中间对象文件仅包含一个段,并将所有的section放到里面。静态连接器。在静态连接器构建最终产品时,回见每个section放到指定的段中。(任意文件不仅限于类型是MH_OBJECT
)。
addr
整数,指定了section在虚拟内存中的地址。
size
整数,指定了section在虚拟内存中所占大小。
offset
整数,指定了section在文件中的偏移。
align
整数,指定了section的字节对齐方式。这个整数是2的幂次方;比如,一个八字节对齐方式的section的align值是3.
reloff
整数,指定了此section第一个重定位入口的文件偏移。
nreloc
整数,指定了此section重定位入口的数量,位于reloff。
flags
这个整数被分为两部分。最低有效8位包含节类型,最多的有效24位包含了一组标记,这些标记表示节的其他属性。这些类型和标记主要是静态连接器和文件分析工具在使用,比如otool
,决定了要怎么修改和显示这些节。下面是有效类型:
-
S_REGULAR
--这个节没有特殊的类型。标准工具会创建这个类型的__TEXT
的__text
节。 -
S_ZEROFILL
--按需填充零的节--当这个section第一次读取或者写入的会后,每页都会自动的被包含0的字节填充。 -
S_CSTRING_LITERALS
--这个节仅包含C的常量字符串。标准工具会创建此类型的__TEXT
段__cstring
section。 -
S_4BYTE_LITEBALS
--这个节仅包含4字节长的常量值。标准工具会创建此类型的__TEXT
的__literal4
节。 -
S_8BYTE_LITERALS
--这个节仅包含8字节长的常量值。标准工具会创建此类型的__TEXT
的__literal8
节。 -
S_LITERAL_POINTERS
--这个section仅包含常量值的指针。 -
S_NON_LASY_SYMBOL_POINTERS
--这个section仅包含非懒加载符号指针。标准工具会创建此类型的__DATA
段的__nl_symbol_ptrs
节 -
S_SYMBOL_STUBS
--这个section包含了符号桩(存根),标准工具会创建此类型的__TEXT
的__symbol_stub
节和__TEXT
的__picsymbol_stub
节。详情请看Mach-O编程主题中的“动态代码生成”。 -
S_MOD_INIT_FUNC_POINTERS
--这个section包含了模块初始化函数的指针。标准工具会此类型的__DATA
的__mod_init_func
节。 -
S_MOD_TERM_FUNC_POINTERS
--这个section包含了模块终止函数的指针。标准工具会创建此类型的__DATA
的__mod_term_func
节。 -
S_COALESCED
--本节包含由静态链接器(可能还有动态链接器)合并的符号。多个文件可以包含同一符号的合并定义,而不会导致多个定义符号错误。 -
S_GB_ZEROFILL
--这是一个零填充随需应变的section。他可能大于4GB。这个section一定要被放在仅包含零填充section的段中。如果你将一个零填充section放到一个非零填充的段中,会导致那些section用31位偏移访问不到。这个结果源于一个事实,即一个零填充的部分的大小可以大于4 GB(在32位地址空间中)。正如结果一样,静态连接器是不能构建输出文件的。
以下是一个部分可能的属性:
-
S_ATTR_PURE_INSTRUCTIONS
--这个section仅包含可执行机器指令。标准工具会为__TEXT
的__text
、__TEXT
的__symbol_stub
和__TEXT
的__picsymbol_stub
设置此flag。 -
S_ATTR_SOME_INSTRUCTIONS
--这个section包含了可执行机器指令。 -
S_ATTR_NO_TOC
--section包含了合并符号,而且它是不会被放到静态归档库内容的表中的。 -
S_ATTR_EXT_RELOC
--section包含了一定被重定位的引用。这些引用涉及到了其他文件存在的数据(未定义的符号)。为了支持重定位扩展,段中最大受保护的虚拟内存包含的section必须允许被读写。 -
S_ATTR_LOC_RELOC
--section包含了一定被重定位的引用。这些引用涉及到了文件中的数据。 -
S_ATTR_STRIP_STATIC_SYMS
--如果镜像mach_header
头部中的结构设置了MH_DYLDLINK
标志,那么这个section的静态符号是可以被去掉的。 -
S_ATTR_NO_DEAD_STRIP
--这部分不能拆装。详情可以参考Xcode用户指南中的"Linking"。 -
S_ATTR_LIVE_SUPPORT
--如果引用的代码是活跃的,但引用是不可检测的,则此部分不能被拆装。
讨论
在Mach-O文件中的每个section都包含类型和一组属性标记。在中间对象文件中,这个类型和属性决定了静态连接器怎么将section拷贝到最终产品中。对象文件分析工具(例如otool)用类型和属性决定怎么读取和现实这些section。有些section类型和属性是动态连接器用到的。
这些是符号类型和属性的重要静态链接变体:
- Regular sections.在常规部分中,中间对象文件中只能存在外部符号的一个定义。如果发现任何重复的外部符号定义,静态链接器将返回一个错误。
- Coalesced sections.在最终产品中,静态连接器在每个合并section符号定义中只有一个实例。为了支持复杂的语言(比如C++的vtables和RTTI)编译器会为每个中间对象文件创建一个特别的符号定义。然后,静态连接器和动态连接器将多个定义减少到程序用的单个定义。
- Coalesced sections with weak definitions弱引用符号定义可能仅显示在合并section中。当用静态连接器查找一个符号的多个定义时,它将忽略一些合并符号定义中被设置为弱定义。如果这里没有非弱定义,第一个弱引用定义将被替换。这样设计是为了支持C++模板;它允许一个明确的模板实例去复写模糊的另一个模板。C++编译器会将明确的定义放到工整的section中,而且会把模糊的定义放到合并section中,标记为若定义。用弱定义构建的中间目标文件(以及静态存档库)只能在Mac OS X v10.2及更高版本的静态链接器中使用。如果最终产品不会被用到macOS更好版本中,就不会包含若定义。
twolevel_hints_command
定义了LC_TWOLEVEL_HINTS
加载命令的一些属性。
struct twolevel_hints_command
{
uint32_t cmd;
uint32_t cmdsize;
uint32_t offset;
uint32_t nhints;
};
字段
cmd 所有加载命令的结构都有,次结构设置为LC_TWOLEVEL_HINTS
。
cmdsize 所有下载命令的结构都有。次结构,设置为sizeof(twoevel_hints_command)
。
offset 表示从文件开始到twolevel_hint
数据结构的数组的偏移量,称为二级命名空间提示表。
nhints 位于偏移处二级命名数据结构的数量。
讨论
当静态连接器构建一个二级命名空间镜像时,会增加LC_TWOLEVEL_HINTS
加载命令和二级命名空间提示表在输出文件中。
特殊注意事项
默认,在HM_BUNDLE
文件中ld
是不包含LC_TWOLEVEL_HINTS
命令或者耳机命名空间提示表的,因为这样命令的存在会导致Mac OS X v10.0装载动态连接器时会崩溃。如果你的代码仅运行在Mac OS X v10.1及之后版本,明确二级命名空间提示表可以被使用。
twolevel_hint
指定二级命名空间提示变的一个入口
struct twolevel_hint {
uint32_t isub_image:8,
itoc:24;
};
字段
isub_image 定义符号中的子镜像。它是构成伞形镜像的镜像列表的索引。如果该字段为0,则符号在伞镜像本身中。如果镜像不是伞形框架或库,则此字段为0。
itoc 将符号索引放入由isub_image字段指定的镜像内容表中。
讨论
两级名称空间提示表为动态链接器提供了建议的位置,以便开始在当前镜像所链接的库中搜索符号。
在两级名称空间映像中,每个未定义的符号(即N_UNDF或N_PBUD类型的每个符号)在同一索引处的两级提示表中都有相应的条目。
当构建二级命名空间镜像时,静态连接器会添加LC_TWOLEVEL_HINTS
加载命令和二级命名空间提示表到输出文件中。
默认情况下,连接器是不会将LC_TWOLEVEL_HINTS
加载命令或者二级命名空间提示表加到MH_BUNDLE
文件中的,因为这个load命令的存在会导致Mac OS X v10.0附带的动态链接器版本崩溃。
lc_str
定义一个可变长度字符串。
union lc_str
{
uint32_t offset;
#ifndef __LP64__
char *ptr;
#endif
};
字段
offset 长整型。从包含此字符串的加载命令开始到字符串数据的开始的字节偏移。
ptr 指向字节数组的指针。在运行时,这个指针包含了字符串数据的虚拟内存地址。在Mach-O文件中不适用ptr
字段。
讨论
加载命令存储了可变长度的数据,比如使用lc_str
数据结构的库名称。除非另外指定,数据由一个C字符串组成。
指向的数据存储在load命令之后,而且大小是加到加载命令大小上的。这个字符串应该以null终止;任何额外的字节都应该是null。通过减少加载命令数据结构中的字段cmdsize
来决定字符串的大小。
dylib
定义动态链接器使用的数据,以便将共享库与链接到共享库的文件进行匹配。专用于dylib_command(第32页)数据结构。
struct dylib {
union lc_str name;
uint_32 timestamp;
uint_32 current_version;
uint_32 compatibility_version;
};
字段
name 类型为lc_str
的数据结构。表示共享库的名称。
timetamp 构建共享库数据的时间
current_version 共享库当前版本号
compatibility_version 共享库的兼容版本号
dylib_command
定义了LC_LOAD_DYLIB
和LC_ID_DYLIB
加载命令的属性
struct dylib_command {
uint_32 cmd;
uint_32 cmdsize;
struct dylib dylib;
};
字段
cmd 所有加载命令结构都有的字段。次结构中,设置为LC_LOAD_DYLIB
,LC_LOAD_WEAK_DYLIB
或者LC_ID_DYLIB
。
cmdsize 所有加载命令结构都有的字段。此结构中,设置为sizeof(dylib_command)加上由dylib字段的name字段指向的数据的大小。
dylib 类型为dylib
的数据结构。表示共享库的属性。
讨论
文件链接到的每个共享库,静态链接器创建一个LC_LOAD_DYLIB
命令,并将其dylib字段设置为目标库的LC_ID_DYLD
装载命令的dylib字段的值。所有的LC_LOAD_DYLIB
命令一起组成一个列表,根据文件中的位置排序,最早的LC_LOAD_DYLIB
命令放在第一个。对于二级命名空间文件,符号表中未定义的符号项通过索引引用到此列表中的父共享库。这个索引被称为library ordinal
,并且它被存储在nlist
数据结构中的n_desc
字段中。
在运行时,动态连接器用LC_LOAD_DYLIB
命令dyld
字段中的name字段定位共享库。如果找到共享库,动态连接器拿LC_LOAD_DYLIB
加载命令中版本信息和动态库的做比较。要使动态链接器成功链接共享库,共享库的兼容版本必须小于或等于LC_LOAD_DYLIB
命令中的兼容版本。
动态连接器用时间戳决定是否能被预绑定信息。函数NSVersionOfRunTimeLibrary
返回的当前版本信息,由你来决定你程序用到库的版本。
dylinker_command
定义了LC_LOAD_DYILNKER
和LC_ID_DYLINKER
加载命令的属性。
struct dylinker_command {
uint32_t cmd;
uint32_t cmdsize;
union lc_str name;
};
字段
cmd 所有加载命令结构都有。此结构中,设置为LC_ID_DYLINKER
或者LC_LOAD_DYLINKER
。
cmdsize 所有加载命令结构都有。此结构中,设置为sizeof(dylinker_command)
加上name
字段指向的数据大小。
name 一个类型为lc_str
的数据结构。表示动态连接器的名称。
讨论
每个动态链接的可执行文件都包含一个指定动态连接器名称的LC_LOAD_DYLINKER
指令,这个名称是内核为了加载可执行文件所必需的。动态连接器本身的名称用LC_ID_DYLINKER
加载指令表示。
prebound_dylib_command
定义了LC_PREBOUND_DYLIB
加载指令的属性。对于预绑定的可执行文件链接到的每个库,静态链接器添加一个LC_PREBOUND_DYLIB
指令。
struct prebound_dylib_command {
uint32_t cmd;
uint32_t cmdsize;
union lc_str name;
uint32_t nmodules;
union lc_str linked_modules;
};
字段
cmd 每个加载指令结构中都有的字段。对于此结构,设置为LC_PERBOUND_DYLIB
。
cmdsize 每个加载指令结构中都有的字段。对于此结构,设置为sizeof(prebound_dylib_command)
加上name
和linked_moudules
字段指向数据的大小。
name 一个类型为lc_str
的数据结构。表示预绑定共享库的名称。
nmodules 一个整数。表示预绑定共享库包含模块的数量。linked_modules
字符串的大小是(modules / 8) + (nmodules % 8)
。
linked_modules 一个类型为lc_str
的数据结构。通常,这个数据结构定义了一个C字符串的偏移;这种用法中,它是一个变长位集,包含了每个module的位。没个位代表了与之对应的module是否被连接到当前文件当中的module,1为yes,0位no。第一个module的位是第一个字节的低位。
thread_command
定义了LC_THREAD
和LC_UNIXTHREAD
加载指令的属性。这个指令的数据对于每个架构都是特殊的,在thread_status.h
中显示,位于架构目录/usr/include/mach
中。
struct thread_command {
uint32_t cmd;
uint32_t cmdsize;
/* uint32_t flavor;*/
/* uint32_t count; */
/* struct cpu_thread_state state;*/
};
字段
cmd 所有加载指令结构都有的字段。对于此结构,设置为LC_THREAD
或者LC_UNIXTHREAD
。
cmdsize 设置为sizeof(thread_command)
加上flavor
和count
字段的大小再加上特定CPU线程状态数据结构的大小。
flavor 指定线程状态数据结构的特定样式的整数。可以参考你目标架构中的thread_status.h
文件。
count 线程状态数据的大小,以32位整数的数量为单位。线程状态数据结构必须填充到32位对齐方式。
routines_command_64
定义了LC_ROUTINES_64
加载指令的属性,被用在64位架构体系中。描述了共享库初始化函数的位置,这是动态链接器在允许调用库中的任何例行程序之前调用的函数。
struct routines_command_64 {
uint32_t cmd;
uint32_t cmdsize;
uint64_t init_address;
uint64_t init_module;
uint64_t reserved1;
uint64_t reserved2;
uint64_t reserved3;
uint64_t reserved4;
uint64_t reserved5;
uint64_t reserved6;
};
字段
cmd 所有指令结构都有的字段。此结构中,设置为LC_ROUTINES_64
cmdsize 所有的指令结构都有的字段。设置为sizeof(routines_command_64)
init_address 指定初始化函数的虚拟内存地址。
init_module 表示包含初始化函数模型的模型表中的索引值。
Symbol Table and Related Data Structure
两个加载指令LC_SYMTAB
和LC_DYSYMTAB
,描述了符号表的大小和位置以及其他元数据。本节中列出的其他数据结构表示符号表本身。
symtab_command
定义了LC_SYMTAB
加载指令的属性。描述了符号表数据结构的大小和位置。
struct symtab_command {
uint_32 cmd;
uint_32 cmdsize;
uint_32 symoff;
uint_32 nsyms;
uint_32 stroff;
uint_32 strsize;
};
字段
cmd 设置为LC_SYMTAB
cmdsize 设置为sizeof(symtab_command)
symoff 从文件开始到符号表入口位置的偏移量。符号表是nlist
数据结构的数组。
nsyms 符号表入口数量的值
stroff 从镜像开始处到字符串表位置的偏移量。
strsize 字符串表的大小(用字节表示)
讨论
LC_SYMTAB
应该同时存在于静态链接和动态链接的文件类型中。
nlist_64
描述了64位架构的符号表中的入口
struct nlist_64 {
union {
uint32_t n_strx;
} n_un;
uint8_t n_type;
uint8_t n_sect;
uint16_t n_desc;
uint64_t n_value;
};
字段
n_un 将索引保存到字符串表中的联合,用0表示空字符串("")
n_type 由使用四位掩码访问的数据组成的字节值:
-
N_STAB
(0xe0)--如果设置了这三位的任意一位,这个符号就代表符号调试表(stab)的条目。这个例子中,整个n_type
字段被解释为stab
值。