Search code examples
assemblykernelnasmqemubootloader

Kernel not executed, after being loaded


Issue

Kernel seems to be loaded, by the bootloader, but no further execution. It seems like there is a problem either with the way I run the image with qemu or with the way the kernel is loaded.

Source code

makefile

bold=`tput bold`

black=`tput setaf 0`
red=`tput setaf 1`
green=`tput setaf 2`
yellow=`tput setaf 3`
blue=`tput setaf 4`
magenta=`tput setaf 5`
cyan=`tput setaf 6`
white=`tput setaf 7`

reset=`tput sgr0`


default: build run

build:
    @echo "${bold}${green}[INFO]${reset} Compiling Bootloader"
    @nasm -f bin src/bootloader.asm -o bin/boot.bin

    @echo "${bold}${green}[INFO]${reset} Compiling Kernel"
    @nasm -f bin src/kernel.asm -o bin/kernel.bin

    @echo "${bold}${green}[INFO]${reset} Compiling the OS"
    @cat bin/boot.bin bin/kernel.bin > bin/os.bin

    @echo "${bold}${green}[INFO]${reset} Compiled."

run:
    @echo "${bold}${green}[INFO]${reset} Launching the VM:"
    @qemu-system-x86_64 -enable-kvm -drive format=raw,file=bin/os.bin -m 4G -cpu host -smp 2 -vga virtio -display sdl,gl=on

