This question has been solved!!! I have updated the bootloader and kernal code to match the fixed one for any Operating System Makers who are struggling with the same question(might add comments soon to the kernal). Below is the explanation to the fixed code which was made by Nate Eldredge. Unrelated but OS is originally called POVESK. Turns out something is already named that, so it is changed now in bootloader code.
I have been making an operating system recently and have encountered a big problem in making the GDT
kernal code(thats how I spell kernel):
[org 0x8000]
[bits 16]
jmp short start
%define ENDL 0x0D, 0x0A
gdt_start:
dq 0x0000000000000000
dq 0x00CF9A000000FFFF
dq 0x00CF92000000FFFF
dq 0x00CF92000000FFFF
gdt_end:
gdt_descriptor:
dw gdt_end - gdt_start - 1
dd gdt_start
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:
mov ah, 0x00
mov al, 0x03
int 0x10
cli
lgdt [gdt_descriptor]
mov eax, cr0
or eax, 1
mov cr0, eax
jmp dword 0x08:.afterstart
[bits 32]
VIDEO_MEMORY equ 0xb8000
WHITE_ON_BLACK equ 0x0f
.afterstart:
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov ebx, start_thirty_two
call print32
cli
hlt
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', 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
bootloader code(is basically the nanobyte os bootloader at part 3 but minorly changed):
[org 0x7C00]
[bits 16]
%define ENDL 0x0D, 0x0A
jmp short start
nop
bdb_oem: db 'CLIPIN..'
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 'CLIPIN.. 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 0x0000
kernal_LOAD_OFFSET equ 0x8000
times 510-($-$$) db 0
dw 0AA55h
buffer:
the issue lies in the kernal code. I did some debugging and have determined that this is probably because of a triple fault(conclusion made by chatGPT) and that it usually happened in jmp dword 0x08:.afterstart but strangely after many attempts at fixing this problem(finding many tutorials and projects and fixing them up into my code) the triple fault have appeared in many different lines of code. I am pretty inexperienced in this topic so I may be just really dumb and missing something obvious.
If anybody has a solution please tell me. Thanks in advance!
triple fault in jmp dword 0x08:.afterstart
and has started appearing in many other lines.
Build commands:
--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
ecm pointed out the bug with gdt_start
needing + kernal_LOAD_SEGMENT * 16
, but the same applies to every label in kernal.asm
that is used in 32-bit mode.
From the 16-bit bootloader, you load the kernal at linear address 0x20000 and far jump to 0x2000:0x0000, so CS = 0x2000 on entry to the kernal. With org 0
at the top of the file, the assembler computes the addresses (offsets) of all labels starting with address 0. This is correct in 16-bit mode when CS = 0x2000, but incorrect as soon as you are in 32-bit mode, and CS is a segment with base = 0.
So the first problem occurs with jmp dword 0x08:.afterstart
. The assembler has given .afterstart
the address 0x6E, which is correct relative to the start of the file and relative to 16-bit segment 0x2000, but certainly not correct relative to 32-bit segment 0x08 whose base address is 0. The actual code at .afterstart
is at linear address 0x2006E, but you are jumping to linear address 0x0006E, which is somewhere in the 16-bit interrupt vector table and whose bytes certainly aren't any useful code. You crash sometime after that when those bytes decode as an instruction which faults (causing a triple fault because you have no exception handlers installed).
You could fix it by replacing .afterstart
with .afterstart + kernal_LOAD_SEGMENT * 16
as you did before, but you would have to do the same with every other label that gets used in 32-bit mode: start_thirty_two
, Xval
, Yval
, etc, etc.
So a better solution might be to load your kernel in the low 64 KB of memory, and far-jump to it with a segment of 0. For instance, load it at 0x0000:0x8000 (linear address 0x8000), do a far jump from the bootloader to 0x0000:0x8000 (so that CS = 0), and then put org 0x8000
at the top of your file. Then every label in your file has the correct address whether interpreted as an offset to 16-bit segment 0x0000, or as an offset to a 32-bit segment with base address 0. This is fine so long as your kernel fits in 32 KB; and once it exceeds that, you'll probably need a multi-stage bootloader in any case.
Another bug is that [bits 32]
needs to go right before .afterstart
, since that code is executing in 32-bit mode. As it stands, mov ax, 0x10
is given its 16-bit encoding, which in 32-bit mode decodes as mov eax, imm32
and uses an additional 2 bytes as the immediate. The result is that you load the high bits of eax
with the bytes that make up the following instruction mov ds, ax
and don't actually execute it.
After these fixes the STAGE TWO START
message is successfully displayed.
Next bug (minor): you don't want ENDL
in the start_thirty_two
string, because those bytes don't start a new line when writing directly to video memory; they just show up as funny looking characters.
Finally, draw_spr
is going to need some serious debugging work as well. I don't understand how the arithmetic there is supposed to work, but you end up with EDI = 0xcbec which is certainly not anywhere within video memory. And you are using stosd
to store all 32 bits of eax
but only the low 8 bits (al
) seems to have been initialized to anything meaningful; did you mean stosb
instead?
More fundamentally, the label names suggest you're trying to draw pixels, but you haven't changed video modes, so you're still in text mode where bytes of video memory are interpreted as characters, not pixels. (Unless you're going for some ASCII art effect, where each "pixel" is a text-mode character cell?)
Given that you haven't commented your code at all, it's unclear what it is supposed to be doing, so you're on your own from here on out.
Get a debugger working! I found all these bugs by single-stepping your code in Bochs. It's a complete waste of time to try to develop any kind of assembly program unless you first make sure you have a debugger and know how to use it.