diff options
author | Aqua-sama <aqua@iserlohn-fortress.net> | 2021-01-31 22:08:01 +0200 |
---|---|---|
committer | Aqua-sama <aqua@iserlohn-fortress.net> | 2021-01-31 22:08:01 +0200 |
commit | 61b4f7fb29db2e8f8af266fcb0836b6b9232245a (patch) | |
tree | c5aa7d9049cbeb60e17e8440ac8eb0f79decf66b | |
download | kernel.cpp-61b4f7fb29db2e8f8af266fcb0836b6b9232245a.tar.xz |
Initial commit
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | boot.asm | 90 | ||||
-rw-r--r-- | grub.cfg | 3 | ||||
-rw-r--r-- | kernel.cc | 26 | ||||
-rw-r--r-- | linker.ld | 45 | ||||
-rw-r--r-- | makefile | 41 | ||||
-rw-r--r-- | readme.md | 7 | ||||
-rw-r--r-- | stdlib/stdlib.h | 14 | ||||
-rw-r--r-- | stdlib/stdlib/console.cc | 28 | ||||
-rw-r--r-- | stdlib/string.cc | 26 | ||||
-rw-r--r-- | stdlib/string.h | 69 | ||||
-rw-r--r-- | stdlib/types.h | 30 | ||||
-rw-r--r-- | vga.cc | 67 | ||||
-rw-r--r-- | vga.h | 45 |
14 files changed, 493 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8deeab7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.o +*.bin diff --git a/boot.asm b/boot.asm new file mode 100644 index 0000000..e4897e4 --- /dev/null +++ b/boot.asm @@ -0,0 +1,90 @@ +; Declare constants for the multiboot header. +MBALIGN equ 1 << 0 ; align loaded modules on page boundaries +MEMINFO equ 1 << 1 ; provide memory map +FLAGS equ MBALIGN | MEMINFO ; this is the Multiboot 'flag' field +MAGIC equ 0x1BADB002 ; 'magic number' lets bootloader find the header +CHECKSUM equ -(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 + dd MAGIC + dd FLAGS + dd 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: +resb 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. +; Declare _start as a function symbol with the given symbol size. +section .text +global _start:function (_start.end - _start) +_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 <stdio.h> 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 our + ; 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 esp, stack_top + + ; 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 since pushed a multiple of 16 bytes to the + ; stack since (pushed 0 bytes so far) and the alignment is thus + ; preserved and the call is well defined. + ; note, that if you are building on Windows, C functions may have "_" prefix in assembly: _kernel_main + extern kernel_main + 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 +.hang: hlt + jmp .hang +.end: diff --git a/grub.cfg b/grub.cfg new file mode 100644 index 0000000..b2f8404 --- /dev/null +++ b/grub.cfg @@ -0,0 +1,3 @@ +menuentry "myos" { + multiboot /boot/myos.bin +} diff --git a/kernel.cc b/kernel.cc new file mode 100644 index 0000000..3df1903 --- /dev/null +++ b/kernel.cc @@ -0,0 +1,26 @@ +#include <stdlib.h> +#include <types.h> + +/* Check if the compiler thinks you are targeting the wrong operating system. */ +#if defined(__linux__) +#error "You are not using a cross-compiler" +#endif + +/* This tutorial will only work for the 32-bit ix86 targets. */ +#if !defined(__i386__) +#error "This tutorial needs to be compiled with a ix86-elf compiler" +#endif + +#include "vga.h" + +extern "C" void kernel_main(void) { + VGA terminal; + console_set(&terminal); + + terminal.set_color(VGA::VGA_COLOR_CYAN, VGA::VGA_COLOR_BROWN); + terminal.write("Hello, kernel World! xxx--\n", "--xyxyxz\n", 10); + + printk("This is printk\n"); + + abort(); +} diff --git a/linker.ld b/linker.ld new file mode 100644 index 0000000..732e5a0 --- /dev/null +++ b/linker.ld @@ -0,0 +1,45 @@ +/* The bootloader will look at this image and start execution at the symbol + designated as the entry point. */ +ENTRY(_start) +OUTPUT_FORMAT(elf32-i386) +OUTPUT_ARCH(i386:i386) + +/* Tell where the various sections of the object files will be put in the final + kernel image. */ +SECTIONS +{ + /* Begin putting sections at 1 MiB, a conventional place for kernels to be + loaded at by the bootloader. */ + . = 1M; + + /* First put the multiboot header, as it is required to be put very early + early in the image or the bootloader won't recognize the file format. + Next we'll put the .text section. */ + .text : + { + *(.multiboot) + *(.text) + } + + /* Read-only data. */ + .rodata : + { + *(.rodata) + } + + /* Read-write data (initialized) */ + .data : + { + *(.data) + } + + /* Read-write data (uninitialized) and stack */ + .bss : + { + *(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/makefile b/makefile new file mode 100644 index 0000000..82e3bba --- /dev/null +++ b/makefile @@ -0,0 +1,41 @@ +# https://www.gnu.org/software/make/manual/html_node/Setting.html +# := expanded variable, right-hand side can contain variables +# ?= only set if it doesn't have a value +# != execute a shell script on the right-hand side and assign its result to the left-hand side + +TARGET := i686-elf + +NASM := nasm +CXX := clang++ +CXXFLAGS := -std=c++20 -ffreestanding -nostdinc -fno-exceptions -fno-rtti -Wall -Wextra -O2 +INCLUDE := stdlib + +OBJ := kernel.o vga.o stdlib/string.o stdlib/stdlib/console.o + +# $@ is target +# $< is first dependency +# $^ is all dependencies + +default: boot.o $(OBJ) + ld.lld -T linker.ld -o myos.bin boot.o $(OBJ) + +boot.o: boot.asm + $(NASM) -felf32 -o $@ $^ + +$(OBJ): %.o : %.cc + $(CXX) -target $(TARGET) $(CXXFLAGS) -I$(INCLUDE) -c $^ -o $@ + +iso: + mkdir -p isodir/boot/grub + cp myos.bin isodir/boot/myos.bin + cp grub.cfg isodir/boot/grub/grub.cfg + grub-mkrescue -o myos.iso isodir + +run: + qemu-system-i386 -kernel myos.bin + +clean: + rm boot.o $(OBJ) myos.bin + +clean-iso: + rm -rf isodir diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..5e5bbdc --- /dev/null +++ b/readme.md @@ -0,0 +1,7 @@ +## Compiling + +- gnu make +- llvm/clang++ +- nasm +- grub +- qemu diff --git a/stdlib/stdlib.h b/stdlib/stdlib.h new file mode 100644 index 0000000..3e6619d --- /dev/null +++ b/stdlib/stdlib.h @@ -0,0 +1,14 @@ +#pragma once + +#include <string.h> + +class Console { +public: + virtual void write(const String &msg) = 0; +}; + +void console_set(Console *ptr); + +void printk(const String &msg); + +__attribute__((__noreturn__)) void abort(); diff --git a/stdlib/stdlib/console.cc b/stdlib/stdlib/console.cc new file mode 100644 index 0000000..dbc9b86 --- /dev/null +++ b/stdlib/stdlib/console.cc @@ -0,0 +1,28 @@ +#include "stdlib.h" + +static Console *global_console = nullptr; + +void console_set(Console *ptr) { + if (ptr != nullptr) { + global_console = ptr; + } +} + +void printk(const String &msg) { + if (global_console == nullptr) + return; + + global_console->write(msg); +} + +void abort() { + /* + * On gcc, a 'while(true) {}' will infinitely loop + * but clang will optimize it away on -O2 if it's empty + * therefore, add no-op + */ + while (true) { + asm volatile("nop"); + } + __builtin_unreachable(); +} diff --git a/stdlib/string.cc b/stdlib/string.cc new file mode 100644 index 0000000..5cd4bb2 --- /dev/null +++ b/stdlib/string.cc @@ -0,0 +1,26 @@ +#include "string.h" + +// TODO + +// int strcpy(char *dst, const char *stc); +// void strcat(void *dst, const void *src); +// char* strncpy(char *dest, const char *src, int length); +// int strncmp(const char *s1, const char *s2, int c); + +/* strcmp - compare two C-strings */ +constexpr int strcmp(const char *s1, const char *s2) +{ + const auto s1_len = strlen(s1); + for(size_t i = 0; i < s1_len; ++i) { + if(s1[i] == s2[i]) continue; + if(s1[i] > s2[i]) return 1; + if(s1[i] < s2[i]) return -1; + } + return 0; +} + +static_assert(strcmp("one", "one") == 0); +static_assert(strcmp("one", "two") < 0); +static_assert(strcmp("foo", "bar") > 0); + + diff --git a/stdlib/string.h b/stdlib/string.h new file mode 100644 index 0000000..deb6fde --- /dev/null +++ b/stdlib/string.h @@ -0,0 +1,69 @@ +#pragma once +#include "types.h" + +constexpr size_t strlen(const char *str) { + int len = 0; + while (str[len]) + ++len; + return len; +} + +/* reverse: reverse string s in place */ +constexpr void reverse(char s[]) { + int i, j; + char c; + + for (i = 0, j = strlen(s) - 1; i < j; i++, j--) { + c = s[i]; + s[i] = s[j]; + s[j] = c; + } +} + +/* itoa: convert n to characters in s */ +template <int base = 10> constexpr void itoa(int n, char s[]) { + int i, sign; + + if ((sign = n) < 0) /* record sign */ + n = -n; /* make n positive */ + + i = 0; + do { /* generate digits in reverse order */ + s[i++] = "0123456789abcdef"[n % base]; /* get next digit */ + } while ((n /= base) > 0); /* delete it */ + + if (sign < 0) + s[i++] = '-'; + + s[i] = '\0'; + + reverse(s); +} + +class String { +public: + class Iterator { + friend class String; + + public: + char next() { return p->buffer[pos++]; } + operator bool() const { return (pos < p->m_length); } + + private: + Iterator(const String *s) : p(s) {} + + size_t pos = 0; + const String *p; + }; + + friend class Iterator; + + String(const char *d) : buffer{d}, m_length{strlen(d)} {} + String(const char *d, size_t l) : buffer{d}, m_length{l} {} + + Iterator begin() const { return Iterator(this); } + +private: + const char *const buffer; + const size_t m_length; +}; diff --git a/stdlib/types.h b/stdlib/types.h new file mode 100644 index 0000000..0818342 --- /dev/null +++ b/stdlib/types.h @@ -0,0 +1,30 @@ +#pragma once + +typedef unsigned short size_t; +static_assert(sizeof(size_t) >= 2); + + +typedef unsigned char uint8_t; +static_assert(sizeof(uint8_t) == 1); + +typedef unsigned short uint16_t; +static_assert(sizeof(uint16_t) == 2); + +typedef unsigned int uint32_t; +static_assert(sizeof(uint32_t) == 4); + +//typedef unsigned long uint64_t; +//static_assert(sizeof(uint64_t) == 8); + + +typedef char int8_t; +static_assert(sizeof(int8_t) == 1); + +typedef short int16_t; +static_assert(sizeof(int16_t) == 2); + +typedef int int32_t; +static_assert(sizeof(int32_t) == 4); + +//typedef long int64_t; +//static_assert(sizeof(int64_t) == 8); @@ -0,0 +1,67 @@ +#include "vga.h" +#include <string.h> + +constexpr uint8_t vga_entry_color(VGA::vga_color fg, VGA::vga_color bg) { + return fg | bg << 4; +} + +constexpr uint16_t vga_entry(unsigned char uc, uint8_t color) { + return (uint16_t)uc | (uint16_t)color << 8; +} + +VGA::VGA(uint32_t address) { + color = vga_entry_color(VGA_COLOR_LIGHT_GREY, VGA_COLOR_BROWN); + buffer = (uint16_t *)address; + + // clear buffer + for (size_t y = 0; y < max_rows; y++) { + for (size_t x = 0; x < max_columns; x++) { + const size_t index = y * max_columns + x; + buffer[index] = vga_entry(' ', color); + } + } +} + +void VGA::put_char(char c, size_t x, size_t y, uint8_t color) { + const size_t index = y * max_columns + x; + buffer[index] = vga_entry(c, (color == 0) ? this->color : color); +} + +void VGA::write(const String &data) { + auto it = data.begin(); + while (it) { + switch (const auto c = it.next()) { + case '\n': + column = 0; + ++row; + break; + default: + put_char(c, column, row, color); + ++column; + } + + if (column == max_columns) { + column = 0; + ++row; + } + + if (row == max_rows) { + // scroll up - move rows 1~25 up by one + for (size_t y = 1; y < max_rows; y++) { + const auto prev_y = y - 1; + for (size_t x = 0; x < max_columns; ++x) { + const auto prev = prev_y * max_columns + x; + const auto idx = y * max_columns + x; + buffer[prev] = buffer[idx]; + } + } + --row; + } + } +} + +void VGA::write(int n) { + char buffer[max_columns]; + itoa(n, buffer); + write(buffer); +} @@ -0,0 +1,45 @@ +#pragma once +#include <stdlib.h> + +class VGA : public Console { +public: + /* 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, + }; + + VGA(uint32_t address = 0xB8000); + ~VGA() = default; + + void put_char(char c, size_t x, size_t y, uint8_t color = 0); + void write(const String &data) override; + void write(int n); + + template <typename... Args> void write(const String &f, const Args &...a) { + write(f); + write(a...); + } + + void set_color(vga_color fg, vga_color bg) { color = (fg | bg << 4); } + +private: + const size_t max_columns = 80, max_rows = 25; + size_t column = 0, row = 0; + uint8_t color; + uint16_t *buffer; +}; |