clean:
    @echo "${bold}${green}[INFO]${reset} Cleaning..."
    @rm -rfv bin/*.bin
    @echo "${bold}${green}[INFO]${reset} Cleaned"

helper functions: src/utils/print.asm

print_string_buffer:
    ; The function prints a string from the buffer 
    ; stored in the SI register, it expects a 
    ; null symbol to be found in the buffer.  
    ; Parameters:
    ;   si - memory offset to the buffer 

    push si
    push ax

.loop:
    lodsb
    or al, al
    jz .done

    ; display character
    mov ah, 0x0A
    mov bh, 0x00
    mov cx, 1
    int 0x10

    ; move cursor
    mov ah, 0x02
    inc dl
    int 0x10

    jmp .loop

.done:
    pop  ax
    pop  si
    ret

src/disk.asm

disk_load:
    ; The function reads DH number of sectors
    ; into ES:BX memory location from drive DL
    ; Parameters:
    ;   es:bx - buffer memory address 

    push dx             ; store dx on stack for error handling later 

    mov ah, 0x02        ; INT 13H 02H, BIOS read disk sectors into memory
    mov al, dh          ; number of sectors
    mov ch, 0x00        ; cylinder  
    mov dh, 0x00        ; head
    mov cl, 0x02        ; start reading sector (2 is the sector after the bootloader)
    int 0x13            ; BIOS interrupt for disk functions 

    jc disk_error       ; checks if CF (carry flag) set to 1

    pop dx              ; restore dx value from stack
    cmp dh, al          ; checks dh (number of read sectors) vs al (number of desired read sectors) 
    jne disk_error      ; if not the desired amount of sectors were read, then error 

    call disk_success
    ret                 ; return to caller

disk_error:
    mov si, DISK_ERROR_MESSAGE
    call print_string_buffer
    hlt

disk_success:
    mov si, DISK_SUCCESS_MESSAGE
    call print_string_buffer

    ; move cursor
    mov ah, 0x02
    inc dh
    mov dl, 0
    int 0x10

    ret

DISK_ERROR_MESSAGE: db "could not read from disk", 0
DISK_SUCCESS_MESSAGE: db "successfully loaded the disk", 0

bootloader src/bootloader.asm

org 0x7C00

%define color_black   0
%define color_blue    1
%define color_green   2
%define color_cyan    3
%define color_red     4
%define color_magenta 5
%define color_orange  6
%define color_gray    7
%define color_yellow  14
%define color_white   15

;;;;;;;;
; INIT ;
;;;;;;;;

clear_screen:
    ; Int 0x10
    ; AH = 06h
    ; AL = number of lines by which to scroll up (00h = clear entire window)
    ; BH = attribute used to write blank lines at bottom of window
    ; CH, CL = row, column of window's upper left corner
    ; DH, DL = row, column of window's lower right corner

    mov ax, 0x0600                              ; AH = 6 = Scroll Window Up, AL = 0 = clear window
    mov bh, color_black << 4 | color_magenta    ; Attribute to clear screen with (White on Red)
    xor cx, cx                                  ; Clear window from 0, 0
    mov dx, 25 << 8 | 80                        ; Clear window to 24, 80
    int 0x10                                    ; Clear the screen

    mov ah, 0x02                                ; Set cursor
    mov bh, 0x00                                ; Page 0
    mov dx, 0x00                                ; Row = 0, col = 0
    int 0x10



set_custom_cursor:
    ; Int 0x10
    ; AH = 01h
    ; CH = start scan line of character matrix (0-1fH; 20H=no cursor)
    ; CL = end scan line of character matrix (0-1fH)

    mov ax, 0x0100                              ; AH = 1 = Set Cursor Shape & Size, AL = 0 = nothing
    mov ch, 0x1                                 ; Sets the width of the cursor, the higher the thicker
    mov cl, 0x10                                ; Sets the height of the cursor, the less the higher
    int 0x10

; bootloader start-up message
mov si, start_up_message 
call print_string_buffer


; set up DX for disk loading
mov dh, 0x1       ; number of sectors that will be loaded into memory
mov dl, 0x0       ; drive number to load (0 = boot disk)

; set up ES:BX memory address to load sectors into
mov bx, 0x1000  ; load sector to memory address 0x1000
mov es, bx      ; ES = 0x1000 
mov bx, 0x0       ; ES:BX = 0x1000:0 (segment:offset)

; set up segment registers for RAM
mov ax, 0x1000
mov ds, ax      ; data segment
mov es, ax      ; extra segment
mov fs, ax       
mov gs, ax
mov ss, ax      ; stack segment


;;;;;;;;;;;;;;;;;;;;;;;;;;
; BOOTLOADER ENTRY POINT ; 
;;;;;;;;;;;;;;;;;;;;;;;;;;
call disk_load  ; loads kernel into memory
jmp 0x1000:0x0


;;;;;;;;;;;
; IMPORTS ;
;;;;;;;;;;;

%include "src/utils/print.asm"
%include "src/disk.asm"


;;;;;;;
; VAR ;
;;;;;;;

start_up_message: db "Bootloader bootstrap", 0


;;;;;;;;;;;;;;;;;
; MAGIC NUMBERS ;
;;;;;;;;;;;;;;;;;

times 510-($-$$) db 0
dw 0xAA55

kernel src/kernel.asm

;;;;;;;;;;;;;;;
; ENTRY POINT ;
;;;;;;;;;;;;;;;

start:
    mov si, OS_VERSION 
    call print_string_buffer

    hlt


;;;;;;;;;;;
; IMPORTS ;
;;;;;;;;;;;

%include "src/utils/print.asm"


OS_VERSION: db "OS v1.0", 0

; sector padding
times 512-($-$$) db 0

After running make, the following project's structure is seen:

.
├── bin
│   ├── boot.bin
│   ├── kernel.bin
│   └── os.bin
├── makefile
└── src
    ├── bootloader.asm
    ├── disk.asm
    ├── kernel.asm
    └── utils
        └── print.asm

I tried to make the jump from the bootloader to the kernel at a different location, by setting ES:BX registers to values, like 0x0:0x1000. Also, I increased the memory of the virtual machine to 4GB of RAM. None of the following helped.

Any theories or ideas will be helpful. Thanks.


Update on new findings

Making a floopy image and running it in the VirtualBox seems to work just fine. Whereas the same floopy image still freezes after the execution of the bootloader, not reaching the kernel. Thus the source code should be right, and the issue is with the qemu arguments.

changes of the makefile:

...
build:
    @echo "${bold}${green}[INFO]${reset} Compiling Bootloader"
    @nasm -f bin src/bootloader.asm -o bin/boot.bin

    @echo "${bold}${green}[INFO]${reset} Compiling Kernel"
    @nasm -f bin src/kernel.asm -o bin/kernel.bin

    @echo "${bold}${green}[INFO]${reset} Compiling the OS"
    @cat bin/boot.bin bin/kernel.bin > bin/os.bin

    @echo "${bold}${green}[INFO]${reset} Compiling the Floopy image"
    @dd if=/dev/zero of=bin/floopy.img bs=1024 count=1440
    @dd conv=notrunc if=bin/os.bin of=bin/floopy.img

    @echo "${bold}${green}[INFO]${reset} Compiled."

run:
    @echo "${bold}${green}[INFO]${reset} Launching the VM:"
    @qemu-system-x86_64 -enable-kvm -drive format=raw,file=bin/floopy.img -m 4G -cpu host -smp 2 -vga virtio -display sdl,gl=on
...

Solution

Saving the initial value of the DL register, which was set up by BIOS, before the control was given to the bootloader, solved the issue.

fixed bootloader

org 0x7C00

%define color_black   0
%define color_blue    1
%define color_green   2
%define color_cyan    3
%define color_red     4
%define color_magenta 5
%define color_orange  6
%define color_gray    7
%define color_yellow  14
%define color_white   15

;;;;;;;;
; INIT ;
;;;;;;;;

; save DL (drive number) value from the BIOS
mov [drive_number], dl


clear_screen:
    ; Int 0x10
    ; AH = 06h
    ; AL = number of lines by which to scroll up (00h = clear entire window)
    ; BH = attribute used to write blank lines at bottom of window
    ; CH, CL = row, column of window's upper left corner
    ; DH, DL = row, column of window's lower right corner

    mov ax, 0x0600                              ; AH = 6 = Scroll Window Up, AL = 0 = clear window
    mov bh, color_black << 4 | color_magenta    ; Attribute to clear screen with (White on Red)
    xor cx, cx                                  ; Clear window from 0, 0
    mov dx, 25 << 8 | 80                        ; Clear window to 24, 80
    int 0x10                                    ; Clear the screen

    mov ah, 0x02                                ; Set cursor
    mov bh, 0x00                                ; Page 0
    mov dx, 0x00                                ; Row = 0, col = 0
    int 0x10



set_custom_cursor:
    ; Int 0x10
    ; AH = 01h
    ; CH = start scan line of character matrix (0-1fH; 20H=no cursor)
    ; CL = end scan line of character matrix (0-1fH)

    mov ax, 0x0100                              ; AH = 1 = Set Cursor Shape & Size, AL = 0 = nothing
    mov ch, 0x1                                 ; Sets the width of the cursor, the higher the thicker
    mov cl, 0x10                                ; Sets the height of the cursor, the less the higher
    int 0x10

; bootloader start-up message
print_start_up_message:
    mov si, start_up_message 
    call print_string_buffer

    ; move cursor
    mov ah, 0x02
    inc dh
    mov dl, 0
    int 0x10

; set up DX for disk loading
mov dh, 0x1       ; number of sectors that will be loaded into memory
mov dl, [drive_number]       ; drive number to load (0 = boot disk)

; set up ES:BX memory address to load sectors into
mov bx, 0x1000  ; load sector to memory address 0x1000
mov es, bx      ; ES = 0x1000 
mov bx, 0x0       ; ES:BX = 0x1000:0 (segment:offset)

; set up segment registers for RAM
mov ax, 0x1000
mov ds, ax      ; data segment
mov es, ax      ; extra segment
mov fs, ax       
mov gs, ax
mov ss, ax      ; stack segment


;;;;;;;;;;;;;;;;;;;;;;;;;;
; BOOTLOADER ENTRY POINT ; 
;;;;;;;;;;;;;;;;;;;;;;;;;;
call disk_load  ; loads kernel into memory
jmp 0x1000:0x0


;;;;;;;;;;;
; IMPORTS ;
;;;;;;;;;;;

%include "src/utils/print.asm"
%include "src/disk.asm"


;;;;;;;
; VAR ;
;;;;;;;

start_up_message: db "Bootloader bootstrap", 0
drive_number: resb 8


;;;;;;;;;;;;;;;;;
; MAGIC NUMBERS ;
;;;;;;;;;;;;;;;;;

times 510-($-$$) db 0
dw 0xAA55

Solution

  • mov dl, 0x0       ; drive number to load (0 = boot disk)
    

    You already solved the main issue with the help of @Jester and @MichaelPetch.
    A number of problems still remain:

    • problem 1

      org 0x7C00
      %define color_black   0
      ...
      mov [drive_number], dl
      

      Probably you're accustomed to your emulator (qemu) launching your bootloader with zeroed segment registers. On many systems this will not be the case! If you care about developing robust software then at least add next to your bootloader:

      org  0x7C00
      %define color_black   0
      ...
      xor  ax, ax
      mov  ds, ax
      mov  es, ax
      mov  ss, ax             ; Keep these two together
      mov  sp, 0x7C00         ;  and in this order
      cld                     ; So LODSB functions allright
      mov  [drive_number], dl ; Depends on DS=0
      ...
      
    • problem 2

      mov ax, 0x1000
      mov ds, ax      ; data segment
      mov es, ax      ; extra segment
      mov fs, ax       
      mov gs, ax
      mov ss, ax      ; stack segment
      call disk_load  ; loads kernel into memory
      jmp 0x1000:0x0
      

      Because you setup these segment registers even before loading the kernel via the disk_load procedure, there's no way that the disk_load procedure can display its messages, be it disk_error or disk_success.
      What is even worse is that you change the SS segment register and don't follow with assigning a suitable SP register. For now, this poses no problem because you only load a single sector. But imagine what will happen once your project grows and you load eg. 100 sectors. Then whatever value happens to be in SP would (combined with the new SS) create havoc in the kernel and it would be hard to detect that error...
      In conclusion, change these segment registers and SP in the first lines of the kernel (that for readability also could do with an org 0 on top , even if that setting should happen to be the default).

    • problem 3

      The print_string_buffer procedure relies on DL and DH holding a suitable column and row indication, but when invoked from within disk_load (to show an error or success message) this will not be the case! There's no telling if or where these messages would appear.

    • problem 4

      mov dx, 25 << 8 | 80           ; Clear window to 24, 80
      

      The lower right corner of the 80x25 screen is at (79,24). This instruction should read: mov dx, 24 << 8 | 79.

    • problem 5

      start_up_message: db "Bootloader bootstrap", 0
      drive_number: resb 8
      

      The storage for the drive_number requires just 1 byte. Don't use resb 8 that reserves 8 bytes, but simply write:

      start_up_message: db "Bootloader bootstrap", 0
      drive_number:     db 0