我最近在 * 的回答中写了一些 General Bootloader Tips,可能对你有用。 提示 #1 可能适用于您的问题:
当 BIOS 跳转到您的代码时,您不能依赖具有有效或预期值的 CS、DS、ES、SS、SP 寄存器。当您的引导加载程序启动时,它们应该被适当地设置。您只能保证您的引导加载程序将从物理地址 0x00007c00 加载并运行,并且引导驱动器号已加载到 DL 寄存器中。
基于 printChar 有效的事实,并且写出整个字符串并不意味着 DS:SI 没有指向内存中的正确位置你的字符串驻留。造成这种情况的常见原因是开发人员错误地认为当 BIOS 跳转到引导加载程序时,CS 和/或 DS 寄存器已正确设置。它必须手动设置。在原点为 0x7c00 的情况下,DS 需要设置为 0。在 16 位实模式下,物理内存地址由 segment:offset pairs 使用公式 (segment<<4)+offset 计算得出。在您的情况下,您使用的偏移量为 0x7C00。 DS 中的值为 0 将产生正确的物理地址 (0
您可以在程序开始时将 DS 设置为 0,例如:
xor ax, ax ; set AX to zero
mov ds, ax ; DS = 0
在 QEMU 的情况下,BIOS 跳转到 0x07c0:0x0000 。这也代表相同的物理内存位置 (0x07c0CS=0x07c0(而不是 CS=0)。由于有许多段:偏移量对映射到相同的物理内存位置,因此您需要适当地设置 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