我正在学习 x86 汇编,我正在尝试在 NASM 中制作一个玩具操作系统,但我不明白一些东西。
我制作了一个成功启动内核的引导加载程序:
- 从包含内核文件的软盘加载 14 个扇区;
kernel.feo
在这些标记为;的扇区中搜索文件- 将该文件加载到内存中的偏移量
0x2000
; - 使用远跳转执行内核
jmp 0x2000:0x0000
。
所以我的内核代码位于0x2000:0
内存中。CS
由于使用了远跳,可能会正确设置。在这个内核代码中,我想进入 32 位保护模式,但我不确定 GDT 是如何工作的。当我在虚拟机上运行下面的代码时(QEMU)
,它什么也不做。
我想请你帮我进入 32 位保护模式!
也就是说,您有以下问题:
- 您假设代码是
0x7c00:0
由于 加载的org 0
,但情况可能并非如此。唯一保证的是物理地址。您应该使用远跳转到您的入口点,以便CS
正确设置。- 您出于某种原因设置
DS
为,0x2000
因此您的代码根本找不到任何数据。您应该设置DS
为 matchCS
,或在任何地方使用CS
覆盖(不推荐)。- 保护模式代码假定从零开始的段,这反过来意味着它期望
org 0x7c00
这当然与您的设置冲突。您应该切换到org 0x7c00
和 段0
。- VGA 文本模式段为
0xb8000
not0xb80000
(减一)。0x55 0xaa
引导扇区末尾没有引导签名字节。
我在我的代码中更正了这些事情:
[org 0x0]
被更正为[org 0x2000]
,段被设置为0
;DS
被更正为,0
而不是0x2000
,所以现在它匹配CS
;- VGA 文本模式段更正为
0xb8000
;
但是代码无法使用这些更正,它应该打印两个字符串但它什么都不做!
请注意,此内核代码不应以引导签名结尾0x55 0xAA
,因为它不是引导扇区。
这是更正的内核代码(不起作用):
[bits 16]
[org 0x2000]
jmp 0:kernel_start
gdt_start:
gdt_null:
dd 0x0
dd 0x0
gdt_code:
dw 0xffff
dw 0x0
db 0x0
db 10011010b
db 11001111b
db 0x0
gdt_data:
dw 0xffff
dw 0x0
db 0x0
db 10010010b
db 11001111b
db 0x0
gdt_end:
gdt_descriptor:
dw gdt_end - gdt_start
dd gdt_start
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
print:
mov ah, 14
mov bh, 0
lodsb
cmp al, 0
je .done
int 0x10
jmp print
.done:
ret
uzenet_real db 'uzenet16', 0
uzenet_prot db 'uzenet32', 0
kernel_start:
mov ax, 0
mov ss, ax
mov sp, 0xFFFC
mov ax, 0
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov si, uzenet_real
call print
cli
lgdt[gdt_descriptor]
mov eax, cr0
or eax, 0x1
mov cr0, eax
jmp CODE_SEG:b32
[bits 32]
VIDEO_MEMORY equ 0xb8000
WHITE_ON_BLACK equ 0x0f
print32:
pusha
mov edx, VIDEO_MEMORY
.loop:
mov al, [ebx]
mov ah, WHITE_ON_BLACK
cmp al, 0
je .done
mov [edx], ax
add ebx, 1
add edx, 2
jmp .loop
.done:
popa
ret
b32:
mov ax, DATA_SEG
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov ebp, 0x90000
mov esp, ebp
mov ebx, uzenet_prot
call print32
jmp $