Search code examples
c++x86virtualboxosdevgrub

Unable to boot a custom kernel in VirtualBox : "could not read from boot medium"


I followed the tutorial series on Write your own operating system in 1 hour to create a basic OS to print just "Hello World" with just 4 files : Makefile, kernel.cpp, loader.s and linker.ld.

I am creating a mykernel.iso file but when I boot it into VirtualBox, I get the error "Could not read from boot medium : System halted". I confirmed that the .iso file is linked with my machine instance. Looks like there is some other problem in the code perhaps.

Here is my Makefile:

#we need to tell the compiler to stop assuming that this will be executed inside an OS
CPPPARAMS = -m32 -fno-use-cxa-atexit -nostdlib -fno-builtin -fno-rtti -fno-exceptions -fno-leading-underscore
ASPARAMS = --32
LDPARAMS = -melf_i386

objects = loader.o kernel.o

%.o: %.cpp
    g++ $(CPPPARAMS) -o $@ -c $< 

%.o: %.s
    as $(ASPARAMS) -o $@ $<

mykernel.bin: linker.ld $(objects)
    ld $(LDPARAMS) -T $< -o $@ $(objects)

install: mykernel.bin
    sudo cp $< /boot/mykernel.bin

mykernel.iso: mykernel.bin
    mkdir iso
    mkdir iso/boot
    mkdir iso/boot/grub
    cp $< iso/boot/
    echo 'set default=0' > iso/boot/grub/grub.cfg
    echo 'set timeout=0' >> iso/boot/grub/grub.cfg
    echo '' >> iso/boot/grub/grub.cfg
    echo 'menuentry "My Personal OS" {' >> iso/boot/grub/grub.cfg
    echo 'multiboot /boot/mykernel.bin' >> iso/boot/grub/grub.cfg
    echo 'boot' >> iso/boot/grub/grub.cfg
    echo '}' >> iso/boot/grub/grub.cfg
    grub-mkrescue --output $@ iso
    rm -rf iso

clean:
    rm -rf iso
    rm *.o
    rm mykernel.iso
    rm mykernel.bin

Here is the kernel.cpp

void printf(char *str)
{
    unsigned short *VideoMemory = (unsigned short*)0xb8000;

    for(int i=0;str[i] != '\0';i++)
        VideoMemory[i] = (VideoMemory[i] & 0xFF00) | str[i];
}

typedef void (*constructor)();
extern "C" constructor start_ctors;
extern "C" constructor end_ctors;
extern "C" void callConstructors()
{
    for(constructor * i=&start_ctors;i!=&end_ctors;i++)
        (*i)();
}

extern "C" void kernelMain(void * multiboot_structure, unsigned int magic_number)
{
    printf("Hello World!");

    //we do not want to exit from the kernel
    while(1);
}

Here is the loader.s :

.set MAGIC, 0x1badb002
.set FLAGS, (1<<0 | 1<<1)
.set CHECKSUM, -(MAGIC + FLAGS)

.section .multiboot
    .long MAGIC
    .long FLAGS
    .long CHECKSUM

.section .text
.extern kernelMain
.extern callConstructors
.global loader

loader:
    mov $kernel_stack, %esp
    call callConstructors
    push %eax #AX register has the pointer of multiboot structure stored by bootloader
    push %ebx #BX register has the magic number
    call kernelMain

#double check to not come out of the kernel, creating one more loop
_stop:
    cli
    hlt
    jmp _stop


.section .bss
.space 2*1024*1024 #2MB  for stack to grow towards left side
kernel_stack:

Here is the linker.ld :

ENTRY(loader)
OUTPUT_FORMAT(elf32-i386)
OUTPUT_ARCH(i386:i386)

SECTIONS
{
    . = 0x100000;

    .text :
    {
        *(.multiboot)
        *(.text*)
        *(.rodata)
    }

    .data :
    {
        start_ctors = .;
        KEEP(*(.init_array));
        KEEP(*(SORT_BY_INIT_PRIORITY(.init_array.*)));
        end_ctors = .;

        *(.data)
    }

    .bss :
    {
        *(.bss)
    }

    /DISCARD/ :
    {
        *(.fini_array*)
        *(.comment)
    }
}

My development environment is Linux Mint 18.1 64-bit with virtualbox installed. My code almost matches the code of the tutor in the series, still I am not able to boot in the virtual machine.

EDIT

I tried with qemu-system-i386 -kernel mykernel.bin and it works fine with a message Hello World.That means there is some problem with VirtualBox environment and configurations.


Solution

  • I have no official source for this answer. It is actually based on experience and other questions I have seen on Stackoverflow and some findings I have made.

    It appears if you create large kernel bootstrap stacks in the BSS segment it causes GRUB to crash in some environments and not others. This often happens when the total size of the BSS segment seems to reach about 2mb. Virtualbox seems to be a particular case where the issue seems to arise. Issues in Virtualbox seems to vary depending on the version and the virtual hardware configuration being used.

    The stack you create in your loader.s to bootstrap your C++ environment doesn't need to be all that big. Once you get your memory management and allocators in place you can reserve area for a larger kernel stack and set SS:ESP to it at that time.

    To that end you should consider changing:

    .section .bss
    .space 2*1024*1024 #2MB  for stack to grow towards left side
    kernel_stack:
    

    To something 1mb or smaller. I'd probably go with something like 64kb:

    .section .bss
    .space 64*1024 #64KB  for stack to grow towards left side
    kernel_stack: