我目前正在学习一些操作系统开发并编写了一个非常基本的引导加载程序。
它只是获取软盘映像的一些扇区并将其加载到 RAM 的地址 0x10000 处。然后切换到32位保护模式并跳转到0x10000。效果很好。
在该地址,只有一些代码显示“Hello, World!”通过写入 BIOS 视频缓冲区。然后,它跳回 16 位实模式并在无限循环中停止。
这是加载广告 0x10000 的代码:
; file is loaded at address 0x10000
org 0x10000
; Entry point is here
; 32-bit protected mode
bits 32
pm32:
; print "Hello World!" using the BIOS video buffer
VideoBuffer equ 0xb8200
; Set up data segments so we can access the "Hello World!" string
mov ax, 10h
mov ds, ax
mov ss, ax
sti
mov edi, VideoBuffer
mov esi, msg
print:
lodsb
; check if we reached the null char
test al, al
jz print_done
; print text and move destination pointer
mov [edi], al
inc edi
inc edi
jmp print
print_done:
; jump to "pmdone" in 16-bit protected mode
jmp 18h:pmdone
bits 16
pmdone:
; switch to real mode
mov eax, cr0
xor al, 1
mov cr0, eax
halt:
; halt the CPU using an infinite loop
; (gotta use 1000h: segment because the address of halt is cut to 16 bits)
jmp 1000h:halt
msg: db "Hello World!", 0
我的 GDT 看起来像这样: 0x8:32位代码 0x10:32位数据 0x18:16位代码 0x20:16位数据
我使用 QEMU 来执行代码,并使用 Bochs 来调试它。然而,当我在 Bochs 中运行代码并输入 c 继续时,它工作得很好。它显示 hello world 消息,然后陷入循环。
但是当我在 QEMU 中运行它时,它不断崩溃并重新启动。我稍微降低了速度,发现它只是有时显示“Hello World”消息,但并非总是如此。
它在 QEMU 中不起作用有什么原因吗?我在寻址或跳转方面做错了什么吗?因为我不太确定那里。
这是退出 32 位模式以停止 CPU 的最佳方式吗?还是我应该以其他方式进行?
谢谢各位的解答
(编辑): 这是最后一次中断 QEMU 日志。不确定这是否有帮助
2: v=08 e=0000 i=0 cpl=0 IP=0018:0000002b pc=0000002b SP=0010:00000000 env->regs[R_EAX]=00000011
EAX=00000011 EBX=00000000 ECX=00000002 EDX=00000000
ESI=0001003f EDI=000b8218 EBP=00000000 ESP=00000000
EIP=0000002b EFL=00000293 [--S-A-C] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =1000 00010000 0000ffff 00009300 DPL=0 DS16 [-WA]
CS =0018 00000000 000fffff 000f9a00 DPL=0 CS16 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
FS =0000 00000000 0000ffff 00009300 DPL=0 DS16 [-WA]
GS =0000 00000000 0000ffff 00009300 DPL=0 DS16 [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT= 00007c7a 00000027
IDT= 00000000 000003ff
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
DR6=ffff0ff0 DR7=00000400
CCS=000000d4 CCD=ffffff3d CCO=SUBW
EFER=0000000000000000
check_exception old: 0x8 new 0xd
好吧,伙计们,看来我解决了一个主要问题,但又有一个新问题。
CPU没有执行
jmp 1000h:halt
和jmp 18h:pmdone
,因为地址是16位的。我需要使用 dword
关键字指定地址的大小。所以,我将跳转更改为:
jmp dword 18h:pmdone
和
jmp dword halt
(看起来 Bochs 只是因为一些废话而忽略了这一点)
然后我使用 E9 端口进行了一些调试。你只需要在运行QEMU时设置
-debugcon stdio
,然后你就可以通过向终端写入字符
mov al, <char>
out 0xE9, al
通过这些指令,我看到我的代码现在成功跳转到
pmdone
并在那里执行。但不知何故,QEMU 仍然不断崩溃。经过一些额外的调试后,我在我的pmdone
点得到了这段代码:
pmdone:
; write logs
mov al, '1'
out 0xE9, al
; (code now seems to crash HERE)
; write more logs
mov al, '2'
out 0xE9, al
; switch back to real mode
mov eax, cr0
xor al, 1
mov cr0, eax
; more logging
mov al, '3'
out 0xE9, al
我的终端输出刚刚打印了
1
。因此,不知何故,第一个 out
指令被调用,但随后代码崩溃了。我在第一个指令之后添加了第二个 out
指令,但它仍然只打印了一次 1
然后崩溃了。
经过一些额外的调试,我发现当这些指令之一被调用时,我的
pmdone
中的代码(因此在16位保护模式下运行的代码)崩溃了:
in
或 out
指示mov
来自控制寄存器(cr0、cr1 等)为什么 QEMU 不喜欢这个指令?这里有什么问题? (因为 Bochs 只是运行指令,禁用保护模式,然后进入无限循环。)
希望你有一些想法。如果我发现什么我会评论。