commit 30e0d6d9f8b553e43ab4b85beb2fa6292bfa43c3 Author: Ole Morud Date: Mon Jul 29 23:59:10 2024 +0200 Add initial barebones kernel and cross compiler container image diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac116c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/ +*.iso diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4d66148 --- /dev/null +++ b/Makefile @@ -0,0 +1,59 @@ + +.SUFFIXES: + +.PHONY: all clean + +SOURCE_DIR := src +BUILD_DIR := build + +CONTAINER_CMD := podman run -v "$(shell pwd)":"/scratch" \ + --workdir="/scratch" \ + -e TERM \ + -t \ + cc-i686:latest + +CC := $(CONTAINER_CMD) i686-elf-gcc +LD := $(CONTAINER_CMD) i686-elf-ld +AS := $(CONTAINER_CMD) i686-elf-as +AR := $(CONTAINER_CMD) i686-elf-ar + +C_SOURCES := $(shell find $(SOURCE_DIR) -name '*.c') +ASM_SOURCES := $(shell find $(SOURCE_DIR) -name '*.S') +OBJECTS := $(patsubst $(SOURCE_DIR)/%, $(BUILD_DIR)/%, $(C_SOURCES:.c=.o) $(ASM_SOURCES:.S=.o)) +DEPENDS := $(patsubst $(SOURCE_DIR)/%, $(BUILD_DIR)/%, $(C_SOURCES:.c=.d)) + +CFLAGS := -MMD -ffreestanding -O0 -Wall -Wextra -Werror -std=c2x -I$(SOURCE_DIR)/include -no-pie -fstack-protector-strong +ASFLAGS := + +$(info C_SOURCES is $(C_SOURCES)) +$(info ASM_SOURCES is $(ASM_SOURCES)) +$(info OBJECTS is $(OBJECTS)) +$(info DEPENDS is $(DEPENDS)) + +all: myos.iso + +run: myos.iso + qemu-system-i386 -cdrom myos.iso + +cross-compiler: cross-compiler-image/Dockerfile + podman build cross-compiler-image -t cc-i686 + +myos.iso: $(BUILD_DIR)/myos.bin + @mkdir -p $(BUILD_DIR)/image/boot/grub + cp -v $< $(BUILD_DIR)/image/boot/ + cp -v grub.cfg $(BUILD_DIR)/image/boot/grub/grub.cfg + grub-mkrescue -o $@ $(BUILD_DIR)/image + +$(BUILD_DIR)/myos.bin: $(OBJECTS) + mkdir -p $(BUILD_DIR) + $(CC) -T linker.ld -o $@ $(CFLAGS) -nostdlib $^ -lgcc + +-include $(DEPENDS) + +$(BUILD_DIR)/%.o: $(SOURCE_DIR)/%.c Makefile + @mkdir -p $(@D) + $(CC) -c $(CFLAGS) $< -o $@ + +$(BUILD_DIR)/%.o: $(SOURCE_DIR)/%.S Makefile + @mkdir -p $(@D) + $(AS) $(ASFLAGS) $< -o $@ diff --git a/cross-compiler-image/Dockerfile b/cross-compiler-image/Dockerfile new file mode 100644 index 0000000..9e57d2e --- /dev/null +++ b/cross-compiler-image/Dockerfile @@ -0,0 +1,67 @@ + +FROM ubuntu:latest + +RUN apt-get update + +COPY packages.txt . +RUN apt-get install -y --no-install-recommends --no-install-suggests $(cat packages.txt) + +COPY gnu-keyring.gpg /gnu-keyring.gpg +RUN gpg --import /gnu-keyring.gpg + +#ARG BINUTILS_MIRROR="https://ftp.gnu.org/gnu/binutils/" +ARG BINUTILS_MIRROR="https://gnuftp.uib.no/binutils/" +ARG BINUTILS_NAME="binutils-2.42" +ARG BINUTILS_ARCHIVE=${BINUTILS_NAME}".tar.gz" + +#ARG GCC_MIRROR="https://ftp.gnu.org/gnu/gcc/gcc-14.1.0/" +ARG GCC_MIRROR="https://gnuftp.uib.no/gcc/gcc-14.1.0/" +ARG GCC_NAME="gcc-14.1.0" +ARG GCC_ARCHIVE=${GCC_NAME}".tar.gz" + +ARG PREFIX=/opt/cross/ +ARG TARGET=i686-elf +ENV PATH=${PREFIX}bin:${PATH} + +WORKDIR build + +# download and build gcc and binutils source +RUN set -x \ + && wget --no-verbose ${BINUTILS_MIRROR}${BINUTILS_ARCHIVE} \ + && wget --no-verbose ${BINUTILS_MIRROR}${BINUTILS_ARCHIVE}.sig \ + && gpg --verify "${BINUTILS_ARCHIVE}.sig" \ + && mkdir binutils \ + && tar -xf ${BINUTILS_ARCHIVE} -C binutils \ + && mkdir binutils-build \ + && cd binutils-build \ + && ../binutils/${BINUTILS_NAME}/configure \ + --target=$TARGET \ + --prefix=$PREFIX \ + --with-sysroot \ + --disable-nls \ + --disable-werror \ + && make -j 8 \ + && make install \ + && rm -rf binutils-build binutils ${BINUTLS_ARCHIVE} + +RUN set -x \ + && wget --no-verbose ${GCC_MIRROR}${GCC_ARCHIVE} \ + && wget --no-verbose ${GCC_MIRROR}${GCC_ARCHIVE}.sig \ + && gpg --verify "${GCC_ARCHIVE}.sig" \ + && mkdir gcc \ + && tar -xf ${GCC_ARCHIVE} -C gcc \ + && mkdir gcc-build \ + && cd gcc-build \ + && ../gcc/${GCC_NAME}/configure \ + --target=$TARGET \ + --prefix=$PREFIX \ + --disable-nls \ + --enable-languages=c \ + --without-headers \ + && make -j 8 all-gcc \ + && make -j 8 all-target-libgcc \ + && make install-gcc \ + && make install-target-libgcc \ + && rm -rf gcc-build gcc ${GCC_ARCHIVE} + +WORKDIR /runtime diff --git a/cross-compiler-image/gnu-keyring.gpg b/cross-compiler-image/gnu-keyring.gpg new file mode 100644 index 0000000..032d9c1 Binary files /dev/null and b/cross-compiler-image/gnu-keyring.gpg differ diff --git a/cross-compiler-image/packages.txt b/cross-compiler-image/packages.txt new file mode 100644 index 0000000..39e5167 --- /dev/null +++ b/cross-compiler-image/packages.txt @@ -0,0 +1,13 @@ +build-essential +bison +flex +libgmp3-dev +libmpc-dev +libmpfr-dev +texinfo +libisl-dev +wget +gpg +gpg-agent +gzip +ca-certificates diff --git a/grub.cfg b/grub.cfg new file mode 100644 index 0000000..1191921 --- /dev/null +++ b/grub.cfg @@ -0,0 +1,4 @@ +menuentry "myos" { + multiboot /boot/myos.bin + boot +} diff --git a/linker.ld b/linker.ld new file mode 100644 index 0000000..281f08c --- /dev/null +++ b/linker.ld @@ -0,0 +1,65 @@ +/* + * URL: https://wiki.osdev.org/Bare_Bones#Linking_the_Kernel + * Archive: https://web.archive.org/web/20240718110628/https://wiki.osdev.org/Bare_Bones#Linking_the_Kernel + * Date: 2024-07-29 + */ + +/* The bootloader will look at this image and start execution at the symbol + designated as the entry point. */ +ENTRY(_start) + +/* Tell where the various sections of the object files will be put in the final + kernel image. */ +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) + { + KEEP(*(.multiboot)) + KEEP(*(.text)) + } + + /* explicitly put .note.gnu.build-id header after multiboot header */ + .note.gnu.build-id BLOCK(4K) : ALIGN(4K) + { + KEEP(*(.note.gnu.build-id)) + } + + /* 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. */ +} + diff --git a/src/boot.S b/src/boot.S new file mode 100644 index 0000000..275cba7 --- /dev/null +++ b/src/boot.S @@ -0,0 +1,109 @@ +/* Declare constants for the multiboot header. */ +.set ALIGN, 1<<0 /* align loaded modules on page boundaries */ +.set MEMINFO, 1<<1 /* provide memory map */ +.set FLAGS, (ALIGN | MEMINFO) /* this is the Multiboot 'flag' field */ +.set MAGIC, 0x1BADB002 /* 'magic number' lets bootloader find the header */ +.set CHECKSUM, -(MAGIC + FLAGS) /* checksum of above, to prove we are multiboot */ + +/* +Declare a multiboot header that marks the program as a kernel. These are magic +values that are documented in the multiboot standard. The bootloader will +search for this signature in the first 8 KiB of the kernel file, aligned at a +32-bit boundary. The signature is in its own section so the header can be +forced to be within the first 8 KiB of the kernel file. +*/ +.section .multiboot +.align 4 +.long MAGIC +.long FLAGS +.long CHECKSUM + +/* +The multiboot standard does not define the value of the stack pointer register +(esp) and it is up to the kernel to provide a stack. This allocates room for a +small stack by creating a symbol at the bottom of it, then allocating 16384 +bytes for it, and finally creating a symbol at the top. The stack grows +downwards on x86. The stack is in its own section so it can be marked nobits, +which means the kernel file is smaller because it does not contain an +uninitialized stack. The stack on x86 must be 16-byte aligned according to the +System V ABI standard and de-facto extensions. The compiler will assume the +stack is properly aligned and failure to align the stack will result in +undefined behavior. +*/ +.section .bss +.align 16 +stack_bottom: +.skip 16384 # 16 KiB +stack_top: + +/* +The linker script specifies _start as the entry point to the kernel and the +bootloader will jump to this position once the kernel has been loaded. It +doesn't make sense to return from this function as the bootloader is gone. +*/ +.section .text +.global _start +.type _start, @function +_start: + /* + The bootloader has loaded us into 32-bit protected mode on a x86 + machine. Interrupts are disabled. Paging is disabled. The processor + state is as defined in the multiboot standard. The kernel has full + control of the CPU. The kernel can only make use of hardware features + and any code it provides as part of itself. There's no printf + function, unless the kernel provides its own header and a + printf implementation. There are no security restrictions, no + safeguards, no debugging mechanisms, only what the kernel provides + itself. It has absolute and complete power over the + machine. + */ + + /* + To set up a stack, we set the esp register to point to the top of the + stack (as it grows downwards on x86 systems). This is necessarily done + in assembly as languages such as C cannot function without a stack. + */ + mov $stack_top, %esp + + /* + This is a good place to initialize crucial processor state before the + high-level kernel is entered. It's best to minimize the early + environment where crucial features are offline. Note that the + processor is not fully initialized yet: Features such as floating + point instructions and instruction set extensions are not initialized + yet. The GDT should be loaded here. Paging should be enabled here. + C++ features such as global constructors and exceptions will require + runtime support to work as well. + */ + + /* + Enter the high-level kernel. The ABI requires the stack is 16-byte + aligned at the time of the call instruction (which afterwards pushes + the return pointer of size 4 bytes). The stack was originally 16-byte + aligned above and we've pushed a multiple of 16 bytes to the + stack since (pushed 0 bytes so far), so the alignment has thus been + preserved and the call is well defined. + */ + call kernel_main + + /* + If the system has nothing more to do, put the computer into an + infinite loop. To do that: + 1) Disable interrupts with cli (clear interrupt enable in eflags). + They are already disabled by the bootloader, so this is not needed. + Mind that you might later enable interrupts and return from + kernel_main (which is sort of nonsensical to do). + 2) Wait for the next interrupt to arrive with hlt (halt instruction). + Since they are disabled, this will lock up the computer. + 3) Jump to the hlt instruction if it ever wakes up due to a + non-maskable interrupt occurring or due to system management mode. + */ + cli +1: hlt + jmp 1b + +/* +Set the size of the _start symbol to the current location '.' minus its start. +This is useful when debugging or when you implement call tracing. +*/ +.size _start, . - _start diff --git a/src/drivers/ps2-keyboard.c b/src/drivers/ps2-keyboard.c new file mode 100644 index 0000000..287393e --- /dev/null +++ b/src/drivers/ps2-keyboard.c @@ -0,0 +1,35 @@ + +#include "ps2-keyboard.h" + +const struct str ps2_cmd_str[PS2CMD_COUNT] = { + [PS2CMD_SET_LEDS ] = str_attach("PS2CMD_SET_LEDS"), + [PS2CMD_ECHO ] = str_attach("PS2CMD_ECHO"), + [PS2CMD_GET_SCAN ] = str_attach("PS2CMD_GET_SCAN"), + [PS2CMD_IDENTIFY_KEYBOARD ] = str_attach("PS2CMD_IDENTIFY_KEYBOARD"), + [PS2CMD_SET_TYPEMATIC_RATE ] = str_attach("PS2CMD_SET_TYPEMATIC_RATE"), + [PS2CMD_ENABLE_SCANNING ] = str_attach("PS2CMD_ENABLE_SCANNING"), + [PS2CMD_DISABLE_SCANNING ] = str_attach("PS2CMD_DISABLE_SCANNING"), + [PS2CMD_SET_DEFAULT_PARAMETERS ] = str_attach("PS2CMD_SET_DEFAULT_PARAMETERS"), + [PS2CMD_SET_ALL_KEYS_TO_TA ] = str_attach("PS2CMD_SET_ALL_KEYS_TO_TA"), + [PS2CMD_SET_ALL_KEYS_TO_MR ] = str_attach("PS2CMD_SET_ALL_KEYS_TO_MR"), + [PS2CMD_SET_ALL_KEYS_TO_M ] = str_attach("PS2CMD_SET_ALL_KEYS_TO_M"), + [PS2CMD_SET_ALL_KEYS_TO_TAMR ] = str_attach("PS2CMD_SET_ALL_KEYS_TO_TAMR"), + [PS2CMD_SET_KEY_TO_TA ] = str_attach("PS2CMD_SET_KEY_TO_TA"), + [PS2CMD_SET_KEY_TO_MR ] = str_attach("PS2CMD_SET_KEY_TO_MR"), + [PS2CMD_SET_KEY_TO_M ] = str_attach("PS2CMD_SET_KEY_TO_M"), + [PS2CMD_RESEND_LAST_BYTE ] = str_attach("PS2CMD_RESEND_LAST_BYTE"), + [PS2CMD_RESEND_RESET_AND_SELF_TEST] = str_attach("PS2CMD_RESEND_RESET_AND_SELF_TEST"), +}; + +const struct str ps2_response_str[PS2RESPONSE_COUNT] = { + [PS2RESPONSE_ERROR_1 ] = str_attach("PS2RESPONSE_ERROR_1"), + [PS2RESPONSE_ERROR_2 ] = str_attach("PS2RESPONSE_ERROR_2"), + [PS2RESPONSE_SELF_TEST_PASSED ] = str_attach("PS2RESPONSE_SELF_TEST_PASSED"), + [PS2RESPONSE_ECHO_RESPONSE ] = str_attach("PS2RESPONSE_ECHO_RESPONSE"), + [PS2RESPONSE_ACK ] = str_attach("PS2RESPONSE_ACK"), + [PS2RESPONSE_SELF_TEST_FAILED_1] = str_attach("PS2RESPONSE_SELF_TEST_FAILED_1"), + [PS2RESPONSE_SELF_TEST_FAILED_2] = str_attach("PS2RESPONSE_SELF_TEST_FAILED_2"), + [PS2RESPONSE_RESEND ] = str_attach("PS2RESPONSE_RESEND"), +}; + + diff --git a/src/include/libc.h b/src/include/libc.h new file mode 100644 index 0000000..bb6a8fb --- /dev/null +++ b/src/include/libc.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include "str.h" +#include "tty.h" + +__attribute__((noreturn)) void panic(struct str s); + +void* memmove(void *dest, const void *src, size_t n); +void* memset(void *s, int c, size_t n); +void* memcpy(void *dest, const void *src, size_t n); +int memcmp(const void *s1, const void *s2, size_t n); + +int printf(struct str format, ...); diff --git a/src/include/printf.h b/src/include/printf.h new file mode 100644 index 0000000..e69de29 diff --git a/src/include/ps2-keyboard.h b/src/include/ps2-keyboard.h new file mode 100644 index 0000000..966302e --- /dev/null +++ b/src/include/ps2-keyboard.h @@ -0,0 +1,91 @@ +#pragma once + +#include "str.h" + +/* commands that the system sends to the keyboard */ +enum ps2_cmd { + /* Set LEDs */ + PS2CMD_SET_LEDS = 0xED, + + /* Echo (for diagnostic purposes, and useful for device removal detection) */ + PS2CMD_ECHO = 0xEE, + + /* Get/set current scan code set */ + PS2CMD_GET_SCAN = 0xF0, + + /* Identify keyboard */ + PS2CMD_IDENTIFY_KEYBOARD = 0xF2, + + /* Set typematic rate and delay */ + PS2CMD_SET_TYPEMATIC_RATE = 0xF3, + + /* Enable scanning (keyboard will send scan codes) */ + PS2CMD_ENABLE_SCANNING = 0xF4, + + /* Disable scanning (keyboard won't send scan codes)*/ + /* Note: May also restore default parameters */ + PS2CMD_DISABLE_SCANNING = 0xF5, + + /* Set default parameters */ + PS2CMD_SET_DEFAULT_PARAMETERS = 0xF6, + + /* Set all keys to typematic/autorepeat only (scancode set 3 only) */ + PS2CMD_SET_ALL_KEYS_TO_TA = 0xF7, + + /* Set all keys to make/release (scancode set 3 only) */ + PS2CMD_SET_ALL_KEYS_TO_MR = 0xF8, + + /* Set all keys to make only (scancode set 3 only) */ + PS2CMD_SET_ALL_KEYS_TO_M = 0xF9, + + /* Set all keys to typematic/autorepeat/make/release (scancode set 3 only) */ + PS2CMD_SET_ALL_KEYS_TO_TAMR = 0xFA, + + /* Set specific key to typematic/autorepeat only (scancode set 3 only) */ + PS2CMD_SET_KEY_TO_TA = 0xFB, + + /* Set specific key to make/release (scancode set 3 only) */ + PS2CMD_SET_KEY_TO_MR = 0xFC, + + /* Set specific key to make only (scancode set 3 only) */ + PS2CMD_SET_KEY_TO_M = 0xFD, + + /* Resend last byte */ + PS2CMD_RESEND_LAST_BYTE = 0xFE, + + /* Reset and start self-test */ + PS2CMD_RESEND_RESET_AND_SELF_TEST = 0xFF, + + PS2CMD_COUNT, +}; + +/* commands that the keyboard sends to the system */ +enum ps2_response { + + /* Self test passed (sent after "0xFF (reset)" command or keyboard power up) */ + PS2RESPONSE_SELF_TEST_PASSED = 0xAA, + + /* Response to "0xEE (echo)" command */ + PS2RESPONSE_ECHO_RESPONSE = 0xEE, + + /* Command acknowledged (ACK) */ + PS2RESPONSE_ACK = 0xFA, + + /* Self test failed (sent after "0xFF (reset)" command or keyboard power up) */ + PS2RESPONSE_SELF_TEST_FAILED_1 = 0xFC, + PS2RESPONSE_SELF_TEST_FAILED_2 = 0xFD, + + /* Resend (keyboard wants controller to repeat last command it sent) */ + PS2RESPONSE_RESEND = 0xFE, + + /* Key detection error or internal buffer overrun */ + PS2RESPONSE_ERROR_1 = 0x00, + PS2RESPONSE_ERROR_2 = 0xFF, + + PS2RESPONSE_COUNT, +}; + +extern const struct str ps2_cmd_str[PS2CMD_COUNT]; + +extern const struct str ps2_response_str[PS2RESPONSE_COUNT]; + diff --git a/src/include/str.h b/src/include/str.h new file mode 100644 index 0000000..82dbd14 --- /dev/null +++ b/src/include/str.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +struct str { + char* data; + size_t len; +}; + +#define str_attach(cstr) (struct str){.data = cstr, .len = sizeof(cstr)-1} diff --git a/src/include/tty.h b/src/include/tty.h new file mode 100644 index 0000000..11e1ce2 --- /dev/null +++ b/src/include/tty.h @@ -0,0 +1,57 @@ +/* + * Inspired by OSDev - Bare Bones + * URL: https://wiki.osdev.org/Bare_Bones#Writing_a_kernel_in_C + * Archive: https://web.archive.org/web/20240718110628/https://wiki.osdev.org/Bare_Bones#Writing_a_kernel_in_C + * Date: 2024-07-29 + */ + +#pragma once + +#include +#include "str.h" + +static constexpr size_t VGA_WIDTH = 80; +static constexpr size_t VGA_HEIGHT = 25; + +static uint16_t* const terminal_buf = (uint16_t*)0xB8000; + +struct [[nodiscard]] terminal_state { + size_t row; + size_t column; + uint8_t color; + uint16_t* const buf; +}; + +/* Hardware text mode color constants. */ +enum vga_color : uint8_t { + 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, +}; + +#define vga_color(fg, bg) (fg | bg << 4) + +void terminal_clear(); + +void terminal_set_color(uint8_t fg, uint8_t bg); + +void terminal_putentryat(char c, uint8_t color, size_t x, size_t y); + +void terminal_scroll(int n); + +void terminal_putchar(int c); + +void terminal_write(struct str str); diff --git a/src/kernel/kernel.c b/src/kernel/kernel.c new file mode 100644 index 0000000..b03f0a8 --- /dev/null +++ b/src/kernel/kernel.c @@ -0,0 +1,16 @@ +#include +#include +#include + +#include "libc.h" +#include "tty.h" + +void kernel_main(void) +{ + terminal_clear(); + + for (size_t i = 0; i < 15; i++) { + printf(str_attach("hello {u32}!\n"), i); + } +} + diff --git a/src/lib/libc.c b/src/lib/libc.c new file mode 100644 index 0000000..1232628 --- /dev/null +++ b/src/lib/libc.c @@ -0,0 +1,96 @@ +/* + * This file is for any library function that require no system calls. In + * other words it's for freestanding functions. + * */ + +#include +#include +#include "tty.h" +#include "str.h" + +/* + * Error handling + * ============== + * */ +__attribute__((noreturn)) +void panic(struct str s) +{ + terminal_clear(); + terminal_set_color(VGA_COLOR_WHITE, VGA_COLOR_RED); + terminal_write(s); + __asm__ volatile("hlt"); + __builtin_unreachable(); +} + +/* + * Stack smashing protection + * ========================= + * */ +uintptr_t __stack_chk_guard = 0x00112233; // this is just a random value + +__attribute__((noreturn)) +void __stack_chk_fail(void) +{ + panic(str_attach("Stack smashing detected")); +} + +/* + * State-enforced suffering + * ======================== + * GCC and Clang reserve the right to generate calls to the following + * 4 functions even if they are not directly called. + * Implement them as the C specification mandates. + * DO NOT remove or rename these functions, or stuff will eventually break! + * They CAN be moved to a different .c file. + * */ +void* memcpy(void *dest, const void *src, size_t n) { + uint8_t *pdest = dest; + const uint8_t *psrc = src; + + for (size_t i = 0; i < n; i++) { + pdest[i] = psrc[i]; + } + + return dest; +} + +void* memset(void *s, int c, size_t n) { + uint8_t *p = s; + + for (size_t i = 0; i < n; i++) { + p[i] = (uint8_t)c; + } + + return s; +} + +void* memmove(void *dest, const void *src, size_t n) { + uint8_t *pdest = dest; + const uint8_t *psrc = src; + + if (src > dest) { + for (size_t i = 0; i < n; i++) { + pdest[i] = psrc[i]; + } + } else if (src < dest) { + for (size_t i = n; i > 0; i--) { + pdest[i-1] = psrc[i-1]; + } + } + + return dest; +} + +int memcmp(const void *s1, const void *s2, size_t n) { + const uint8_t *p1 = s1; + const uint8_t *p2 = s2; + + for (size_t i = 0; i < n; i++) { + if (p1[i] != p2[i]) { + return p1[i] < p2[i] ? -1 : 1; + } + } + + return 0; +} + diff --git a/src/lib/printf.c b/src/lib/printf.c new file mode 100644 index 0000000..d436b7e --- /dev/null +++ b/src/lib/printf.c @@ -0,0 +1,171 @@ + +#include +#include + +#include "libc.h" + +typedef int (*printf_function)(struct printf_state* s, void* data); + +constexpr int EOF = -1; + +struct printf_state { + struct str str; + va_list ap; + size_t i; + int written; +}; + +static inline int ps_peek(struct printf_state* s) +{ + if (s->i == s->str.len) { + return EOF; + } + return s->str.data[s->i]; +} + +static inline int ps_get(struct printf_state* s) +{ + if (s->i == s->str.len) { + return EOF; + } + return s->str.data[s->i++]; +} + +static int print_long(unsigned long n, struct str alphabet, bool is_signed) +{ + constexpr size_t BUF_SZ = 21; + char buf[BUF_SZ]; + size_t i = 0; + + if (n == 0) { + terminal_putchar('0'); + return 1; + } + + bool is_negative = false; + if (is_signed) { + signed long signed_n = (signed long)n; + if (signed_n < 0) { + is_negative = true; + n = -signed_n; + } + } + + if (is_negative) + terminal_putchar('-'); + + while (n) { + i += 1; + buf[BUF_SZ-i] = alphabet.data[n % alphabet.len]; + n /= alphabet.len; + } + + terminal_write((struct str){.data = &buf[BUF_SZ-i], .len = i}); + + return i; +} + +static int print_i16(struct printf_state* s) +{ + // int16_t is promoted to 'int' when passed through '...' + long n = va_arg(s->ap, int32_t); + return print_long(n, str_attach("0123456789"), true); +} + +static int print_i32(struct printf_state* s) +{ + long n = va_arg(s->ap, int32_t); + return print_long(n, str_attach("0123456789"), true); +} + +static int print_i64(struct printf_state* s) +{ + panic(str_attach("print_i64 not implemented on i686")); + long n = va_arg(s->ap, int64_t); + return print_long(n, str_attach("0123456789"), true); +} + +static int print_u16(struct printf_state* s) +{ + // uint16_t is promoted to 'unsigned int' when passed through '...' + unsigned long n = va_arg(s->ap, unsigned int); + return print_long(n, str_attach("0123456789"), false); +} + +static int print_u32(struct printf_state* s) +{ + unsigned long n = va_arg(s->ap, uint32_t); + return print_long(n, str_attach("0123456789"), false); +} + +static int print_u64(struct printf_state* s) +{ + panic(str_attach("print_u64 not implemented on i686")); + unsigned long n = va_arg(s->ap, uint64_t); + return print_long(n, str_attach("0123456789"), false); +} + +#pragma GCC diagnostic ignored "-Wmultichar" +static int parse_format_cmd(struct printf_state* s) +{ + int c; + uint32_t cmd = 0; + + constexpr uint32_t CHAR_MASK = (1<>= CHAR_BIT; + } + terminal_putchar('\n'); + return -1; + } +} + +int printf(struct str format, ...) +{ + int c; + + struct printf_state s = { + .str = format, + .i = 0, + .written = 0, + }; + va_start(s.ap, format); + + while ((c = ps_get(&s)) != EOF) { + switch (c) { + + case '{': + int ok = parse_format_cmd(&s); + if (ok == -1) + return -1; + s.written += ok; + break; + + default: + terminal_putchar(c); + s.written += 1; + break; + } + } + + return s.written; +} diff --git a/src/lib/str.c b/src/lib/str.c new file mode 100644 index 0000000..e69de29 diff --git a/src/lib/tty.c b/src/lib/tty.c new file mode 100644 index 0000000..dd5a349 --- /dev/null +++ b/src/lib/tty.c @@ -0,0 +1,90 @@ +/* + * Inspired by OSDev - Bare Bones + * URL: https://wiki.osdev.org/Bare_Bones#Writing_a_kernel_in_C + * Archive: https://web.archive.org/web/20240718110628/https://wiki.osdev.org/Bare_Bones#Writing_a_kernel_in_C + * Date: 2024-07-29 + */ + +#include "tty.h" +#include "libc.h" + +static struct terminal_state t = { + .row = 0, + .column = 0, + .color = vga_color(VGA_COLOR_LIGHT_GREY, VGA_COLOR_BLACK), + .buf = (uint16_t*)0xB8000, +}; + +static inline bool isprint(int c) +{ + return (c >= ' ') && (c <= '~'); +} + +static inline uint16_t vga_entry(unsigned char uc, uint8_t color) +{ + return (uint16_t) uc | (uint16_t) color << 8; +} + +void terminal_clear() +{ + t.row = 0, + t.column = 0, + t.color = vga_color(VGA_COLOR_LIGHT_GREY, VGA_COLOR_BLACK); + for (size_t i = 0; i < VGA_WIDTH * VGA_HEIGHT; i++) { + t.buf[i] = vga_entry(' ', t.color); + } +} + +void terminal_set_color(uint8_t fg, uint8_t bg) +{ + t.color = vga_color(fg, bg); +} + +void terminal_putentryat(char c, uint8_t color, size_t x, size_t y) +{ + terminal_buf[y*VGA_WIDTH + x] = vga_entry(c, color); +} + +void terminal_scroll(int n) +{ + memmove(terminal_buf, + terminal_buf + VGA_WIDTH * n, + VGA_WIDTH * (VGA_HEIGHT-n)); + for (size_t i = VGA_WIDTH * (VGA_HEIGHT-n); i < VGA_WIDTH * VGA_HEIGHT; i++) { + terminal_buf[i] = vga_entry(' ', t.color); + } + t.row -= n; +} + +void terminal_putchar(int c) +{ + // TODO: implement other control characters + switch (c) { + + case '\n': + t.column = 0; + t.row += 1; + break; + + default: + c = isprint(c) ? c : '?'; + terminal_putentryat(c, t.color, t.column, t.row); + t.column += 1; + if (t.column == VGA_WIDTH) { + t.column = 0; + t.row += 1; + } + } + + if (t.row == VGA_HEIGHT) { + terminal_scroll(1); + } +} + +void terminal_write(struct str str) +{ + for (size_t i = 0; i < str.len; i++) { + terminal_putchar(str.data[i]); + } +} +