aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAqua-sama <aqua@iserlohn-fortress.net>2021-01-31 22:08:01 +0200
committerAqua-sama <aqua@iserlohn-fortress.net>2021-01-31 22:08:01 +0200
commit61b4f7fb29db2e8f8af266fcb0836b6b9232245a (patch)
treec5aa7d9049cbeb60e17e8440ac8eb0f79decf66b
downloadkernel.cpp-61b4f7fb29db2e8f8af266fcb0836b6b9232245a.tar.xz
Initial commit
-rw-r--r--.gitignore2
-rw-r--r--boot.asm90
-rw-r--r--grub.cfg3
-rw-r--r--kernel.cc26
-rw-r--r--linker.ld45
-rw-r--r--makefile41
-rw-r--r--readme.md7
-rw-r--r--stdlib/stdlib.h14
-rw-r--r--stdlib/stdlib/console.cc28
-rw-r--r--stdlib/string.cc26
-rw-r--r--stdlib/string.h69
-rw-r--r--stdlib/types.h30
-rw-r--r--vga.cc67
-rw-r--r--vga.h45
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);
diff --git a/vga.cc b/vga.cc
new file mode 100644
index 0000000..17061bb
--- /dev/null
+++ b/vga.cc
@@ -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);
+}
diff --git a/vga.h b/vga.h
new file mode 100644
index 0000000..b08a221
--- /dev/null
+++ b/vga.h
@@ -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;
+};