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)
KERNEL_START_ADDRESS
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.0x1000
then the linear address is0x10000
which is 4 zeroes not 5. As a sanity check, obviously it can not be0x7C00 + 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).int13/02
expectses:bx
so you should setes
toKERNEL_LOAD_SEG
and zerobx
. With all of these fixed it works.