# 第 1 章：认识 XIRASM 可执行文件格式

## 从处理器指令到可加载文件

自然书写的处理器指令描述处理器需要执行的操作：

```asm
// 选择 64 位 x86 指令模式，并定义一段返回零的代码。
x86.use64();

start:
    xor eax, eax
    ret
```

直接汇编时，这些指令只会形成连续的字节序列。加载器无法仅凭指令判断它们属于哪一种可执行文件格式，还需要知道：

- 哪些字节是可以执行的代码；
- 哪些内存范围允许读取或写入；
- 程序从哪里开始执行；
- 哪些数据实际占用文件空间，哪些空间只在加载后的内存中存在；
- 哪些符号由其他库提供；
- 文件映像加载到不同地址时，哪些地址需要调整。

标准文件格式通过文件头和数据表记录这些信息。格式接口让源代码描述预期的文件映像，再由 XIRASM 根据最终布局推导常规的表项、位置和计数。

## 常用格式接口

普通程序从导入一个文件开始：

```asm
// 导入 PE、COFF 和 ELF 的常用格式构造接口。
import("format/format.inc");
```

常用格式接口提供以下构造函数：

| 输出类型 | 常用构造函数 |
| --- | --- |
| PE32 与 PE64 映像 | `format_pe32`、`format_pe64` |
| COFF32 与 COFF64 目标文件 | `format_coff32`、`format_coff64` |
| ELF32 与 ELF64 可执行文件 | `format_elf32`、`format_elf64` |
| ELF32 与 ELF64 目标文件 | `format_elfobj32`、`format_elfobj64` |
| ELF64 共享对象 | `format_elf64_so` |

构造函数会创建一份格式方案。格式方案记录所选文件系列、映像选项，以及源代码准备写入的完整节或段列表。

源代码随后遵循统一的构建流程：

```text
创建格式方案
开始构造格式
写入每个已经声明的节或段
附加入口、符号、导入、导出或重定位信息
完成格式
```

可执行文件、目标文件和共享库使用的具体声明并不完全相同，但原则一致：源代码提供具有实际含义的信息，格式接口根据这些信息推导数据表的位置和数量。

## 第一个 PE64 可执行文件

下面的源代码创建一个 64 位 Windows 控制台程序，其中只有一个代码节：

```asm
// 导入常用格式接口，并选择 64 位 x86 指令模式。
import("format/format.inc");
x86.use64();

// 声明 PE64 控制台程序及其唯一的可执行代码节。
const image0: map = format_pe64(
    format_pe_exe
        | format_pe_console
        | format_pe_nx
        | format_pe_aslr_auto,
    list.of(
        format_section(
            ".text",
            format_code | format_readable | format_executable
        )
    )
)
format_begin(image0);

// 在已经声明的 .text 节中写入程序入口代码。
format_section_begin(image0, ".text");
start:
    xor eax, eax
    ret
format_section_end(image0, ".text");

// 绑定入口标号，并根据最终布局完成文件映像。
const image: map = format_entry(image0, start)
format_finish(image);
```

汇编时显式选择 x86-64 目标平台：

```powershell
# 生成 PE64 可执行文件，然后运行它。
xirasm hello.asm --target x86-64 -o hello.exe
.\hello.exe
```

生成程序的退出状态为 0。

`xor eax, eax` 和 `ret` 等处理器指令行末不写分号。`format_begin(image0);` 等编译期函数调用需要分号。

## 按构建流程阅读源代码

上面的示例可以分成六个阶段：

1. `import("format/format.inc")` 选择常用格式接口；
2. `x86.use64()` 选择文件映像中代码使用的指令模式；
3. `format_pe64` 创建 PE64 格式方案，选择可执行文件、控制台子系统、数据页不可执行策略和自动地址空间布局随机化策略；
4. `format_section` 声明一个命名代码节及其访问权限；
5. `format_section_begin` 与 `format_section_end` 包围真正属于 `.text` 节的指令；
6. `format_entry` 绑定入口标号，`format_finish` 完成并检查文件映像。

格式选择和指令模式选择彼此相关，但职责不同。`x86.use64()` 控制指令编码，`format_pe64(...)` 控制包围这些指令的文件结构。

描述项列表必须出现在 `format_begin` 之前，因为它定义了预期的文件映像。列表长度决定需要多少个节表项，排列顺序决定各节在文件中的顺序，名称则把后续内容块连接到对应的表项。

