From aa4f5f070b6c603c3d658998cebc41b99f1bb450 Mon Sep 17 00:00:00 2001 From: lordtet Date: Fri, 23 May 2025 01:18:12 -0400 Subject: [PATCH] First content commit. Added sample code hello world with personal comments for study Also made a makefile that can build and run the thing. Current run method is directly from QEMU - but the makefile will later make an image with a given bootloader. --- Makefile | 42 ++++++++++++++++++++++++++++++++ src/linker.ld | 37 ++++++++++++++++++++++++++++ src/main.c | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/start.s | 48 ++++++++++++++++++++++++++++++++++++ 4 files changed, 194 insertions(+) create mode 100644 Makefile create mode 100644 src/linker.ld create mode 100644 src/main.c create mode 100644 src/start.s diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..acb14ec --- /dev/null +++ b/Makefile @@ -0,0 +1,42 @@ +# Makefile + +#Some definitions +SRC_DIR = src +BUILD_DIR = build + +KERNEL_MAIN = $(SRC_DIR)/main.c +KERNEL_MAIN_OBJ = $(BUILD_DIR)/main.o + +ENTRY_ASM = $(SRC_DIR)/start.s +ENTRY_ASM_OBJ = $(BUILD_DIR)/start.o + +LINKING_RECIPE = $(SRC_DIR)/linker.ld + +KERNEL_IMG = $(BUILD_DIR)/mykernel.elf + +QEMU = qemu-system-i386 +GCC = i686-elf-gcc + + +#Actual recipe +all: $(KERNEL_IMG) + +$(BUILD_DIR): + mkdir -p $(BUILD_DIR) + +$(KERNEL_MAIN_OBJ): + $(GCC) -std=gnu99 -ffreestanding -g -c $(KERNEL_MAIN) -o $(BUILD_DIR)/main.o + +$(ENTRY_ASM_OBJ): + $(GCC) -std=gnu99 -ffreestanding -g -c $(ENTRY_ASM) -o $(ENTRY_ASM_OBJ) + +#now kith (link) +$(KERNEL_IMG): $(KERNEL_MAIN_OBJ) $(ENTRY_ASM_OBJ) + $(GCC) -ffreestanding -nostdlib -g -T $(LINKING_RECIPE) $(ENTRY_ASM_OBJ) $(KERNEL_MAIN_OBJ) -o $(KERNEL_IMG) -lgcc + +run: all + $(QEMU) -kernel $(KERNEL_IMG) + +clean: + rm -rf $(BUILD_DIR)/* + diff --git a/src/linker.ld b/src/linker.ld new file mode 100644 index 0000000..4a782f2 --- /dev/null +++ b/src/linker.ld @@ -0,0 +1,37 @@ +/*We have to write out this linker script so the linker knows where to put all the soup of stuff we put in start.s and main.c. i'll write out reasoning as i go. */ +/*letting em know our entry point is actually label "start" in start.s, and not some function in the c file.*/ +ENTRY(start) +SECTIONS +{ + /*start me at 1Mb because below that is x86 essential stuff, which we dont want to be written on top of.*/ + . = 2M; + + /* we're going to maintain 4K alignment - apparently useful for paging, and i'm not complaining about the lost space anyway. */ + + /*our multiboot header from start.s - has to go at the beginning of the executable so the bootloader knows we're loadable.*/ + /*executable section*/ + .text BLOCK(4K) : ALIGN(4K) + { + *(.multiboot) + *(.text) + } + + /*general read only data*/ + .rodata BLOCK(4K) : ALIGN(4K) + { + *(.rodata*) + } + + /*initialized rw data. */ + .data BLOCK(4K) : ALIGN(4K) + { + *(.data) + } + + /*uninitialized data, and our stack as defined in start.s*/ + .bss BLOCK(4K) : ALIGN(4K) + { + *(COMMON) + *(.bss) + } +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..09509d9 --- /dev/null +++ b/src/main.c @@ -0,0 +1,67 @@ +// Reworked sample code from OSDev! Stripped down some of the extra stuff to leave as an exercise. +//Headers provided by GCC :). No need for libs! We don't even have those yet. +#include +#include + + +//VGA buffer is the easiest way to write to the screen at this stage. it's just a massive array with two datapoints: what color, and what character? +//array starts at the absolute address 0xB8000 +volatile uint16_t* vga_buffer = (uint16_t*)0xB8000; +//the VGA buffer is 80x25 to represent the screen. +const int GRID_COLS = 80; +const int GRID_ROWS = 25; + + +//grid is top left origin. This is our cursor! +int cursor_col = 0; +int cursor_row = 0; +uint16_t term_color = 0x0F00; // Black background, White foreground + + +//wipe the screen +void term_clear() { + for (int col = 0; col < GRID_COLS; col ++) { + for (int row = 0; row < GRID_ROWS; row ++) { + //works out to iterating every cell + const size_t index = (GRID_COLS * row) + col; + //vga buffer looks something like xxxxyyyyzzzzzzzz + //x=bg color + //y=fg color + //c=character to use + //Therefore, to write, we just take our color data and tack on the character to the end. + vga_buffer[index] = term_color | ' '; //blank out + } + } +} + +// +void term_printc(char c) +{ + const size_t index = (GRID_COLS * cursor_row) + cursor_col; //where am i puttin it + vga_buffer[index] = term_color | c; //put it there by putting color+char into that spot in the array + cursor_col++; //next time put it in the next spot. +} + +//print a string! +void term_println(const char* out) +{ + for (int i = 0; out[i] != '\0'; i++) + term_printc(out[i]); + //go to the next line for a println func. + cursor_col = 0; + cursor_row++; +} + + +//finally, main. +void kern_main() +{ + + //wipe the screen + term_clear(); + + //IT IS TIME. TO PRINT. + term_println("hello my chungus world"); + term_println(":100:"); +} + diff --git a/src/start.s b/src/start.s new file mode 100644 index 0000000..8643d4e --- /dev/null +++ b/src/start.s @@ -0,0 +1,48 @@ +//C main function for our kernel +//See: kernel.c +.extern kern_main + +//This will be our entrypoint function name - gotta initialize it now as global so the linker knows later. +.global start + + +//multiboot for GRUB to boot it. Ideally stage01_bootloader will be able to support the multiboot standard. +//regardless of who's doing it, we have to set the required stuff +.set MB_MAGIC, 0x1BADB002 // bytes that bootloader will use to find this place +.set MB_FLAGS, (1 << 0) | (1 << 1) // flags request the following from the bootloader: maintain page boundaries + provide a memory map +.set MB_CHECKSUM, (0 - (MB_MAGIC + MB_FLAGS)) // Fails if checksum doesn't pass. Kind of arbitrary, but required. + + +//Now we actually place the multiboot stuff into the resulting executable... +.section .multiboot + .align 4 // 4 byte alignment + .long MB_MAGIC + .long MB_FLAGS + .long MB_CHECKSUM + +// Set up for C code. Practically the only requirement for C-generated assembly to work properly is alignment and the presence of a stack. +.section .bss + .align 16 + stack_bottom: + .skip 4096 // 4096 bytes (4kb) large stack. by skipping some amount of data (and eventually filling it with zeroes?), we've essentially just reserved space for our stack. + //Remember, stack grows DOWNWARD! So the last thing in the section -> the highest memory address -> the very first thing on the stack! + //Therefore, we put a label here to represent the top of our stack for later. + stack_top: + + +//Actual code. Entry point goes here! +.section .text + //Here it is! + start: + //Lets set up the stack. Stack grows downward on x86. We did the work earlier of defining where the top of the stack is, so just tell esp. + mov $stack_top, %esp //set the stack pointer + + //To C-land! + call kern_main + + //You should never get here, but in case you do, we will just hang. + hang: + cli //Interrupts: off + hlt //Halt! + jmp hang //just in case... +