assemblyx86kernelbootloaderosdev

Kernel message not displaying in custom OS: Bootloader or Kernel issue?


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:

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:

  1. 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.
  2. Why not push dx?
    • This is because dh serves as a return value in this context.

To work around these constraints, I took this approach:

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:

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.


Solution

  • Update:

    Below is Your code:

    1. I added few xor dx,dx here and there
    2. mul word bx should be div word bx
    3. Changed in kernel.asm first line to old value 0x7e00.
    4. push and pop instructions do nothing special. You can remove them.
    5. I don't know if I understand conversion LBA -> CHS correctly. What take into account? Values from ax or dx?

    Source:
    https://datacadamia.com/io/drive/lba
    https://en.wikipedia.org/wiki/Logical_block_addressing

    But code seems to work fine!

    Result is:

    enter image description here

    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                                   ;prev value 0x7b00
    _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
        
    ;(* 1 *)
         ;; before call to LBA_to_CHS
         ;; ax = 0x07c0
         ;; bx = unknown value, could be anything from 16-bit range (0x0000 - 0xFFFF)
         ;; cx = same cx, not initialized
         ;; dx = same here
         
         ;; unknown values could cause unexpected results ;)
         
         ;; Inside LBA_to_CHS You push ax / push bx / mov bl, dl / push bx
         ;; and before return from LBA_to_CHS You pop those values
         ;; So after procedure:
         
         ;; ax = popped value 0x07c0, then al = _kernel_size = 1, ah = 0x07 changed inside read_disk to 0x02
         ;; bx = unknown value but set later to 0x0200
         ;; cx = ch - cylinder, cl - sector
         ;; dx = dh - head, dl - popped value, are you sure this is drive number (0 = A:)
         
        mov si, 1               ;LBA = 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 ax and push bx won't destroy any necessary data 
        push bx                             
    
        mov bl, dl                        ; dl = trash values
    
        push bx                           ; delete this too
        
        ; C = LBA ÷ (HPC × SPT)
        mov ax, [_head_count]               ; ax = 2  
        mul word [_sectors_per_track]         ; ax = 2 * 18 = 36
    
        mov bx, ax                        ; bx = 36 
        mov ax, si                        ; ax = 1  
        xor dx,dx                         ; dx = 0
    ;(* 2 *)
    ; here You have mul word bx so I changed this to div word bx
    ; we divide LBA mod (Heads * Sectors) not mul
        ;mul word bx
        ;should be div word bx
        div word bx                       ; ax = 0 dx = 1
        mov ch, al                        ; Put lower bits of ax as the cylinder address
    ;(* 3 *)    
        ; I used xor dx, dx before div instructions because in 16-bit division dividend is in dx:ax
        ; so we have to clear dx or our result maybe wrong value
        ; H = (LBA ÷ SPT) mod HPC
        xor dx, dx                        ; dx = 0            
        mov ax, si                        ; ax = 1
        div word [_sectors_per_track]     ; 18 ; result in dx:ax, ax = 0 dx = 1
        xor dx, dx
        div word [_head_count]            ; 2  ; result also in dx:ax (dx is remainder) ax = 0 dx = 0
        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                           ; save dx
        ; S = (LBA mod SPT) + 1
        xor dx, dx                        ; dx = 0   
        mov ax, si                        ; ax = 1
        div word [_sectors_per_track]     ; 18 ; remainder in dx, ax = 0 dx = 1 
        inc dx                            ; dx = 2
    
        mov cl, dl      
    
        pop dx  
    
        pop bx                            ; delete
        mov dl, bl                         ; dl = trash
        pop bx                            ; delete
        pop ax                            ; delete
     
        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