为什么在这种情况下用汇编语言的`int 0x10`可以显示一个字符,而不能显示一个字符串?

Why a character can be displayed, but a string can't with `int 0x10` in assembly language in this situation?

我是操作系统开发的新手。今天,我试图用 NASM 编写一个引导加载程序,我写了这段代码:

org 0x7c00
mov ax, cs
mov ds, ax
mov es, ax
call dispStr
jmp $

dispStr:
    mov ax, 0x0003
    int 0x10
    mov ax, BootMessage
    mov bp, ax
    mov cx, 16
    mov ax, 0x1301
    mov bx, 0x001f
    mov dl, 0x0000
    int 0x10
    ret

BootMessage db "Hello, OS world!"

times 510 - ($ - $$) db 0

dw 0xaa55

期望在蓝色背景上显示白色字符串“Hello, OS world!”,光标会移动到字符串末尾之后。它奏效了,但并不完全。在我的 virtualbox 中,它显示如下:

只有蓝色背景,没有任何白色像素。

但它完全适用于此代码:

mov ah, 0x0e
mov al, 'X'
int 0x10

jmp $

times 510 - ($ - $$) db 0

dw 0xaa55

所以我认为我的开发环境是正确的。我想找出它发生的原因。

我正在使用 nasm+wsl+VSCode+virtualbox,这是我的 tasks.json:

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "nasm-binary",
            "type": "shell",
            "command": "nasm",
            "args": [
                "-g",
                "-f",
                "bin",
                "-o",
                "${fileBasenameNoExtension}.bin",
                "${file}"
            ],
            "problemMatcher": []
        },
        {
            "label": "dd-BIOS",
            "type": "shell",
            "command": "wsl",
            "args": [
                "dd",
                "if=/mnt/d/****/LearnOS/${fileBasenameNoExtension}.bin",
                "of=/mnt/d/****/VBOX/0.vhd",
                "bs=512",
                "count=1",
                "conv=notrunc"
            ],
            "dependsOn": "nasm-binary",
            "problemMatcher": []
        }
    ]
}

这里0.vhd是用diskpart.exe创建的虚拟磁盘,它的命令是

create vdisk file=D:\****\VBOX[=16=].vhd maximum=10 type=fixed .

虽然 运行正在执行 dd-BIOS 任务,但 nasm-binarydd-BIOS 运行.

这是我的虚拟机:

错了可以重现

对于实模式,CPU 通过执行 physical_address = (segment * 16 + offset) & A20_gate_mask 计算物理地址(其中 A20_gate_mask 如果启用 A20 门则为 0xFFFFFFFF,如果禁用 A20 则为 0xFFEFFFFF)。

对于 BIOS,唯一严格的要求是您的引导加载程序将从物理地址 0x00007C00 开始。这意味着(当跳转到您的引导加载程序时)BIOS 可以将 CS:IP 设置为满足 (segment * 16 + offset) & A20_gate_mask == 0x00007C00 的任何值。通常(取决于哪台计算机)这将是 0x0000:0x7C00 或 0x07C0:0x0000;但它可能是 0x0100:0x6C00 或 0x0123:69D0,或(如果 A20 被禁用)0xFFFF:0x7C10,或其他任何东西。

通过告诉汇编程序 org 0x7C00 你是在告诉它从你的程序开始的偏移量(例如 BootMessage 标签)是从 0x7C00 开始的偏移量。如果 CS 恰好是 0x0000 并被复制到 ES 中,那么 ES:BootMessage 将是字符串物理地址的正确 segment:offset。如果 CS 恰好是任何其他值,那么 ES:BootMessage 将是字符串物理地址的不正确 segment:offset,并且您将打印垃圾。

best/easiest 解决方案(特别是如果您稍后切换到保护模式或长模式)是通过尽可能将所有段寄存器设置为零(并稍后启用 A20)来避免所有不必要的混淆;这样地址计算就有效地变成了physical_address = offset。这可以在将 CS 复制到其他段寄存器之前通过远跳转(例如 jmp 0x0000:start 然后 start:)来完成,但是如果您知道 CS 将为零,则将显式零加载到其他段寄存器会更快。

注意:在大多数情况下,您可以作弊并单独使用 CS(call 并且大多数跳转都会起作用,因为它们与 IP 相关),但它更容易(用于调试等) ) 完成所有段寄存器,而不必担心意外使用 calls/jumps 不是相对的不常见情况。

您应该(最终)做的另一件事是在已知位置设置一个安全堆栈,这样您就可以使用其他内存(例如加载内核)而不必担心破坏您自己的堆栈。 SS:SP(或您开始使用的堆栈所在的位置)中没有关于 BIOS 的规则。

完整的“安全启动”顺序可能是:

    org 0x7C00

    jmp 0x0000:start
start:
    xor ax,ax              ;Set AX to zero
    mov ds,ax
    mov es,ax

    cli                    ;Only needed for 8086
    mov ss,ax
    mov sp,0x7C00          ;SS:SP = 0x0000:0x7C00 = about 30 KiB of stack space below boot loader's code
    sti

    cld                    ;Clear direction flag (used by instructions like "rep movsb" and "lodsb").