3

I am making an OS mostly in C++, but for the bootloader, I'm using FASM. When I try to set the GDT, Qemu clears the screen and re-prints "SeaBIOS" at the top. It continues in a loop like that until I close it. Here is a gif of it:

I tried running it with -nographic, but it does the same thing in the Windows console.

Oh yeah, OS/version info.
Windows: 20H2
FASM: 1.73.25
Qemu: 5.1.0

Here is my code:

gdt_start:
    dd 0x00
    dd 0x00

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 - 1
    dd gdt_start

CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start

switch_to_pm:
    cli
    lgdt [gdt_descriptor]
    mov eax, cr0
    or eax, 0x1
    mov cr0, eax
    jmp CODE_SEG:init_pm

use32

init_pm:
    mov ax, DATA_SEG
    mov ds, ax
    mov ss, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    
    mov ebp, 0x90000
    mov esp, ebp
    
    call BEGIN_PM

BEGIN_PM:
    mov ebx, MSG_PROT_MODE
    call print_string_pm
    call KERNEL_OFFSET
    jmp $

Update: Full code:

; boot.asm

org 0x7c00
KERNEL_OFFSET equ 0x1000

mov [BOOT_DRIVE], dl
mov bp, 0x9000
mov sp, bp

mov bx, MSG_REAL_MODE
call print
call print_nl

call load_kernel

call switch_to_pm

jmp $

print:
    pusha

start:
    mov al, [bx]
    cmp al, 0
    je done
    
    mov ah, 0x0e
    int 0x10
    
    add bx, 1
    jmp start
    
done:
    popa
    ret

print_nl:
    pusha
    
    mov ah, 0x0e
    mov al, 0x0a
    int 0x10
    mov al, 0x0d
    int 0x10
    
    popa
    ret

load_kernel:
    mov bx, MSG_LOAD_KERNEL
    call print
    call print_nl
    
    mov bx, KERNEL_OFFSET
    mov dh, 1
    mov dl, [BOOT_DRIVE]
    call disk_load
    ret

disk_load:
    pusha
    push dx
    
    mov ah, 0x02
    mov al, dh
    mov cl, 0x02
    
    mov ch, 0x00
    mov dh, 0x00
    
    int 0x13
    jc disk_error
    
    pop dx
    cmp al, dh
    jne sectors_error
    popa
    ret

disk_error:
    mov bx, DISK_ERROR
    call print
    call print_nl
    mov dh, ah
    call print_hex
    jmp disk_loop

sectors_error:
    mov bx, SECTORS_ERROR
    call print

disk_loop:
    jmp $

print_hex:
    pusha
    mov cx, 0

hex_loop:
    cmp cx, 4
    je end_hex
    
    mov ax, dx
    and ax, 0x000f
    add al, 0x30
    cmp al, 0x39
    jle step2
    add al, 7

step2:
    mov bx, HEX_OUT + 5
    sub bx, cx
    mov [bx], al
    ror dx, 4
    
    add cx, 1
    jmp hex_loop

end_hex:
    mov bx, HEX_OUT
    call print
    popa
    ret

HEX_OUT:
    db '0x0000', 0

gdt_start:
    dd 0x00
    dd 0x00

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 - 1
    dd gdt_start

CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start

switch_to_pm:
    cli
    lgdt [gdt_descriptor]
    mov eax, cr0
    or eax, 0x1
    mov cr0, eax
    jmp CODE_SEG:init_pm

use32

init_pm:
    mov ax, DATA_SEG
    mov ds, ax
    mov ss, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    
    mov ebp, 0x90000
    mov esp, ebp
    
    call BEGIN_PM

BEGIN_PM:
    mov ebx, MSG_PROT_MODE
    call print_string_pm
    call KERNEL_OFFSET
    jmp $

VIDEO_MEMORY equ 0xb8000
WHITE_ON_BLACK equ 0x0f

print_string_pm:
    pusha
    mov edx, VIDEO_MEMORY

print_string_pm_loop:
    mov al, [ebx]
    mov ah, WHITE_ON_BLACK
    
    cmp al, 0
    je print_string_pm_done
    
    mov [edx], ax
    add ebx, 1
    add edx, 2
    
    jmp print_string_pm_loop

