1

I'm getting into OS Develop and recently finished writing my bootloader, now I started to make the kernel. The first thing I've decided to implement was writing text from my kernel_main.cpp (using VGA memory at 0xB8000). After finishing all of the print functions and running my project the character it was supposed to display didn't came up. I debugged it and found out that it could print from boot.asm (my main bootloader code), but after doing a far jump to 0x100000 it wouldn't work anymore.

boot.asm:

; Starting in real mode
[BITS 16]

[ORG 0x7C00] ; BIOS will load the bootloader to this address


; Addresses and offsets
CODE_SEG_OFFSET equ 0x08
DATA_SEG_OFFSET equ 0x10
VGA_SEG_OFFSET equ 0x18

KERNEL_LOAD_SEG equ 0x1000
KERNEL_START_ADDRESS equ 0x100000 ; 0x7C00 + 1 MiB


start:
    cli ; Dissabling interferences

    ; Setting up segment registers and stack pointer
    mov ax, 0x00
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0x7c00

    sti ; Enabling interferences


    ; Set VGA text mode (80x25) to ensure control over the screen
    mov ah, 0x00
    mov al, 0x03    ; 80x25 text mode
    int 0x10

    ; Loading the Global Descriptor Table
    lgdt [gdt_descriptor]

    ; Loading the kernel using CHS
    mov bx, KERNEL_LOAD_SEG
    mov dh, 0x00
    mov dl, 0x80
    mov cl, 0x02
    mov ch, 0x00
    mov ah, 0x02
    mov al, 8
    int 0x13

    jc disk_read_error ; Managing disk read errors


    ; Checking the first few bytes of the kernel in memory
    ; mov bx, KERNEL_LOAD_SEG        ; Load segment where kernel is loaded
    ; mov ds, bx                     ; Set data segment to point to kernel

    ; mov eax, [0x1000]               ; Load the first word from kernel (segment 0x1000)
    ; cmp eax, 0xDEADBEE1             ; Magic number
    ; jne kernel_load_error          ; If not matching, jump to the failure handler


load_protected_mode:
    cli ; Dissabling interferences

    ; Switching to protected mode
    mov eax, cr0
    or eax, 1
    mov cr0, eax

    ; Far jump
    jmp CODE_SEG_OFFSET:protected_mode_main



; Error labels
disk_read_error:
    mov eax, 0xB8000         ; VGA buffer address
    mov byte [eax], 'D'      ; Display 'D' for disk
    mov byte [eax + 1], 0x4F ; Red background with white text

    jmp $


kernel_load_error:
    mov eax, 0xB8000               ; VGA buffer address
    mov byte [eax], 'K'            ; Display 'K' for kernel
    mov byte [eax + 1], 0x4F       ; Red background with white text

    jmp $ 


; Global Descriptor Table
gdt_table:
    ; Null descriptor
    dd 0x0
    dd 0x0

    ; Code segment descriptor
    dw 0xFFFF ; Limit
    dw 0x0000 ; Base address low
    db 0x00 ; Base address middle
    db 10011010b ; Defining access rights of the memory segment
    db 11001111b ; Defining flags
    db 0x00 ; Base high

    ; Data segment descriptor
    dw 0xFFFF ; Limit
    dw 0x0000 ; Base address low
    db 0x00 ; Base address middle
    db 10010010b ; Defining access rights of the memory segment
    db 11001111b ; Defining flags
    db 0x00 ; Base high

    ; VGA text mode segment descriptor
    dw 0x0FFF ; Limit
    dw 0x8000 ; Base address low
    db 0x0B ; Base address middle
    db 10010010b ; Defining access rights of the memory segment
    db 11001111b ; Defining flags
    db 0x00 ; Base high

gdt_end:
    ; For calculating GDT size

gdt_descriptor:
    dw gdt_end - gdt_table - 1 
    dd gdt_table




[BITS 32] ; 32 bit protected mode

protected_mode_main:
    ; Setting up segment registers
    mov ax, DATA_SEG_OFFSET
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov ss, ax
    mov gs, ax

    mov ebp, 0x9C00
    mov esp, ebp

    ; Set VGA segment descriptor for ES to access VGA memory at 0xB8000
    mov ax, VGA_SEG_OFFSET
    mov es, ax   

    mov eax, 0xB8000
    mov byte [eax], '+'
    mov byte [eax + 1], 0x4F
    

    ; Far jump to kernel_jump.asm
    jmp CODE_SEG_OFFSET:KERNEL_START_ADDRESS

    
; Filling remaining space with 0
times 510 - ($ - $$) db 0
dw 0xAA55

kernel_jump.asm (This is loaded to 0x100000 and make a jump to kernel_main.cpp):

[BITS 32] ; We are in protected mode

global _start 

extern kernel_main ; External symbol of kernel_main.cpp

_start:
    ; Setting up VGA segment
    mov ax, 0x18
    mov es, ax


    ; Print directly to VGA buffer in protected mode
    mov eax, 0xB8000
    mov byte [eax], 'C'        ; Character
    mov byte [eax + 1], 0x4F       ; Attribute byte


    call kernel_main ; Connecting to kernel_main.cpp (void kernel_main())

    jmp $ ; Infinite loop
    
times 512 - ($ - $$) db 0 ; Filling remainder of 512 bytes with 0

Linker

ENTRY(_start)
OUTPUT_FORMAT(binary) /* .bin */