## 源代码负责提供什么

常用格式接口会让用户必须作出的决定清楚地保留在源代码中：

| 源代码提供的内容 | 示例 |
| --- | --- |
| 文件系列与映像类别 | PE64 可执行文件 |
| 映像策略 | 控制台、数据页不可执行、自动地址空间布局随机化 |
| 节或段的名称 | `.text` |
| 用途与权限 | 代码、可读、可执行 |
| 指令与数据 | `xor eax, eax`、`ret` |
| 入口、符号、导入、导出与重定位信息 | `start` |

这些决定会影响程序含义或加载器行为，因此应当在源代码中明确表达。

## 格式接口负责推导什么

上面的示例没有手工提供：

- 节的数量；
- 节表项索引；
- `.text` 的文件偏移；
- `.text` 的相对虚拟地址；
- 入口字段的文件偏移；
- 手工计算的文件头大小；
- 原始的对齐填充字节。

格式接口会根据格式方案和已经完成的节布局推导这些值。增加一个描述项时，所需的格式结构会随之变化，用户不必另外修改数量或表项编号。

后续章节会把同一规则应用到自动生成的导入、导出、符号和重定位数据。普通源代码只需指出相关函数、标号、节、段、权限和重定位种类，格式接口负责分配索引、数量、偏移和数据表位置。

## 可执行文件、目标文件与共享库

本指南介绍三类主要输出：

| 输出用途 | 加载者或使用者 | 常见内容 |
| --- | --- | --- |
| 可执行文件映像 | 操作系统加载器 | 入口地址、运行时映射、导入 |
| 目标文件 | 本机链接器 | 节、符号、外部引用、重定位 |
| 共享库 | 加载器与调用程序 | 导出、导入、运行时元数据 |

PE 可执行文件和动态链接库使用节。COFF 目标文件也使用节，但它描述的不是完整运行时映像。ELF 可执行文件和共享对象使用可加载段表示运行时映射，ELF 目标文件则使用节组织链接阶段需要的信息。

格式接口会使用所选文件格式本身的概念：

```text
format_section_begin(plan, name)
format_section_end(plan, name)

format_segment_begin(plan, name)
format_segment_end(plan, name)
```

不能因为节和段都可以包含代码和数据，就随意把一种构建流程替换成另一种。

## 常用接口与高级格式工作

当常用格式接口能够描述要生成的文件时，应使用 `format/format.inc`。它支持普通的多节和多段流程，包括可执行文件、动态链接库、目标文件、共享对象、导入、导出和重定位。

格式专用的包含文件会提供范围更窄或层次更低的辅助接口。它们适合实现特殊布局或扩展常用格式接口，但其中一部分要求调用者自行维护通常由常用接口负责的格式约束。

不要随意混用两个层次。仅仅因为程序需要多个节、导入表或重定位表，并不意味着应该放弃自动计数和自动布局。

独立的格式接口参考会明确区分常用接口与高级接口。本指南只关注完整的用户构建流程。

## 从命令行开始

命令行工具可以生成已经使用常用格式接口的初始项目。

构建子命令必须写在源码覆盖参数和其他选项之前，完整形式为
`xirasm build [source.xir] [options]`。例如应写 `xirasm build --timings`，不要写成
`xirasm --timings build`。

创建 PE64 项目：

```powershell
# 创建并构建 64 位 Windows 项目，然后运行生成的程序。
xirasm init hello-win --isa x86-64 --os windows --abi msvc
cd hello-win
xirasm build
.\build\app.exe
```

创建 ELF64 项目：

```powershell
# 创建并构建采用 System V 应用二进制接口的 64 位 Linux 项目。
xirasm init hello-linux --isa x86-64 --os linux --abi sysv
cd hello-linux
xirasm build
```

生成的源代码采用本章介绍的同一构建流程：创建格式方案、开始格式、写入命名内容、绑定入口并完成格式。

## 后续章节

第 2 章将详细介绍格式方案，包括描述项顺序、名称、构建状态、入口绑定和自动生成的内容。第 3 章随后区分节、段、文件中有实际字节的数据、未初始化数据区、权限和对齐。

先记住一条简单规则：

```text
普通程序 -> import("format/format.inc")
特殊格式实现 -> 有意识地选择更底层的接口
```

[返回可执行文件格式指南](../formats.md)
