I am building an operating system and am experiencing a problem where the bootloader successfully prints its message, but the kernel's message isn't displayed. Here's a breakdown:
Setup:
Assembler and Environment:
- NASM with -f bin option
- Tested on qemu-system-i386
- Environment: Windows Subsystem for Linux (WSL) with Xlaunch (though I suspect this isn't relevant)
Quick Note
push ax
push bx
mov bl, dl ; This is to preserve dl
push bx
This code snippet is part of a bootloader, and it might seem confusing initially.
The requirement here is to preserve the dl register throughout the function. You might ask:
- Why not just
push dl
onto the stack?- The reason is the stack only works with 16-bit values. dl is an 8-bit register, so pushing it directly isn't possible.
- Why not
push dx
?- This is because
dh
serves as a return value in this context.
- This is because
To work around these constraints, I took this approach:
- Pushed bx onto the stack.
- Moved the value of
dl into the lower bits of bx
(i.e., bl). - Pushed bx (
now holding the value of dl
) onto the stack again. - Later, to restore the value, I simply popped bx and then
mov dl, bl
.
Files:
bootloader.asm
[org 0x7c00]
[bits 16]
jmp main
_message_read_err: db 'Error in reading floppy!', 0
_message_boot: db 'Booting SSoS...', 0
_sectors_per_track: dw 18
_head_count: dw 2
_kernel_start_LBA: dw 1
_kernel_size: dw 1
_stack_ptr_addr: dw 0x7c00
_es_start: dw 0x7c0
; Arguments:
; None
; Returns:
; None
main:
mov ax, 0 ; can't write to ds and ss directly
mov ds, ax
mov ss, ax
mov sp, [_stack_ptr_addr] ; stack pointer
mov ax, [_es_start] ; address * 16 = real address -> 0x7c0 * 16 (dec) = 0x7c00
mov es, ax
mov si, _message_boot
call puts
; Converting LBA to CHS
;; Parameters
mov si, 1
call LBA_to_CHS ; after this call, CH, CL, and DH will have cylinder, sector, and head values
mov bx, 0x200
mov al, [_kernel_size] ; number of sectors to read
call read_disk
jmp 0:7e00h
; Arguments:
; si - Points to the string to be printed
; Returns:
; None
puts:
push ax
.begin:
mov ah, 0x0E
lodsb
cmp al, 0
je .done
int 10h
jmp .begin
.done:
pop ax
ret
; Arguments:
; si - LBA adress
; Returns:
; ch, dh, cl - CHS adress
;; dx and cx will have to be modified
LBA_to_CHS:
push ax
push bx
mov bl, dl ; This is to preserve dl
push bx
mov ax, [_head_count]
mul word [_sectors_per_track]
mov bx, ax
mov ax, si
mul word bx
mov ch, al ; Put lower bits of ax as the cylinder address
mov ax, si
div word [_sectors_per_track] ; result in dx:ax
div word [_head_count] ; result also in dx:ax (dx is remainder)
mov dh, dl ; since dx is composed of dh (higher bits) and dl (lower bits) we want to move the lower bits into dh
push dx
mov ax, si
div word [_sectors_per_track] ; remainder in dx
inc dx
mov cl, dl
pop dx
pop bx
mov dl, bl
pop bx
pop ax
ret
; Arguments:
; bx - Address to load the data
; ch, cl, dh - CHS values
; Returns:
; None
read_disk:
push ax
push si
mov si, 4
.retry:
dec si
cmp si, 0
je .read_error
mov ah, 02h
mov al, [_kernel_size]
int 13h
jc .retry
pop si
pop ax
ret
.read_error:
mov si, _message_read_err
call puts
cli
hlt
times 510-($-$$) db 0
dw 0aa55h
kernel.asm
[org 0x7E00]
[bits 16]
jmp main
_message: db 'Hello from kernel!', 0
main:
mov si, _message
call puts
cli
hlt
puts:
mov ah, 0x0E
.begin:
lodsb
cmp al, 0
je .done
int 10h
jmp .begin
.done:
ret
Makefile
BUILD_DIR = build
SRC_DIR = src
IMG_NAME = dev
dev: image
qemu-system-i386 -fda $(BUILD_DIR)/$(IMG_NAME).img
image: $(BUILD_DIR)/bootloader.bin $(BUILD_DIR)/kernel.bin
@dd if=$(BUILD_DIR)/bootloader.bin of=$(BUILD_DIR)/$(IMG_NAME).img bs=512
@dd if=$(BUILD_DIR)/kernel.bin of=$(BUILD_DIR)/$(IMG_NAME).img bs=512 seek=1
@truncate -s 1440k $(BUILD_DIR)/$(IMG_NAME).img
@hexdump -C $(BUILD_DIR)/$(IMG_NAME).img
@objdump -b binary -m i8086 -M intel -D build/dev.img
clean:
rm -f $(BUILD_DIR)/bootloader.bin $(BUILD_DIR)/kernel.bin
$(BUILD_DIR)/%.bin: $(SRC_DIR)/%.asm
nasm $^ -f bin -o $@
Specifics on Constants in bootloader.asm:
- _stack_ptr_addr: Set to 0x7b00 because it's 256 bytes under the bootloader. I believe this is a safe location since the stack grows downwards.
- _es_start: Set to 0x7c0 so the actual physical address (in real mode) equals 0x7c00 — the bootloader's load address. I offset by 0x200 when jumping to the kernel, which matches the kernel's load address.
for clarity shows that the first instruction appears at byte 0x200.
The issue:
Upon booting, the message "Booting SSoS..." is displayed, but the kernel's "Hello from kernel!" message isn't.
Observations & Assumptions:
Someone also had the same problem here. I tried removing the mov dl, 0
in the read_disk function but it still didin't work.
I suspect the issue might be in the read_disk function within the bootloader.
Even though no error message displays, the qemu-system-i386 command seems unusual.
Constants like _sectors_per_track and drive number (register dl in read_disk function) seem accurate. However, even when I attempted different disk-reading methods, the problem was still occurring.
Seeking Help:
I know this is a complex and low-level topic which requires a deep understanding of operating systems. That's why I thank everyone who will try to help me. Thanks a lot!
I tried different methods of reading the floppy disk (like reading directly with CHS adressing) however none of them worked. I also double-checked my constants and looked on the disassebly of the image but everything seems to be right. I looked into others code but mine seems (almost except it's not that perfect) identical.