print_string_pm_done:
    popa
    ret

BOOT_DRIVE db 0
MSG_REAL_MODE db "Started in 16-bit real mode", 0
MSG_LOAD_KERNEL db "Loading kernel into memory", 0
DISK_ERROR db "Disk read error", 0
SECTORS_ERROR db "Incorrect number of sectors read", 0
MSG_PROT_MODE db "Loaded 32-bit protected mode", 0

times 510-($-$$) db 0
dw 0xAA55
; loader.asm

format ELF

extrn main

public _start

_start:
    call main
    jmp $
// kernel.cpp
void main() {
    char* video_memory = (char*) 0xb8000;
    *video_memory = 'X';
}
rem compile.bat

@echo off

<NUL set /p="Cleaning binaries..."
    del *.bin > NUL
    del *.o > NUL
    del *.elf > NUL
echo Done

<NUL set /p="Compiling boot.asm..."
    fasm boot.asm > NUL
echo Done

<NUL set /p="Compiling loader.asm..."
    fasm loader.asm > NUL
echo Done

<NUL set /p="Compiling kernel.c..."
    wsl gcc -m32 -ffreestanding -c kernel.cpp -o kernel.o
echo Done

<NUL set /p="Linking..."
    wsl objcopy kernel.o -O elf32-i386 kernel.elf
    wsl /usr/local/i386elfgcc/bin/i386-elf-ld -o kernel.bin -Ttext 0x1000 loader.o kernel.elf
    type boot.bin kernel.bin > os_image.bin
echo Done

<NUL set /p="Running..."
    qemu-system-i386 os_image.bin
echo Done
Lysander Mealy
  • 113
  • 1
  • 6
  • 1
    Can you show the complete bootloader code please? I'd recommend using BOCHS debugger to step through this. But it is possible the problem is not in the code you are showing. It is clear you are getting a triple fault that repeatedly reboot the processor. I don't necessarily see anything wrong with what you have here but if you aren't using an org of 0x7c00 (maybe 0x0000) and you set CS to 0x7c0 I can see that failing. I'd alsp move the `jmp $` right after `BEGIN_PM:` so that you can rule out calling the `KERNEL_OFFSET` as a problem or the call to `print_string_pm`. – Michael Petch Dec 30 '20 at 01:16
  • 2
    Please make a [mcve] that other people can reproduce on their own. Include the exact command invocations used to build it. – fuz Dec 30 '20 at 01:42
  • @fuz mre would be kind of hard seeing as the whole 209 line program is made to be minimal. I can add a binary, if that would be helpful. – Lysander Mealy Dec 30 '20 at 02:56
  • @LysanderMealy Make it as minimal as possible. The important thing is that it must be possible to reproduce your problem without having to guess what other code you didn't provide might have looked like. This is because quite often the devil is in the details and the code you didn't consider important enough to include into the question is actually crucial to understanding why your problem occurs. – fuz Dec 30 '20 at 03:55
  • @fuz is that good? – Lysander Mealy Dec 30 '20 at 12:36
  • @LysanderMealy Quite! – fuz Dec 30 '20 at 14:20

1 Answers1

5

The problem isn't in the code but how you build it. This sequence actually creates an ELF executable called kernel.bin:

wsl objcopy kernel.o -O elf32-i386 kernel.elf
wsl /usr/local/i386elfgcc/bin/i386-elf-ld -o kernel.bin -Ttext 0x1000 loader.o kernel.elf

It should be:

wsl /usr/local/i386elfgcc/bin/i386-elf-ld -o kernel.elf -Ttext 0x1000 loader.o kernel.o
wsl objcopy -O binary kernel.elf kernel.bin

This change links the object files to an ELF executable kernel.elf and then converts the ELF executable to a binary file kernel.bin that can be loaded by the bootloader to address 0x01000.

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
  • How could I do that with C++, rather than C? i386-elf-ld can't find -lstdc++ or -llibstdc++. – Lysander Mealy Dec 30 '20 at 19:30
  • @LysanderMealy : if you are building a freestanding program you shouldn't be linking to either the c or c++ libraries since they don't exist in a freestanding environment. You have to provide your own code. – Michael Petch Dec 30 '20 at 20:14