load_seg_reg(ES, 0xfffc): invalid segment when trying to run kernel code from boot sector

216 views Asked by At

So im building a simple OS and i am running into the error stated above when calling the kernel code from the boot sector. I've spent a few days struggling and still at a dead end.

The code fails at call KERNEL_OFFSET. I know its failing because im trying to jump to an invalid segment but i cant figure out how and where. I also noticed that it's not really reading the disk. My load_kernel code asks for 10 sectors to be read but only seemed to have been read. The program is able to enter protected mode and able to load what i need it to load from the disk, despite the loading behavior being weird.

Im also showing my makefile because i suspect perhaps i didnt link things properly. I am running OSX btw so idk if the shell commands im using are the correct ones.

I've been at this a few days, any help will be appreciated. Thanks

boot_sector

;
; A simple boot sector program.
; BIOS stores the boot drive in DL
;
[org 0x7c00]
[bits 16]

mov [BOOT_DRIVE], dl        ; Move boot drive info to memory.

; Setup stack to a position we know is free.
mov bp, 0x9000
mov sp, bp

; Output a nice message.
mov dx, REAL_MODE_MSG
call PrintString16

; Load the kernel.
mov dx, [BOOT_DRIVE]
call LoadKernel

mov dx, LOADED_KERNEL_MSG
call PrintString16

; Switch to protected mode.
; Note, we never break from Protected mode.
call SwitchToProtectedMode

jmp $

; Including some useful routines.
%include "string_utils_16.s"
%include "gdt.s"
%include "string_utils.s"
%include "load_kernel.s"
%include "switch_to_pm.s"

[bits 32]
BeginProtectedMode:
    mov edx, PROT_MODE_MSG
    call PrintString

    ; This call should, theoritecally, run the instructions we just loaded.
    ; AKA, the C code.
    ; KERNEL_OFFSET is defined in load_kernel.s

    call KERNEL_OFFSET
    jmp $


BOOT_DRIVE          db  0
LOADED_KERNEL_MSG   db  "Loaded kernel with no errors!", 0
REAL_MODE_MSG       db  "Welcome! Started in 16-bit Real Mode!", 0
PROT_MODE_MSG       db  "Now running in 32-bit Protected Mode", 0

times 510 -( $ - $$ ) db 0

dw 0xaa55

switch_to_pm

[bits 16]
SwitchToProtectedMode:
    cli
    lgdt [gdt_descriptor]
    ; To actually switch to 32 bit mode, set LSB of cr0 to 1
    ; Can't touch the cr0 register directly so gotta do it the hard way.
    mov eax, cr0
    or eax, 0x1
    mov cr0, eax
    ; Technically, after that last move instruction, we're in 32 bit mode BUT
    ; the CPU may have been doing work in between all this, since, the CPU can do
    ; certain things in parallel if its got different circuitry to do those things, 
    ; which it probably does. Few of the things the CPU could do in parallel is
    ; fetch, decode and execute. We dont want the CPU to be fetching the next
    ; instruction while our stuff is happening (those next things that will be 
    ; fetched probably wont work in 32 bit mode) and we want it to finish whatever
    ; it is currently executing. so, we're going to do a far jump to
    ; somewhere so that the CPU cannot make any extrapolations on what to fetch next
    ; and anything being executed can finish executing. 
    jmp CODE_SEGMENT:init_pm

    [bits 32]
    ; Initialize segment registers. In 32 bit mode, they all point to an entry in
    ; the GDT.
    init_pm:
        mov ax, DATA_SEGMENT
        mov ds, ax
        mov ss, ax
        mov es, ax
        mov fs, ax
        mov gs, ax

        ; Define the stack somewhere we're sure has free memory.
        mov ebp, 0x90000
        mov esp, ebp

        call BeginProtectedMode 

load_kernel

[bits 16]
KERNEL_OFFSET equ 0x1000

; Will load the kernel into memory.
; dl will contain the boot drive.
LoadKernel:
    push dx                 ; Save the boot drive info
    mov dx, LOAD_KERNEL_MSG
    call PrintString16
    pop dx                  ; Get it back. DL contains boot drive.

    mov bx, KERNEL_OFFSET   ; Where we want kernel to be loaded to.
    mov dh, 10              ; How many sectors to load
    ; dl is also a parameter but we already have it.
    call LoadFromDisk

    ret

%include "disk_load.s"

LOAD_KERNEL_MSG db  "Loading kernel...", 0

kernel

// Simple kernel.
#include "screen.h"

void main() {
    print("It worked!");
    // char* video = (char*) 0xb800;
    // video[0] = 'S';
}

