PAYATHON 2026

如何在 x86 32 位保护模式下使用 NASM 输出字符 E?

数据小陈

在已启用 VGA 80×25 彩色文本模式的情况下,向地址 0xB8000 写入一个字 0x0F45,即可在屏幕左上角显示黑底亮白色的字母 E。原参考说明中写的“右上角”有误。

这段代码可以用来检查程序是否执行到了预期的 32 位入口,但还需要正确配置代码段、数据段和地址映射。

为什么写入 0x0F45 能显示 E?

题目给出的参考方案采用 VGA 文本模式。在这里讨论的 80×25 彩色文本模式下,每个字符单元占 2 字节:

字节偏移含义示例值
+0字符码0x45,即十进制 69,对应 E
+1显示属性0x0F,表示黑底亮白色

第二个字节是属性字节,原参考内容中的 proprietary 表述不准确。

x86 使用小端字节序。将 0x0F45 写入一个 16 位内存单元后,低地址处保存 0x45,后一个字节保存 0x0F,正好符合字符单元的布局。

NASM 中的最小输出代码

下面的代码应放在切换到保护模式后的 32 位入口处:

[BITS 32]

protected_mode_entry:
    mov ax, DATA_SEL
    mov ds, ax

    mov word [0xB8000], 0x0F45

.hang:
    jmp .hang

DATA_SEL 是占位名称,必须替换为你自己的 GDT 数据段选择子,或者在代码中将其定义为对应的选择子值。不了解 GDT 布局时,不能直接套用固定数值。

这段示例假设:

  • 当前代码段是可执行的 32 位代码段,段描述符的 D 位为 1。
  • DATA_SEL 指向有效、可写的数据段,段基址为 0,段界限覆盖 0xB8000。
  • 分页尚未开启;如果已开启,线性地址 0xB8000 必须映射到 VGA 文本显存的物理地址 0xB8000,并允许当前代码写入。
  • 显示设备已经处于上述 VGA 文本模式。

[BITS 32] 只控制 NASM 如何编码后续指令,不会让 CPU 自动切换到保护模式。实际切换仍需要正确加载 GDT、设置 CR0.PE,并通过 far jump 加载目标代码段。

如何指定字符的位置?

行号和列号从 0 开始,字符地址为:

address = 0xB8000 + (row * 80 + column) * 2

在 80×25 模式下,row 的有效范围为 0..24,column 的有效范围为 0..79。

例如,左上角对应第 0 行、第 0 列:

mov word [0xB8000], 0x0F45

右上角对应第 0 行、第 79 列,字节偏移为 158,即 0x9E:

mov word [0xB8000 + 0x9E], 0x0F45

显示字符串时,可以逐个写入字符码和属性字节,每写入一个字符就将目标地址增加 2。换行、自动折行和滚屏需要另外实现,直接写显存也不会自动移动硬件光标。

如果屏幕没有显示 E,应检查什么?

先确认显示模式。如果启动代码留下的是图形模式,写入 0xB8000 通常不会显示预期的文本字符。在传统 BIOS 启动环境中,可以在进入保护模式之前通过 BIOS 设置文本模式;普通保护模式代码不能直接照搬实模式下的 int 0x10 调用方式。

然后检查 far jump 的目标选择子和入口偏移,以及目标代码段是否确实配置为 32 位。不要只凭 [BITS 32] 判断处理器当前的执行模式。

接着检查 DS。示例中的 [0xB8000] 默认通过 DS 寻址;如果数据段基址不是 0,它就不会访问预期的线性地址。若分页已经开启,还要检查映射和写权限。

如果 QEMU 出现重启,或程序在写入前停止,可能涉及异常以及异常处理配置,需要结合调试信息判断。没有原始启动代码、GDT 和运行日志,无法确定具体原因。

看到 E 可以直观地检查代码是否已执行到显存写入位置。要确认 far jump 后的处理器状态和段配置,仍应配合 QEMU 的调试功能检查寄存器及目标代码段描述符。

备注:内容仅供参考。