> For the complete documentation index, see [llms.txt](https://man.bsdcn.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://man.bsdcn.org/man5/a.out.5.md).

# a.out(5)

`a.out` — 可执行二进制文件格式

## 名称

`a.out`

## 概要

`#include <a.out.h>`

## 描述

头文件

`#include <a.out.h>`

声明了三个结构和若干宏。这些结构描述了系统中可执行机器代码文件（“二进制文件”）的格式。

一个二进制文件最多由 7 个节（section）组成。这些节依次为：

**exec** 头部 包含内核用于将二进制文件加载到内存并执行、以及链接编辑器 [ld(1)](/man1/ld.lld.1.md) 用于将二进制文件与其他二进制文件组合的参数。此节是唯一必需的节。

**text** 段 包含程序执行时加载到内存的机器代码及相关数据。可以以只读方式加载。

**data** 段 包含已初始化的数据；始终加载到可写内存中。

**text** 重定位 包含链接编辑器在组合二进制文件时用于更新 text 段中指针的记录。

**data** 重定位 类似于 text 重定位节，但用于 data 段指针。

**symbol** 表 包含链接编辑器用于在二进制文件之间交叉引用命名变量和函数（“符号”）地址的记录。

**string** 表 包含与符号名称对应的字符串。

每个二进制文件都以一个 `exec` 结构开始：

```sh
struct exec {
	unsigned long	a_midmag;
	unsigned long	a_text;
	unsigned long	a_data;
	unsigned long	a_bss;
	unsigned long	a_syms;
	unsigned long	a_entry;
	unsigned long	a_trsize;
	unsigned long	a_drsize;
};
```

各字段的功能如下：

**`EX_DYNAMIC`** 表示该可执行文件需要运行时链接编辑器的服务。

**`EX_PIC`** 表示该目标文件包含位置无关代码。当 as(1) 收到 ‘`-k`’ 标志时设置此标志，必要时由 [ld(1)](/man1/ld.lld.1.md) 保留。

**`OMAGIC`** text 和 data 段紧随头部之后且连续。内核将 text 和 data 段都加载到可写内存中。

**`NMAGIC`** 与 `OMAGIC` 一样，text 和 data 段紧随头部之后且连续。但内核将 text 加载到只读内存中，并将 data 加载到 text 之后下一页边界处的可写内存中。

**`ZMAGIC`** 内核按需从二进制文件加载单个页面。头部、text 段和 data 段都由链接编辑器填充为页面大小的整数倍。内核从 text 段加载的页面是只读的，而从 data 段加载的页面是可写的。

**`a_midmag`** 此字段以主机字节序存储。它有若干子组件，分别通过宏 `N_GETFLAG`、`N_GETMID` 和 `N_GETMAGIC` 访问，并由宏 `N_SETMAGIC` 设置。宏 `N_GETFLAG` 返回若干标志：如果 `EX_DYNAMIC` 和 `EX_PIC` 都已设置，则该目标文件是一个位置无关的可执行映像（例如共享库），将由运行时链接编辑器加载到进程地址空间中。宏 `N_GETMID` 返回机器 ID。它指示该二进制文件可在哪些机器上运行。`N_GETMAGIC` 指定魔数，用于唯一标识二进制文件并区分不同的加载约定。此字段必须包含以下值之一：

**`a_text`** 包含 text 段的字节大小。

**`a_data`** 包含 data 段的字节大小。

**`a_bss`** 包含 “bss 段” 中的字节数，内核使用它在 data 段之后设置初始 break（brk(2)）。内核加载程序时，使此数量的可写内存看起来跟在 data 段之后，并且初始读取时为零。（*bss* = block started by symbol，由符号起始的块）

**`a_syms`** 包含符号表节的字节大小。

**`a_entry`** 包含内核加载程序后程序入口点在内存中的地址；内核从该地址处的机器指令开始执行程序。

**`a_trsize`** 包含 text 重定位表的字节大小。

**`a_drsize`** 包含 data 重定位表的字节大小。

头文件

`#include <a.out.h>`

定义了若干宏，这些宏使用 `exec` 结构来测试一致性或定位二进制文件中各节的偏移量。

**`N_BADMAG`** `exec` 如果 `a_magic` 字段不包含可识别的值，则返回非零值。

**`N_TXTOFF`** `exec` text 段起始处在二进制文件中的字节偏移量。

**`N_SYMOFF`** `exec` 符号表起始处的字节偏移量。

**`N_STROFF`** `exec` 字符串表起始处的字节偏移量。

重定位记录具有标准格式，由 `relocation_info` 结构描述：

```sh
struct relocation_info {
	int		r_address;
	unsigned int	r_symbolnum : 24,
			r_pcrel : 1,
			r_length : 2,
			r_extern : 1,
			r_baserel : 1,
			r_jmptable : 1,
			r_relative : 1,
			r_copy : 1;
};
```

`relocation_info` 的各字段用途如下：

**`r_address`** 包含需要进行链接编辑的指针的字节偏移量。text 重定位偏移量从 text 段起始处计算，data 重定位偏移量从 data 段起始处计算。链接编辑器将此偏移量处已存储的值加到使用此重定位记录计算得到的新值上。

**`r_symbolnum`** 包含符号表中符号结构的序号（*不是* 字节偏移量）。链接编辑器解析此符号的绝对地址后，将该地址加到正在重定位的指针上。（如果 `r_extern` 位未设置，情况有所不同；见下文。）

**`r_pcrel`** 如果设置此位，链接编辑器假定它正在更新一个使用 pc 相对寻址的机器代码指令中的指针。当运行中的程序使用该指针时，重定位指针的地址会被隐式加到其值上。

**`r_length`** 包含指针长度（以字节为单位）的以 2 为底的对数；0 表示 1 字节位移，1 表示 2 字节位移，2 表示 4 字节位移。

**`r_extern`** 如果此重定位需要外部引用则设置；链接编辑器必须使用符号地址来更新指针。当 `r_extern` 位未设置时，重定位是 “本地” 的；链接编辑器更新指针以反映各段加载地址的变化，而非符号值的变化（除非同时设置了 `r_baserel`，见下文）。在这种情况下，`r_symbolnum` 字段的内容是一个 `n_type` 值（见下文）；此类型字段告知链接编辑器重定位指针指向哪个段。

**`r_baserel`** 如果设置此位，由 `r_symbolnum` 字段标识的符号将被重定位为全局偏移表（Global Offset Table）中的偏移量。在运行时，全局偏移表中此偏移量处的条目被设置为该符号的地址。

**`r_jmptable`** 如果设置此位，由 `r_symbolnum` 字段标识的符号将被重定位为过程链接表（Procedure Linkage Table）中的偏移量。

**`r_relative`** 如果设置此位，此重定位相对于该目标文件所属映像的（运行时）加载地址。这种类型的重定位只出现在共享目标中。

**`r_copy`** 如果设置此位，此重定位记录标识一个符号，其内容应被复制到 `r_address` 给出的位置。复制工作由运行时链接编辑器从共享目标中的合适数据项完成。

符号将名称映射到地址（更一般地，将字符串映射到值）。由于链接编辑器会调整地址，在分配绝对值之前，必须使用符号的名称来代表其地址。符号由符号表中的定长记录和字符串表中的变长名称组成。符号表是一个 `nlist` 结构数组：

```sh
struct nlist {
	union {
		const char	*n_name;
		long		n_strx;
	} n_un;
	unsigned char		n_type;
	char			n_other;
	short			n_desc;
	unsigned long		n_value;
};
```

各字段的用途如下：

**`N_UNDF`** 一个未定义的符号。链接编辑器必须在另一个二进制文件中定位同名的外部符号，以确定此符号的绝对值。作为特殊情况，如果 `n_value` 字段非零且链接编辑中没有二进制文件定义此符号，链接编辑器会将此符号解析为 bss 段中的一个地址，保留等于 `n_value` 的字节数。如果此符号在多个二进制文件中未定义且这些二进制文件对大小未达成一致，链接编辑器选择所有二进制文件中找到的最大大小。

**`N_ABS`** 一个绝对符号。链接编辑器不会更新绝对符号。

**`N_TEXT`** 一个 text 符号。此符号的值是一个 text 地址，链接编辑器在合并二进制文件时会更新它。

**`N_DATA`** 一个 data 符号；类似于 `N_TEXT`，但用于 data 地址。text 和 data 符号的值不是文件偏移量而是地址；要恢复文件偏移量，需要确定对应节起始处的加载地址并减去它，然后加上该节的偏移量。

**`N_BSS`** 一个 bss 符号；类似于 text 或 data 符号，但在二进制文件中没有对应的偏移量。

**`N_FN`** 一个文件名符号。链接编辑器在合并二进制文件时，会将此符号插入到来自该二进制文件的其他符号之前。该符号的名称是提供给链接编辑器的文件名，其值是该二进制文件的第一个 text 地址。文件名符号对于链接编辑或加载并非必需，但对调试器有用。

`#include <link.h>`

**`n_un.n_strx`** 包含此符号名称在字符串表中的字节偏移量。当程序通过 nlist(3) 函数访问符号表时，此字段会被替换为 `n_un.n_name` 字段，后者是指向内存中字符串的指针。

**`n_type`** 链接编辑器用它来确定如何更新符号的值。`n_type` 字段通过位掩码分解为三个子字段。链接编辑器将 `N_EXT` 类型位设置的符号视为 “外部” 符号，并允许来自其他二进制文件的引用。`N_TYPE` 掩码选择链接编辑器关注的位：`N_STAB` 掩码选择符号调试器（如 gdb(1)）（`ports/devel/gdb`）关注的位；这些值在 [stab(5)](/man5/stab.5.md) 中描述。

**`n_other`** 此字段提供符号性质的信息，独立于由 `n_type` 字段确定的符号在段中的位置。目前，`n_other` 字段的低 4 位持有以下两个值之一：`AUX_FUNC` 和 `AUX_OBJECT`（定义见 link.h）。`AUX_FUNC` 将符号与可调用函数关联，而 `AUX_OBJECT` 将符号与数据关联，无论它们位于 text 段还是 data 段。此字段旨在供 [ld(1)](/man1/ld.lld.1.md) 用于构造动态可执行文件。

**`n_desc`** 保留供调试器使用；由链接编辑器原样传递。不同的调试器将此字段用于不同目的。

**`n_value`** 包含符号的值。对于 text、data 和 bss 符号，这是一个地址；对于其他符号（如调试器符号），该值可以是任意的。

字符串表由一个 *unsigned long* 长度值和随后以 null 结尾的符号字符串组成。该长度表示整个表的字节大小，因此其最小值（即第一个字符串的偏移量）在 32 位机器上始终为 4。

## 参见

as(1), gdb(1) (`ports/devel/gdb`), [ld(1)](/man1/ld.lld.1.md), brk(2), execve(2), nlist(3), [core(5)](/man5/core.5.md), [elf(5)](/man5/elf.5.md), [link(5)](/man5/link.5.md), [stab(5)](/man5/stab.5.md)

## 历史

头文件

`#include <a.out.h>`

出现于 Version 7 AT\&T UNIX。

## 缺陷

由于并非所有受支持的架构都使用 `a_midmag` 字段，因此在不检查其实际机器代码的情况下，可能难以确定二进制文件将在哪种架构上执行。即使有机器标识符，`exec` 头部的字节序也取决于具体机器。
