Initial commit.
This commit is contained in:
commit
0b05970c71
48
.gitignore
vendored
Normal file
48
.gitignore
vendored
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# Project Folder (at least for now)
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Compiled Binaries
|
||||||
|
*.bin
|
||||||
|
|
||||||
|
# Compile Commands
|
||||||
|
compile_commands.json
|
||||||
|
|
||||||
|
# Prerequisites
|
||||||
|
*.d
|
||||||
|
|
||||||
|
# Compiled Object files
|
||||||
|
*.slo
|
||||||
|
*.lo
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
|
||||||
|
# Precompiled Headers
|
||||||
|
*.gch
|
||||||
|
*.pch
|
||||||
|
|
||||||
|
# Compiled Dynamic libraries
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.dll
|
||||||
|
|
||||||
|
# Fortran module files
|
||||||
|
*.mod
|
||||||
|
*.smod
|
||||||
|
|
||||||
|
# Compiled Static libraries
|
||||||
|
*.lai
|
||||||
|
*.la
|
||||||
|
*.a
|
||||||
|
*.lib
|
||||||
|
|
||||||
|
# Executables
|
||||||
|
*.exe
|
||||||
|
*.out
|
||||||
|
*.app
|
||||||
|
|
||||||
|
# for projects that use SCons for building: http://http://www.scons.org/
|
||||||
|
.sconsign.dblite
|
||||||
|
|
||||||
|
# When configure fails, SCons outputs these
|
||||||
|
config.log
|
||||||
|
.sconf_temp
|
39
SConstruct
Normal file
39
SConstruct
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
env = Environment(tools = ['default', 'compilation_db'])
|
||||||
|
env['AS'] = 'i686-elf-as'
|
||||||
|
env['CC'] = 'i686-elf-gcc'
|
||||||
|
env['CXX'] = 'i686-elf-g++'
|
||||||
|
env['LD'] = 'i686-elf-g++'
|
||||||
|
env.Append(CXXFLAGS = ['-ffreestanding', '-fno-exceptions', '-fno-rtti', '-std=c++20'])
|
||||||
|
env.Append(LINKFLAGS = ['-T', 'linker.ld', '-ffreestanding', '-nostdlib'])
|
||||||
|
env.Append(CPPPATH = ['#include'])
|
||||||
|
|
||||||
|
def get_crt_object(name: str) -> str:
|
||||||
|
cmd = [env['CXX']]
|
||||||
|
cmd.extend(env['CXXFLAGS'])
|
||||||
|
cmd.append(f'-print-file-name={name}')
|
||||||
|
result = subprocess.run(cmd, stdout=subprocess.PIPE)
|
||||||
|
return result.stdout.decode('utf-8').strip()
|
||||||
|
crtbegin_o = get_crt_object('crtbegin.o')
|
||||||
|
crtend_o = get_crt_object('crtend.o')
|
||||||
|
crti_o = env.Object('src/crt/crti.s')
|
||||||
|
crtn_o = env.Object('src/crt/crtn.s')
|
||||||
|
|
||||||
|
os_sources = Split('''
|
||||||
|
src/kernel/boot.s
|
||||||
|
src/kernel/startup.cpp
|
||||||
|
|
||||||
|
src/os/tty.cpp
|
||||||
|
''')
|
||||||
|
env['LINKCOM'] = env['LINKCOM'].replace('$_LIBFLAGS', f'{crti_o[0]} {crtbegin_o} $_LIBFLAGS {crtend_o} {crtn_o[0]}')
|
||||||
|
|
||||||
|
prog_os = env.Program(
|
||||||
|
target = 'os.bin',
|
||||||
|
source = os_sources
|
||||||
|
)
|
||||||
|
env.Depends(prog_os, [crti_o, crtn_o])
|
||||||
|
env.Default(prog_os)
|
||||||
|
comp_db = env.CompilationDatabase(target = '#compile_commands.json')
|
||||||
|
env.Default(comp_db)
|
3
boot/grub.cfg
Normal file
3
boot/grub.cfg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
menuentry "Bad Apple OS" {
|
||||||
|
multiboot /boot/os.bin
|
||||||
|
}
|
51
include/os/tty.hpp
Normal file
51
include/os/tty.hpp
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(OS_TTY_HPP_INCLUDED)
|
||||||
|
#define OS_TTY_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
namespace tty
|
||||||
|
{
|
||||||
|
enum class VgaColor : uint8_t
|
||||||
|
{
|
||||||
|
BLACK = 0,
|
||||||
|
BLUE = 1,
|
||||||
|
GREEN = 2,
|
||||||
|
CYAN = 3,
|
||||||
|
RED = 4,
|
||||||
|
MAGENTA = 5,
|
||||||
|
BROWN = 6,
|
||||||
|
LIGHT_GREY = 7,
|
||||||
|
DARK_GREY = 8,
|
||||||
|
LIGHT_BLUE = 9,
|
||||||
|
LIGHT_GREEN = 10,
|
||||||
|
LIGHT_CYAN = 11,
|
||||||
|
LIGHT_RED = 12,
|
||||||
|
LIGHT_MAGENTA = 13,
|
||||||
|
LIGHT_BROWN = 14,
|
||||||
|
WHITE = 15
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VgaDoubleColor
|
||||||
|
{
|
||||||
|
VgaColor foreground : 4 = VgaColor::LIGHT_GREY;
|
||||||
|
VgaColor background : 4 = VgaColor::BLACK;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] size_t strlen(const char* str); // TODO: move to standard lib
|
||||||
|
void initialize();
|
||||||
|
void setColor(VgaDoubleColor color);
|
||||||
|
void putEntryAt(char chr, VgaDoubleColor color, size_t posX, size_t posY);
|
||||||
|
void putChar(char chr);
|
||||||
|
void write(const char* data, size_t size);
|
||||||
|
|
||||||
|
inline void write(const char* data)
|
||||||
|
{
|
||||||
|
write(data, strlen(data));
|
||||||
|
}
|
||||||
|
} // namespace tty
|
||||||
|
|
||||||
|
#endif // OS_TTY_HPP_INCLUDED
|
52
linker.ld
Normal file
52
linker.ld
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/* 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)
|
||||||
|
{
|
||||||
|
*(.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. */
|
||||||
|
}
|
16
src/crt/crti.s
Normal file
16
src/crt/crti.s
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/* x86 crti.s */
|
||||||
|
.section .init
|
||||||
|
.global _init
|
||||||
|
.type _init, @function
|
||||||
|
_init:
|
||||||
|
push %ebp
|
||||||
|
movl %esp, %ebp
|
||||||
|
/* gcc will nicely put the contents of crtbegin.o's .init section here. */
|
||||||
|
|
||||||
|
.section .fini
|
||||||
|
.global _fini
|
||||||
|
.type _fini, @function
|
||||||
|
_fini:
|
||||||
|
push %ebp
|
||||||
|
movl %esp, %ebp
|
||||||
|
/* gcc will nicely put the contents of crtbegin.o's .fini section here. */
|
10
src/crt/crtn.s
Normal file
10
src/crt/crtn.s
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/* x86 crtn.s */
|
||||||
|
.section .init
|
||||||
|
/* gcc will nicely put the contents of crtend.o's .init section here. */
|
||||||
|
popl %ebp
|
||||||
|
ret
|
||||||
|
|
||||||
|
.section .fini
|
||||||
|
/* gcc will nicely put the contents of crtend.o's .fini section here. */
|
||||||
|
popl %ebp
|
||||||
|
ret
|
109
src/kernel/boot.s
Normal file
109
src/kernel/boot.s
Normal file
@ -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 <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 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
|
23
src/kernel/startup.cpp
Normal file
23
src/kernel/startup.cpp
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "os/tty.hpp"
|
||||||
|
|
||||||
|
/* Check if the compiler thinks you are targeting the wrong operating system. */
|
||||||
|
#if defined(__linux__)
|
||||||
|
#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 "This tutorial needs to be compiled with a ix86-elf compiler"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern "C" void kernel_main(void)
|
||||||
|
{
|
||||||
|
/* Initialize terminal interface */
|
||||||
|
tty::initialize();
|
||||||
|
|
||||||
|
/* Newline support is left as an exercise. */
|
||||||
|
tty::write("Hello, kernel World!\n");
|
||||||
|
}
|
79
src/os/tty.cpp
Normal file
79
src/os/tty.cpp
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
|
||||||
|
#include "os/tty.hpp"
|
||||||
|
|
||||||
|
namespace tty
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
inline const size_t VGA_WIDTH = 80;
|
||||||
|
inline const size_t VGA_HEIGHT = 25;
|
||||||
|
|
||||||
|
constexpr uint8_t vgaEntryColor(VgaDoubleColor color)
|
||||||
|
{
|
||||||
|
return static_cast<uint8_t>(color.foreground) | static_cast<uint8_t>(color.background) << 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr uint16_t vgaEntry(const unsigned char chr, const VgaDoubleColor color)
|
||||||
|
{
|
||||||
|
return static_cast<uint16_t>(chr) | static_cast<uint16_t>(vgaEntryColor(color) << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t gTerminalRow = 0;
|
||||||
|
size_t gTerminalColumn = 0;
|
||||||
|
VgaDoubleColor gTerminalColor = VgaDoubleColor();
|
||||||
|
uint16_t* gTerminalBuffer = reinterpret_cast<uint16_t*>(0xB8000);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t strlen(const char* str) // TODO: move to standard lib
|
||||||
|
{
|
||||||
|
size_t len = 0;
|
||||||
|
while (str[len] != '\0') {
|
||||||
|
++len;
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
void initialize()
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
gTerminalBuffer[index] = vgaEntry(' ', gTerminalColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setColor(VgaDoubleColor color)
|
||||||
|
{
|
||||||
|
gTerminalColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
void putEntryAt(char chr, VgaDoubleColor color, size_t posX, size_t posY)
|
||||||
|
{
|
||||||
|
const size_t index = posY * VGA_WIDTH + posX;
|
||||||
|
gTerminalBuffer[index] = vgaEntry(chr, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
void putChar(char chr)
|
||||||
|
{
|
||||||
|
putEntryAt(chr, gTerminalColor, gTerminalColumn, gTerminalRow);
|
||||||
|
if (++gTerminalColumn == VGA_WIDTH)
|
||||||
|
{
|
||||||
|
gTerminalColumn = 0;
|
||||||
|
if (++gTerminalRow == VGA_HEIGHT)
|
||||||
|
{
|
||||||
|
gTerminalRow = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void write(const char* data, size_t size)
|
||||||
|
{
|
||||||
|
for (size_t idx = 0; idx < size; idx++)
|
||||||
|
{
|
||||||
|
putChar(data[idx]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user