From b7156335cd5d57d73c9a6af21c8ac48c68738982 Mon Sep 17 00:00:00 2001 From: garrettmills Date: Sat, 21 Aug 2021 20:32:20 -0500 Subject: [PATCH] Big bang --- .gitignore | 2 ++ Makefile | 54 +++++++++++++++++++++++++++++ asm/boot.asm | 8 +++++ asm/boot/bootable.asm | 10 ++++++ asm/boot/disk16.asm | 27 +++++++++++++++ asm/boot/gdt16.asm | 38 ++++++++++++++++++++ asm/boot/header.asm | 27 +++++++++++++++ asm/boot/kern16.asm | 19 ++++++++++ asm/boot/main16.asm | 20 +++++++++++ asm/boot/main32.asm | 11 ++++++ asm/boot/print16.asm | 76 ++++++++++++++++++++++++++++++++++++++++ asm/boot/print32.asm | 80 +++++++++++++++++++++++++++++++++++++++++++ asm/boot/switch16.asm | 30 ++++++++++++++++ src/kernel.cpp | 8 +++++ 14 files changed, 410 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 asm/boot.asm create mode 100644 asm/boot/bootable.asm create mode 100644 asm/boot/disk16.asm create mode 100644 asm/boot/gdt16.asm create mode 100644 asm/boot/header.asm create mode 100644 asm/boot/kern16.asm create mode 100644 asm/boot/main16.asm create mode 100644 asm/boot/main32.asm create mode 100644 asm/boot/print16.asm create mode 100644 asm/boot/print32.asm create mode 100644 asm/boot/switch16.asm create mode 100644 src/kernel.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a83bb87 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/ +.idea/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..39d2038 --- /dev/null +++ b/Makefile @@ -0,0 +1,54 @@ +BUILD_DIR ?= ./build +SRC_DIRS ?= ./src +BOOTLOADER_DIR ?= ./asm/boot + +SRCS := $(shell find $(SRC_DIRS) -name *.cpp -or -name *.c -or -name *.s) +OBJS := $(SRCS:%=$(BUILD_DIR)/%.o) +DEPS := $(OBJS:.o=.d) + +INC_DIRS := $(shell find $(SRC_DIRS) -type d) +INC_FLAGS := $(addprefix -I,$(INC_DIRS)) + +CPPFLAGS ?= $(INC_FLAGS) -MMD -MP -g -std=c++11 -Wall + +# Default makefile target +all: os-image + +run: os-image + qemu-system-x86_64 -hda build/os-image.bin + +# Builds a bootable x86 image that loads the kernel +os-image: $(BUILD_DIR)/kernel.bin $(BUILD_DIR)/bootloader.bin + cat $(BUILD_DIR)/bootloader.bin $(BUILD_DIR)/kernel.bin > build/os-image.bin + +# Builds the binary kernel image with the entry prefix +$(BUILD_DIR)/kernel.bin: $(BUILD_DIR)/kernel_entry.o $(OBJS) + ld -m elf_i386 -o $@ -Ttext 0x1000 --oformat binary $(BUILD_DIR)/kernel_entry.o $(OBJS) + +# Builds the x86 -> 32-bit bootloader +$(BUILD_DIR)/bootloader.bin: $(BOOTLOADER_DIR)/main16.asm + cd $(BOOTLOADER_DIR); nasm -f bin -o ../../build/bootloader.bin main16.asm + +# Builds the kernel entry object file +$(BUILD_DIR)/kernel_entry.o: asm/boot.asm + $(MKDIR_P) $(dir $@) + nasm asm/boot.asm -f elf -o $@ + +# Build C++ sources +$(BUILD_DIR)/%.cpp.o: %.cpp + $(MKDIR_P) $(dir $@) + g++ $(CPPFLAGS) $(CXXFLAGS) -m32 -ffreestanding -c $< -o $@ -fno-pie + +# Build C sources +$(BUILD_DIR)/%.c.o: %.c + $(MKDIR_P) $(dir $@) + gcc $(CFLAGS) $(CXXFLAGS) -m32 -ffreestanding -c $< -o $@ -fno-pie + +# Clean all build objects +.PHONY: clean +clean: + $(RM) -rf $(BUILD_DIR) + +-include $(DEPS) + +MKDIR_P ?= mkdir -p diff --git a/asm/boot.asm b/asm/boot.asm new file mode 100644 index 0000000..407a3e8 --- /dev/null +++ b/asm/boot.asm @@ -0,0 +1,8 @@ +; NOT part of the bootloader +; this is the kernel entry code that calls main() in kernel.cpp + +[bits 32] ; we're in 32-bit protected mode at this point +[extern main] ; going to call main in kernel.c + +call main +jmp $ \ No newline at end of file diff --git a/asm/boot/bootable.asm b/asm/boot/bootable.asm new file mode 100644 index 0000000..c49526a --- /dev/null +++ b/asm/boot/bootable.asm @@ -0,0 +1,10 @@ +[bits 16] + +; Catch end-of-execution in an infinite loop +g_infinite_loop: + jmp g_infinite_loop + +; Fill in the remaining space with 00s and set the +; magic bit to make the bios boot the sector. +times 510-($-$$) db 0 +dw 0xaa55 diff --git a/asm/boot/disk16.asm b/asm/boot/disk16.asm new file mode 100644 index 0000000..188e207 --- /dev/null +++ b/asm/boot/disk16.asm @@ -0,0 +1,27 @@ +[bits 16] + +i_disk16_error_message: db "Disk read error!", 0 + +disk16_load: + push dx ; keep track of the # of sectors requested to be read + + mov ah, 0x02 ; bios read sector + mov al, dh ; read dh # sectors + mov ch, 0x00 ; select cylinder 0 + mov dh, 0x00 ; select head 0 + mov cl, 0x02 ; start reading from 2nd sector + ; (i.e. the sector after the boot sector) + + int 0x13 ; bios interrupt to trigger read + + jc disk16_on_error ; jump if carry (fault) flag was set + + pop dx ; restore original # of registers in case it was modified + cmp dh, al ; compare # sectors read (al) to # sectors expected (dh) + jne disk16_on_error ; error if # sectors differs + ret + +disk16_on_error: + mov bx, i_disk16_error_message + call print16 + jmp g_infinite_loop diff --git a/asm/boot/gdt16.asm b/asm/boot/gdt16.asm new file mode 100644 index 0000000..7fff472 --- /dev/null +++ b/asm/boot/gdt16.asm @@ -0,0 +1,38 @@ +[bits 16] +; Sets up the GDT for the switch to 32-bit protected mode + +gdt_setup_flat: + gdt_flat_null: ; mandatory null descriptor + dd 0x0 ; dd means define double-word (i.e. 4 bytes) + dd 0x0 + + gdt_flat_code_section: + ; base: 0x0, limit: 0xfffff + ; 1st flags: (present) 1 (privilege) 00 (descriptor type) 1 -> 1001b + ; Type flags: (code) 1 (conforming) 0 (readable) 1 (accessed) 0 -> 1010b + ; 2nd flags: (granularity) 1 (32-bit default) 1 (64-bit segment) 0 (AVL) 0 -> 1100b + dw 0xffff ; limit (bits 0 - 15) + dw 0x0 ; base (bits 0 - 15) + db 0x0 ; base (bits 16 - 23) + db 10011010b ; 1st flags, type flags + db 11001111b ; 2nd flags, limit (bits 16 - 19) + db 0x0 ; base (bits 24 - 31) + + gdt_flat_data_section: + ; Same as code segment except for the type flags: + ; type flags: (code) 0 (expand down) 0 (writable) 1 (accessed) 0 -> 0010b + dw 0xffff ; limit (bits 0 - 15) + dw 0x0 ; base (bits 0 - 15) + db 0x0 ; base (bits 16 - 23) + db 10010010b ; 1st flags, type flags + db 11001111b ; 2nd flags, limit (bits 16 - 19) + db 0x0 ; base (bits 24 - 31) + + gdt_flat_end: ; so the assembler can calculate the size of the gdt section + + gdt_flat_descriptor: + dw gdt_flat_end - gdt_setup_flat - 1 ; size of our GDT, always less 1 of the true size + dd gdt_setup_flat ; start address of the gdt + + i_gdt_code_segment equ gdt_flat_code_section - gdt_setup_flat + i_gdt_data_segment equ gdt_flat_data_section - gdt_setup_flat diff --git a/asm/boot/header.asm b/asm/boot/header.asm new file mode 100644 index 0000000..d5bb865 --- /dev/null +++ b/asm/boot/header.asm @@ -0,0 +1,27 @@ +; this offsets all memory addresses in the asm code +; to be relative to the start of the assembled bytecode +; in memory, where the BIOS loads it. +[org 0x7c00] +[bits 16] + +; store the boot drive - bios puts in dl +mov [i_global_boot_drive], dl + +jmp main16 ; start the main label + +i_global_boot_drive: db 0 + +; Helper label that pops all stack registers and returns +; Used as a one-line return in service routines +; e.g. jmp global_return +g_return: + popa + ret + +; Include common code +%include "print16.asm" +%include "disk16.asm" +%include "gdt16.asm" +%include "switch16.asm" +%include "kern16.asm" +%include "print32.asm" diff --git a/asm/boot/kern16.asm b/asm/boot/kern16.asm new file mode 100644 index 0000000..5cbe307 --- /dev/null +++ b/asm/boot/kern16.asm @@ -0,0 +1,19 @@ +[bits 16] + +; Loads the 32-bit kernel + +i_kernel16_string: db 'Loading 32-bit kernel...', 0 + +i_kernel16_offset equ 0x1000 + +kernel16_load: + pusha + mov bx, i_kernel16_string + call print16 + + mov bx, i_kernel16_offset ; specify the disk load location + mov dh, 15 ; load first 15 sectors + mov dl, [i_global_boot_drive] ; from the boot disk + call disk16_load + + jmp g_return diff --git a/asm/boot/main16.asm b/asm/boot/main16.asm new file mode 100644 index 0000000..2b9a8b6 --- /dev/null +++ b/asm/boot/main16.asm @@ -0,0 +1,20 @@ +[bits 16] +%include "header.asm" + +i_16bit_string: db 'Started 16-bit real mode.', 0 + +main16: + mov bp, 0x9000 ; move the stack out of the way of anything important + mov sp, bp + + mov bx, i_16bit_string + call print16 + + call kernel16_load ; load the kernel + + call switch16_to_32 ; never returns. Calls main32. + jmp g_infinite_loop + + +%include "main32.asm" +%include "bootable.asm" diff --git a/asm/boot/main32.asm b/asm/boot/main32.asm new file mode 100644 index 0000000..cb1a41f --- /dev/null +++ b/asm/boot/main32.asm @@ -0,0 +1,11 @@ +[bits 32] + +i_32bit_string: db 'Switched to 32-bit protected mode.', 0 + +main32: + mov ebx, i_32bit_string + call print32 ; print the 32-bit protected mode message + + call i_kernel16_offset ; jump to the address of the loaded kernel + + jmp g_infinite_loop ; hang diff --git a/asm/boot/print16.asm b/asm/boot/print16.asm new file mode 100644 index 0000000..a8c2124 --- /dev/null +++ b/asm/boot/print16.asm @@ -0,0 +1,76 @@ +[bits 16] + +; Prints \r\n using BIOS teletype output +print16_newline: + push ax + + mov ah, 0x0e ; bios teletype output + mov al, 0x0a ; newline char + int 0x10 + mov al, 0x0d ; carriage return + int 0x10 + + pop ax + ret + +; Prints a null-terminated string beginning in the addr in bx +print16_string: + pusha + + print16_string_char_loop: + mov al, [bx] ; move current value of bx into lower-half of ax + cmp al, 0 ; check if char is null + je g_return ; if so, end of string, so return + + ; otherwise, print the character + mov ah, 0x0e ; bios teletype output + int 0x10 + + ; Now, advance mem addr of bx by 1 to point to next char + add bx, 1 + jmp print16_string_char_loop + +; Calls print16_string then print16_newline +print16: + call print16_string + call print16_newline + ret + +; Template for printing hex strings +i_print16_hex_template: db '0x0000', 0 + +; Prints the value of dx register as a hex-string +print16_hex_string: + pusha + mov cx, 0 ; init looping variable + + print16_hex_string_loop: + cmp cx, 4 ; check if looping is == 4 + je print16_hex_string_end ; if so, break the loop + + mov ax, dx ; copy the hex value to a mutable register + and ax, 0x000f ; mask off all but the last bit + add al, 0x30 ; convert the number to ASCII 0-9 + cmp al, 0x39 ; check if number is 0-9 or a-f + jle print16_hex_string_update_template ; if 0-9, no further adding needed + add al, 39 ; otherwise, a-f so offset to proper ASCII range + + print16_hex_string_update_template: + mov bx, i_print16_hex_template + 5 ; end of ascii template + sub bx, cx ; make bx point to currently-updated char + mov [bx], al ; copy ASCII char we computed above into current position of bx + shr dx, 4 ; shift dx over 1 byte so we can compute next char + + add cx, 1 ; increment looping var + jmp print16_hex_string_loop + + print16_hex_string_end: + mov bx, i_print16_hex_template ; copy the filled-out template addr to bx + call print16_string ; print it out + jmp g_return + +; Calls print16_hex_string then print16_newline +print16_hex: + call print16_hex_string + call print16_newline + ret diff --git a/asm/boot/print32.asm b/asm/boot/print32.asm new file mode 100644 index 0000000..5cbf6c8 --- /dev/null +++ b/asm/boot/print32.asm @@ -0,0 +1,80 @@ +[bits 32] + +; 32-bit protected print helpers +; VGA is 80 x 25 characters starting at 0xb8000 +; 1st byte is ASCII code of char to display, 2nd byte is display info +; Address of particular row/col as: 0xb8000 + 2 * (row * 80 + col) + +; Constants +i_video_memory equ 0xb8000 +i_white_on_black equ 0x0f + +; Variables +i_print32_row_start equ 0xb8000 +i_print32_row_current equ 0xb8000 +i_print32_row_end equ 0xb8002 + 160 + +print32_raw_simple: + ; prints null-terminated string pointed to by edx + pusha + mov edx, i_video_memory ; set edx to start of the row + + print32_raw_simple_loop: + mov al, [ebx] ; store char at ebx in al + mov ah, i_white_on_black ; store display info in ah + + cmp al, 0 ; if al is null, then this is the end of the string + je g_return + + mov [edx], ax ; store char and attributes at current char cell + + add ebx, 1 ; increment ebx to next char in string + add edx, 2 ; move to next char cell in video memory + + jmp print32_raw_simple_loop + + +print32_newline: ; TODO fix this... + pusha + mov eax, i_print32_row_start + mov ebx, i_print32_row_current + mov ecx, i_print32_row_end + add eax, 0xa0 + add ebx, 0xa0 + mov ecx, eax + jmp g_return + + +print32_raw: + ; prints null-terminated string pointed to by edx + pusha + mov edx, i_print32_row_current ; set edx to start of the row + + jmp print32_raw_loop + print32_raw_next_line: + call print32_newline + + print32_raw_loop: + cmp edx, i_print32_row_end + je print32_raw_next_line + + mov al, [ebx] ; store char at ebx in al + mov ah, i_white_on_black ; store display info in ah + + cmp al, 0 ; if al is null, then this is the end of the string + je g_return + + mov [edx], ax ; store char and attributes at current char cell + + add ebx, 1 ; increment ebx to next char in string + add edx, 2 ; move to next char cell in video memory + + jmp print32_raw_loop + + +print32: + call print32_raw + call print32_newline + ret + +[bits 16] diff --git a/asm/boot/switch16.asm b/asm/boot/switch16.asm new file mode 100644 index 0000000..8f135d6 --- /dev/null +++ b/asm/boot/switch16.asm @@ -0,0 +1,30 @@ +[bits 16] + +switch16_to_32: + cli ; disable interrupts until we finish setup, since the + ; 16-bit interrupt vector will be invalid + + lgdt [gdt_flat_descriptor] ; load the GDT which defines the flat code and data segments + + mov eax, cr0 ; move the control register to eax so we can modify it + or eax, 0x1 ; set the 1st bit to enable 32-bit pm + mov cr0, eax ; write the modified control value back + + jmp i_gdt_code_segment:initialize32 ; far-jump to our new 32-bit code, forcing CPU to clear the pipeline of 16-bit instructions + + +[bits 32] + +initialize32: + ; Set up registers and stack once in 32-bit protected mode + mov ax, i_gdt_data_segment ; point the segment registers to the new data segment in 32-bit + mov ds, ax + mov ss, ax + mov es, ax + mov fs, ax + mov gs, ax + + mov ebp, 0x90000 ; update stack position to be at the top of free space + mov esp, ebp + + call main32 diff --git a/src/kernel.cpp b/src/kernel.cpp new file mode 100644 index 0000000..5c111e0 --- /dev/null +++ b/src/kernel.cpp @@ -0,0 +1,8 @@ +// Called by boot.asm +void main() { + // Create pointer to a char and point to the first text cell of VGA + char* video_memory = (char*) 0xb8000; + + // Write the char X to the video memory to display it on the top-left of screen + *video_memory = 'X'; +}