commit a2a4a155c259d24101435771607b81f167b62cf3 Author: Simponic Date: Sat Feb 20 23:24:25 2021 -0700 Changed to nasm diff --git a/boot.o b/boot.o new file mode 100644 index 0000000..68fc8c6 Binary files /dev/null and b/boot.o differ diff --git a/gdt.o b/gdt.o new file mode 100644 index 0000000..6034c8b Binary files /dev/null and b/gdt.o differ diff --git a/include/gdt.h b/include/gdt.h new file mode 100644 index 0000000..8b239dd --- /dev/null +++ b/include/gdt.h @@ -0,0 +1,17 @@ +#ifndef GDT_H +#define GDT_H + +#include "types.h" + +struct GDT { + uint32_t limit; + uint32_t base; + uint8_t type; +} __attribute((packed)); + +struct GDT_ptr { + uint16_t limit; + uint32_t base; +} __attribute((packed)); + +#endif //GDT_H diff --git a/include/print.h b/include/print.h new file mode 100644 index 0000000..c46843c --- /dev/null +++ b/include/print.h @@ -0,0 +1,19 @@ +#ifndef PRINT_H +#define PRINT_H + +#include "types.h" + +typedef struct TextOutput { + int terminal_row; + int terminal_column; + int max_row; + int max_column; + uint16_t* vid_mem; +}__attribute__((packed)) TextOutput; + +TextOutput createOutput(const int max_row, const int max_column, uint16_t* vid_mem); +void scrollText(TextOutput* textOutput); +void putChar(uint8_t character, uint8_t background, uint8_t foreground, TextOutput* textOutput); +void print(char* string, uint8_t background, uint8_t foreground, TextOutput* textOutput); + +#endif // PRINT_H diff --git a/include/types.h b/include/types.h new file mode 100644 index 0000000..a4795c6 --- /dev/null +++ b/include/types.h @@ -0,0 +1,16 @@ +#ifndef TYPES_H +#define TYPES_H + +typedef char int8_t; +typedef unsigned char uint8_t; + +typedef short int16_t; +typedef unsigned short uint16_t; + + +typedef int int32_t; +typedef unsigned int uint32_t; + +typedef long long int int64_t; +typedef unsigned long long int uint64_t; +#endif diff --git a/linker.ld b/linker.ld new file mode 100644 index 0000000..38ddd94 --- /dev/null +++ b/linker.ld @@ -0,0 +1,43 @@ +/* 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 +{ + /* 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 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. */ +} diff --git a/makefile b/makefile new file mode 100644 index 0000000..acda2c3 --- /dev/null +++ b/makefile @@ -0,0 +1,27 @@ +# vim: noexpandtab tabstop=4 shiftwidth=4 +CXX = i386-elf-gcc +CFLAGS = -std=gnu99 -ffreestanding -O2 -Wall -Wextra -nostdlib -Iinclude +ASM = nasm + +OBJECTS = gdt.o boot.o print.o kernel.o + +%.o : src/%.c + $(CXX) $(CFLAGS) -o $@ -c $< + +%.o : src/%.s + $(ASM) -felf32 $< -o $@ + +os.bin : $(OBJECTS) + $(CXX) -T linker.ld -o os.bin $(CFLAGS) $(OBJECTS) -lgcc + +os.iso : os.bin + mkdir -p isodir/boot/grub + mv os.bin isodir/boot/os.bin + echo "menuentry 'os' {" >> grub.cfg + echo " multiboot /boot/os.bin" >> grub.cfg + echo "}" >> grub.cfg + mv grub.cfg isodir/boot/grub/grub.cfg + grub-mkrescue -o os.iso isodir + +clean : + rm -rf *.o *.iso *.bin *.cfg isodir/ diff --git a/print.o b/print.o new file mode 100644 index 0000000..1e31a59 Binary files /dev/null and b/print.o differ diff --git a/src/boot.s b/src/boot.s new file mode 100644 index 0000000..2472103 --- /dev/null +++ b/src/boot.s @@ -0,0 +1,106 @@ +; 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 _gdt_flush ; Allows the C code to link to this +extern _gp ; Says that '_gp' is in another file +_gdt_flush: + lgdt [_gp] ; Load the GDT with our '_gp' which is a special pointer + mov ax, 0x10 ; 0x10 is the offset in the GDT to our data segment + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + jmp 0x08:flush2 ; 0x08 is the offset to our code segment: Far jump! +flush2: + ret ; Returns back to the C code! + +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 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/src/gdt.c b/src/gdt.c new file mode 100644 index 0000000..a9808aa --- /dev/null +++ b/src/gdt.c @@ -0,0 +1,36 @@ +#include "gdt.h" + +void encodeGDT(uint8_t* gdtEntry, struct GDT source) { + if ((source.limit > 65536) && ((source.limit & 0xFFF) == 0xFFF)) { + // Set the GDT to use paging + // To do this we need to make sure the limit is aligned to 4KiB + source.limit = source.limit >> 12; + // Granularity: 1 (use paging with 4KiB segments) + // Size: 1 (32 bit protected mode) + gdtEntry[6] = 0xC0; + } + else { + // Granularity: 0 (1 byte segments) + // Size: 1 + gdtEntry[6] = 0x40; + } + + // Here we are encoding the limit + + // Bits 0-15 encode the bottom 16 bits of the limit + gdtEntry[0] = source.limit & 0xFF; + gdtEntry[1] = (source.limit >> 8) & 0xFF; + // Bits 48-51 encode the last 4 bits of the limit + gdtEntry[6] |= (source.limit >> 16) & 0xF; + + // Bits 16-39 encode the bottom 24 bits of the base + gdtEntry[2] = source.base & 0xFF; + gdtEntry[3] = (source.base >> 8) & 0xFF; + gdtEntry[4] = (source.base >> 16) & 0xFF; + // Bits 56-64 encode the last byte of the base + gdtEntry[7] = (source.base >> 24) & 0xFF; + + // Bits 40-47 set the access byte, which is documented at https://wiki.osdev.org/GDT, + // where most of the ideas for this function are taken from shamelessly + gdtEntry[5] = source.type; +} diff --git a/src/kernel.c b/src/kernel.c new file mode 100644 index 0000000..d24c24e --- /dev/null +++ b/src/kernel.c @@ -0,0 +1,22 @@ +#include "gdt.h" +#include "types.h" +#include "print.h" + +#define FOREGROUND 0x0 +#define BACKGROUND 0xF + +void PrintWithScreenFill(char* string, TextOutput* output_stream) { + // Print a string and fill the screen + print(string, BACKGROUND, FOREGROUND, output_stream); + int row = output_stream->terminal_row; + while (output_stream->terminal_row < output_stream->max_row) { + putChar('\n', BACKGROUND, FOREGROUND, output_stream); + } + output_stream->terminal_row = row; +} + +void kernel_main(void) { + TextOutput output_stream = createOutput(25,80,(uint16_t*)0xB8000); + PrintWithScreenFill("Hello, Logan World!\n", &output_stream); +} + diff --git a/src/print.c b/src/print.c new file mode 100644 index 0000000..0a0220c --- /dev/null +++ b/src/print.c @@ -0,0 +1,73 @@ +#include "print.h" + +TextOutput createOutput(const int max_row, const int max_column, uint16_t* vid_mem) { + // Create a new TextOutput interface + TextOutput output; + output.max_row = max_row; + output.max_column = max_column; + output.vid_mem = vid_mem; + output.terminal_row = 0; + output.terminal_column = 0; + return output; +} + +void scrollText(TextOutput* textOutput) { + // Move each character up one row + for (int y = 0; y < textOutput->max_row; y++) { + for (int x = 0; x < textOutput->max_column; x++) { + *(textOutput->vid_mem + textOutput->max_column*y + x) = + *(textOutput->vid_mem + textOutput->max_column*(y+1) + x); + } + } + textOutput->terminal_row--; +} + +void putChar(uint8_t character, uint8_t background, uint8_t foreground, + TextOutput* textOutput) { + foreground = foreground & 0xF; background = background & 0xF; + + // Handle putting a character to the screen + if (textOutput->terminal_row == textOutput->max_row) { + scrollText(textOutput); + } + + if (character == '\r') { + // Delete the character before this \r + if (textOutput->terminal_column == 0) { + textOutput->terminal_row--; + textOutput->terminal_column = 80; + } + textOutput->terminal_column--; + *(textOutput->vid_mem + textOutput->terminal_row*textOutput->max_column + + textOutput->terminal_column) = background << 12; + return; + } + + else if (character == '\n' || textOutput->terminal_column == textOutput->max_column) { + // Make a new line + for (int i = textOutput->terminal_column; i < textOutput->max_column; i++) { + *(textOutput->vid_mem + textOutput->terminal_row*textOutput->max_column + + textOutput->terminal_column) = background << 12; + textOutput->terminal_column++; + } + + textOutput->terminal_row++; + textOutput->terminal_column = 0; + } + + if (character != '\n' && character != '\r') { + // Write character to video memory + uint16_t entry = (background << 12) | (foreground << 8) | character; + *(textOutput->vid_mem + textOutput->terminal_row*textOutput->max_column + + textOutput->terminal_column) = entry; + textOutput->terminal_column++; + } +} + +void print(char* string, uint8_t background, uint8_t foreground, TextOutput* textOutput) { + // Print a string + for (string; *string; string++) { + putChar(*string, background, foreground, textOutput); + } +} +