makefile

INCLUDE = include/
# List is expanded when used not when declared.
OBJECTS = $(wildcard temp/*.o)

# -------------------- Build the os_image
os_image.bin : temp/boot_sect.bin temp/kernel.bin
    cat temp/boot_sect.bin temp/kernel.bin > os_image.bin

# -------------------- Build the boot sector image
temp/boot_sect.bin : boot/boot_sect.s
    nasm boot/boot_sect.s -i boot/ -f bin -o temp/boot_sect.bin

# -------------------- Build kernel image
temp/kernel.bin : kernel.o kernel_entry.o
    clang -ffreestanding -m32 kernel_entry.o $(OBJECTS) kernel.o -o temp/kernel.bin

# -------------------- Build object files
kernel.o : kernel/kernel.c temp/screen.o
    clang -ffreestanding -m32 -c -I $(INCLUDE) kernel/kernel.c -o kernel.o 

kernel_entry.o : kernel/kernel_entry.s
    nasm -f macho -o kernel_entry.o kernel/kernel_entry.s 

# -------------------- Build driver objects
temp/screen.o : include/screen.h drivers/screen.c temp/low_level.o
    clang -ffreestanding -m32 -c -I $(INCLUDE) drivers/screen.c -o temp/screen.o 

temp/low_level.o : include/low_level.h kernel/low_level.c
    clang -ffreestanding -m32 -c -I $(INCLUDE) kernel/low_level.c -o temp/low_level.o 

clean :
    rm *.o
    rm ./temp/*.o
    rm ./temp/*.bin
    rm *.bin

relevant bochs console dump

00014040953i[BIOS ] Booting from 0000:7c00
00014479618i[FDD  ] partial read() on floppy image returns 172/512
00014561257i[MEM0 ] allocate_block: block=0x10 used 0x3 of 0x20
00014561312e[CPU0 ] load_seg_reg(ES, 0xfffc): invalid segment
00014561312e[CPU0 ] interrupt(): gate descriptor is not valid sys seg (vector=0x0d)
00014561312e[CPU0 ] interrupt(): gate descriptor is not valid sys seg (vector=0x08)
00014561312i[CPU0 ] CPU is in protected mode (active)
00014561312i[CPU0 ] CS.mode = 32 bit
00014561312i[CPU0 ] SS.mode = 32 bit
00014561312i[CPU0 ] EFER   = 0x00000000
00014561312i[CPU0 ] | EAX=0008fffc  EBX=00001000  ECX=00090003  EDX=ffff0136
00014561312i[CPU0 ] | ESP=0008fff8  EBP=00090003  ESI=000e0000  EDI=00007d2d
00014561312i[CPU0 ] | IOPL=0 id vip vif ac vm RF nt of df if tf SF zf AF pf CF
00014561312i[CPU0 ] | SEG sltr(index|ti|rpl)     base    limit G D
00014561312i[CPU0 ] |  CS:0008( 0001| 0|  0) 00000000 ffffffff 1 1
00014561312i[CPU0 ] |  DS:0010( 0002| 0|  0) 00000000 ffffffff 1 1
00014561312i[CPU0 ] |  SS:0010( 0002| 0|  0) 00000000 ffffffff 1 1
00014561312i[CPU0 ] |  ES:0010( 0002| 0|  0) 00000000 ffffffff 1 1
00014561312i[CPU0 ] |  FS:0010( 0002| 0|  0) 00000000 ffffffff 1 1
00014561312i[CPU0 ] |  GS:0010( 0002| 0|  0) 00000000 ffffffff 1 1
00014561312i[CPU0 ] | EIP=0000107c (0000107c)
00014561312i[CPU0 ] | CR0=0x60000011 CR2=0x00000000
00014561312i[CPU0 ] | CR3=0x00000000 CR4=0x00000000
(0).[14561312] [0x000000000000107c] 0008:000000000000107c (unk. ctxt): pop es                    ; 07
00014561312e[CPU0 ] exception(): 3rd (13) exception with no resolution, shutdown status is 00h, resetting
1

There are 1 answers

0
Chuck Onwuzuruike On

The problem was from the makefile and how I was linking and creating the temp/kernel.bin binary.

The rule should be:

clang -ffreestanding -m32 kernel_entry.o kernel.o $(OBJECTS) -o temp/kernel_temp.o
gobjcopy -O binary temp/kernel_temp.o temp/kernel.bin

instead. I needed to create a temporary object file with kernel_entry.o and kernel.o linked then create a binary out of the temporary object file. If you are running OSX, you probably have to do something similar since we do not have access to all the fancy linux ld options.