SECTIONS
{
    . = 0x100000; /* 1 MiB (beyond the real mode) */

    .text : ALIGN(4096)
    {
        *(.text)
    }

    .rodata : ALIGN(4096)
    {
        *(.rodata)
    }

    .data : ALIGN(4096)
    {
        *(.data)
    }

    .bss : ALIGN(4096)
    {
        *(COMMON)
        *(.bss)
    }
}

Print function in cpp

// Adding new line
void print_newline() {
    col = 0;

    // Only adding new line if possible
    if(row < NUM_ROWS - 1) {
        ++row;
    } else {
        // Scrolling the screen up

        for(size_t r = 1; r < NUM_ROWS; ++r) {
            for(size_t c = 0; c < NUM_COLS; ++c) {
                // Copying this row to the one above
                buffer[c + NUM_COLS * (r - 1)] = buffer[c + NUM_COLS * r];
            }   
        }

        clear_row(NUM_ROWS - 1);
    }
}

P.S.: Writing from protected and real mode both work in boot.asm, but not in kernel_jump.asm or in cpp.

I've been debugging, checking my code and trying to figure this out for the past week. Everything seems to be up in code.

After running GDB it successfully makes it to both kernel_main and _start(in kernel_jump.asm).

My Makefile just in case:

# ==========================================
# Makefile
# For building the project
#
# Author: Ioane Baidoshvili
# Date: 02-Sep-24
# ==========================================

# Directories
BOOTLOADER_DIR = src/bootloader
KERNEL_DIR = src/kernel

BIN = $(CURDIR)/bin
BUILD = $(CURDIR)/build

# HPP include directories
INCLUDE = -I src/include

# Tools
ASM = nasm
LD = ld 

CXX = i686-elf-g++
CXX_FLAGS = $(INCLUDE) -g -Wall -O2 -ffreestanding -fno-exceptions -fno-rtti

ELF_LD = i686-elf-ld
ELF_LD_FLAGS = -g -relocatable

GCC = i686-elf-gcc 
GCC_FLAGS = -g -ffreestanding -nostdlib -nostartfiles -nodefaultlibs -Wall -O0 -Iinc

# Output files
OS_BIN = $(BIN)/os.bin # Linked binary file
BOOT_BIN = $(BIN)/boot.bin
KERNEL_JUMP_BIN = $(BIN)/kernel_jump.bin

KERNEL_JUMP_OBJ = $(BUILD)/kernel_jump.o
KERNEL_OBJ = $(BUILD)/kernel_main.o 
LINKED_OBJ = $(BUILD)/linkedKernel.o
PRINT_OBJ = $(BUILD)/print.o

all:

# Compiling ASM files
    $(ASM) -f bin $(BOOTLOADER_DIR)/boot.asm -o $(BOOT_BIN)
    $(ASM) -f elf -g $(BOOTLOADER_DIR)/kernel_jump.asm -o $(KERNEL_JUMP_OBJ)

# Compiling CPP files
    $(CXX) -I./src $(CXX_FLAGS) -std=c++23 -c $(KERNEL_DIR)/kernel_main.cpp -o $(KERNEL_OBJ)
    $(CXX) -I./src $(CXX_FLAGS) -std=c++23 -c $(KERNEL_DIR)/drivers_src/print_src/print.cpp -o $(PRINT_OBJ)

# Merging kernel_jump.o with kernel_main.o
    $(ELF_LD) $(ELF_LD_FLAGS) $(KERNEL_JUMP_OBJ) $(KERNEL_OBJ) $(PRINT_OBJ) -o $(LINKED_OBJ)

# Making final product
    $(GCC) $(GCC_FLAGS) -T tools/linker.ld -o $(KERNEL_JUMP_BIN) -ffreestanding -O0 -nostdlib $(LINKED_OBJ)

# Polishing result
    dd if=$(BOOT_BIN) >> $(OS_BIN)
    dd if=$(KERNEL_JUMP_BIN) >> $(OS_BIN)
    dd if=/dev/zero bs=512 count=8 >> $(OS_BIN)


# Cleaning the build
clean:
    rm -f $(OS_BIN)
    rm -f $(BOOT_BIN) 
    rm -f $(KERNEL_JUMP_BIN)

    rm -f $(KERNEL_JUMP_OBJ) 
    rm -f $(KERNEL_OBJ) 
    rm -f $(LINKED_OBJ) 
    rm -f $(PRINT_OBJ) 
8
  • I don't see you writing anything to the address given in KERNEL_START_ADDRESS
    – ecm
    Commented Sep 21 at 9:51
  • What do you mean by writing, can you explain. Isn't KERNEL_START_ADDRESS a location in memory? I'm loading the kernel at 0x100000 Commented Sep 21 at 9:58
  • No, you're loading something at 0:KERNEL_LOAD_SEG which is linear 01000h. (The equate is also confusingly named because you don't use it as a segment.) To load something to KERNEL_START_ADDRESS you would need to relocate your code to there, after entering Protected Mode. Setting something in the linker script just tells the linker where it's supposed to be executed at. You have to put it there somehow separately.
    – ecm
    Commented Sep 21 at 10:01
  • 1
    If you load to segment 0x1000 then the linear address is 0x10000 which is 4 zeroes not 5. As a sanity check, obviously it can not be 0x7C00 + 1 MiB because you can't access that memory in real mode so you can't possibly have your kernel loaded there (unless you copy it once in PM or your segment base is not zero).
    – Jester
    Commented Sep 21 at 11:43
  • 1
    Also int13/02 expects es:bx so you should set es toKERNEL_LOAD_SEG and zero bx. With all of these fixed it works.
    – Jester
    Commented Sep 21 at 12:02

0

Browse other questions tagged or ask your own question.