# 跑起来

> 从 5,446 个字节开始——把一份 1981 年的源码重新跑起来

<!-- HTML 由 _build/build_run.py 生成 -->

---

## 目次

0. [纸面材料如何成为可运行系统](#ch0)
1. [5,446 个字节](#ch1)
2. [49 个字节的构成](#ch2)
3. [开机过程](#ch3)
4. [CALL 5 调用链](#ch4)
5. [功能 12 行为实验](#ch5)
6. [TRANS.COM 的身份](#ch6)
7. [执行证据的边界](#ch7)
8. [复现材料与限制](#ch8)
- [术语](#terms) · [文献](#refs) · [复现包](#repro)

---


## 0　纸面材料如何成为可运行系统

前三册主要依据源码、打印件、批注和其他史料。它们可以证明代码文本写了什么，却不能单独证明这些指令在目标环境中实际怎样运行。

本册增加执行证据，并把它与前三册的材料分开：

```
Paterson 当事人材料   → 解释最初的兼容目标（册一）
源码时间序列         → 记录接口如何变化（册二、册三）
IBM PC 硬件证据      → 解释部分新语义的来源（册三）
执行证据             → 检查特定源码、镜像和仿真配置下的实际行为（本册）
```

本册回答两个工程问题：一份来自转录和打印件的源码能否重新汇编成机器码；在给定镜像和仿真配置中运行后，哪些行为可以被直接观察，哪些仍然只能由源码推断？

## 重建过程的四个环节

「重新编译」不是单个动作，而是四个可以分别验证的环节：汇编器、格式转换、引导链和运行环境。每个环节失败，代表的边界不同。

第一道是汇编器。86-DOS 源码使用 SCP ASM v2.44 的语法，现代汇编器不能直接接受。本册使用 `asm244l`，即 pts-86-dos-reconstruction 项目用 NASM 编写并编译的兼容实现。它不是 Paterson 当年的原始汇编器；`Error Count = 0` 只能证明这份源码副本与该兼容实现在语法和目标格式上相容，不能单独证明历史真实性或运行行为。

第二道是格式转换。SCP 工具链先产生 Intel HEX 等目标文件，再通过 HEX2BIN 转成裸二进制；这一步决定内核字节如何写入磁盘。

第三道是引导链。引导扇区、BPB、DOSIO、内核和系统文件的落位必须彼此匹配。这里使用的盘基线来自 TheBrokenPipe/86-DOS_PCAdaptation 的现代 IBM PC 适配复刻工程（文献 [5]），不是 1981 年的历史盘；盘上 `DOSIO.ASM` 自述 `Revised 01/15/24`。它是可复现的执行载体，不是历史发行物。

第四道是运行环境。实验使用 DOSBox-X（8086 + CGA 配置，文献 [7]）。本册所说的「运行」均指在该仿真环境中的实际执行，不等同于真实 IBM PC 上的逐周期行为；仿真环境的优势是可重复，限制是不能证明真机时序。

构建命令、哈希、日志和截图都保存在 `_evidence/vol4/`。

## 实验判定标准

各项实验使用同一台仿真器、同一份测试程序，只替换被测内核映像。测试前先写入已知状态，再读取结果；例如验证功能 12 时，先把键盘缓冲头尾设为非空值，调用功能后再读取。无法完成的步骤标为「未验证」，不改写成成功。


---


## 1　5,446 个字节

先区分两个大小：60,124 字节是源码文本的文件大小，5,446 字节是汇编后内核映像的大小。后者不包括盘上的 COMMAND、ASM、DEBUG、EDLIN 等工具，也不等于完整的 IBM PC 适配工程。本册使用的源码锚点是 Paterson-Listings 中标为 86-DOS 1.00、日期为 1981-04-28 的转录文件。

## 汇编：Error Count = 0

将 `86DOS.ASM` 交给 `asm244i`（由 `asm244l` 源码编译出的兼容汇编器）后，输出包括：

```
Seattle Computer Products 8086 Assembler Version 2.44pts1
Copyright 1979-1983 by Seattle Computer Products, Inc.

Error Count =    0
```

`Error Count = 0` 表明，这份 3,622 行的源码副本被现代 SCP ASM v2.44 兼容实现接受，并产出 5,446 字节机器码。它只支持一个较窄的结论：该转录副本与该兼容汇编器在语法和目标格式上相容。它不能单独证明历史版本的真实性、每个伪指令都与 1981 年原汇编器完全等价，也不能证明运行行为。

## 校验：与适配盘逐字节比对

源码汇编产物 `86DOS.bin` 与现代适配复刻盘中的内核映像，可以用逐字节比较。盘内内核从偏移 2560（扇区 5）开始，占 5,446 字节。结果是：

- 差异字节数：49
- 差异偏移全集：`3–26`（连续 24 字节），以及 25 个散点（306、314、843、1320、1368、
  1902、1903、2178、2529、2539、2661、3734、3738、3950、4136、4149、4154、4161、4170、
  4305、4336、4343、4349、4357、5108）
- 把源码编译内核补上这 49 字节后，与适配盘内核 sha256 完全一致

这次比对复现了 phyous/DOS-1.0 项目报告的 49 字节差异，同时可以看到，24 字节连续区（偏移 3–26）位于键盘/终端映射表，其他 25 个散点分布在 BIOS 段地址、文件系统和行编辑相关代码中，具体分类见第 2 章。

先记住一个事实：适配盘的内核与源码编译产物之间存在 49 个字节级差异。这 49 个字节，
是这份源码编译内核与适配盘实际内核之间的全部差异；它们记录的是这份内核映像在适配盘中
留下的改动，而不是整个 IBM PC 移植工程的全部变化，移植同样发生在 BOOT、DOSIO、
COMMAND 与盘上工具里，那些不在内核映像内。

## 落位：扇区 5

现代适配复刻盘的布局如下：

| 盘偏移 | 内容 |
|---|---|
| 0–511 | 引导扇区（含 BPB：512 B/扇区、16 保留扇区、2 FAT、64 根目录项、320 总扇区） |
| 512–2559 | DOSIO 区（装载区共 0x800 = 2,048 字节；其中提取出的 DOSIO 代码为 1,424 字节，其余为填充） |
| 2560–8191 | 内核区（11 扇区 = 5,632 字节；DOSSIZE=1600H） |
| 8192 起 | FAT ×2、根目录、数据区 |

引导扇区反汇编记录显示，它把 15 个扇区（先 7 后 8）装载到 `0x0060:0x0000`，再跳转到该地址。内核从盘偏移 2560 开始，占 11 个扇区；5,632 字节的区域中还剩 186 字节。

这个余量数字请记住：5,632 减 5,446 = 186。第 5 章会看到，四十多天后的另一个版本
（5,683 字节），超出了这 5,632 字节的搬运上限，那是后话，先按数字记下。

## 引导链：谁把内核送到哪里

将这张复刻盘的引导链按执行顺序列出：

```
引导扇区（读 15 扇区 → 60h:0，跳 60h:0）
  └→ DOSIO（BIOS 跳表 + 初始化 + COMMAND 装载器，与内核同载）
       ├→ 把 DOS 区从 OLDSEG(0xE0) 拷到 DOSSEG（运行时计算，本次实测 0xCA），DOSSIZE=1600H 字节封顶
       ├→ far call [DOSADR] → 内核 DOSINIT（打印版本、问日期）
       └→ 返回后按簇 2 固定装载 COMMAND → 交棒
```

盘上自带的 DOSIO.ASM 源码头三个常量就是这条链的钥匙：

```
DOSSIZE	EQU	1600H		;Max length of 86-DOS in bytes
BIOSSEG	EQU	60H		;BIOS segment for the IBM PC
OLDSEG	EQU	0E0H		;Original 86-DOS segment
```

`DOSSIZE=1600H` 即 5,632 字节，是这份 DOSIO 为内核复制操作设定的上限。1.00 内核为 5,446 字节，可以装入；6/16 内核为 5,683 字节，超过该上限。

## 术语

- asm244l / asm244i：SCP ASM v2.44 的兼容复刻（NASM 源码编译），本册的汇编器。
- DOSIO：适配盘上的 I/O 系统模块（BIOS 存根 + 初始化 + COMMAND 装载器）。
- DOSSIZE：DOSIO 内核拷贝的字节数上限（1600H = 5,632）。


---


## 2　49 个字节的构成

上一章得到 49 个字节的差异。本章按字节位置和源码上下文，把它们分成五类。

## 先限定这 49 个字节的范围

这 49 个字节不是 IBM PC 适配工程在内核层的全部工作。适配还涉及 DOSIO、引导扇区和盘上工具；这些内容不在 5,446 字节的内核映像中。本章的口径是：49 个字节表示本次源码编译产物与现代适配复刻盘内核之间的全部差异，不把它扩展成 1981 年历史移植规模的结论。

## 三方字节对账

先做一组三方对账，确认公开构建流程、源码编译产物和现代适配复刻盘之间的关系。

公开复现项目 phyous/DOS-1.0 的 README 把这一步概括为 `patches 49 bytes of IBM PC keyboard mappings`，
并在另一处写成 `replacing 49 bytes of SCP keyboard scan codes with their IBM PC equivalents`。
这两句是项目说明，不是本册独立测量的分类结果；如果按后一种概括理解，49 个字节似乎都应当
属于键盘。下面的三方对账显示，字节级事实并不支持这种整体归类。

三方对账使用三个内核对象：

| 对象 | 来源 | 说明 |
|---|---|---|
| A | 源码编译产物（`86DOS.bin`，asm244l 家族工具汇编，Error Count = 0） | Paterson 源码的忠实编译 |
| B | phyous 构建流程产出的内核（`86-DOS_1.00.img` 内核区） | 公开项目「构建+补丁」的产物 |
| C | 现代适配复刻盘内核（TheBrokenPipe/86-DOS_PCAdaptation，盘偏移 2560 起 5,446 字节） | 2024 年复刻工程的 IBM PC 适配产物，不是 1981 年历史物件 |

对账结果（逐字节 diff，脚本与输出在证据包）：

| 对比 | 差异字节 | 结论 |
|---|---:|---|
| A → B | 49 | phyous 的「补丁」把全部差异同步了进去 |
| B → C | **0** | **B 与 C 逐字节完全一致** |
| A → C | 49 | 与 A→B 的偏移集合完全相同 |

B 与 C 完全一致，说明 phyous 的构建流程把源码编译产物与适配盘内核逐字节比较，再将 49 个差异同步到构建结果中。换言之，这条流程不是只依靠源码生成最终 IBM PC 内核，而是以现代适配盘内核作为差异基准。

49 个差异字节中有相当一部分并非键盘映射。README 的概括与字节级分类因此存在口径差异：19 个字节是 BIOS 段基址从 40H 到 60H 的重定位，源码原文写的是 40H，不能把这部分描述为 Paterson 源码原样生成的字节。

因此，本文采用字节级结果作为分类依据：README 的「键盘映射」是项目方的概括，不能替代逐字节对账。它不影响构建流程本身是否成功，只影响本文如何描述这 49 个字节。

## 五类差异

对 49 个差异字节逐个分类（连续段与散点分开统计，脚本输出在证据包）：

| 类 | 字节数 | 位置 | 内容 |
|---|---:|---|---|
| Ⅰ 键位表 | 24 | 0x003–0x01A 连续 | 两字符转义对表（表尾两个 1B,1B 结束标记）→ IBM PC 扫描码对 |
| Ⅱ BIOS 段基址 | 19 | 19 个散点 | `CALL BIOSxxx,BIOSSEG` 的段立即数 40H → 60H |
| Ⅲ 搜索检查旁路 | 2 | 0x76E–0x76F | `JNZ +34h` → `NOP NOP`：目录搜索例程的返回状态检查被旁路 |
| Ⅳ FCB 寻址修正 | 1 | 0x0A65 | 文件大小计算的第二处读取 `[ES:DI+10H]` → `[ES:DI+12H]` |
| Ⅴ 行编辑引子 | 3 | 0x0E96、0x0E9A、0x0F6E | 引子检查链调整（见下） |
| | **49** | | |

Ⅰ 类在源码里长这样（ESCTAB，键表以两字符转义对组织，表尾以两个 ESC 对作结束
标记，表头不是标记，而是第一组功能对 "SC"）：

```
ESCTAB:
	DB	"SC"	;Copy one char
	DB	"VN"	;Skip one char
	DB	"TA"	;Copy to char
	DB	"WB"	;Skip to char
	DB	"UH"	;Copy line
	DB	"HH"	;Kill line (no change in template)
	DB	"RM"	;Reedit line (new template)
	DB	"DD"	;Backspace
	DB	"P@"	;Enter insert mode
	DB	"QL"	;Exit insert mode
	DB	ESCCH,ESCCH	;Escape character
	DB	ESCCH,ESCCH	;End of table
```

适配盘的同一位置换成了 IBM PC 的扫描码对，实测包含左右方向键、Del、F1–F7、F9、F10，
以及表尾的 `00,00` 结束标记；没有把 Insert（扫描码 `52`）列入，因为差异字节中没有它。
「SCP 终端上按 Ctrl 组合键完成的行编辑，到了 IBM PC 的键盘上变成了
专用的方向键和功能键。」

Ⅱ 类对应源码常量 `BIOSSEG: EQU 40H`：内核所有 `CALL BIOSxxx,BIOSSEG` 远调用的段
立即数。适配盘引导链把 DOSIO+内核装到段 60h，BIOS 存根随之搬到 60h 段，于是这 19 个
立即数全部从 40H 改成 60H。分布很有规律：一簇 10 点在控制台输入输出例程（0x1028–0x1105），
一簇 5 点在 FCB 磁存取（0x132–0x558），3 点落在两簇之间（0x882、0x9E1、0x9EB），
最后 1 点在 DOSINIT 的取立即数处（0x13F4）。

Ⅲ、Ⅳ、Ⅴ 三类是本册对账时的新发现，它们既不是键位表、也不是段基址：

- Ⅲ（0x76E）：目录搜索例程调用后的状态检查（`OR AL,AL / JNZ +34h`）被两条 NOP
  旁路。适配盘版本选择无视该例程的返回状态继续执行。这个改动的动机，源码层面
  没有对应注释，如实标注「动机待考」。
- Ⅳ（0x0A65）：文件大小/记录数计算里，第二处 FCB 字段读取的偏移立即数从
  10H 改为 12H，源码编译版两次读取同一个字（低字），适配盘版改为低字+高字。
  这是六个散点里唯一看起来像「修 bug」的一个：源码版对大于 64KB 的文件会算错
  块数，适配盘版修正了它。它同时说明：适配盘内核里至少有这一处改动，在现存的
  86-DOS 1.00 源码文本里找不到对应，适配盘内核并不完全是现存源码的忠实编译。
- Ⅴ（0x0E96、0x0E9A、0x0F6E）：行编辑的引子字符检查链，三处改动同一方向，
  0x0E96 由 18H（Ctrl-X）改为 1BH，0x0E9A 与 0x0F6E 两处由 1BH 改为 00H（字节级
  实测：A=1B → C=00，两处同款）。ESC 被提为第一引子，与 IBM 键盘的 ESC 键位置一致；
  00H 正是 IBM 扩展键扫描码序列的前缀字节，适配盘键表（Ⅰ 类）尾部的结束标记也
  随之换成了 00,00，Ⅰ 与 Ⅴ 两处改动互相咬合。

所以「键盘补丁」只是便于传播的简称，49 个字节实际分成五类：24 字节键位表、19 字节段基址、
2 字节搜索检查旁路、1 字节 FCB 寻址修正、3 字节行编辑引子调整，合计 49 个字节。
Ⅰ 类回答「用户按什么键」，Ⅱ 类回答「内核到哪里找 BIOS」；Ⅲ、Ⅳ、Ⅴ 类散布在文件系统和
行编辑路径中。它们共同说明源码编译版怎样与这张现代 IBM PC 适配盘的内核保持一致，
但每一类改动的历史动机，并不能都从现存材料中恢复。

## 键盘表为何需要适配

24 字节的连续差异位于 `ESCTAB`。源码用两字符转义序列表示行编辑操作，现代适配复刻盘则在同一位置使用 IBM PC 键盘扫描码对。适配盘保持了表的位置和长度，只替换了内容；这保留了上层行编辑接口，同时改变了底层键盘输入表示。

这组字节说明的是键盘输入适配，不足以单独证明整个 IBM PC 移植工程只改了键盘。19 个 BIOS 段地址字节、文件系统修正和行编辑引子调整都在同一内核差异中。


---


## 3　开机过程

内核写入磁盘后，还要通过引导扇区、DOSIO 和初始化流程才能接管机器。本章记录这条引导链，并检查日期输入中的 1980 年边界。

## 引导扇区

DOSBox-X 挂上盘映像，BOOT 命令把引导扇区载入 0x7C00 执行。引导扇区做三件事：复位
磁盘系统（INT 13h AH=0）、读 15 个扇区到 0x0060:0000（先读 7 个再读 8 个，两次读之间
用一个小例程推进 CHS 坐标）、然后 `jmp 0x0060:0x0000` 交棒。

反汇编确认了 BPB 字段、从扇区 2 开始装载 15 个扇区、`ES:BX = 0060:0000` 的装载位置和跳转方式都与盘上 `BOOT.ASM` 的主要说明一致。唯一明确的差异是卷标：源码写 `NO NAME`，实际引导扇区写 `86-DOS 1.00`。这张引导扇区属于现代 IBM PC 适配复刻工程，不是 SCP 1981 年原始引导扇区的直接复原。

## DOSIO 的三项工作

交棒之后，DOSIO 接管。这一层在源码注释里自称「I/O System for 86-DOS」，实际干的是
三层活：

第一层，装配 BIOS。 把 INT 1Bh、INT 00h（除零）、INT 01h/03h/04h（调试）的向量改指
到 BIOSSEG（60h 段）的处理例程；清打印屏幕状态标志。

第二层，搬 DOS。 计算内核的最终落位（BIOS 结束之后的第一个可用段），然后把
DOSSIZE（5,632 字节）的 DOS 区从 OLDSEG 拷贝过去，因为 BIOS 段里已经堆了 DOSIO 自己
的代码，内核不能和它重叠。

第三层，装载 COMMAND。 在内核数据区登记驱动器表（INITTAB：驱动器数、DPT 指针、
缓冲区与栈的保留区），调用内核的 DOSINIT，此时控制台还没有任何输出，等内核完成
初始化返回后，再用 INT 21h 的功能 15（打开文件）和功能 39（随机块读）把 COMMAND 的
前几个扇区读进内存，远跳过去执行。

这一层有一道铁闸：DOSSIZE。 DOSIO 源码第一页写着 `DOSSIZE EQU 1600H ;Max length
of 86-DOS in bytes`，所有对 DOS 区的搬运都以 5,632 字节封顶。1.00 内核 5,446 字节，
剩 186 字节余量，装得下。这个数字现在看起来只是个设计余量；第 5 章里，它会变成
一堵墙。

## 启动输出

引导和初始化完成后，屏幕显示：

```
86-DOS version 1.00
Copyright 1980,81 Seattle Computer Products, Inc.
```

紧接着是提示：

```
Enter today's date (m-d-y):
```

日期输入和年份边界单独放在下一节讨论。这里先记录启动结果：输入合法日期后，内核继续装载 COMMAND。

![1979 被拒绝：日期提示行出现两次（测试盒 screenshots/d79_1979_reject.png）](shots/d79_1979_reject.png)

输入合法日期之后，内核装载 COMMAND，屏幕上出现：

```
COMMAND v. 1.00
A>
```

`A>` 提示符出现，说明这份源码、这张现代适配复刻盘和当前 DOSBox-X 配置已经完成汇编、装载、初始化并进入可交互状态。这个结论的适用范围限于该组合。

![完整引导链实录：BOOT → 挂载 → 横幅 → 日期 1-1-81 → COMMAND v. 1.00 → A>（E2 引导实验，DOSBox-X）](shots/E2_a5_a_prompt.png)

## 日期输入的年份边界

源码和运行截图共同给出了一个边界样本：

先按证据口径摆清楚：源码给出的接受范围不是「1980–1999」，而是「1980 年及以后、
上限 2100」；「1999」只是两位年写法的上限。 GETDAT 的完整判定链：月 1–12、日
1–31、年经 `MYD` 以 2100 封顶；`SUB AX,80 / JC` 拒绝 80 以下；80–99 按 1980–1999
存入；`SUB AX,1900 / JC` 再拒绝 100–1979。运行时实测：1979 被拒绝（截图在档）；
1980 / 1999 / 2000 按源码判定应接受，未实测，本节标题因此只声明源码级的边界。

这里能确认的是设计边界，而不是所谓「千年虫」：目录日期字段以 1980 为基准，7 位年份偏移在表达上可到 2107 年，输入代码又把可接受年份限制到 2100。运行截图进一步确认，输入 1979 年会被拒绝；1980、1999 和 2000 只由源码判定，尚未留下完整运行记录。

执行证据补充了源码分析，但范围仍是这张盘和这套 DOSBox-X 配置；它不能替代对其他历史副本的验证。


---


## 4　CALL 5 调用链

本章检查 `CALL 5` 的调用链。原计划使用盘上 `DEBUG.COM` 单步，但在当前镜像和 DOSBox-X 配置中，外部命令无法执行，因此没有单步记录。证据改为两部分：离线机器码定位和源码逐行对照；运行时只保留功能 12 的 INT 21h 分发以及探针转储，并明确标出探针缺陷。

## 为什么检查 CALL 5

`CALL 5` 是检查 CP/M 接口的合适入口：DOS 加载程序时，会在 PSP（程序段前缀）的 0x0005 处写入一个调用入口，CP/M 程序沿用这一约定即可进入 DOS。本章把源码描述与内核机器码对应起来。

## 调用链的字节级证据

CP/M 程序的 `CALL 5` 是一条近调用（E8）：把返回地址压栈，跳到本段偏移 5。真正的
机关在 PSP:5 那里放的字节上。加载程序时，内核的 PSP 构建例程在 PSP:5–9 写入一条
直接远调用（操作码 9AH），源码 :3150 的注释自述 "[5] to [9] form a long call
to the entry point"，:3181、:3182、:3184 三行是实体（:3183 的 `MOV [0],20CDH` 写的是
0:0 处的中断门，不属于 PSP:5–9 这组；行内中文注释为本文所加，源码此处无注释）：

```
MOV [6],CX             ; PSP:6 = 偏移 = 程序段大小 × 16
MOV [8],BX             ; PSP:8 = 段 = ENTRYPOINTSEG(0CH) − 程序段数
MOV B,[5],LONGCALL     ; PSP:5 = 9AH（LONGCALL: EQU 9AH，:54）
```

段地址和偏移的计算使这个远调用落到固定物理地址 0x000C0。`DOSINIT` 随后在 0:0xC0 写入 5 个字节（:3446–3448；源码原文没有下列中文注释）：

```
MOV B,[ENTRYPOINT],LONGJUMP   ; 0:C0 = EAH（远跳转操作码）
MOV [ENTRYPOINT+1],ENTRY      ; 0:C1 = 内核 ENTRY 的偏移
MOV [ENTRYPOINT+3],AX         ; 0:C3 = 内核代码段
```

按源码和机器码，调用链为：

```
近 CALL 5（用户程序，E8）
  → PSP:5 的 9A 远调用（压 CS:IP，落点恒为物理 0xC0）
    → 0:C0 的 EA 远跳转（DOSINIT 预埋的蹦床，直达内核 ENTRY）
      → ENTRY：三次 POP 取回三层返回地址，PUSHF 把栈重排成 INT 形态
        → CMP CL,MAXCALL(36) / JA BADCALL → MOV AH,CL → SAVREGS → DISPATCH
```

需要区分地址位置和中断指令：这条链路没有执行 `INT 30h`。0xC0 恰好等于 30h×4，是 IVT 中 INT 30h 向量的地址；这里使用的是直接远调用落到 0:C0 的代码跳转，而不是通过中断向量查表。

离线字节定位记录在 `E3_BINARY.md`，可由 `vol4_bin2.py` 复跑：内核偏移 0x7A 起是 ENTRY，0x81 的 `82 F9 24` 对应 `CMP CL,36`，随后是 `JA BADCALL`；0x86 的 `8A E1` 对应 `MOV AH,CL`，0x88 起是保存寄存器的 PUSH 序列。运行探针 E4T3 曾把 0:C0 和 IVT[21h] 等位置转储到显存，但打印例程的低半字节恒显示为 F，因此截图只作定性旁证，字节值以离线取证为准。

![E4T3 探针的运行时转储（打印例程缺陷：低半字节恒为 F，仅作定性旁证）](shots/e4t3_call5_dump.png)

## MAXCALL=36：机器码边界与未运行探针

册三论证过：86-DOS 的功能号上限是 36（`MAXCALL EQU 36`），第 37 号开始就不属于
CALL 5 能到达的范围，入口检查会把它们挡回 BADCALL。

机器码中可以直接定位这条边界：内核 0x81 的 `82 F9 24 / 77 E9` 对应 `CMP CL,36 / JA BADCALL`；任何 CL ≥ 37 的 CALL 5 请求都会跳到 `BADCALL`，随后返回 `AL=0` 并执行 `IRET`（源码 :202–204）。

动态验证尚未完成。探针 E4T2 设计为先后以 CL=36 和 CL=37 调用 CALL 5，并显示返回的 AL；但它依赖真实 PSP 环境，而当前注入式 COMMAND 环境没有内核铺设的有效 PSP:5 入口。因此 E4T2 只作为待运行测试保存，预期输出 `55 00`，不计入已验证结果。

因此，`MAXCALL=36` 不仅存在于源码常量中，也对应内核偏移 0x81 的机器码比较；任何 CL≥37 的 CALL 5 请求都会跳到 `BADCALL`。

## INT 21h：另一条入口

同一个功能号体系还有一条入口：INT 21h。它的向量在 DOSINIT 里被指向内核的 COMMAND
入口，功能号直接在 AH，上限更宽（`CMP AH,MAXCOM(41) / JBE SAVREGS`，:199–201），
不需要 CALL 5 的地址回绕技巧。两条入口在 SAVREGS 汇合（内核偏移 0x88），之后共用
同一个分派器。

第 5 章的功能 12 实验使用 INT 21h，红屏结果证明该入口在 1.00 内核上可用；CALL 5 则只有静态链路和探针转储旁证，尚无完整的动态边界测试。


---


## 5　功能 12 行为实验

本章测试功能 12 的行为。源码显示，1.00 的槽 12 是返回 0 的空壳，6/16 快照则指向 `FLUSHKB`；运行实验只验证这些静态差异在当前环境中是否表现为可观察的行为差异。

## 实验设计与判定

测试程序 E4T（105 字节）的流程：

```
铺红底（B800 直写，红底白空格）
键盘缓冲设为已知非空：0040:001A ← 30h，0040:001C ← 40h
MOV AH,12 / INT 21h              ← 调功能 12
读回 0040:001A / 0040:001C
按读回值铺色：
  红 = 头尾仍是 30/40（未清）
  绿 = 头尾均为 1E（被清为缓冲起点）
  洋红 = 其他（异常）
挂起
```

调用前后使用不同颜色编码，屏幕颜色直接对应读回的缓冲指针。测试程序通过替换簇 2 中 `COMMAND` 的数据注入，在内核初始化完成、控制权交给 COMMAND 时执行。

## 先验证注入路径

在信任何颜色之前，先验证注入路径本身。两个控制组：

A 组（只铺屏）：测试程序只铺屏、挂起，不碰任何 DOS 调用。结果：整屏铺满，
注入点正确、替换 COMMAND 的数据真实被执行。

C 组（铺红 → INT 21h → 铺绿）：中间夹一次 DOS 调用。结果：屏幕变绿，该位置的
INT 21h 真实可用，1.00 内核的分发路径畅通。

两个控制组分别确认了测试程序确实执行，以及 INT 21h 在 1.00 内核上可用。
之后再看功能 12 的实际结果。

## 1.00 的结果：缓冲未清

测试程序（E4T，105 字节）跑在 1.00 内核上：铺红底 → 键盘缓冲设 30/40 → INT 21h
AH=12 → 读回 → 按结果铺色。

屏幕：红。

![1.00 内核的功能 12 实验：红屏 = 缓冲头尾保持 30/40 未清（测试盒 screenshots/T12_100_red_screen.png）](shots/T12_100_red_screen.png)

读回头尾仍为 30/40，说明功能 12 没有清空键盘缓冲。这与 1.00 槽 12 指向 `MOV AL,0 / RET` 的源码相符；空壳行为获得了运行时证据。

## 排错过程

排错过程中，前两版测试程序尝试通过 INT 21h 打印 HEAD/TAIL，却没有得到可见输出；第三版改为直接写 B800 显存，才把「测试代码是否执行」与「DOS 输出路径是否可靠」分开。A 组确认注入代码执行，C 组确认 INT 21h 可用，最终测试再用颜色编码缓冲区状态。

## 6/16 的状态：完整运行未完成

对 6/16 快照运行同一测试，若完整引导成立，预期应读回 1E/1E。实际引导没有完成，原因包括复制上限和初始化数据区契约：

第一层是 `DOSSIZE` 上限：6/16 快照（`DSKTEST=0`）汇编后为 5,683 字节，超过 DOSIO 的 5,632 字节复制上限；1.00 为 5,446 字节，余量 186 字节。若仍使用 1.00 DOSIO，6/16 内核的尾部会超出复制范围。

第二层是初始化数据区契约。即便放宽复制上限，6/16 内核仍在 `DOSINIT` 早期挂起。入口探针显示两版收到的 `DS=0x60`、`SI=0x322` 和 `INITTAB` 内容一致，挂点范围被缩小到驱动器表遍历及后续初始化之间。现有证据最支持 DOSIO 对数据区布局的假设与 6/16 内核不匹配，但没有直接定位到具体指令；相关探针也受到存根位置与内核数据区冲突的影响。

第三层是缺少匹配的 DOSIO。6/16 对应的 DOSIO 没有现存公开存档，当前盘上的 DOSIO 是为 1.00 内核准备的；要完整运行 6/16，需要按其数据区布局重新适配 I/O 层。

另做过一次有限的移植尝试：把 6/16 的 `FLUSHKB`（含 `RET` 共 37 字节）嵌入 1.00 内核并重定向槽 12。结果仍为红屏，读回 30/40，像素计数为 28,516。由于这次没有同时移植 6/16 的完整调用上下文和 I/O 层，所以它不提供 6/16 完整系统运行行为的正面或反面证据，只说明这次嵌入式尝试没有构成有效行为实验。

## 失败所能说明的范围

现有证据不支持把失败归因于 6/16 源码本身：源码可以汇编通过，结构也完整。更准确的结论是，1.00 DOSIO 与 6/16 内核之间存在接口或数据区契约不匹配；具体是哪一项契约变化，仍无法直接定位。

数字关系是明确的：1.00 内核 5,446 字节，复制上限 5,632 字节，余量 186 字节；6/16 内核 5,683 字节，超出旧上限 51 字节。42 天内源码文本增长 266 行，对应的编译产物增加 237 字节，最终越过了旧 DOSIO 的复制边界。

## 6/16 侧的静态和部分运行证据

完整引导未完成，但 6/16 侧仍有三类证据：

第一，按印刷快照的 `DSKTEST: EQU 0` 重建后，内核为 5,683 字节，分发表从偏移 0x81 开始，共 46 项；槽 12 指向偏移 0xE0，机器码写入 `0040:001A` 和 `0040:001C` 的低字节 `1E`。这对应 `FLUSHKB`。早期 `DSKTEST=1` 工作副本的偏移不同，本章统一使用快照版 `0xE0`。

第二，入口探针确认 DOSIO 可以把内核交给入口，且两版收到的初始化表相同（`DS=0x60`、`SI=0x322`，表内容逐字节一致）。第三，复制 6/16 的中断表初始化块的探针确认，设置 INT 21h 向量的前半段可以执行。

这些证据确认了 6/16 功能 12 的静态实现和部分调用路径，但没有确认完整系统运行时确实完成清空。

## 两版内核的行为对照矩阵

| 版本 | 槽 12 静态指向 | 例程 | 完整引导 | 功能 12 行为 | 状态 |
|---|---|---|---|---|---|
| 1.00 | VERSION 空壳（`MOV AL,0 / RET`） | 空壳 | ✅ | **实测：缓冲未清**（红） | 行为已证 |
| 6/16 | FLUSHKB（写 1Eh 到缓冲头尾） | 清缓冲 | ❌ | **未实测** | 行为未证 |

这张表是本章的最终成绩单：同一个测试程序、同一个判读标准，1.00 侧给出了确定的红色
结果；6/16 侧还没有走完引导链。红与绿之间缺的不是槽 12 静态字节的判断，那部分已经由
机器码证据已经确认槽 12 的静态实现；缺的是让 6/16 内核与 DOSIO 协同运行所需的复制上限和 I/O 层契约。

## 零键入镜像：排除日期输入因素

为排除日期询问的键盘输入环节，测试盒制作了零输入镜像：提取 6/16 内核 DOSINIT 中的中断表初始化块（kernel 0x155A–0x15D0），在测试体之前直接执行，从而跳过日期询问。

同一镜像换回 1.00 内核（1.00 版初始化块，kernel 0x13B2–0x1414），结果完整复现：
铺红 → 设缓冲 30/40 → INT 21h AH=12 → 读回 30/40 → 红屏（截图
`shots/p16b_1.00_red_zerokey.png`）。零键入机制本身是可靠的。

![零键入镜像 + 1.00 内核：跳过日期询问，直接红屏（测试盒 screenshots/p16b_1.00_red_zerokey.png）](shots/p16b_1.00_red_zerokey.png)

6/16 侧曾出现过一组既不是 1E/1E、也不是 30/40 的读回值，但该轮没有截图，且事后发现配置脚本曾错误指向 1.00 镜像，无法复跑确认。因此该观察撤回，按未验证处理，不作为结论依据。修正后的镜像和脚本保留在测试盒中。

零键入探针中的 CALL 5 检测目标不是实际 PSP，且输出位置随后被铺色覆盖；这部分不产生有效证据，只有 INT 21h 铺色判读有效。DOSINIT 挂起和 FLUSHKB 嵌入尝试的完整记录保存在 `E4_ATTEMPT.md` 和测试盒中。


---


## 6　TRANS.COM 的身份

适配盘上有一个 3,088 字节的 `TRANS.COM`。phyous/DOS-1.0 的文件列表把它标为 `File transfer utility`，但源码和二进制字符串都指向另一种身份。

## 三个源码候选

| 文件 | 头部原文 | 版本 | 运行环境 |
|---|---|---|---|
| `86-DOS-0.11/CPM Tools/TRANS86.Z80` | Z80 to 8086 Translator / Runs on the Z80 under CP/M | 1.0 | CP/M（Z80） |
| `86-DOS-0.11/Source/TRANS.A86` | Z80 to 8086 Translator / Runs on the 8086 under 86-DOS | 2.0 | 86-DOS（8086） |
| `MS-DOS/v1.25/source/TRANS.ASM` | Z80 to 8086 Translator / Runs on the 8086 under 86-DOS | 2.21 | 86-DOS（8086） |

三个候选的头部自述一致：Z80 to 8086 Translator，把 Z80 程序翻译成 8086 程序的
工具。没有一个是「文件传输工具」。

## 二进制比对结果

从适配盘目录项 `TRANS   COM`（簇 13，3,088 字节）提取二进制后，先做可打印字符串比对。结果包含：

```
Z80 to 8086 Translator  version 2.0
Insufficient directory space to create file
File not found
Translation complete
Out of disk space
*** Opcode Error
```

版本字符串 `version 2.0` 与 `TRANS.A86` 源码中的头部字符串逐字符一致，五条错误和状态消息也都能在该源码中找到。再与 pts 重建仓的 0.11 构建产物 `trans.com.orig` 比对，前 3,082 字节逐字节一致，差异只在尾部 8 字节中的 4 个位置。现有证据足以把盘上文件识别为 86-DOS 版 TRANS v2.0；MS-DOS 1.25 源码中的 `TRANS.ASM` 为 v2.21，说明工具后来仍有演化。

phyous 标注引述的 1994 年 Paterson 信件（本册未见原件，按二手转述标注）说的是
「hosted on the 8080 and CP/M」，
那说的是 v1.0（跑在 CP/M/Z80 上）。随 86-DOS 分发、跑在 8086 上的是 v2.0 及之后的
2.21。同一个名字，两个阶段，两份不同的头部自述。

## TRANS 的作用

`TRANS` 是把 Z80 程序迁移到 8086 的另一条路径：不手工重写每个指令，而是使用源码翻译器。它与 CP/M 调用约定、`RDCPM` 和早期系统调用编号共同构成了迁移目标的旁证。

这个工具后来仍存在于 MS-DOS 1.25 源码树中，并从 v2.0 的 16,128 字节增至 v2.21 的 16,223 字节。工具本身的持续演化，说明迁移需求并没有随着某一个空壳槽位的改变立即消失。

## TRANS 的运行状态

在当前镜像和 DOSBox-X 配置中，`DIR`、`TYPE`、`DEBUG`、`TRANS` 均返回 `Bad command or file name`；换用项目提供的原始镜像副本，现象一致。因此本册没有得到 `TRANS.COM` 的运行记录。

这个结论只适用于当前测试配置。phyous README 声称这些命令可用，但本册没有完整复现其 `run.sh` 配置，不能据此断言所有 DOSBox-X 配置都无法执行外部命令。`TRANS.COM` 的身份由字符串和二进制两级证据确定，运行状态仍是未验证。


---


## 7　执行证据的边界

本册的执行结果需要限定适用范围：它们证明的是特定源码、镜像和 DOSBox-X 配置的组合行为。

## 执行可以证明什么

一，可启动性：引导链走通、内核接管并出现提示符，证明这份源码、这张现代适配盘和当前 DOSBox-X 配置组合可启动。

二，特定 API 的实际分发：1.00 内核的 `INT 21h AH=12` 确实到达分发表；CALL 5 的入口、机器码比较和跳转关系可以离线定位，运行时只获得了探针旁证。

三，特定行为：红屏结果证明 1.00 功能 12 调用后键盘缓冲仍未变化；1979 年日期输入被拒绝的截图证明该边界在当前环境中生效。

## 执行不能证明什么

一，作者动机：屏幕行为不能说明 Paterson 当时为什么写下某个空壳；动机仍依赖当事人材料和源码序列。

二，真机时序：DOSBox-X 不是周期精确仿真器，不能据此判断真实硬件上的速度和竞争条件。

三、与具体 IBM PC 的完全一致：仿真环境无法覆盖某一台 1981 年机器的全部 CGA、键盘控制器和软盘行为。

四，历史使用情况：某功能在测试中可以运行，不证明 1981 年确实有程序调用过它。

五，历史唯一性：本册证明的是一份源码转录、一张现代适配复刻盘和一套 DOSBox-X 配置的组合行为，不等于所有历史 86-DOS 1.00 副本。



## 证据分工

执行证据补充了「代码在特定环境中确实运行」这一层；它不能替代源码对照、当事人材料或历史使用记录。四类证据各自回答不同问题，不能把一种证据的结论外推到其他层面。

## 全册结论的证据矩阵

✓ 表示该证据类型参与支持该结论，不表示单独一项证据足以证明整条结论，每条
结论的成立依赖一行内各格证据的组合。

| 结论 | 源码 | 二进制 | 执行 | 历史材料 | 当前状态 |
|---|:-:|:-:|:-:|:-:|---|
| 1.00 内核可构建（5,446 B，Error Count = 0） | ✓ | ✓ | ✓ | — | 已证 |
| 49 字节 = 五类适配（含 19B 段基址、1B FCB 修正） | — | ✓ | — | — | 已证（三方对账） |
| 适配盘引导链真实可达内核入口 | ✓ | ✓ | ✓ | — | 已证 |
| 1.00 可启动（banner/日期/A>） | ✓ | ✓ | ✓ | — | 已证 |
| CALL 5 → DISPATCH 路径 | ✓ | ✓ | 部分 | — | 源码+机器码已证；运行时仅探针转储旁证 |
| MAXCALL = 36（CL≥37 必被 BADCALL 挡回） | ✓ | ✓ | — | — | 机器码级已证；运行时未抽样 |
| 日期边界 1980–2100（两位年 80–99；1979 拒绝已实测） | ✓ | ✓ | 部分 | — | 源码已证；1980/1999/2000 行为未测 |
| 1.00 功能 12 为空壳（缓冲未清） | ✓ | ✓ | ✓ | — | 已证 |
| 6/16 功能 12 = FLUSHKB（静态） | ✓ | ✓ | — | — | 已证 |
| 6/16 功能 12 运行时清缓冲 | ✓ | — | — | — | **行为未证** |
| 6/16 内核无法由 1.00 DOSIO 完整引导 | — | ✓ | ✓（失败） | — | 执行已证 |
| 挂起原因与 DOSIO/数据区契约不兼容 | — | — | — | — | 高置信推断，未直接定位 |
| TRANS.COM 身份 = TRANS v2.0 | ✓ | ✓（字符串+二进制） | — | — | 已确定（未运行） |

这张表是全册的扫描版摘要：✓ 的格子各自指向证据包里的一份文件；空格子就空着，
其中「6/16 功能 12 运行时」一行明确标为未验证，构成当前证据包的主要缺口。


---


## 8　复现材料与限制

本章列出复现包、数字来源和当前仍未完成的实验。

## 复现包结构

```
_evidence/vol4/
├── BUILD_RECORD.md          # 工具版本、汇编命令、Error Count 输出
├── E2_SUCCESS.md / E2_ATTEMPT_LOG.md  # 引导过程记录与尝试日志
├── E3_BINARY.md / E3_BLOCKER.md   # 机器码定位与引导链记录
├── E4_ATTEMPT.md / E4_SUCCESS.md  # 功能 12 实验 + 6/16 侧失败实验战记
├── DISK_FORENSICS.md        # 盘映像 17 文件的目录取证
├── HASHES.txt               # 源码/内核/盘映像 sha256 全套
├── FINAL_CHECK_SNAPSHOT.md  # 历轮外审终检快照
├── SCREENSHOTS/             # 引导过程截图（banner/日期/A>）
├── work/                    # 汇编产物、工具链、测试程序源码与 COM
└── vol4_bin.py / vol4_bin2.py   # 机器码级验证脚本（分发表/空壳簇/入口）

_out/86dos-e4-testbox.zip    # E4 测试盒：四内核、五实验盘、三套测试程序、
                             # 五套探针脚本、四张关键截图，第 5 章全部实验可复跑
```

## 数字单一来源

本册关键数字——5,446、49（24+19+2+1+3）、5,632、0xE0、0x1700——都应能回溯到证据包中的原始输出、脚本计算或固定材料。执行相关的寄存器值、缓冲内容和屏幕结果，还必须注明仿真器、测试程序和被测内核。

## 复现的三层

复现可以分三层：

第一层，核对 `HASHES.txt`，确认源码、内核和盘映像一致；第二层，运行 `vol4_bin2.py` 等机器码脚本，检查文件内容和入口位置；第三层，按 `E4_ATTEMPT.md` 在 DOSBox-X 中运行测试，检查行为。第三层的结果必须保留截图或日志，并区分已验证与未验证。

## 后续工作

尚未完成的主要实验是 6/16 内核的完整运行。下一步需要为 6/16 内核准备匹配的数据区契约和 DOSIO，或使用支持断点、单步的 DOSBox-X 调试版本定位 `DOSINIT` 挂点。现有探针源码、日志和截图均保存在证据包中，可以从当前状态继续。


---


## 术语

| 术语 | 释义 |
|---|---|
| asm244l/asm244i | SCP ASM v2.44 的兼容复刻汇编器（NASM 源码编译），本册的汇编工具 |
| DOSIO | TheBrokenPipe 现代 IBM PC 适配复刻盘中的 I/O 系统模块：BIOS 存根 + 初始化 + COMMAND 装载器；盘上源码自述 `Revised 01/15/24` |
| DOSSIZE | DOSIO 拷贝内核的字节数上限（1600H = 5,632） |
| DOSINIT | 内核的初始化入口：建中断表、读日期、定位驱动器表 |
| DISPATCH | 内核的功能分发表（1.00：0xD8 起 42 个有效表项；6/16：0x81 起 46 项。槽 12：1.00 = 空壳 / 6/16 = FLUSHKB） |
| FLUSHKB | 6/16 的功能 12：清空键盘缓冲（写 1Eh 到缓冲头尾指针） |
| INT 21h | DOS 系统调用的软件中断入口（功能号在 AH） |
| CALL 5 | CP/M 兼容调用入口（PSP:5，功能号在 CL） |
| MAXCALL | CALL 5 的功能号上限（36） |
| PSP | 程序段前缀（Program Segment Prefix），程序加载时 DOS 在段头填的控制块 |
| BDA | BIOS 数据区（BIOS Data Area，0040:0000 起，含键盘缓冲指针） |
| OLDSEG/DOSSEG | DOSIO 的装载段：OLDSEG 是源码常量（0xE0）；DOSSEG 是 DOSIO 运行时计算并存入的变量（本次运行实测 0xCA） |


## 文献

1. Tim Paterson 遗留源码与打印件转录（Paterson-Listings，本系列工作副本）
2. Bob Zeidman, 2016，源码级与二进制级取证比对（册一文献 [2]）
3. OS/2 Museum，86-DOS 与 CP/M 的架构对比（册一文献 [3]）
4. Paterson, 1994，《Microprocessor Report》来信（册一文献 [4]）
5. TheBrokenPipe/86-DOS_PCAdaptation，86-DOS 1.00 IBM PC 适配复刻盘映像（2022–2024 现代工程；盘上 `DOSIO.ASM` 自述 `Revised 01/15/24`）。phyous/DOS-1.0 的构建脚本使用它提供工具与盘基线；它不是 1981 年历史盘。
   <https://github.com/TheBrokenPipe/86-DOS_PCAdaptation>
6. phyous/DOS-1.0，源码重建与 DOSBox-X 启动流程（项目说明中的 49 字节概括属于项目方表述，本册另作字节级复核）。
   <https://github.com/phyous/DOS-1.0>
7. DOSBox-X 便携版（8086 + CGA 配置，官方文档：非周期精确仿真）
8. 86-DOS 0.11 源码转录仓（CPM Tools/TRANS86.Z80 等）
9. MS-DOS 1.25 源码（TRANS.ASM v2.21）
10. BitSavers, Seattle Computer Products 资料：《Z80 to 8086 Translator》
   （bitsavers.org/pdf/seattleComputer/），第 6 章盘上 TRANS.COM 的外部技术资料对照


## 复现包

全部命令、哈希、日志与脚本落在 `_evidence/vol4/`：

- `BUILD_RECORD.md`：工具版本、汇编命令与输出
- `HASHES.txt`：源码 / 内核 / 盘映像的 sha256 全套
- `E4_ATTEMPT.md`：E4 全过程战记（含 6/16 侧失败实验的完整链条）
- `E4_SUCCESS.md`：三态实验的最终证据汇总
- `DISK_FORENSICS.md`：盘映像 17 文件取证
- `work/`：汇编产物、测试程序源码与 COM、工具链副本
- `vol4_bin2.py`：机器码级验证脚本（分发表/空壳簇/入口偏移）
- 复跑口径：同一台仿真器、同一份测试程序、只换被测内核映像；任何一步未复现，
  按本文的判定标准记「未验证」，不记「验证通过」。
