Search code examples
assemblyx86bootloaderdiskbios

I am trying to load my kernel from disk using BIOS 13h interrupt but for unknown reasons it fails even though the kernel gets loaded in RAM


I am trying to make a bootloader and when I try to read my kernel from disk it shows:

Reading from disk failed!

I have checked all the registers to the function and they appear to be correct as suggested on wikipedia.

So I checked the memory address where the kernel is supposed to be loaded and found that it has been loaded correctly. Then I removed the error handling and tried to jump to the kernel location but it didn't execute.

So I tried running it on bochs and it does jump to the kernel location and executes that code, but I don't seem to see any results (I run it on qemu and debug on bochs)

Main file

[bits 16]
[org 0x7c00]

%define ENDL 0x0D, 0x0A, 0

start: jmp boot

%include "bootloader/out.asm"
%include "bootloader/disk.asm"

halt:
  hlt
  jmp halt

boot:
  xor ax, ax ;  sets ax to 0

  ; sets segments to 0
  mov ds, ax
  mov es, ax
  mov ss, ax


  mov si, wlcm_msg
  call print

  mov dh, 1
  mov bx, kernel
  call read_kernel
  jmp kernel

  jmp halt


wlcm_msg: db "Booted to 16 bit real mode", ENDL

times 510-($-$$) db 0
dw 0xaa55
kernel: db 0

Disk routines file

;
; @params:
;   dx(dl) - number of sectors to load
;   bx - loaction in ram to store the read data
;
read_kernel:
  pusha
  push dx

  mov ah, 02h 
  mov al, dh ; sectors to read
  mov cl, 02h ; the sector to read 1 is our bootloader

 mov ch, 0
 mov dl, 0
 mov dh, 0


  int 13h
  jc read_err ; if carry flag is set then there is an error

  pop dx
  cmp al, dh  ; sector read count
  jne sector_err

  popa
  ret

read_err:
  mov si, read_err_msg
  call print
  mov dh, ah
  jmp halt

sector_err:
  mov si, sector_err_msg
  call print
  jmp halt


read_err_msg: db "Reading from disk failed!", ENDL
sector_err_msg: db "Incorrect number of sectors to read!", ENDL

Kernel

[bits 16]

%define ENDL 0x0D, 0x0A, 0

start: jmp main

main:

  mov ah, 9
  xor bh, bh
  mov cx, 1
  mov al, 'E'
  int 10h


  cli 
  hlt

Build script

ASM = nasm

BOOT_SRC = bootloader/main.asm
KENREL_SRC = kernel/main.asm
BUILD_DIR = build

.PHONY: all clean create run debug

all: $(BUILD_DIR)/disk.img

$(BUILD_DIR)/disk.img: $(BUILD_DIR)/bootloader.bin $(BUILD_DIR)/kernel.bin
    dd if=/dev/zero of=$@ bs=512 count=2880
    dd if=$< of=$@ bs=512 conv=notrunc seek=0
    dd if=$(word 2,$^) of=$@ bs=512 conv=notrunc seek=1

$(BUILD_DIR)/bootloader.bin: $(BOOT_SRC) create
    $(ASM) -f bin $< -o $@

$(BUILD_DIR)/kernel.bin: $(KENREL_SRC) create
    $(ASM) -f bin $< -o $@

create:
    mkdir -p $(BUILD_DIR)

clean:
    rm -rf $(BUILD_DIR)/*

run: $(BUILD_DIR)/disk.img
    qemu-system-i386 -machine q35 -drive file=$<,format=raw

run_floppy: $(BUILD_DIR)/disk.img
    qemu-system-i386 -machine q35 -fda $<

debug: bochs_config $(BUILD_DIR)/disk.img
    bochs -f $<

I tried lots of ways to fix this but none seem to work, so I am pretty much convinced that this is an issue with my build script or the way I set up segments. I've found many questions with the same problem here, but I didn't really understand how that would apply here.


Solution

  • You have this line in your code:

    mov ss, ax
    

    The SS register is closely linked to the SP register. If SS changes, then the existing SP will 99.9% of the time not make sense in the new segment, so it needs to change too!

    Add a line such as:

    mov sp, 0x7C00
    

    Since your bootloader is located in the region 0x7C00-0x7DFF, placing the stack at 0x7C00 (growing downwards) is the most common choice (but feel free to choose anything else that doesn't conflict with already existing data such as the interrupt table, the BDA or your own code).

    Setting SP should be done in the next instruction after the mov ss, ax instruction, because the CPU disables interrupts briefly after it, so that you have time to set SP without interrupts corrupting the stack (this behavior is implemented on all x86 processors, save for a few old steppings of the ancient 8088, for which you have to enclose the stack pointer change in cli - sti).

    If you already know that the machine you are working with has a 32-bit CPU (meaning you have access to EAX, EBX etc), you can also use the lss sp, [mem] instruction, which loads the SS:SP pair from the memory location in a single instruction. However, it could be best to avoid this in bootloader code because the machine's hardware might still be unknown at this stage in the boot process.