最近在做操作系统,在制作GDT时遇到了一个大问题
内核代码(这就是我拼写内核的方式):
[org 0x0]
[bits 16]
jmp short start
%define ENDL 0x0D, 0x0A
kernal_LOAD_SEGMENT equ 0x2000
gdt_start:
dq 0x0000000000000000
dq 0x00CF9A000000FFFF
dq 0x00CF92000000FFFF
dq 0x00CF92000000FFFF
gdt_end:
gdt_descriptor:
dw gdt_end - gdt_start - 1
dd gdt_start + kernal_LOAD_SEGMENT * 16
print:
push si
push ax
push bx
.loop:
lodsb
or al, al
jz .done
mov ah, 0x0E
mov bh, 0
int 0x10
jmp .loop
.done:
pop bx
pop ax
pop si
ret
start:
mov si, start_sixteen
call print
in al, 0x64
.wait_ibe:
test al, 0x02
jnz .wait_ibe
mov al, 0xD1
out 0x64, al
.wait_ibe2:
in al, 0x64
test al, 0x02
jnz .wait_ibe2
mov al, 0xDF
out 0x60, al
first_sixteen:
cli
lgdt [gdt_descriptor]
mov eax, cr0
or eax, 1
mov cr0, eax
jmp dword 0x08:.afterstart
.afterstart:
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
[bits 32]
VIDEO_MEMORY equ 0xb8000
WHITE_ON_BLACK equ 0x0f
mov ebx, start_thirty_two
call print32
jmp short start_prog
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
start_prog:
mov al, 2
mov eax, 1004
mov [Xval], eax
mov eax, 50
mov [Yval], eax
mov eax, 10
mov [Wval], eax
mov eax, 20
mov [Hval], eax
lea esi, [PySpr]
call draw_spr
.halt:
cli
hlt
draw_spr:
xor edx, edx
mov ecx, [Hval]
draw_sprite:
mov eax, [Yval]
mov ebx, 1024
mul ebx
add eax, [Xval]
mov edi, eax
push ecx
mov ecx, [Wval]
draw_pixels:
mov al, [esi]
cmp al, 5
je skip_pixel_2
stosd
jmp skip_pixel
skip_pixel_2:
inc edi
skip_pixel:
inc esi
loop draw_pixels
pop ecx
mov eax, [Yval]
inc eax
mov [Yval], eax
loop draw_sprite
ret
start_sixteen: db 'STAGE ONE START', ENDL, 0
start_thirty_two: db 'STAGE TWO START', ENDL, 0
Xval dd 0
Yval dd 0
Wval dd 0
Hval dd 0
PySpr db 5,5,5,2,2,2,2,5,5,5
db 5,5,2,2,2,2,2,2,5,5
db 5,2,2,2,2,2,2,2,2,5
db 2,2,2,2,2,2,2,2,2,2
db 5,8,8,8,8,8,8,8,8,5
db 5,8,15,0,8,8,0,15,8,5
db 5,8,8,8,8,8,8,8,8,5
db 5,8,8,0,0,0,0,8,8,5
db 5,5,8,8,8,8,8,8,5,5
db 2,2,2,2,2,2,2,2,2,2
db 2,2,2,2,2,2,2,2,2,2
db 2,5,2,2,2,2,2,2,5,2
db 2,5,2,2,2,2,2,2,5,2
db 2,5,2,2,2,2,2,2,5,2
db 8,5,2,2,2,2,2,2,5,8
db 5,5,6,6,6,6,6,6,5,5
db 5,5,6,6,5,5,6,6,5,5
db 5,5,6,6,5,5,6,6,5,5
db 5,5,6,6,5,5,6,6,5,5
db 5,7,7,7,5,5,7,7,7,5
引导加载程序代码(基本上是第 3 部分的 Nanobyte 操作系统引导加载程序,但略有更改):
org 0x7C00
bits 16
%define ENDL 0x0D, 0x0A
jmp short start
nop
bdb_oem: db 'POVESK..'
bdb_bytes_per_sector: dw 512
bdb_sectors_per_cluster: db 1
bdb_reserved_sectors: dw 1
bdb_fat_count: db 2
bdb_dir_entries_count: dw 0E0h
bdb_total_sectors: dw 2880
bdb_media_descriptor_type: db 0F0h
bdb_sectors_per_fat: dw 9
bdb_sectors_per_track: dw 18
bdb_heads: dw 2
bdb_hidden_sectors: dd 0
bdb_large_sector_count: dd 0
ebr_drive_number: db 0
db 0
ebr_signature: db 29h
ebr_volume_id: db 12h, 34h, 56h, 78h
ebr_volume_label: db 'POVESK.. OS'
ebr_system_id: db 'FAT12 '
start:
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7C00
push es
push word .after
retf
.after:
mov [ebr_drive_number], dl
mov si, msg_loading
call puts
push es
mov ah, 08h
int 13h
jc floppy_error
pop es
and cl, 0x3F
xor ch, ch
mov [bdb_sectors_per_track], cx
inc dh
mov [bdb_heads], dh
mov ax, [bdb_sectors_per_fat]
mov bl, [bdb_fat_count]
xor bh, bh
mul bx
add ax, [bdb_reserved_sectors]
push ax
mov ax, [bdb_dir_entries_count]
shl ax, 5
xor dx, dx
div word [bdb_bytes_per_sector]
test dx, dx
jz .root_dir_after
inc ax
.root_dir_after:
mov cl, al
pop ax
mov dl, [ebr_drive_number]
mov bx, buffer
call disk_read
xor bx, bx
mov di, buffer
.search_kernal:
mov si, file_kernal_bin
mov cx, 11
push di
repe cmpsb
pop di
je .found_kernal
add di, 32
inc bx
cmp bx, [bdb_dir_entries_count]
jl .search_kernal
jmp kernal_not_found_error
.found_kernal:
mov ax, [di + 26]
mov [kernal_cluster], ax
mov ax, [bdb_reserved_sectors]
mov bx, buffer
mov cl, [bdb_sectors_per_fat]
mov dl, [ebr_drive_number]
call disk_read
mov bx, kernal_LOAD_SEGMENT
mov es, bx
mov bx, kernal_LOAD_OFFSET
.load_kernal_loop:
mov ax, [kernal_cluster]
add ax, 31
mov cl, 1
mov dl, [ebr_drive_number]
call disk_read
add bx, [bdb_bytes_per_sector]
mov ax, [kernal_cluster]
mov cx, 3
mul cx
mov cx, 2
div cx
mov si, buffer
add si, ax
mov ax, [ds:si]
or dx, dx
jz .even
.odd:
shr ax, 4
jmp .next_cluster_after
.even:
and ax, 0x0FFF
.next_cluster_after:
cmp ax, 0x0FF8
jae .read_finish
mov [kernal_cluster], ax
jmp .load_kernal_loop
.read_finish:
mov dl, [ebr_drive_number]
mov ax, kernal_LOAD_SEGMENT
mov ds, ax
mov es, ax
jmp kernal_LOAD_SEGMENT:kernal_LOAD_OFFSET
jmp wait_key_and_reboot
cli
hlt
floppy_error:
mov si, msg_read_failed
call puts
jmp wait_key_and_reboot
kernal_not_found_error:
mov si, msg_kernal_not_found
call puts
jmp wait_key_and_reboot
wait_key_and_reboot:
xor ah, ah
int 16h
jmp 0FFFFh:0
.halt:
cli
hlt
puts:
push si
push ax
push bx
.loop:
lodsb
or al, al
jz .done
mov ah, 0x0E
xor bh, bh
int 0x10
jmp .loop
.done:
pop bx
pop ax
pop si
ret
lba_to_chs:
push ax
push dx
xor dx, dx
div word [bdb_sectors_per_track]
inc dx
mov cx, dx
xor dx, dx
div word [bdb_heads]
mov dh, dl
mov ch, al
shl ah, 6
or cl, ah
pop ax
mov dl, al
pop ax
ret
disk_read:
push ax
push bx
push cx
push dx
push di
push cx
call lba_to_chs
pop ax
mov ah, 02h
mov di, 3
.retry:
pusha
stc
int 13h
jnc .done
popa
call disk_reset
dec di
test di, di
jnz .retry
.fail:
jmp floppy_error
.done:
popa
pop di
pop dx
pop cx
pop bx
pop ax
ret
disk_reset:
pusha
xor ah, ah
stc
int 13h
jc floppy_error
popa
ret
msg_loading: db 'BOOT START', ENDL, 0
msg_read_failed: db 'FAIL READ DISK', ENDL, 0
msg_kernal_not_found: db 'KERNAL NOT FOUND', ENDL, 0
file_kernal_bin: db 'KERNAL BIN'
kernal_cluster: dw 0
kernal_LOAD_SEGMENT equ 0x2000
kernal_LOAD_OFFSET equ 0
times 510-($-$$) db 0
dw 0AA55h
buffer:
问题出在内核代码上。我做了一些调试,并确定这可能是因为三重错误(chatGPT 得出的结论),并且它通常发生在 jmp dword 0x08:.afterstart 中,但奇怪的是,在多次尝试解决此问题后(找到许多教程和项目以及将它们修复到我的代码中)三重错误出现在许多不同的代码行中。我在这个话题上非常缺乏经验,所以我可能真的很愚蠢并且错过了一些明显的东西。
如果有人有解决方案请告诉我。预先感谢!
jmp dword 0x08:.afterstart
中的三重故障,并已开始出现在许多其他线路中。构建命令:
--WinCMD NASM
nasm scr/boot.asm -f bin -o build/boot.bin
nasm scr/kernal.asm -f bin -o build/kernal.bin
--WSL
dd if=/dev/zero of=v_004.img bs=512 count=2880
mkfs.fat -F 12 -n NBOS v_004.img
dd if=boot.bin of=v_004.img conv=notrunc
mcopy -i v_004.img kernal.bin ::kernal.bin
--QEMUCMD
qemu-system-x86_64 v_004.img
gdt_start
需要
+ kernal_LOAD_SEGMENT * 16
的错误,但这同样适用于
kernal.asm
中在 32 位模式下使用的每个标签。从 16 位引导加载程序中,您在线性地址 0x20000 处加载内核并远跳转到 0x2000:0x0000,因此在进入内核时 CS = 0x2000。 通过文件顶部的
org 0
,汇编器计算从地址 0 开始的所有标签的地址(偏移量)。当 CS = 0x2000 时,这在 16 位模式下是正确的,但一旦处于 32 位模式下,就会出现错误。位模式,CS 是基数 = 0 的段。所以第一个问题出现在
jmp dword 0x08:.afterstart
。 汇编器给
.afterstart
的地址为0x6E,相对于文件开头和16位段0x2000来说,它是正确的,但相对于基地址为0的32位段0x08来说,肯定不正确。实际代码at
.afterstart
位于线性地址 0x2006E,但您正在跳转到线性地址0x0006E,它位于 16 位中断向量表中的某个位置,其字节肯定不是任何有用的代码。 之后的某个时候,当这些字节解码为出错的指令时,您会崩溃(导致三重错误,因为您没有安装异常处理程序)。您可以像以前一样通过将
.afterstart
替换为
.afterstart + kernal_LOAD_SEGMENT * 16
来修复此问题,但您必须对 32 位模式下使用的所有其他标签执行相同的操作:
start_thirty_two
、
Xval
、
Yval
等等等等因此,更好的解决方案可能是将内核加载到低 64 KB 内存中,并使用 0 段远跳转到它。例如,将其加载到 0x0000:0x8000(线性地址 0x8000),进行远跳转从引导加载程序到 0x0000:0x8000(以便 CS = 0),然后将
org 0x8000
放在文件的顶部。 然后,文件中的每个标签都具有正确的地址,无论是解释为 16 位段 0x0000 的偏移量,还是基地址为 0 的 32 位段的偏移量。只要您的内核适合 32 KB,就可以了;一旦超过这个范围,您可能在任何情况下都需要一个多阶段引导加载程序。