From 8e4a0c9607f496215164be2477631592b8d9d660 Mon Sep 17 00:00:00 2001 From: ImagineHaxing Date: Tue, 25 Aug 2026 18:52:12 +0300 Subject: [PATCH] Add kernel. Link everything --- nob.c | 39 ++++++++++++++++++++ shell.nix | 1 + src/kernel.c | 99 ++++++++++++++++++++++++++++++++++++++++++++++++--- src/linker.ld | 48 +++++++++++++++++++++++++ 4 files changed, 182 insertions(+), 5 deletions(-) diff --git a/nob.c b/nob.c index 21a0004..b2799ad 100644 --- a/nob.c +++ b/nob.c @@ -61,6 +61,40 @@ bool build_c(Nob_Cmd *cmd) { return true; } +bool link_everything(Nob_Cmd *cmd) { + Nob_File_Paths objectFiles = {0}; + nob_da_append(&objectFiles, BUILD_FOLDER "kernel.o"); + nob_da_append(&objectFiles, BUILD_FOLDER "boot.o"); + nob_cmd_append(cmd, + "i686-elf-gcc", + "-T", SRC_FOLDER "linker.ld", + "-o", BUILD_FOLDER "kernel", + "-ffreestanding", + "-O2", + "-nostdlib"); + + for (size_t i = 0; i < objectFiles.count; ++i) { + nob_cmd_append(cmd, objectFiles.items[i]); + } + + nob_cmd_append(cmd, "-lgcc"); + if (!nob_cmd_run(cmd)) + return false; + + return true; +} + +bool check_kernel(Nob_Cmd *cmd) { + nob_cmd_append(cmd, "grub-file", "--is-x86-multiboot", BUILD_FOLDER "kernel"); + if (!nob_cmd_run(cmd)) + return false; + + return true; +} + +bool clean_build() { +} + int main(int argc, char **argv) { NOB_GO_REBUILD_URSELF(argc, argv); @@ -71,10 +105,15 @@ int main(int argc, char **argv) { if (!render_clangd_config()) return 1; + if (!build_assembly(&cmd)) return 1; if (!build_c(&cmd)) return 1; + if (!link_everything(&cmd)) + return 1; + if (!check_kernel(&cmd)) + return 1; return 0; } diff --git a/shell.nix b/shell.nix index 4d635d9..fc7ed7c 100644 --- a/shell.nix +++ b/shell.nix @@ -6,6 +6,7 @@ pkgs.mkShell { pkgsCross.i686-embedded.buildPackages.gcc pkgsCross.i686-embedded.buildPackages.binutils gdb + grub2 ]; shellHook = '' diff --git a/src/kernel.c b/src/kernel.c index 5e312c6..40abccb 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -2,15 +2,104 @@ #include #include +/* Check if the compiler thinks you are targeting the wrong operating system. */ #if defined(__linux__) -#error "Not using a cross compiler" +#error "You are not using a cross-compiler, you will most certainly run into trouble" #endif +/* This tutorial will only work for the 32-bit ix86 targets. */ #if !defined(__i386__) -#error "Not using the right compiler" +#error "This tutorial needs to be compiled with a ix86-elf compiler" #endif -int main() { - return 1; - return false; +/* Hardware text mode color constants. */ +enum vga_color { + VGA_COLOR_BLACK = 0, + VGA_COLOR_BLUE = 1, + VGA_COLOR_GREEN = 2, + VGA_COLOR_CYAN = 3, + VGA_COLOR_RED = 4, + VGA_COLOR_MAGENTA = 5, + VGA_COLOR_BROWN = 6, + VGA_COLOR_LIGHT_GREY = 7, + VGA_COLOR_DARK_GREY = 8, + VGA_COLOR_LIGHT_BLUE = 9, + VGA_COLOR_LIGHT_GREEN = 10, + VGA_COLOR_LIGHT_CYAN = 11, + VGA_COLOR_LIGHT_RED = 12, + VGA_COLOR_LIGHT_MAGENTA = 13, + VGA_COLOR_LIGHT_BROWN = 14, + VGA_COLOR_WHITE = 15, +}; + +static inline uint8_t vga_entry_color(enum vga_color fg, enum vga_color bg) { + return fg | bg << 4; +} + +static inline uint16_t vga_entry(unsigned char uc, uint8_t color) { + return (uint16_t)uc | (uint16_t)color << 8; +} + +size_t strlen(const char *str) { + size_t len = 0; + while (str[len]) + len++; + return len; +} + +#define VGA_WIDTH 80 +#define VGA_HEIGHT 25 +#define VGA_MEMORY 0xB8000 + +size_t terminal_row; +size_t terminal_column; +uint8_t terminal_color; +uint16_t *terminal_buffer = (uint16_t *)VGA_MEMORY; + +void terminal_initialize(void) { + terminal_row = 0; + terminal_column = 0; + terminal_color = vga_entry_color(VGA_COLOR_LIGHT_GREY, VGA_COLOR_BLACK); + + for (size_t y = 0; y < VGA_HEIGHT; y++) { + for (size_t x = 0; x < VGA_WIDTH; x++) { + const size_t index = y * VGA_WIDTH + x; + terminal_buffer[index] = vga_entry(' ', terminal_color); + } + } +} + +void terminal_setcolor(uint8_t color) { + terminal_color = color; +} + +void terminal_putentryat(char c, uint8_t color, size_t x, size_t y) { + const size_t index = y * VGA_WIDTH + x; + terminal_buffer[index] = vga_entry(c, color); +} + +void terminal_putchar(char c) { + terminal_putentryat(c, terminal_color, terminal_column, terminal_row); + if (++terminal_column == VGA_WIDTH) { + terminal_column = 0; + if (++terminal_row == VGA_HEIGHT) + terminal_row = 0; + } +} + +void terminal_write(const char *data, size_t size) { + for (size_t i = 0; i < size; i++) + terminal_putchar(data[i]); +} + +void terminal_writestring(const char *data) { + terminal_write(data, strlen(data)); +} + +void kernel_main(void) { + /* Initialize terminal interface */ + terminal_initialize(); + + /* Newline support is left as an exercise. */ + terminal_writestring("Hello, kernel World!\n"); } diff --git a/src/linker.ld b/src/linker.ld index e69de29..620793f 100644 --- a/src/linker.ld +++ b/src/linker.ld @@ -0,0 +1,48 @@ +ENTRY(_start) + +SECTIONS +{ + /* It used to be universally recommended to use 1M as a start offset, + as it was effectively guaranteed to be available under BIOS systems. + However, UEFI has made things more complicated, and experimental data + strongly suggests that 2M is a safer place to load. In 2016, a new + feature was introduced to the multiboot2 spec to inform bootloaders + that a kernel can be loaded anywhere within a range of addresses and + will be able to relocate itself to run from such a loader-selected + address, in order to give the loader freedom in selecting a span of + memory which is verified to be available by the firmware, in order to + work around this issue. This does not use that feature, so 2M was + chosen as a safer option than the traditional 1M. */ + . = 2M; + + /* First put the multiboot header, as it is required to be put very early + in the image or the bootloader won't recognize the file format. + Next we'll put the .text section. */ + .text BLOCK(4K) : ALIGN(4K) + { + *(.multiboot) + *(.text) + } + + /* Read-only data. */ + .rodata BLOCK(4K) : ALIGN(4K) + { + *(.rodata) + } + + /* Read-write data (initialized) */ + .data BLOCK(4K) : ALIGN(4K) + { + *(.data) + } + + /* Read-write data (uninitialized) and stack */ + .bss BLOCK(4K) : ALIGN(4K) + { + *(COMMON) + *(.bss) + } + + /* The compiler may produce other sections, by default it will put them in + a segment with the same name. Simply add stuff here as needed. */ +}