如何在 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 的调试功能检查寄存器及目标代码段描述符。
备注:内容仅供参考。
