BIOS int 10h 在 QEMU 上打印垃圾

BIOS int 10h printing garbage on QEMU

我在编写在 QEMU 中作为引导加载程序运行的 x86 实模式汇编程序时遇到问题。我正在尝试通过 BIOS 中断 0x10 打印文本。我的代码是:

print:
    pusha
.loop:
    mov AL, [SI]
    cmp AL, 0
    je .end
    call printChar
    inc SI
    jmp .loop
.end:
    popa
    ret

printChar:
    pusha
    mov AH, 0x0E
    mov BH, 0
    mov BL, 0x0F
    int 0x10
    popa
    ret

我使用 [ORG 0x7c00] 作为起点。我测试了 printChar 标签并用 AL 中的一些字母调用它并且它工作正常。当我尝试将内存地址加载到这样的消息时:

loadMsg      db "Loading",0
mov SI, loadMessage
call print

我在 QEMU 模拟器上得到像 'U' 这样的垃圾输出。昨天,我写了一段和这个非常相似的代码,一点问题都没有。是什么导致了我的问题,如何解决?

这是一个问题:

loadMsg    db "Loading",0
mov        SI, loadMessage
call    print

除非程序跳过 "loading" 文本,否则它会执行那些字节可能(或可能不)代表的任何指令。像这样修复它:

    jmp      print_msg
    loadMsg    db "Loading",0
print_msg:
    mov        SI, loadMessage
    call    print

我最近在 Whosebug 的回答中写了一些 ,可能对您有用。 提示 #1 可能适用于您的问题:

When the BIOS jumps to your code you can't rely on CS,DS,ES,SS,SP registers having valid or expected values. They should be set up appropriately when your bootloader starts. You can only be guaranteed that your bootloader will be loaded and run from physical address 0x00007c00 and that the boot drive number is loaded into the DL register.

基于 printChar 有效的事实,并且写出整个字符串并不表明 DS:SI 不是指向字符串所在的内存中的正确位置。造成这种情况的通常原因是开发人员错误地假设 CS and/or DS 寄存器在 BIOS 跳转到引导加载程序时已正确设置。它必须手动设置。在原点为 0x7c00 的情况下,DS 需要设置为 0。在 16 位实模式中,物理内存地址使用公式 segment:offset pairssegment:offset pairs 计算得出13=]。在您的情况下,您使用的是偏移量 0x7C00。 DS 中的值为 0 将产生 (0<<4)+0x7c00 = 0x07c00 的正确物理地址。

您可以在程序开始时将 DS 设置为 0,例如:

xor ax, ax       ; set AX to zero
mov ds, ax       ;     DS = 0  

QEMU的情况下,BIOS跳转到0x07c0:0x0000。这也代表相同的物理内存位置 (0x07c0<<4)+0 = 0x07c00 。这样的跳转将设置 CS=0x07c0(而不是 CS=0)。由于有许多 segment:offset 对映射到同一物理内存位置,因此您需要适当地设置 DS。您不能指望 CS 是您期望的值。所以在 QEMU 中,像这样的代码甚至不会正确设置 DS(当使用 ORG 0x7c00 时):

mov ax, cs
mov ds, ax       ; Make DS=CS

这可能适用于一些模拟器,如 DOSBOX 和一些物理硬件,但不是全部。这段代码可以工作的环境是当 BIOS 跳转到 0x0000:0x7c00 时。在这种情况下,CS 在到达您的引导加载程序代码时将为零,并且将 CS 复制到 DS 会工作。不要假设 CS 在所有环境中都为零是我的要点。始终将段寄存器设置为您明确想要的。

应该工作的代码示例是:

    BITS  16
    ORG   0x7c00
    GLOBAL main

main:
    xor ax, ax        ; AX = 0
    mov ds, ax        ; DS = 0
    mov bx, 0x7c00

    cli               ; Turn off interrupts for SS:SP update
                      ; to avoid a problem with buggy 8088 CPUs
    mov ss, ax        ; SS = 0x0000
    mov sp, bx        ; SP = 0x7c00
                      ; We'll set the stack starting just below
                      ; where the bootloader is at 0x0:0x7c00. The
                      ; stack can be placed anywhere in usable and
                      ; unused RAM.
    sti               ; Turn interrupts back on

    mov SI, loadMsg
    call print

    cli
.endloop:
    hlt
    jmp .endloop      ; When finished effectively put our bootloader
                      ; in endless cycle

print:
    pusha
.loop:
    mov AL, [SI]      ; No segment on [SI] means implicit DS:[SI]
    cmp AL, 0
    je .end
    call printChar
    inc SI
    jmp .loop
.end:
    popa
    ret

printChar:
    pusha
    mov AH, 0x0E
    mov BH, 0
    mov BL, 0x0F
    int 0x10
    popa
    ret

; Place the Data after our code
loadMsg db "Loading",0

times 510 - ($ - $$) db 0   ; padding with 0 at the end
dw 0xAA55                   ; PC boot signature