User:Sortie/Meaty Skeleton

From OSDev Wiki
Jump to navigation Jump to search

Note: This is a draft version of the proposed new Meaty Skeleton tutorial. It is being peer reviewed by the community.

This page or section refers to its readers or editors using I, my, we or us. It should be edited to be in an encyclopedic tone.

WAIT! Have you read Getting Started, Beginner Mistakes, and some of the related OS theory?

Difficulty level
Difficulty 1.png
Beginner
Kernel Designs
Models
Other Concepts

This tutorial continues from Bare Bones and creates a minimal template operating system in the Stan Dard style suitable for further modification or as inspiration for your initial operating system version. The Bare Bones tutorial only gives you the absolutely minimal code to demonstrate how to correctly cross-compile a kernel, however this is unsuitable as an example operating system. Additionally, this tutorial implements necessary ABI features needed to satisfy the ABI and compiler contracts to prevent possible mysterious errors.

This tutorial also serves as the initial template tutorial on how to create your own libc (Standard C Library). The GCC documentation explicitly states that libgcc requires the freestanding environment to supply the memcmp, memcpy, memmove, and memset functions, as well as abort on some platforms. We will satisfy this requirement by creating a special kernel C library (libk) that contains the parts of the user-space libc that are freestanding (doesn't require any kernel features) as opposed to hosted libc features that need to do system calls.

This tutorial implements a simple C kernel for i686-elf (32-bit Intel), using a Multiboot 2 bootloader protocol, with a proper terminal driver system with ANSI escape sequences that can output to the VGA text mode, to a graphical buffer, or to the serial line. This tutorial has a full source code example that you can learn from.

This tutorial has been peer reviewed by the osdev community to contain the best practices.

Preface

This tutorial is an example on how you could structure your operating system in a manner that will continue to serve you well for the foreseeable future. This tutorial serves as both inspiration and as an example for those that wish to something different, while serving as a base for the rest. The tutorial does embed a few important concepts into your operating system such as the existence of a libc, proper Unix terminal semantics, as well as indirectly other minor Unix and ABI semantics. Adapt what you wish from this tutorial. Note that the Make-based build system constructed in this tutorial is meant for Unix systems. There is no need to make this tutorial portable across all operating systems as the provided code is just an example.

We will name this new example operating system myos. This is just a placeholder and you should replace all occurrences of myos with what you decide to call your operating system.

First this tutorial will examine the design principles, then the example code of MyOS will follow, and finally this tutorial will explore future directions.

Bare Bones

Main article: Bare Bones

You are expected to have completed the Bare Bones tutorial before continuing to this tutorial. It is not strictly necessary to have completed Bare Bones, but doing so confirms that your development environment works as well as explaining a number of core things.

You should probably discard the code you got from toying around with Bare Bones and start over with this tutorial as your basis.

Building a Cross-Compiler

Main article: GCC Cross-Compiler, Why do I need a Cross Compiler?

You must use a GCC Cross-Compiler in this tutorial as in the Bare Bones tutorial. You should use the i686-elf target in your cross-compiler, though any ix86-elf target (but no less than i386) will do fine for our purposes here.

You must configure your cross-binutils with the --with-sysroot option, otherwise linking will mysteriously fail with the this linker was not configured to use sysroots error message. If you forgot to configure your cross-binutils with that option, you'll have to rebuild it, but you can keep your cross-gcc.

You should configure your cross-gcc with the --enable-initfini-array option to use the new .init_array and .fini_array ABI for global constructors.

Dependencies

You will need these dependencies in order to complete this tutorial:

  • i686-elf toolchain, as discussed above.
  • GRUB, for the grub-mkrescue command, along with the appropriate runtime files.
  • Xorriso, the .iso creation engine used by grub-mkrescue.
  • GNU make 4.0 or later.
  • Qemu, optionally for testing the operating system.

This tutorial requires a GNU/Linux system, or a similar enough system. The BSD systems may almost work. macOS is not supported but can possibly be made to work with some changes. Windows is not supported, but Windows environments like Windows Subsystem For Linux (WSL) should work.

Debian-family Users

Install the i686-elf toolchain as described above and then install the packages xorriso grub-pc-bin.

System Root

Normally when you compile programs for your local operating system, the compiler locates development files such as headers and libraries in system directories such as:

/usr/include
/usr/lib

These files are of course not usable for your operating system. Instead you want to have your own version of these directories that contains files for your operating system:

/home/bwayne/myos/sysroot/usr/include
/home/bwayne/myos/sysroot/usr/lib

The /home/bwayne/myos/sysroot directory acts as a fake root directory for your operating system. This is called a system root, or sysroot.

You can think of the sysroot as the root directory for your operating system. Your build process will build each component of your operating system (kernel, standard library, programs) and gradually install them into the system root. Ultimately the system root will be a fully functional root filesystem for your operating system, you format a partition and copy the files there, add the appropriate configuration files, configure a bootloader to load the kernel from there, and use your harddisk driver and filesystem driver to read the files from there. The system root is thus a temporary directory that will ultimately become the actual root directory of your operating system.

In this example the cross system root is located as sysroot/, which is a directory created by the build scripts and populated by the make install targets. The makefiles will install the system headers into the sysroot/usr/include directory, the system libraries into the sysroot/usr/lib directory and the kernel itself into the sysroot/boot directory.

We already use system roots because it will make it smoother to add a user-space when you get that far. This scheme is very convenient when you later Port Third-Party Software by Cross-Compiling It.

The -elf targets have no user-space and are incapable of having one. We configured the compiler with system root support, so it will look in ${SYSROOT}/usr/lib as expected. We prevented the compiler from searching for a standard library using the --without-headers option when building i686-elf-gcc, so it will not look in ${SYSROOT}/usr/include. (Once you add a user-space and a libc, you will configure your custom cross-gcc with --with-sysroot and it will look in ${SYSROOT}/usr/include. As a temporary work-around until you get that far, we fix it by passing -isystem=/usr/include).

You can change the system root directory layout if you wish, but you will have to modify some Binutils and GCC source code and tell them what your operating system is. This is advanced and not worth doing until you add a proper user-space. Note that the cross-linker currently looks in /lib, /usr/lib and /usr/local/lib by default, so you can move files there without changing Binutils. Also note that we use the -isystem option for GCC (as it was configured without a system include directory), so you can move that around freely.

System Headers

The make sysroot-headers target simply installs the headers for your libc and kernel (system headers) into sysroot/usr/include, but doesn't actually cross-compile your operating system. This is useful as it allows you to provide the compiler a copy of your headers before you actually compile your system. You will need to provide the standard library headers when you build a Hosted GCC Cross-Compiler in the future that is capable of a user-space.

Note how your cross-compiler comes with a number of fully freestanding headers such as stddef.h and stdint.h. These headers simply declare types and macros that are useful. Your kernel standard library will supply a number of useful functions (such as strlen) that doesn't require system calls and are freestanding except they need an implementation somewhere.

The libc and kernel headers cooperate in this example operating system, and work together to provide the standard interfaces in accordance with the [[C|ISO C and POSIX standards. The libc headers such as stdio.h may need to include kernel headers to get important declarations that are ABI specific. In this example OS, the kernel installs a header abi/winsize.h that declares struct winsize. The kernel needs to know the structure for its own internal uses, but it would later be exposed in the libc header termios.h for the tcgetwinsize syscall. Other systems use e.g. the bits/ (glibc) or machine/ directories for the internal ABI headers. Software should never include those headers internally and you can structure them as you please. Sortix for instance has __/foo.h headers that declare important declarations from foo.h but prefixed with __, which allows sharing these declarations without violating C and POSIX namespace rules.

Makefile Design

The top-level makefile iterates each module (libc and kernel) to build it and install it into the sysroot. Every makefile includes the build-aux/common.mk for shared logic about the toolchain and directory structure.

To build MyOS:

make HOST=i686-elf

Or to just build one module:

cd libc && make HOST=i686-elf SYSROOT=../sysroot

The top-level Makefile sets the SYSROOT variable. You will have to override it when building individual modules, otherwise the compiler will use its default sysroot (which was disabled using --without-headers until you build a OS Specific Toolchain).

The build system properly supports cross-compilation with a BUILD system (the machine that you compile MyOS on, e.g. x86_64-linux-gnu) and the HOST system (the machine that MyOS will run on, e.g. i686-elf).

By default, the build system does not cross-compile, and assumes the build system is already running on MyOS. These semantics are how standard software packages work. One day MyOS will be self-hosting and the default will feel natural. As a counter-argument against cross-compilation by default, if two architectures are added, e.g. i686-elf and x86_64-elf, then the build system can't know which one to be the default cross-compilation host.

The makefiles in this example respect the environment variables (such as CFLAGS that tell what default compile options are used to compile C programs). This lets the user control stuff such as which optimization levels are used, while a default is used if the user has no opinion. The makefiles also make sure that particular options are always in CFLAGS. This is done by having two phases in the makefiles: one that sets a default value and one that adds mandatory options the project makefile requires:

# Default CFLAGS:
CFLAGS?=-O2 -g

# Add mandatory options to CFLAGS:
CFLAGS:=$(CFLAGS) -Wall -Wextra

The build-aux/common.mk file has special logic to override the default CC value (since Make provides its own default value, which is not desirable due to the HOST variable), while respecting the environment.

The makefiles use standard Make .SUFFIXES rules such as .c.o to produce a .o file from a .c file, with special variables that contain the inferred input and output files.

The -MD compiler option is used to generate makefile fragments with the header dependencies of each object file. The makefile includes these fragments, and make only recompiles the needed object files when source files and headers are modified. The include statements use a leading hyphen, which means the make continues if the fragments does not exist. The compiler crt files are copied into the kernel directory to ensure the object dependency tracking is working correctly, and to keep the kernel compilation commands simple.

Architecture Directories

The projects in this example (libc and kernel) store all the architecture dependent source files inside an arch/ directory with their own sub-makefile that has special configuration. This cleanly separates the systems you support and will make it easier to port to other systems in the future.

The build system determines the HOST variable and places the name of the architecture-specific directory into the HOSTARCH variable, from which the arch.mk file is included with additional per-architecture logic.

The boot.S _start assembly entry point will invoke the kernel_early C function in the architecture-dependent i386.c file. This file will perform the Multiboot 2 and i386 specific initialization before passing control to the kernel_main function in the platform-independent kernel.c file.

If kernel_early fails, it returns, which causes a hlt loop. There is no panic function in myos yet, and even if there was, there is no safe way to output information until a driver has been initialized for communication.

Kernel Design

We have moved the kernel into its own directory named kernel/. It would perhaps be better to call it something else if your kernel has another name than your full operating system distribution, though calling it kernel/ makes it easier for other hobbyist developers to find the core parts of your new operating system.

The kernel installs its public kernel headers into sysroot/usr/include/kernel. This is useful if you decide to create a kernel with modules, where modules can then simply include the public headers from the main kernel. These are the private kernel headers, whereas sysroot/usr/include/abi contains the public kernel headers in this example.

The kernel implements the correct way of invoking global constructors using the modern .init_array ABI (useful for C++ code and C code using __attribute__((constructor)). The bootstrap assembly calls _init which lets the compiler run its own initialization logic. This logic is likely empty in the new .init_array ABI, but it may add additional logic if you build with various sanization options enabled, and you are supposed to invoke it. Afterwards boot.S iterates the .init_array section and invokes each function pointer, which invokes all the global constructors. This code could be done in C, but is intentionally done in assembly, to avoid a theoretical chicken and egg problem where C code is technically only properly defined after the runtime initialization is complete. These constructors are invoked very early in the boot, and the constructor attribute can ensure they are invoked in a particular order. You should only use them to initialize global variables that could not be initialized at runtime, but this early kernel environment can be dangerously non-initialized and should be avoided and only used per careful rules.

Note how the global constructors require special linker script support.

The special __is_kernel macro lets the source code detect whether it is part of the kernel.

Multiboot2

Main article: Multiboot

GNU GRUB is used as the bootloader and the kernel uses the Multiboot2 bootloader protocol. The Multiboot2 header must be in the first 32768 bytes of the executable with 8-byte alignment. The linker script ensures the header is put first in the myos executable. The invocation of the grub-file program will fail the kernel build if it is not a valid Multiboot2 executable.

Multiboot2 improves on the original Multiboot specification with:

  • Extensible tag system for requesting additional information actions from the bootloader, whereas Multiboot1 had hard-coded abilities.
  • Support for the EFI pointer, which is required to access EFI Runtime Services. Without this ability, MyOS cannot install a bootloader into the EFI non-volatile memory variables.

Multiboot2 requires more complex structures. This example contains a minimal version that has declarations needed for this tutorial only. See the official Multiboot2 specification for the full set of available tags.

This example requests a framebuffer from the bootloader and locates the command line that was given to the kernel by the bootloader configuration.

The provided multiboot tag search code locates the next tag after a tag. Each tag starts on a 8-byte aligned address. The code aligns upwards by negating the size, then aligning downwards with a simple bit mask, and then negating the number again. This trick works due to the properties of unsigned integers, and is very efficient.

libc and libk Design

Main article: Creating a C Library

The libc and libk are actually two versions of the same library, which is stored in the directory libc/. The standard library is split into two versions: freestanding and hosted. The difference is that the freestanding library (libk) doesn't contain any of the code that only works in user-space, such as system calls. The libk is also built with different compiler options, just like the kernel isn't built like normal user-space code.

You are not required to have a libk. You could just as easily have a regular libc and a fully separate minimal project inside the kernel directory. The libk scheme avoids code duplication, so you don't have to maintain multiple versions of strlen and such.

This example doesn't come with a usable libc. It could compile a libc.a that is entirely useless, except being a skeleton we can build on when we add user-space in a later tutorial. The libc archive is disabled until you're ready to turn it on.

Note how the libc and libk split allows the functions such as abort to share logic, but have different backends and implementations. The libc variant would invoke a system call, whereas the libk variant would directly invoke a function in the kernel.

Each standard function is put inside a file with the same name as the function inside a directory with the name of the header. For instance, strlen from string.h is in libc/string/strlen.c and stat from sys/stat.h would be in libc/sys/stat/stat.c.

The standard headers use a BSD-like scheme where sys/cdefs.h declares a bunch of useful preprocessor macros meant for internal use by the standard library. All the function prototypes are wrapped in extern "C" { and } such that C++ code can correctly link against libc (as libc doesn't use C++ linkage). Note also how the compiler provides the internal keyword __restrict unconditionally (even in C89 mode), which is useful for adding the restrict keyword to function prototypes even when compiling code in pre-C99 or C++ mode.

The special __is_libc macro lets the source code detect whether it is part of the libc and __is_libk lets the source code detect whether it's part of the libk binary.

This example comes with a small number of standard functions that serve as examples and serve to satisfy ABI requirements. Note that the printf function included is very minimal and intentionally doesn't handle most common features.

Font Rendering

The Bare Bones tutorial uses the VGA text mode. However, this legacy mode is not always available on modern hardware, and is very limited.

This tutorial requests a 32-bit graphical framebuffer from the bootloader via a multiboot2 tag. The bootloader will inform the kernel what mode was selected in a multiboot2 tag, and it will load the appropriate driver.

A new graphical framebuffer screen driver fb implements font rendering using the VGA font. It is possible to request the VGA font from the VGA hardware, but it may not be available, and this example kernel instead has its own copy of the font in vgafont.h. The font contains VGA glyphs per Codepage 437, which overlaps with ASCII, but the character set is not Latin-1 nor unicode. Each glyph is 16 pixels tall and 9 pixels wide. The 9th column is the same as the 8th column, meaning that each glyph requires 1 byte (8 bits) per row, for a total of 16 bytes per glyph. This driver emulates the rendering of the VGA text mode, so the drivers have the same capabilities, except the fb driver is capable of 32-bit RGB graphics.

It is expensive to read from the framebuffer. For these reasons, the fb driver keeps a grid of which characters have been rendered to each cell, which is used to efficiently re-render when scrolling. Note how the bootloader can select an arbitrary resolution, but the kernel does not implement dynamic memory allocation. For these reasons, the screen is limited to a 256x128 hard-coded window size, which is enough for a 2304x2048 pixel display. This limitation can be lifted once memory allocation is implemented.

The vga driver supports just the VGA text mode, and is used if the bootloader is unable to configure a graphical mode.

The font rendering used here does not implement the cursor. It is left as an exercise for the reader. The VGA hardware will need to be programmed, and it would need to be manually rendered on the framebuffer. Note the special case where the cursor is at the edge of the screen, but a new line has not begun yet.

The font rendering is limited to 32-bit RGB graphics with a standard color channel ordering. It is left as an exercise to the reader to support indexed color palettes, 24-bit color channels, and other color channel orders.

Terminal

Main article: Terminals

This tutorial implements a properly structured Unix terminal system. The semantics are subtle and important. The example here is complex, but the structure is very intentional, and any proper Unix system will end up with this structure. One can avoid a lot of problems and complexity by sticking to this design.

Let's examine this deceptively simple line in kernel/kernel/kernel.c:

printf("Hello, \e[31mkernel \e[91;44mWorld\e[m!\n");

printf is provided in libk and handles sequences such as %c and %s, which makes it a powerful tool to format strings. Additional sequences such as %d and %f will need to be implemented later in libc to provide all the standard features. Each produced output character is sent to the putchar function. In the future, printf will need to become a generic engine with a backend such as vcbprintf to power the many standard variants of the function.

putchar outputs a single byte. libk integrates with the kernel by invoking the console_write function. The libc variant would do something else in the future, such as invoking fputc to buffer the byte on a stdio FILE stream.

console_write will write a byte to the kernel console using tty_write. The kernel has a number of terminals, and one of them is chosen during boot to be the kernel console, where important messages are written.

tty_write performs terminal termios line discipline output processing and invokes the transmit function pointer on the underlying tty driver. The output processing is configured using the tcsetattr system call on a real Unix system. Normally the c_oflag OPOST and ONLCR bits are set, which translates the \n byte to the \r\n sequence. Since these bits don't exist yet in MyOS, this line discipline logic does the newline translation unconditionally. Note how the \n byte only goes to the newline on a physical terminal device, but it does not return the cursor to the beginning of the line. This is why the \r carriage return character is required.

.transmit is invoked in the driver with an output byte. The ttyS ttyS_transmit tty serial driver will directly write the byte to the hardware register to transmit it on the serial line. The vt vt_transmit tty driver instead implements a virtual terminal connected to a screen driver, and maintains the window size, the cursor position, and other terminal emulator state. Ordinary characters are rendered by invoking the set_char method on the screen, while special sequences are sent to the vt_escaped function.

vt_escaped processes the next byte in an ANSI escape sequence. These sequences start with a \e (ESC) ASCII byte. The next byte is normally a [ byte, but other kinds of sequences exist. Additional numeric parameters follow, each separated by a semicolon. The sequence ends with a letter in the ASCII range @ to ~, after which vt_run_ansi is invoked to execute the command.

vt_run_ansi executes the escape sequence after the parameters have been parsed. For instance, \e[32;43m switches to font color 2 and background color 3. The ansi_to_vga_color array is used to find the appropriate VGA text mode color and the palette array is used to find the appropriate RGB color. Many other standardized escape sequences exist, see e.g. the venerable VT100 terminal and the many extension sets.

.set_char renders the character and its metadata (vga color, foreground color, background color, additional attributes) using the screen driver. The vga vga_set_char driver will simply copy the character to the screen using the VGA memory. The fb fb_set_char driver will use the emulated VGA font rendering (see above) to draw the character directly on the framebuffer.

Drivers

This example implements two tty drivers: vt for a local virtual terminal and ttyS for a serial line terminal. The vt driver is connected to a screen driver.

This example implements two screen drivers: vga for rendering text to the VGA text mode, and fb for rendering text to a graphical framebuffer.

The vt driver provides a single tty1 device at this time, which is connected to the appropriate screen driver, based on the graphics settings selected by the bootloader.

The ttyS driver provides a single ttyS0 device connected to the first traditional serial line. A proper driver would do a lot more careful initialization and the kernel command line would inform it about the baud settings, the terminal window dimensions, and the possibly the initial TERM environment variable value. This driver will work in virtual machines and possibly on real hardware, depending on the initialization done by the firmware and the bootloader. The driver uses x86 Port IO, which is outside the scope of this tutorial.

The kernel selects which terminal is the kernel console based on its --console= command line option. The tty1 terminal is the default. This way the kernel can be dynamically configured to output using arbitrary drivers.

Drivers are implemented by declaring interfaces, which can have multiple implementations. This example uses idiomatic C style driver interfaces as found in other modern C kernels. For instance, struct tty starts with a pointer to its struct tty_ops object, which contains the function pointers for the driver. struct tty is the live instance object, and struct screen_ops is the static method table. This design is equivalent to but distinct from the vtable approach the C++ programming language. The container_of macro is used to safely downcast a pointer from a base class to the appropriate subclass, which allows better refactoring as well as polymorphic multiple-inheritance object-oriented programming.

Paging and Memory Allocation

Main article: Paging

The example in this tutorial does not have paging or memory allocation. Paging is one of the most essential parts of a kernel and must be implemented early on as one of the next steps. Paging must be enabled in order to enter 64-bit mode on X86-64.

A basic identity mapping of the kernel should be implemented in boot.S in the future. Note how the multiboot2 structures could be located anywhere in memory, and it is mandatory to carefully map those structures into memory as they are explored. The multiboot2 information is linear in memory and its size is stored in the first page, so it's easy to safely map it. Note also how the graphical framebuffer could also be anywhere in memory, and also must be mapped separately. This example simply avoids these concerns because paging is disabled and the structures can be assumed to be identity mapped.

Source Code

You can easily download the source code using Git from the Meaty Skeleton Git repository. This is preferable to doing a manual error-prone copy, as you may make a mistake or whitespace may get garbled due to bugs in our syntax highlighting. To clone the git repository, do:

git clone https://gitlab.com/sortie/meaty-skeleton.git -b new

Check for differences between the git revision used in this article and what you cloned (empty output means there is no difference):

git diff e7b93d8c5ce5a110371bf004875d4e4fc896b31c..new

Operating systems development is about being an expert. Take the time to read the code carefully through and understand it. Please seek further information and help if you don't understand aspects of it. This code is minimal and almost everything is done deliberately, often to pre-emptively solve future problems.

kernel

These files go into the kernel directory.

kernel/.gitignore

myos
*.d
*.o

kernel/Makefile

# To cross-compile MyOS: make HOST=i386-elf SYSROOT=../sysroot
# By default the build system will assume it's running on MyOS.

include ../build-aux/common.mk

CFLAGS?=-O2 -g
CPPFLAGS?=
LDFLAGS?=
LIBS?=

CFLAGS:=$(CFLAGS) -ffreestanding -Wall -Wextra
CPPFLAGS:=$(CPPFLAGS) -D__is_kernel -Iinclude
LDFLAGS:=$(LDFLAGS)
LIBS:=$(LIBS) -nostdlib -lk -lgcc

ARCHDIR=arch/$(HOSTARCH)

-include $(ARCHDIR)/arch.mk

CFLAGS:=$(CFLAGS) $(KERNEL_ARCH_CFLAGS)
CPPFLAGS:=$(CPPFLAGS) $(KERNEL_ARCH_CPPFLAGS)
LDFLAGS:=$(LDFLAGS) $(KERNEL_ARCH_LDFLAGS)
LIBS:=$(LIBS) $(KERNEL_ARCH_LIBS)

KERNEL_OBJS=\
$(KERNEL_ARCH_OBJS) \
kernel/fb.o \
kernel/kernel.o \
kernel/tty.o \
kernel/vt.o \

OBJS=\
$(ARCHDIR)/crti.o \
$(ARCHDIR)/crtbegin.o \
$(KERNEL_OBJS) \
$(ARCHDIR)/crtend.o \
$(ARCHDIR)/crtn.o \

LINK_LIST=\
$(LDFLAGS) \
$(ARCHDIR)/crti.o \
$(ARCHDIR)/crtbegin.o \
$(KERNEL_OBJS) \
$(LIBS) \
$(ARCHDIR)/crtend.o \
$(ARCHDIR)/crtn.o \

.PHONY: all clean distclean install install-headers install-kernel
.SUFFIXES: .o .c .S

all: myos

myos: $(OBJS) $(ARCHDIR)/linker.ld
	$(CC) -T $(ARCHDIR)/linker.ld -o $@ $(CFLAGS) $(LINK_LIST)
	grub-file --is-x86-multiboot2 myos

$(ARCHDIR)/crtbegin.o $(ARCHDIR)/crtend.o:
	OBJ=`$(CC) $(CFLAGS) $(LDFLAGS) -print-file-name=$(@F)` && cp "$$OBJ" $@

.c.o:
	$(CC) -MD -c $< -o $@ -std=gnu11 $(CFLAGS) $(CPPFLAGS)

.S.o:
	$(CC) -MD -c $< -o $@ $(CFLAGS) $(CPPFLAGS)

clean:
	rm -f myos
	find . -name "*.o" -delete
	find . -name "*.d" -delete

distclean: clean

install: install-headers install-kernel

install-headers:
	mkdir -p $(DESTDIR)$(INCLUDEDIR)
	cp -R --preserve=timestamps include/. $(DESTDIR)$(INCLUDEDIR)/.

install-kernel: myos
	mkdir -p $(DESTDIR)$(BOOTDIR)
	cp myos $(DESTDIR)$(BOOTDIR)

-include $(OBJS:.o=.d)

kernel/arch/i386/arch.mk

KERNEL_ARCH_CFLAGS=
KERNEL_ARCH_CPPFLAGS=
KERNEL_ARCH_LDFLAGS=
KERNEL_ARCH_LIBS=

KERNEL_ARCH_OBJS=\
$(ARCHDIR)/boot.o \
$(ARCHDIR)/i386.o \
$(ARCHDIR)/ttyS.o \
$(ARCHDIR)/vga.o \

kernel/arch/i386/boot.S

# Declare Multiboot2 header.
.section .multiboot2
	.align 8
multiboot2:
	# The multiboot 2 header must be within the first 32768 bytes of the kernel,
	# and declares the architecture and an arbitrary amount of tags with
	# additional requests for the bootloader. The boot will fail if mandatory
	# tags are not supported by the bootloader.
	.set MAGIC, 0xE85250D6 # The bootloader searches for this magic number
	.set ARCHITECTURE, 0   # i386
	.long MAGIC
	.long ARCHITECTURE
	.long .Lmultiboot2_end - multiboot2 # Length
	.long -(MAGIC + ARCHITECTURE + .Lmultiboot2_end - multiboot2) # Checksum

	# Request the bootloader provide us with the following information in the
	# multiboot information structures.
.Lmbi_start:
	.align 8
	.short 1 # Multiboot2 information request tag
	.short 0x1 # Flags (mandatory tag)
	.long .Lmbi_end - .Lmbi_start # Size
	.long 1 # Boot command line
	.long 3 # Modules
	.long 6 # Memory map
	.long 8 # Framebuffer info
.Lmbi_end:

	# Request a 32-bit framebuffer if available.
	.align 8
	.short 5 # Framebuffer tag
	.short 0x0 # Flags (optional)
	.long 20 # Size
	.long 0 # Width (no preference)
	.long 0 # Height (no preference)
	.long 32 # Depth

	# Request that modules are page aligned and do not overlap with any other
	# information. This behavior makes it possible to reclaim those pages.
	.align 8
	.short 6 # Module alignment tag
	.short 0x1 # Flags (mandatory tag)
	.long 8 # Size

	# End of multiboot tags.
	.align 8
	.short 0 # End tag
	.short 0x0 # Flags (optional tag)
	.long 8 # Size
.size multiboot2, . - multiboot2
.Lmultiboot2_end:

# Reserve a stack for the initial thread.
.section .bss
	.align 16
stack_bottom:
	.skip 16384 # 16 KiB
stack_top:

# The kernel entry point.
.section .text
.global _start
.type _start, @function
_start:
	# Use the stack for the initial thread.
	movl $stack_top, %esp

	# Prepare parameters for kernel_early.
	subl $8, %esp # 16-byte align stack for call
	push %ebx # Multiboot2 pointer (to kernel_early)
	push %eax # Multiboot2 magic (to kernel_early)

	# Run the compiler initialization code.
	call _init

	# Run the global constructors by executing the init array function pointers.
	mov $__init_array_start, %ebx
.Lnext:
	cmp $__init_array_end, %ebx
	jae .Ldone
	call *(%ebx)
	add $4, %ebx
	jmp .Lnext
.Ldone:

	# Execute the kernel.
	call kernel_early

	# Hang if the kernel returns.
	cli
1:	hlt
	jmp 1b
.size _start, . - _start

.section bss
	.align 4
	.weak __init_array_end
	.weak __init_array_start
	.hidden __init_array_end
	.hidden __init_array_start

kernel/arch/i386/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. */

kernel/arch/i386/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

kernel/arch/i386/i386.c

#include <stdint.h>
#include <string.h>

#include <kernel/fb.h>
#include <kernel/kernel.h>
#include <kernel/screen.h>
#include <kernel/tty.h>
#include <kernel/vt.h>

#include "multiboot2.h"
#include "ttyS.h"
#include "vga.h"

struct fb fb;
struct vga vga;
struct ttyS ttyS1_tty;
struct vt tty1_tty;

void kernel_early(uint32_t magic, struct multiboot2_info* multiboot2) {
	// Verify the multiboot2 magic number.
	if (magic != MULTIBOOT2_BOOTLOADER_MAGIC)
		return;

	// Locate the kernel command line options.
	struct multiboot2_tag_string* cmdline_tag = (struct multiboot2_tag_string*)
		multiboot2_tag_lookup(multiboot2, MULTIBOOT2_TAG_TYPE_CMDLINE);
	const char* cmdline = cmdline_tag ? cmdline_tag->string : "";

	// Create a /dev/ttyS0 device for the first serial line.
	// The appropriate settings for each serial port will vary, and the command
	// line should be able to inform the kernel about the port, the baud
	// settings, the window dimensions, the TERM variable, and so on. For now,
	// assume traditional values.
	struct tty* ttyS0 = ttyS_init(&ttyS1_tty, 0x3F8, 80, 25);

	// Try to locate a multiboot2 tag with framebuffer information.
	struct multiboot2_tag_framebuffer* fbinfo =
		(struct multiboot2_tag_framebuffer*)
		multiboot2_tag_lookup(multiboot2, MULTIBOOT2_TAG_TYPE_FRAMEBUFFER);
	struct multiboot2_tag_framebuffer_common* fb_common =
		fbinfo ? &fbinfo->common : NULL;

	// Initialize a screen for tty1 on the framebuffer.
	struct screen* tty1_screen;
	if (!fb_common ||
	    fb_common->framebuffer_type == MULTIBOOT2_FRAMEBUFFER_TYPE_EGA_TEXT) {
		// Use the VGA text mode.
		tty1_screen = vga_init(&vga);
	} else if (fb_common->framebuffer_type == MULTIBOOT2_FRAMEBUFFER_TYPE_RGB) {
		// Use a RGB framebuffer.
		// Verify the framebuffer is 32-bit.
		if (fb_common->framebuffer_bpp != 32)
			return;
		tty1_screen = fb_init(
			&fb, fb_common->framebuffer_addr,
			fb_common->framebuffer_width, fb_common->framebuffer_height,
			fb_common->framebuffer_pitch, fb_common->framebuffer_bpp);
	} else {
		// Unsupported framebuffer type.
		return;
	}

	// Create a /dev/tty1 device connected to the screen.
	struct tty* tty1 = vt_init(&tty1_tty, tty1_screen);

	// Select the appropriate terminal device as the kernel /dev/console.
	if (!strcmp(cmdline, "--console=ttyS0")) {
		dev_console = ttyS0;
	} else { // --console=tty1 is default
		dev_console = tty1;
	}

	// Run the main kernel.
	kernel_main();
}

kernel/arch/i386/ioport.h

#ifndef ARCH_I386_IOPORT_H
#define ARCH_I386_IOPORT_H

#include <stdint.h>

__attribute__((unused))
static inline uint8_t outport8(uint16_t port, uint8_t value)
{
	asm volatile ("outb %1, %0" : : "dN" (port), "a" (value));
	return value;
}

__attribute__((unused))
static inline uint8_t inport8(uint16_t port)
{
	uint8_t result;
	asm volatile("inb %1, %0" : "=a" (result) : "dN" (port));
	return result;
}

#endif

kernel/arch/i386/linker.ld

/* 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 multiboot2 header, as it is required to be put in the first
	   32768 bytes in the image or the bootloader won't recognize the format.
	   After we'll put the .text section. */
	.text BLOCK(4K) : ALIGN(4K)
	{
		*(.multiboot2)
		*(.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)
	}

	/* Modern global constructors */
	.init_array :
	{
		PROVIDE_HIDDEN (__init_array_start = .);
		KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
		KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
		PROVIDE_HIDDEN (__init_array_end = .);
	}

	/* Modern global destructors */
	.fini_array :
	{
		PROVIDE_HIDDEN (__fini_array_start = .);
		KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
		KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
		PROVIDE_HIDDEN (__fini_array_end = .);
	}

	/* Compiler process initialization */
	.ctors :
	{
		KEEP (*crtbegin.o(.ctors))
		KEEP (*crtbegin?.o(.ctors))
		KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
		KEEP (*(SORT(.ctors.*)))
		KEEP (*(.ctors))
	}

	/* Compiler process finalization */
	.dtors :
	{
		KEEP (*crtbegin.o(.dtors))
		KEEP (*crtbegin?.o(.dtors))
		KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
		KEEP (*(SORT(.dtors.*)))
		KEEP (*(.dtors))
	}

	/* The compiler may produce other sections, put them in the proper place in
	   in this file, if you'd like to include them in the final kernel. */
}

kernel/arch/i386/multiboot2.h

#ifndef ARCH_I386_MULTIBOOT2_H
#define ARCH_I386_MULTIBOOT2_H

#include <stdint.h>

#define MULTIBOOT2_BOOTLOADER_MAGIC 0x36d76289

struct multiboot2_info {
	uint32_t total_size;
	uint32_t reserved;
};

struct multiboot2_tag {
	uint32_t type;
	uint32_t size;
};

#define MULTIBOOT2_TAG_TYPE_CMDLINE 1

struct multiboot2_tag_string {
	uint32_t type;
	uint32_t size;
	char string[];
};

#define MULTIBOOT2_TAG_TYPE_FRAMEBUFFER 8

#define MULTIBOOT2_FRAMEBUFFER_TYPE_INDEXED 0
#define MULTIBOOT2_FRAMEBUFFER_TYPE_RGB 1
#define MULTIBOOT2_FRAMEBUFFER_TYPE_EGA_TEXT 2

struct multiboot2_tag_framebuffer_common {
	uint32_t type;
	uint32_t size;
	uint64_t framebuffer_addr;
	uint32_t framebuffer_pitch;
	uint32_t framebuffer_width;
	uint32_t framebuffer_height;
	uint8_t framebuffer_bpp;
	uint8_t framebuffer_type;
	uint16_t reserved;
};

struct multiboot2_color {
	uint8_t red;
	uint8_t green;
	uint8_t blue;
};

struct multiboot2_tag_framebuffer {
	struct multiboot2_tag_framebuffer_common common;
	union {
		struct {
			uint16_t framebuffer_palette_num_colors;
			struct multiboot2_color framebuffer_palette[0];
		};
		struct {
			uint8_t framebuffer_red_field_position;
			uint8_t framebuffer_red_mask_size;
			uint8_t framebuffer_green_field_position;
			uint8_t framebuffer_green_mask_size;
			uint8_t framebuffer_blue_field_position;
			uint8_t framebuffer_blue_mask_size;
		};
	};
};

// Find the first tag in the multiboot2 information.
static inline
struct multiboot2_tag* multiboot2_tag_begin(struct multiboot2_info* info) {
	uintptr_t ptr = (uintptr_t) info;
	ptr += sizeof(*info);
	ptr = -(-ptr & ~7UL); // Align upwards: Negate, align downwards, negate.
	struct multiboot2_tag* tag = (struct multiboot2_tag*) ptr;
	return tag->type == 0 ? NULL : tag;
}

// Find the next tag following an existing tag.
static inline
struct multiboot2_tag* multiboot2_tag_next(struct multiboot2_tag* tag) {
	uintptr_t ptr = (uintptr_t) tag;
	ptr += tag->size;
	ptr = -(-ptr & ~7UL);
	tag = (struct multiboot2_tag*) ptr;
	return tag->type == 0 ? NULL : tag;
}

// Search through the multiboot2 tags for a tag with the desired type.
static inline
struct multiboot2_tag* multiboot2_tag_lookup(struct multiboot2_info* info,
                                             uint32_t type) {
	struct multiboot2_tag* tag = multiboot2_tag_begin(info);
	while (tag) {
		if (tag->type == type)
			return tag;
		tag = multiboot2_tag_next(tag);
	}
	return NULL;
}

#endif

kernel/arch/i386/ttyS.c

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>

#include <kernel/kernel.h>
#include <kernel/tty.h>

#include "ioport.h"
#include "ttyS.h"

// Check whether the serial port is ready to transmit another byte.
static bool serial_can_transmit(uint16_t port) {
	return inport8(port + 5) & 0x20;
}

// Transmit a byte on a serial port when it becomes possible to transmit.
void serial_write(uint16_t port, unsigned char byte) {
	while (!serial_can_transmit(port)) {
	}
	outport8(port, byte);
}

// Initialize a ttyS object connected to a console driver.
struct tty* ttyS_init(struct ttyS* self, uint16_t port, unsigned short width,
                      unsigned short height) {
	memset(self, 0, sizeof(*self));
	self->base.ops = &ttyS_ops;
	self->port = port;
	self->width = width;
	self->height = height;
	// Real hardware may require real initialization of settings such as baud.
	return &self->base;
}

// Process the next output byte.
static bool ttyS_transmit(struct tty* base, unsigned char uc) {
	struct ttyS* self = container_of(base, struct ttyS, base);
	serial_write(self->port, uc);
	return true;
}

// Get the terminal window size.
static int ttyS_tcgetwinsize(struct tty* base, struct winsize* ws) {
	struct ttyS* self = container_of(base, struct ttyS, base);
	ws->ws_col = self->width;
	ws->ws_row = self->height;
	return 0;
}

const struct tty_ops ttyS_ops = {
	.transmit = ttyS_transmit,
	.tcgetwinsize = ttyS_tcgetwinsize,
};

kernel/arch/i386/ttyS.h

#ifndef ARCH_I386_TTYS_H
#define ARCH_I386_TTYS_H

#include <stdint.h>

#include <kernel/tty.h>

void serial_write(uint16_t port, unsigned char byte);

// A ttyS device implements a tty connected to a hardware serial line.
struct ttyS {
	struct tty base;
	uint16_t port;
	unsigned short width;
	unsigned short height;
};

extern const struct tty_ops ttyS_ops;

struct tty* ttyS_init(struct ttyS* self, uint16_t port, unsigned short width,
                      unsigned short height);

#endif

kernel/arch/i386/vga.c

#include <stddef.h>
#include <stdint.h>
#include <string.h>

#include <kernel/screen.h>
#include <kernel/kernel.h>
#include <kernel/vga.h>

#include "vga.h"

static uint16_t* const VGA_MEMORY = (uint16_t*) 0xB8000;
static const size_t VGA_WIDTH = 80;
static const size_t VGA_HEIGHT = 25;

// Format a VGA text mode entry with the console character and color.
static inline uint16_t vga_entry_from_sc(const struct screen_char* sc) {
	return vga_entry(sc->uc, sc->vgacolor);
}

// Initialize a VGA console.
struct screen* vga_init(struct vga* self) {
	memset(self, 0, sizeof(*self));
	self->base.ops = &vga_ops;
	self->memory = VGA_MEMORY;
	self->width = VGA_WIDTH;
	self->height = VGA_HEIGHT;
	return &self->base;
}

// Get the window size.
static void vga_tcgetwinsize(struct screen* base, struct winsize* ws) {
	struct vga* self = container_of(base, struct vga, base);
	ws->ws_col = self->width;
	ws->ws_row = self->height;
}

// Clear the console.
static void vga_clear(struct screen* base, const struct screen_char* fill) {
	struct vga* self = container_of(base, struct vga, base);
	uint16_t entry = vga_entry_from_sc(fill);
	for (unsigned short y = 0; y < self->height; y++) {
		for (unsigned short x = 0; x < self->width; x++) {
			size_t index = y * self->width + x;
			self->memory[index] = entry;
		}
	}
}

// Set a character on the console.
static void vga_set_char(struct screen* base, const struct screen_char* sc,
                                 unsigned short x, unsigned short y) {
	struct vga* self = container_of(base, struct vga, base);
	size_t index = y * self->width + x;
	uint16_t entry = vga_entry_from_sc(sc);
	self->memory[index] = entry;
}

// Scroll the console one line.
static void vga_scroll(struct screen* base, const struct screen_char* fill) {
	struct vga* self = container_of(base, struct vga, base);
	for (unsigned short y = 0; y < self->height - 1; y++) {
		for (unsigned short x = 0; x < self->width; x++) {
			size_t to = y * self->width + x;
			size_t from = (y + 1) * self->width + x;
			self->memory[to] = self->memory[from];
		}
	}
	uint16_t entry = vga_entry_from_sc(fill);
	for (unsigned short x = 0; x < self->width; x++) {
		unsigned short y = self->height - 1;
		size_t index = y * self->width + x;
		self->memory[index] = entry;
	}
}

const struct screen_ops vga_ops = {
	.tcgetwinsize = vga_tcgetwinsize,
	.clear = vga_clear,
	.set_char = vga_set_char,
	.scroll = vga_scroll,
};

kernel/arch/i386/vga.h

#ifndef ARCH_I386_VGA_H
#define ARCH_I386_VGA_H

#include <stdint.h>

#include <kernel/screen.h>

struct vga {
	struct screen base;
	uint16_t* memory;
	unsigned short width;
	unsigned short height;
};

extern const struct screen_ops vga_ops;

struct screen* vga_init(struct vga* self);

#endif

kernel/include/abi/winsize.h

#ifndef _ABI_WINSIZE_H
#define _ABI_WINSIZE_H

struct winsize {
	unsigned short ws_row;
	unsigned short ws_col;
};

#endif

kernel/include/kernel/fb.h

#ifndef _KERNEL_FB_H
#define _KERNEL_FB_H

#include <stdint.h>

#include <kernel/screen.h>

struct fb {
	struct screen base;
	uint64_t addr;
	uint32_t width;
	uint32_t height;
	uint32_t pitch;
	uint32_t bpp;
	uint32_t* buffer;
	unsigned short columns;
	unsigned short rows;
	struct screen_char* grid;
};

extern const struct screen_ops fb_ops;

struct screen* fb_init(struct fb* self, uint64_t addr, uint32_t width,
                       uint32_t height, uint32_t pitch, uint32_t bpp);

#endif

kernel/include/kernel/kernel.h

#ifndef _KERNEL_KERNEL_H
#define _KERNEL_KERNEL_H

#include <stddef.h>

#define container_of(ptr, type, member) \
	((type*) ((char*) (ptr) - offsetof(type, member)))

void kernel_main(void);

#endif

kernel/include/kernel/screen.h

#ifndef _KERNEL_SCREEN_H
#define _KERNEL_SCREEN_H

#include <stddef.h>
#include <stdint.h>

#include <abi/winsize.h>

// A generic screen character with attributes and colors, which can be
// rendered with the best approximation possible by the console drivers.
struct screen_char {
	unsigned char uc;
	uint8_t vgacolor;
	uint16_t attr;
	uint32_t fg;
	uint32_t bg;
};

// Base structure for the screen interface.
struct screen {
	const struct screen_ops* ops;
};

// A console provides a two-dimensional grid of characters that can be updated
// and re-rendered by drivers using these efficient functions.
struct screen_ops {
	void (*tcgetwinsize)(struct screen* self, struct winsize* ws);
	void (*clear)(struct screen* self, const struct screen_char* fill);
	void (*set_char)(struct screen* self, const struct screen_char* sc,
	                 unsigned short x, unsigned short y);
	void (*scroll)(struct screen* self, const struct screen_char* fill);
};

#endif

kernel/include/kernel/tty.h

#ifndef _KERNEL_TTY_H
#define _KERNEL_TTY_H

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

#include <abi/winsize.h>

// Base structure for the tty interface.
struct tty {
	const struct tty_ops* ops;
};

// A tty is a terminal implemented by a driver.
//
// Terminals including the tty_console, which graphically renders to a local
// console framebuffer, but can also be a serial driver that communicates with
// a hardware device subject to line discipline, or a pty pseudoterminal
// entirely in software.
struct tty_ops {
	bool (*transmit)(struct tty* self, unsigned char uc);
	int (*tcgetwinsize)(struct tty* self, struct winsize* ws);
};

size_t tty_write(struct tty* self, const void* buf, size_t len);

extern struct tty* dev_console;

size_t console_write(const void* data, size_t size);
int console_tcgetwinsize(struct winsize* ws);

#endif

kernel/include/kernel/vga.h

#ifndef _KERNEL_VGA_H
#define _KERNEL_VGA_H

#include <stdint.h>

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,
};

static inline uint8_t vga_entry_color(enum vga_color fg, enum vga_color bg) {
	return fg | bg << 4;
}

static inline uint16_t vga_entry(unsigned char uc, uint8_t color) {
	return (uint16_t) uc | (uint16_t) color << 8;
}

#endif

kernel/include/kernel/vt.h

#ifndef _KERNEL_VT_H
#define _KERNEL_VT_H

#include <stdint.h>

#include <kernel/tty.h>

enum ansi_state {
	ANSI_STATE_NONE,
	ANSI_STATE_CSI,
	ANSI_STATE_COMMAND,
};

#define ANSI_PARAM_MAX 16

// A vt (virtual terminal) implements a tty connected to a screen.
//
// It handles escape code processing and allows efficient re-renders using an
// arbitrary screen driver.
struct vt {
	struct tty base;
	unsigned short row;
	unsigned short column;
	unsigned short width;
	unsigned short height;
	uint8_t vgacolor;
	uint16_t attr;
	uint32_t fgcolor;
	uint32_t bgcolor;
	struct screen* screen;
	enum ansi_state ansi_state;
	unsigned int ansi_params[ANSI_PARAM_MAX];
	size_t ansi_count;
};

extern const struct tty_ops vt_ops;

struct tty* vt_init(struct vt* self, struct screen* screen);

#endif

kernel/kernel/fb.c

#include <stddef.h>
#include <stdint.h>
#include <string.h>

#include <kernel/fb.h>
#include <kernel/kernel.h>
#include <kernel/screen.h>

#define FONT_REALWIDTH 8
#define FONT_WIDTH 9
#define FONT_HEIGHT 16
#define FONT_CHARSIZE (FONT_REALWIDTH * FONT_HEIGHT / 8)
#define FONT_NUMCHARS 256

#define MAX_COLUMNS 256
#define MAX_ROWS 128

#include "vgafont.h"

// Initialize a framebuffer console.
struct screen* fb_init(struct fb* self, uint64_t addr, uint32_t width,
                       uint32_t height, uint32_t pitch, uint32_t bpp) {
	memset(self, 0, sizeof(*self));
	self->base.ops = &fb_ops;
	self->addr = addr;
	self->width = width;
	self->height = height;
	self->pitch = pitch;
	self->bpp = bpp;
	// Paging is not implemented. addr is a 64-bit physical address. For now,
	// simply truncate it to pointer size and reinterpret it as a pointer.
	self->buffer = (uint32_t*) (uintptr_t) addr;
	self->columns = width / 9;
	self->rows = height / 16;
	// Dynamic memory allocation is not implemented. For now, simply hard-code
	// a reasonably large grid of console characters on the screen, which is
	// used to redraw without expensive reads from the framebuffer. This only
	// works once because it's a global variable.
	if (MAX_COLUMNS < self->columns)
		self->columns = MAX_COLUMNS;
	if (MAX_ROWS < self->rows)
		self->rows = MAX_ROWS;
	static struct screen_char static_grid[MAX_COLUMNS * MAX_ROWS];
	self->grid = static_grid;
	return &self->base;
}

// Render a character using the hard-coded VGA font. It is possible to query
// the VGA hardware for the font, but this solution ensures a font is always
// available, regardless of the hardware.
static void fb_render(struct fb* self, unsigned short col, unsigned short row) {
	// The VGA font uses 16 pixel tall and 9 pixel wide characters, but the 9th
	// column repeats the 8th column. This way each row is one byte (8 bits).
	// Each character takes 16 bytes in the font. The font contains 256
	// characters numbered per Code Page 437, which matches ASCII for the
	// printable characters. There is no unicode support right now.
	// For Unicode support, one would switch struct screen_char to wchar_t, do
	// UTF-8 decoding in tty_console, translate the unicode codepoint here to
	// the best CP437 approximation, and otherwise render a U+FFFD codepoint.
	const struct screen_char* sc = &self->grid[row * MAX_COLUMNS + col];
	const uint8_t* charfont = vgafont + 16 * sc->uc;
	for (size_t y = 0; y < FONT_HEIGHT; y++) {
		size_t buffer_pitch = self->pitch / sizeof(self->buffer[0]);
		uint32_t* data = self->buffer +
		                 buffer_pitch * (FONT_HEIGHT * row + y) +
		                 col * FONT_WIDTH;
		uint8_t line_bitmap = charfont[y];
		for (size_t x = 0; x < FONT_REALWIDTH; x++)
			data[x] = line_bitmap & 1U << (7 - x) ? sc->fg : sc->bg;
		uint32_t last_color = sc->bg;
		// Repeat the 8th column as the 9th column, except for the
		// Box-drawing characters in Code Page 437.
		if (0xB0 <= sc->uc && sc->uc <= 0xDF && (line_bitmap & 1))
			last_color = sc->fg;
		data[FONT_REALWIDTH] = last_color;
	}
}

// Get the window size.
static void fb_tcgetwinsize(struct screen* base, struct winsize* ws) {
	struct fb* self = container_of(base, struct fb, base);
	ws->ws_col = self->columns;
	ws->ws_row = self->rows;
}

// Clear the console.
static void fb_clear(struct screen* base, const struct screen_char* fill) {
	struct fb* self = container_of(base, struct fb, base);
	memset(self->buffer, 0, self->pitch * self->height);
	for (unsigned short y = 0; y < self->rows; y++) {
		for (unsigned short x = 0; x < self->columns; x++) {
			self->grid[y * MAX_COLUMNS + x] = *fill;
			fb_render(self, x, y);
		}
	}
}

// Set a character on the console.
static void fb_set_char(struct screen* base, const struct screen_char* sc,
                        unsigned short col, unsigned short row) {
	struct fb* self = container_of(base, struct fb, base);
	self->grid[row * MAX_COLUMNS + col] = *sc;
	fb_render(self, col, row);
}

// Scroll the console one line.
static void fb_scroll(struct screen* base, const struct screen_char* fill) {
	struct fb* self = container_of(base, struct fb, base);
	for (unsigned short y = 0; y < self->rows - 1; y++) {
		for (unsigned short x = 0; x < self->columns; x++) {
			size_t to = y * MAX_COLUMNS + x;
			size_t from = (y + 1) * MAX_COLUMNS + x;
			self->grid[to] = self->grid[from];
			fb_render(self, x, y);
		}
	}
	for (unsigned short x = 0; x < self->columns; x++) {
		unsigned short y = self->rows - 1;
		size_t index = y * MAX_COLUMNS + x;
		self->grid[index] = *fill;
		fb_render(self, x, y);
	}
}

const struct screen_ops fb_ops = {
	.tcgetwinsize = fb_tcgetwinsize,
	.clear = fb_clear,
	.set_char = fb_set_char,
	.scroll = fb_scroll,
};

kernel/kernel/kernel.c

#include <stdio.h>

#include <kernel/tty.h>

void kernel_main(void) {
	printf("Hello, \e[31mkernel \e[91;44mWorld\e[m!\n");
}

kernel/kernel/tty.c

#include <kernel/tty.h>

// The /dev/console kernel console.
struct tty* dev_console;

size_t tty_write(struct tty* self, const void* buf, size_t len) {
	for (size_t i = 0; i < len; i++) {
		unsigned char uc = ((unsigned char*) buf)[i];
		if (uc == '\n' /* && (c_oflag & OPOST && c_oflag & ONLCR) */) {
			self->ops->transmit(self, '\r');
			self->ops->transmit(self, '\n');
		} else {
			self->ops->transmit(self, uc);
		}
	}
	return len;
}

size_t console_write(const void* data, size_t size) {
	return tty_write(dev_console, data, size);
}

int console_tcgetwinsize(struct winsize* ws) {
	return dev_console->ops->tcgetwinsize(dev_console, ws);
}

kernel/kernel/vgafont.h

#ifndef KERNEL_VGAFONT_H
#define KERNEL_VGAFONT_H

#include <stdint.h>

static uint8_t vgafont[] = {
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x81, 0xA5, 0x81, 0x81, 0xBD,
	0x99, 0x81, 0x81, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0xFF,
	0xDB, 0xFF, 0xFF, 0xC3, 0xE7, 0xFF, 0xFF, 0x7E, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x6C, 0xFE, 0xFE, 0xFE, 0xFE, 0x7C, 0x38, 0x10,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x7C, 0xFE,
	0x7C, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18,
	0x3C, 0x3C, 0xE7, 0xE7, 0xE7, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x18, 0x3C, 0x7E, 0xFF, 0xFF, 0x7E, 0x18, 0x18, 0x3C,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3C,
	0x3C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
	0xFF, 0xFF, 0xE7, 0xC3, 0xC3, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x66, 0x42, 0x42, 0x66, 0x3C, 0x00,
	0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0x99, 0xBD,
	0xBD, 0x99, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x1E, 0x0E,
	0x1A, 0x32, 0x78, 0xCC, 0xCC, 0xCC, 0xCC, 0x78, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x7E, 0x18, 0x18,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x33, 0x3F, 0x30, 0x30, 0x30,
	0x30, 0x70, 0xF0, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x63,
	0x7F, 0x63, 0x63, 0x63, 0x63, 0x67, 0xE7, 0xE6, 0xC0, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x18, 0x18, 0xDB, 0x3C, 0xE7, 0x3C, 0xDB, 0x18, 0x18,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFE, 0xF8,
	0xF0, 0xE0, 0xC0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x06, 0x0E,
	0x1E, 0x3E, 0xFE, 0x3E, 0x1E, 0x0E, 0x06, 0x02, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x18, 0x3C, 0x7E, 0x18, 0x18, 0x18, 0x7E, 0x3C, 0x18, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
	0x66, 0x00, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xDB,
	0xDB, 0xDB, 0x7B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x7C, 0xC6, 0x60, 0x38, 0x6C, 0xC6, 0xC6, 0x6C, 0x38, 0x0C, 0xC6,
	0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0xFE, 0xFE, 0xFE, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3C,
	0x7E, 0x18, 0x18, 0x18, 0x7E, 0x3C, 0x18, 0x7E, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x18, 0x3C, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
	0x18, 0x7E, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x18, 0x0C, 0xFE, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x60, 0xFE, 0x60, 0x30, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0,
	0xC0, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x24, 0x66, 0xFF, 0x66, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x38, 0x7C, 0x7C, 0xFE, 0xFE, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFE, 0x7C, 0x7C,
	0x38, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x18, 0x3C, 0x3C, 0x3C, 0x18, 0x18, 0x18, 0x00, 0x18, 0x18,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x24, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6C,
	0x6C, 0xFE, 0x6C, 0x6C, 0x6C, 0xFE, 0x6C, 0x6C, 0x00, 0x00, 0x00, 0x00,
	0x18, 0x18, 0x7C, 0xC6, 0xC2, 0xC0, 0x7C, 0x06, 0x06, 0x86, 0xC6, 0x7C,
	0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC2, 0xC6, 0x0C, 0x18,
	0x30, 0x60, 0xC6, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x6C,
	0x6C, 0x38, 0x76, 0xDC, 0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x30, 0x30, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x30,
	0x30, 0x30, 0x18, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x18,
	0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x7E,
	0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x02, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0x80, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x3C, 0x66, 0xC3, 0xC3, 0xDB, 0xDB, 0xC3, 0xC3, 0x66, 0x3C,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x38, 0x78, 0x18, 0x18, 0x18,
	0x18, 0x18, 0x18, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6,
	0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0xC6, 0xFE, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x7C, 0xC6, 0x06, 0x06, 0x3C, 0x06, 0x06, 0x06, 0xC6, 0x7C,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x1C, 0x3C, 0x6C, 0xCC, 0xFE,
	0x0C, 0x0C, 0x0C, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xC0,
	0xC0, 0xC0, 0xFC, 0x06, 0x06, 0x06, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x38, 0x60, 0xC0, 0xC0, 0xFC, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xC6, 0x06, 0x06, 0x0C, 0x18,
	0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6,
	0xC6, 0xC6, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0x7E, 0x06, 0x06, 0x06, 0x0C, 0x78,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00,
	0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0C, 0x06,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00,
	0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60,
	0x30, 0x18, 0x0C, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x0C, 0x18, 0x18, 0x18, 0x00, 0x18, 0x18,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xDE, 0xDE,
	0xDE, 0xDC, 0xC0, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x38,
	0x6C, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0xFC, 0x66, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x66, 0x66, 0xFC,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x66, 0xC2, 0xC0, 0xC0, 0xC0,
	0xC0, 0xC2, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x6C,
	0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x6C, 0xF8, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0xFE, 0x66, 0x62, 0x68, 0x78, 0x68, 0x60, 0x62, 0x66, 0xFE,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x66, 0x62, 0x68, 0x78, 0x68,
	0x60, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x66,
	0xC2, 0xC0, 0xC0, 0xDE, 0xC6, 0xC6, 0x66, 0x3A, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18,
	0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x0C,
	0x0C, 0x0C, 0x0C, 0x0C, 0xCC, 0xCC, 0xCC, 0x78, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0xE6, 0x66, 0x66, 0x6C, 0x78, 0x78, 0x6C, 0x66, 0x66, 0xE6,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x60, 0x60, 0x60, 0x60, 0x60,
	0x60, 0x62, 0x66, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xE7,
	0xFF, 0xFF, 0xDB, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0xC6, 0xE6, 0xF6, 0xFE, 0xDE, 0xCE, 0xC6, 0xC6, 0xC6, 0xC6,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6,
	0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x66,
	0x66, 0x66, 0x7C, 0x60, 0x60, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xD6, 0xDE, 0x7C,
	0x0C, 0x0E, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x66, 0x66, 0x66, 0x7C, 0x6C,
	0x66, 0x66, 0x66, 0xE6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6,
	0xC6, 0x60, 0x38, 0x0C, 0x06, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0xFF, 0xDB, 0x99, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6,
	0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xC3,
	0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x66, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xDB, 0xDB, 0xFF, 0x66, 0x66,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xC3, 0x66, 0x3C, 0x18, 0x18,
	0x3C, 0x66, 0xC3, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xC3,
	0xC3, 0x66, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0xFF, 0xC3, 0x86, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0xC3, 0xFF,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x30, 0x30, 0x30, 0x30, 0x30,
	0x30, 0x30, 0x30, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
	0xC0, 0xE0, 0x70, 0x38, 0x1C, 0x0E, 0x06, 0x02, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3C,
	0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x6C, 0xC6, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00,
	0x30, 0x30, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x0C, 0x7C,
	0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x60,
	0x60, 0x78, 0x6C, 0x66, 0x66, 0x66, 0x66, 0x7C, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xC0, 0xC0, 0xC0, 0xC6, 0x7C,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x0C, 0x0C, 0x3C, 0x6C, 0xCC,
	0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x7C, 0xC6, 0xFE, 0xC0, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x38, 0x6C, 0x64, 0x60, 0xF0, 0x60, 0x60, 0x60, 0x60, 0xF0,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xCC, 0xCC,
	0xCC, 0xCC, 0xCC, 0x7C, 0x0C, 0xCC, 0x78, 0x00, 0x00, 0x00, 0xE0, 0x60,
	0x60, 0x6C, 0x76, 0x66, 0x66, 0x66, 0x66, 0xE6, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x18, 0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x06, 0x00, 0x0E, 0x06, 0x06,
	0x06, 0x06, 0x06, 0x06, 0x66, 0x66, 0x3C, 0x00, 0x00, 0x00, 0xE0, 0x60,
	0x60, 0x66, 0x6C, 0x78, 0x78, 0x6C, 0x66, 0xE6, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE6, 0xFF, 0xDB,
	0xDB, 0xDB, 0xDB, 0xDB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0xDC, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDC, 0x66, 0x66,
	0x66, 0x66, 0x66, 0x7C, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x76, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x7C, 0x0C, 0x0C, 0x1E, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0xDC, 0x76, 0x66, 0x60, 0x60, 0x60, 0xF0,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0x60,
	0x38, 0x0C, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x30,
	0x30, 0xFC, 0x30, 0x30, 0x30, 0x30, 0x36, 0x1C, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x76,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xC3, 0xC3,
	0xC3, 0x66, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0xC3, 0xC3, 0xC3, 0xDB, 0xDB, 0xFF, 0x66, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0xC3,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0xC6, 0xC6,
	0xC6, 0xC6, 0xC6, 0x7E, 0x06, 0x0C, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0xFE, 0xCC, 0x18, 0x30, 0x60, 0xC6, 0xFE, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x0E, 0x18, 0x18, 0x18, 0x70, 0x18, 0x18, 0x18, 0x18, 0x0E,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x00, 0x18,
	0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x18,
	0x18, 0x18, 0x0E, 0x18, 0x18, 0x18, 0x18, 0x70, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x76, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x6C, 0xC6,
	0xC6, 0xC6, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x66,
	0xC2, 0xC0, 0xC0, 0xC0, 0xC2, 0x66, 0x3C, 0x0C, 0x06, 0x7C, 0x00, 0x00,
	0x00, 0x00, 0xCC, 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x76,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x18, 0x30, 0x00, 0x7C, 0xC6, 0xFE,
	0xC0, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x6C,
	0x00, 0x78, 0x0C, 0x7C, 0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0xCC, 0x00, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0xCC, 0xCC, 0x76,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x30, 0x18, 0x00, 0x78, 0x0C, 0x7C,
	0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x6C, 0x38,
	0x00, 0x78, 0x0C, 0x7C, 0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x3C, 0x66, 0x60, 0x60, 0x66, 0x3C, 0x0C, 0x06,
	0x3C, 0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x6C, 0x00, 0x7C, 0xC6, 0xFE,
	0xC0, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0x00,
	0x00, 0x7C, 0xC6, 0xFE, 0xC0, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x60, 0x30, 0x18, 0x00, 0x7C, 0xC6, 0xFE, 0xC0, 0xC0, 0xC6, 0x7C,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x38, 0x18, 0x18,
	0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3C, 0x66,
	0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x60, 0x30, 0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C,
	0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0x00, 0x10, 0x38, 0x6C, 0xC6, 0xC6,
	0xFE, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00, 0x38, 0x6C, 0x38, 0x00,
	0x38, 0x6C, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00,
	0x18, 0x30, 0x60, 0x00, 0xFE, 0x66, 0x60, 0x7C, 0x60, 0x60, 0x66, 0xFE,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6E, 0x3B, 0x1B,
	0x7E, 0xD8, 0xDC, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x6C,
	0xCC, 0xCC, 0xFE, 0xCC, 0xCC, 0xCC, 0xCC, 0xCE, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x10, 0x38, 0x6C, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0x00, 0x00, 0x7C, 0xC6, 0xC6,
	0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x30, 0x18,
	0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x30, 0x78, 0xCC, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x76,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x30, 0x18, 0x00, 0xCC, 0xCC, 0xCC,
	0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0x00,
	0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x7E, 0x06, 0x0C, 0x78, 0x00,
	0x00, 0xC6, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C,
	0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6,
	0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x7E,
	0xC3, 0xC0, 0xC0, 0xC0, 0xC3, 0x7E, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x38, 0x6C, 0x64, 0x60, 0xF0, 0x60, 0x60, 0x60, 0x60, 0xE6, 0xFC,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0x66, 0x3C, 0x18, 0xFF, 0x18,
	0xFF, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x66, 0x66,
	0x7C, 0x62, 0x66, 0x6F, 0x66, 0x66, 0x66, 0xF3, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x0E, 0x1B, 0x18, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18,
	0xD8, 0x70, 0x00, 0x00, 0x00, 0x18, 0x30, 0x60, 0x00, 0x78, 0x0C, 0x7C,
	0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x18, 0x30,
	0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x18, 0x30, 0x60, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x30, 0x60, 0x00, 0xCC, 0xCC, 0xCC,
	0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xDC,
	0x00, 0xDC, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00,
	0x76, 0xDC, 0x00, 0xC6, 0xE6, 0xF6, 0xFE, 0xDE, 0xCE, 0xC6, 0xC6, 0xC6,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x6C, 0x6C, 0x3E, 0x00, 0x7E, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x6C, 0x6C,
	0x38, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x30, 0x30, 0x00, 0x30, 0x30, 0x60, 0xC0, 0xC6, 0xC6, 0x7C,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xC0,
	0xC0, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0xFE, 0x06, 0x06, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0xC0, 0xC0, 0xC2, 0xC6, 0xCC, 0x18, 0x30, 0x60, 0xCE, 0x9B, 0x06,
	0x0C, 0x1F, 0x00, 0x00, 0x00, 0xC0, 0xC0, 0xC2, 0xC6, 0xCC, 0x18, 0x30,
	0x66, 0xCE, 0x96, 0x3E, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18,
	0x00, 0x18, 0x18, 0x18, 0x3C, 0x3C, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x6C, 0xD8, 0x6C, 0x36, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD8, 0x6C, 0x36,
	0x6C, 0xD8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x44, 0x11, 0x44,
	0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44,
	0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA,
	0x55, 0xAA, 0x55, 0xAA, 0xDD, 0x77, 0xDD, 0x77, 0xDD, 0x77, 0xDD, 0x77,
	0xDD, 0x77, 0xDD, 0x77, 0xDD, 0x77, 0xDD, 0x77, 0x18, 0x18, 0x18, 0x18,
	0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
	0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xF8, 0x18, 0x18, 0x18, 0x18,
	0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xF8, 0x18, 0xF8,
	0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x36, 0x36, 0x36, 0x36,
	0x36, 0x36, 0x36, 0xF6, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x36, 0x36, 0x36, 0x36,
	0x36, 0x36, 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x18, 0xF8,
	0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x36, 0x36, 0x36, 0x36,
	0x36, 0xF6, 0x06, 0xF6, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
	0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
	0x36, 0x36, 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x06, 0xF6,
	0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
	0x36, 0xF6, 0x06, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0xFE, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0xF8, 0x18, 0xF8,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0xF8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
	0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1F, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xFF,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
	0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1F, 0x18, 0x18, 0x18, 0x18,
	0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18,
	0x18, 0x18, 0x18, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
	0x18, 0x18, 0x18, 0x18, 0x18, 0x1F, 0x18, 0x1F, 0x18, 0x18, 0x18, 0x18,
	0x18, 0x18, 0x18, 0x18, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x37,
	0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
	0x36, 0x37, 0x30, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x30, 0x37, 0x36, 0x36, 0x36, 0x36,
	0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0xF7, 0x00, 0xFF,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0xFF, 0x00, 0xF7, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
	0x36, 0x36, 0x36, 0x36, 0x36, 0x37, 0x30, 0x37, 0x36, 0x36, 0x36, 0x36,
	0x36, 0x36, 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x36, 0x36, 0x36,
	0x36, 0xF7, 0x00, 0xF7, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
	0x18, 0x18, 0x18, 0x18, 0x18, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0xFF,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0xFF, 0x00, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x36, 0x36, 0x36, 0x36,
	0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x3F,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18,
	0x18, 0x1F, 0x18, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x18, 0x1F, 0x18, 0x18, 0x18, 0x18,
	0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F,
	0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
	0x36, 0x36, 0x36, 0xFF, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
	0x18, 0x18, 0x18, 0x18, 0x18, 0xFF, 0x18, 0xFF, 0x18, 0x18, 0x18, 0x18,
	0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xF8,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x1F, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
	0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xF0, 0xF0, 0xF0,
	0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
	0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
	0x0F, 0x0F, 0x0F, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x76, 0xDC, 0xD8, 0xD8, 0xD8, 0xDC, 0x76, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x78, 0xCC, 0xCC, 0xCC, 0xD8, 0xCC, 0xC6, 0xC6, 0xC6, 0xCC,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xC6, 0xC6, 0xC0, 0xC0, 0xC0,
	0xC0, 0xC0, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0xFE, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0xFE, 0xC6, 0x60, 0x30, 0x18, 0x30, 0x60, 0xC6, 0xFE,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0xD8, 0xD8,
	0xD8, 0xD8, 0xD8, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x66, 0x66, 0x66, 0x66, 0x66, 0x7C, 0x60, 0x60, 0xC0, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x76, 0xDC, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x18, 0x3C, 0x66, 0x66,
	0x66, 0x3C, 0x18, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38,
	0x6C, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0x6C, 0x38, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x38, 0x6C, 0xC6, 0xC6, 0xC6, 0x6C, 0x6C, 0x6C, 0x6C, 0xEE,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x30, 0x18, 0x0C, 0x3E, 0x66,
	0x66, 0x66, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x7E, 0xDB, 0xDB, 0xDB, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x03, 0x06, 0x7E, 0xDB, 0xDB, 0xF3, 0x7E, 0x60, 0xC0,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x30, 0x60, 0x60, 0x7C, 0x60,
	0x60, 0x60, 0x30, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C,
	0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0xFE, 0x00, 0x00, 0xFE, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x7E, 0x18,
	0x18, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
	0x18, 0x0C, 0x06, 0x0C, 0x18, 0x30, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x0C, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0C, 0x00, 0x7E,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x1B, 0x1B, 0x18, 0x18, 0x18,
	0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
	0x18, 0x18, 0x18, 0x18, 0xD8, 0xD8, 0xD8, 0x70, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x7E, 0x00, 0x18, 0x18, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xDC, 0x00,
	0x76, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x6C, 0x6C,
	0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0C, 0x0C,
	0x0C, 0x0C, 0x0C, 0xEC, 0x6C, 0x6C, 0x3C, 0x1C, 0x00, 0x00, 0x00, 0x00,
	0x00, 0xD8, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0xD8, 0x30, 0x60, 0xC8, 0xF8, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00,
};

#endif

kernel/kernel/vt.c

#include <string.h>

#include <kernel/kernel.h>
#include <kernel/screen.h>
#include <kernel/tty.h>
#include <kernel/vga.h>
#include <kernel/vt.h>

// RGB palette for the first 16 ANSI colors.
static const uint32_t palette[16] = {
	0x000000, 0xcc0000, 0x3e9a06, 0xc4a000,
	0x3465a4, 0x75507b, 0x06989a, 0xbfbfbf,
	0x555753, 0xef2929, 0x8ae234, 0xfce94f,
	0x729fcf, 0xad7fa8, 0x34e2e2, 0xffffff,
};

// VGA colors for the first 8 ANSI colors. The high bit is used for bright.
static const uint8_t ansi_to_vga_color[8] = {
	VGA_COLOR_BLACK, VGA_COLOR_RED, VGA_COLOR_GREEN, VGA_COLOR_BROWN,
	VGA_COLOR_BLUE, VGA_COLOR_MAGENTA, VGA_COLOR_CYAN, VGA_COLOR_LIGHT_GREY,
};

static const uint8_t DEFAULT_VGACOLOR =
	VGA_COLOR_LIGHT_GREY | VGA_COLOR_BLACK << 4;
static const unsigned int DEFAULT_FOREGROUND = 7;
static const unsigned int DEFAULT_BACKGROUND = 0;

// Initialize a vt (virtual terminal) object connected to a screen driver.
struct tty* vt_init(struct vt* self, struct screen* screen) {
	memset(self, 0, sizeof(*self));
	self->base.ops = &vt_ops;
	self->screen = screen;
	self->row = 0;
	self->column = 0;
	self->vgacolor = DEFAULT_VGACOLOR;
	self->fgcolor = palette[DEFAULT_FOREGROUND];
	self->bgcolor = palette[DEFAULT_BACKGROUND];
	struct winsize ws;
	self->screen->ops->tcgetwinsize(self->screen, &ws);
	self->width = ws.ws_col;
	self->height = ws.ws_row;
	struct screen_char fill = {
		.uc = ' ', .vgacolor = self->vgacolor, .attr = self->attr,
		.fg = self->fgcolor, .bg = self->bgcolor
	};
	self->screen->ops->clear(self->screen, &fill);
	return &self->base;
}

// Move to the next line and scroll if needed.
static void vt_next_line(struct vt* self) {
	if (self->height <= self->row + 1) {
		struct screen_char fill = {
			.uc = ' ', .vgacolor = self->vgacolor, .attr = self->attr,
			.fg = self->fgcolor, .bg = self->bgcolor
		};
		self->screen->ops->scroll(self->screen, &fill);
	} else
		self->row++;
}

// Execute an ANSI escape sequence command.
static void vt_run_ansi(struct vt* self, char c) {
	switch (c) {
	case 'm':
		if (self->ansi_count == 0) {
			// No parameters means turn all attributes off.
			self->ansi_params[0] = 0;
			self->ansi_count = 1;
		}
		// Execute each of the parameters to the command.
		for (size_t i = 0; i < self->ansi_count; i++) {
			unsigned int cmd = self->ansi_params[i];
			if (cmd == 0) {
				// Turn all attributes off.
				self->vgacolor = DEFAULT_VGACOLOR;
				self->attr = 0;
				self->fgcolor = palette[DEFAULT_FOREGROUND];
				self->bgcolor = palette[DEFAULT_BACKGROUND];
			} else if (30 <= cmd && cmd <= 37) {
				// Set text color.
				unsigned int value = cmd - 30;
				self->vgacolor &= 0xF0;
				self->vgacolor |= ansi_to_vga_color[value] << 0;
				self->fgcolor = palette[value];
			} else if (cmd == 39) {
				// Set default text color.
				self->vgacolor &= 0xF0;
				self->vgacolor |= DEFAULT_VGACOLOR & 0x0F;
				self->fgcolor = palette[DEFAULT_FOREGROUND];
			} else if (40 <= cmd && cmd <= 47) {
				// Set background color.
				unsigned int value = cmd - 40;
				self->vgacolor &= 0x0F;
				self->vgacolor |= ansi_to_vga_color[value] << 4;
				self->bgcolor = palette[value];
			} else if (cmd == 49) {
				// Set default background color.
				self->vgacolor &= 0x0F;
				self->vgacolor |= DEFAULT_VGACOLOR & 0xF0;
				self->bgcolor = palette[DEFAULT_BACKGROUND];
			} else if (90 <= cmd && cmd <= 97) {
				// Set text color.
				unsigned int value = cmd - 90 + 8;
				self->vgacolor &= 0xF0;
				self->vgacolor |= (0x8 | ansi_to_vga_color[value - 8]) << 0;
				self->fgcolor = palette[value];
			} else if (100 <= cmd && cmd <= 107) {
				// Set background color.
				unsigned int value = cmd - 100 + 8;
				self->vgacolor &= 0x0F;
				self->vgacolor |= (0x8 | ansi_to_vga_color[value - 8]) << 4;
				self->bgcolor = palette[value];
			} else {
				self->ansi_state = ANSI_STATE_NONE;
			}
		}
		break;
	}
}

// Process the next byte in an ANSI escape sequence.
static void vt_escaped(struct vt* self, char c) {
	if (self->ansi_state == ANSI_STATE_CSI) {
		// Begin a \e[ terminal escape sequence.
		if (c == '[')
			self->ansi_state = ANSI_STATE_COMMAND;
		// Return to normal processing on any unrecognized escape sequence.
		else
			self->ansi_state = ANSI_STATE_NONE;
		return;
	} else if (self->ansi_state == ANSI_STATE_COMMAND) {
		if ('0' <= c && c <= '9') {
			// Parse numeric parameters to the escape sequence.
			if (self->ansi_count == 0)
				self->ansi_count = 1;
			if (self->ansi_count <= ANSI_PARAM_MAX) {
				self->ansi_params[self->ansi_count - 1] *= 10;
				self->ansi_params[self->ansi_count - 1] += c - '0';
			}
		} else if (c == ';') {
			// Parse another parameter.
			if (self->ansi_count < ANSI_PARAM_MAX)
				self->ansi_count++;
			else
				self->ansi_state = ANSI_STATE_NONE;
		} else if ('@' <= c && c <= '~') {
			// Execute the command if the byte is in the right range.
			vt_run_ansi(self, c);
			self->ansi_state = ANSI_STATE_NONE;
		} else {
			// Otherwise ignore any unrecognized escape sequence.
			self->ansi_state = ANSI_STATE_NONE;
		}
	 }
}

// Process the next output byte.
static bool vt_transmit(struct tty* base, unsigned char uc) {
	struct vt* self = container_of(base, struct vt, base);
	if (self->ansi_state) {
		// Process any ongoing terminal escape sequences.
		vt_escaped(self, uc);
	} else if (uc == '\e') {
		// Begin a terminal escape sequence.
		self->ansi_state = ANSI_STATE_CSI;
		self->ansi_count = 0;
		memset(self->ansi_params, 0, sizeof(self->ansi_params));
	} else if (uc == '\n') {
		// Handle a newline without a carriage return.
		vt_next_line(self);
	} else if (uc == '\r') {
		// Handle a carriage return.
		self->column = 0;
	} else {
		// Otherwise render the character.
		// The cursor is allowed to be at the edge of the row if it is full, in
		// that case move to the new line when the next character appears.
		if (self->width <= self->column) {
			self->column = 0;
			vt_next_line(self);
		}
		struct screen_char sc = {
			.uc = uc, .vgacolor = self->vgacolor, .attr = self->attr,
			.fg = self->fgcolor, .bg = self->bgcolor
		};
		self->screen->ops->set_char(self->screen, &sc, self->column, self->row);
		self->column++;
	}
	return true;
}

// Get the terminal window size.
static int vt_tcgetwinsize(struct tty* base, struct winsize* ws) {
	struct vt* self = container_of(base, struct vt, base);
	self->screen->ops->tcgetwinsize(self->screen, ws);
	return 0;
}

const struct tty_ops vt_ops = {
	.transmit = vt_transmit,
	.tcgetwinsize = vt_tcgetwinsize,
};

libc and libk

These files go into the libc directory.

libc/.gitignore

*.a
*.d
*.o

libc/Makefile

# To cross-compile MyOS: make HOST=i386-elf SYSROOT=../sysroot
# By default the build system will assume it's running on MyOS.

include ../build-aux/common.mk

CFLAGS?=-O2 -g
CPPFLAGS?=
LDFLAGS?=
LIBS?=

CFLAGS:=$(CFLAGS) -ffreestanding -Wall -Wextra
CPPFLAGS:=$(CPPFLAGS) -D__is_libc -Iinclude
LIBK_CFLAGS:=$(CFLAGS)
LIBK_CPPFLAGS:=$(CPPFLAGS) -D__is_libk

ARCHDIR=arch/$(HOSTARCH)

-include $(ARCHDIR)/arch.mk

CFLAGS:=$(CFLAGS) $(ARCH_CFLAGS)
CPPFLAGS:=$(CPPFLAGS) $(ARCH_CPPFLAGS)
LIBK_CFLAGS:=$(LIBK_CFLAGS) $(KERNEL_ARCH_CFLAGS)
LIBK_CPPFLAGS:=$(LIBK_CPPFLAGS) $(KERNEL_ARCH_CPPFLAGS)

FREEOBJS=\
$(ARCH_FREEOBJS) \
stdio/printf.o \
stdio/putchar.o \
stdio/puts.o \
stdlib/abort.o \
string/memcmp.o \
string/memcpy.o \
string/memmove.o \
string/memset.o \
string/strcmp.o \
string/strlen.o \

HOSTEDOBJS=\
$(ARCH_HOSTEDOBJS) \

OBJS=\
$(FREEOBJS) \
$(HOSTEDOBJS) \

LIBK_OBJS=$(FREEOBJS:.o=.libk.o)

#BINARIES=libc.a libk.a # Not ready for libc yet.
BINARIES=libk.a

.PHONY: all clean distclean install install-headers install-libs
.SUFFIXES: .o .libk.o .c .S

all: $(BINARIES)

libc.a: $(OBJS)
	$(AR) rcs $@ $(OBJS)

libk.a: $(LIBK_OBJS)
	$(AR) rcs $@ $(LIBK_OBJS)

.c.o:
	$(CC) -MD -c $< -o $@ -std=gnu11 $(CFLAGS) $(CPPFLAGS)

.S.o:
	$(CC) -MD -c $< -o $@ $(CFLAGS) $(CPPFLAGS)

.c.libk.o:
	$(CC) -MD -c $< -o $@ -std=gnu11 $(LIBK_CFLAGS) $(LIBK_CPPFLAGS)

.S.libk.o:
	$(CC) -MD -c $< -o $@ $(LIBK_CFLAGS) $(LIBK_CPPFLAGS)

clean:
	rm -f $(BINARIES)
	find . -name "*.a" -delete
	find . -name "*.d" -delete
	find . -name "*.o" -delete

distclean: clean

install: install-headers install-libs

install-headers:
	mkdir -p $(DESTDIR)$(INCLUDEDIR)
	cp -R --preserve=timestamps include/. $(DESTDIR)$(INCLUDEDIR)/.

install-libs: $(BINARIES)
	mkdir -p $(DESTDIR)$(LIBDIR)
	cp $(BINARIES) $(DESTDIR)$(LIBDIR)

-include $(OBJS:.o=.d)
-include $(LIBK_OBJS:.o=.d)

libc/arch/i386/arch.mk

ARCH_CFLAGS=
ARCH_CPPFLAGS=
KERNEL_ARCH_CFLAGS=
KERNEL_ARCH_CPPFLAGS=

ARCH_FREEOBJS=\

ARCH_HOSTEDOBJS=\

libc/include/stdio.h

#ifndef _STDIO_H
#define _STDIO_H 1

#include <sys/cdefs.h>

#define EOF (-1)

#ifdef __cplusplus
extern "C" {
#endif

int printf(const char* __restrict, ...);
int putchar(int);
int puts(const char*);

#ifdef __cplusplus
}
#endif

#endif

libc/include/stdlib.h

#ifndef _STDLIB_H
#define _STDLIB_H 1

#include <sys/cdefs.h>

#ifdef __cplusplus
extern "C" {
#endif

__attribute__((__noreturn__))
void abort(void);

#ifdef __cplusplus
}
#endif

#endif

libc/include/string.h

#ifndef _STRING_H
#define _STRING_H 1

#include <sys/cdefs.h>

#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif

int memcmp(const void*, const void*, size_t);
void* memcpy(void* __restrict, const void* __restrict, size_t);
void* memmove(void*, const void*, size_t);
void* memset(void*, int, size_t);
int strcmp(const char*, const char*);
size_t strlen(const char*);

#ifdef __cplusplus
}
#endif

#endif

libc/include/sys/cdefs.h

#ifndef _SYS_CDEFS_H
#define _SYS_CDEFS_H 1

#define __myos_libc 1

#endif

libc/stdio/printf.c

#include <limits.h>
#include <stdbool.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>

static bool print(const char* data, size_t length) {
	const unsigned char* bytes = (const unsigned char*) data;
	for (size_t i = 0; i < length; i++)
		if (putchar(bytes[i]) == EOF)
			return false;
	return true;
}

int printf(const char* restrict format, ...) {
	va_list parameters;
	va_start(parameters, format);

	int written = 0;

	while (*format != '\0') {
		size_t maxrem = INT_MAX - written;

		if (format[0] != '%' || format[1] == '%') {
			if (format[0] == '%')
				format++;
			size_t amount = 1;
			while (format[amount] && format[amount] != '%')
				amount++;
			if (maxrem < amount) {
				// TODO: Set errno to EOVERFLOW.
				return -1;
			}
			if (!print(format, amount))
				return -1;
			format += amount;
			written += amount;
			continue;
		}

		const char* format_begun_at = format++;

		if (*format == 'c') {
			format++;
			char c = (char) va_arg(parameters, int /* char promotes to int */);
			if (!maxrem) {
				// TODO: Set errno to EOVERFLOW.
				return -1;
			}
			if (!print(&c, sizeof(c)))
				return -1;
			written++;
		} else if (*format == 's') {
			format++;
			const char* str = va_arg(parameters, const char*);
			size_t len = strlen(str);
			if (maxrem < len) {
				// TODO: Set errno to EOVERFLOW.
				return -1;
			}
			if (!print(str, len))
				return -1;
			written += len;
		} else {
			format = format_begun_at;
			size_t len = strlen(format);
			if (maxrem < len) {
				// TODO: Set errno to EOVERFLOW.
				return -1;
			}
			if (!print(format, len))
				return -1;
			written += len;
			format += len;
		}
	}

	va_end(parameters);
	return written;
}

libc/stdio/putchar.c

#include <stdio.h>

#if defined(__is_libk)
#include <kernel/tty.h>
#endif

int putchar(int ic) {
#if defined(__is_libk)
	char c = (char) ic;
	console_write(&c, sizeof(c));
#else
	// TODO: Implement stdio and the write system call.
#endif
	return ic;
}

libc/stdio/puts.c

#include <stdio.h>

int puts(const char* string) {
	return printf("%s\n", string);
}

libc/stdlib/abort.c

#include <stdio.h>
#include <stdlib.h>

__attribute__((__noreturn__))
void abort(void) {
#if defined(__is_libk)
	// TODO: Add proper kernel panic.
	printf("kernel: panic: abort()\n");
#else
	// TODO: Abnormally terminate the process as if by SIGABRT.
	printf("abort()\n");
#endif
	while (1) {
#if defined(__i386__)
		asm volatile ("hlt");
#endif
	}
	__builtin_unreachable();
}

libc/string/memcmp.c

#include <string.h>

int memcmp(const void* aptr, const void* bptr, size_t size) {
	const unsigned char* a = (const unsigned char*) aptr;
	const unsigned char* b = (const unsigned char*) bptr;
	for (size_t i = 0; i < size; i++) {
		if (a[i] < b[i])
			return -1;
		else if (b[i] < a[i])
			return 1;
	}
	return 0;
}

libc/string/memcpy.c

#include <string.h>

void* memcpy(void* restrict dstptr, const void* restrict srcptr, size_t size) {
	unsigned char* dst = (unsigned char*) dstptr;
	const unsigned char* src = (const unsigned char*) srcptr;
	for (size_t i = 0; i < size; i++)
		dst[i] = src[i];
	return dstptr;
}

libc/string/memmove.c

#include <string.h>

void* memmove(void* dstptr, const void* srcptr, size_t size) {
	unsigned char* dst = (unsigned char*) dstptr;
	const unsigned char* src = (const unsigned char*) srcptr;
	if (dst < src) {
		for (size_t i = 0; i < size; i++)
			dst[i] = src[i];
	} else {
		for (size_t i = size; i != 0; i--)
			dst[i-1] = src[i-1];
	}
	return dstptr;
}

libc/string/memset.c

#include <string.h>

void* memset(void* bufptr, int value, size_t size) {
	unsigned char* buf = (unsigned char*) bufptr;
	for (size_t i = 0; i < size; i++)
		buf[i] = (unsigned char) value;
	return bufptr;
}

libc/string/strcmp.c

#include <stdbool.h>
#include <string.h>

int strcmp(const char* aptr, const char* bptr) {
	const unsigned char* a = (const unsigned char*) aptr;
	const unsigned char* b = (const unsigned char*) bptr;
	for (size_t i = 0; true; i++) {
		if (a[i] == '\0' && b[i] == '\0')
			return 0;
		if (a[i] < b[i])
			return -1;
		if (a[i] > b[i])
			return 1;
	}
}

libc/string/strlen.c

#include <string.h>

size_t strlen(const char* str) {
	size_t len = 0;
	while (str[len])
		len++;
	return len;
}

build-aux

These files go into the build-aux directory.

build-aux/common.mk

# This build system handles cross-compilation, where the compilation happens on
# the machine BUILD, and produces executables that will run on machine HOST,
# whose output (if any) would run on machine TARGET. By default, it will assume
# BUILD == HOST == TARGET, unless overridden.

# Determine the platform per uname.
UNAME_OS:=$(shell uname -s | tr '[:upper:]' '[:lower:]' | sed 's,^linux$$,linux-gnu,g')
UNAME_MACHINE:=$(shell uname -m)

# Detect the platform that the software is being built on.
BUILD?=$(UNAME_MACHINE)-$(UNAME_OS)

# Determine the platform the software will run on.
HOST?=$(BUILD)
HOST_MACHINE:=$(shell expr x$(HOST) : 'x\([^-]*\).*')

# Determine the platform the software will target.
TARGET?=$(HOST)

# Determine the per-arch directory. For i*86-elf, use i386.
HOSTARCH:=$(if $(filter i%86,$(HOST_MACHINE)),i386,$(HOST_MACHINE))

# Determine the prefix for host tools.
ifneq ($(BUILD),$(HOST))
  HOST_TOOL_PREFIX?=$(HOST)-
endif

# Utility function to determine whether a variable is unset or its value is a
# default provided by make. Those defaults must be overridden below, but any
# other user choices must be respected.
is_unset_or_default = $(filter undefined default,$(origin $(1)))

# Determine the names of the cross-tools that target the host platform.
ifneq ($(BUILD),$(HOST))
  ifneq ($(call is_unset_or_default,CC),)
    CC:=$(HOST_TOOL_PREFIX)gcc
  endif
  ifneq ($(call is_unset_or_default,AR),)
    AR:=$(HOST_TOOL_PREFIX)ar
  endif
  ifneq ($(call is_unset_or_default,AS),)
    AS:=$(HOST_TOOL_PREFIX)as
  endif
endif

# Inform the toolchain about the location of the sysroot if overridden.
ifdef SYSROOT
  CC:=$(CC) --sysroot="$(SYSROOT)"
endif

# Determine the system directory structure.
PREFIX?=/usr
EXEC_PREFIX?=$(PREFIX)
BOOTDIR?=/boot
LIBDIR?=$(EXEC_PREFIX)/lib
INCLUDEDIR?=$(PREFIX)/include

# Work around that the -elf gcc targets doesn't have a system include directory
# because it was configured with --without-headers rather than --with-sysroot.
ifneq ($(filter %-elf,$(HOST)),)
  CC+=-isystem=$(INCLUDEDIR)
endif

build-aux/grub.cfg

menuentry "myos" {
	multiboot2 /boot/myos
}
menuentry "myos (serial)" {
	multiboot2 /boot/myos --console=ttyS0
}

Miscellaneous

These files go into the root source directory.

.gitignore

*.iso
isodir
sysroot

Makefile

# To cross-compile MyOS: make HOST=i386-elf
# By default the build system will assume it's running on MyOS.

include build-aux/common.mk

ifndef SYSROOT
  SYSROOT:=$(shell pwd)/sysroot
endif

MODULES=\
libc \
kernel \

QEMU?=qemu-system-$(HOSTARCH)
QEMU_ENABLE_KVM?=-enable-kvm
QEMU_OPTIONS?=

.PHONY: all
all: sysroot

.PHONY: sysroot-headers
sysroot-headers:
	export SYSROOT="$(SYSROOT)" && export DESTDIR="$(SYSROOT)" && \
	(for D in libc kernel; do $(MAKE) -C $$D install-headers || exit $$?; done)

.PHONY: sysroot
sysroot: sysroot-headers
	export SYSROOT="$(SYSROOT)" && export DESTDIR="$(SYSROOT)" && \
	(for D in $(MODULES); do $(MAKE) -C $$D install || exit $$?; done)

.PHONY: clean
clean:
	export SYSROOT="$(SYSROOT)" && export DESTDIR="$(SYSROOT)" && \
	(for D in $(MODULES); do $(MAKE) -C $$D clean || exit $$?; done)

.PHONY: distclean
distclean: clean
	rm -rf "$(SYSROOT)"
	rm -rf isodir
	rm -f myos.iso

isodir/boot/grub/grub.cfg: build-aux/grub.cfg
	mkdir -p isodir/boot/grub
	cp build-aux/grub.cfg $@

isodir/boot/myos: sysroot
	mkdir -p isodir/boot
	cp "$(SYSROOT)$(BOOTDIR)/myos" $@

myos.iso: isodir/boot/grub/grub.cfg isodir/boot/myos
	grub-mkrescue -o myos.iso isodir

.PHONY: iso
iso: myos.iso

.PHONY: qemu
qemu: myos.iso
	$(QEMU) $(QEMU_ENABLE_KVM) $(QEMU_OPTIONS) -cdrom myos.iso

Building

To build the system:

make HOST=i686-elf

Or to just build one module:

cd libc && make HOST=i686-elf SYSROOT=../sysroot

To build the system and make a bootable CD-ROM image:

make HOST=i686-elf myos.iso

To build the system and make a bootable CD-ROM image and launch it in Qemu:

make HOST=i686-elf qemu

Note: If qemu doesn't have hardware acceleration on your platform, add an empty assignment QEMU_ENABLE_KVM= to the make invocation.

To use use the ttyS serial line driver, redirect the Qemu serial line to somewhere that you can use it, and select the myos (serial) bootloader menu option:

make HOST=i686-elf QEMU_OPTIONS='-serial stdio' qemu

Troubleshooting

Double check the source code

If you receive odd errors during the build, you may have made a mistake during manual copying, perhaps missed a file, forgot to make a file executable, or bugs in the highlighting software we use cause unintended whitespace to appear. Perform a git repository clone as described above, and use that code instead, or compare the two directory trees with the diff(1) diff command line utility. If you made personal changes to the code, those may be at fault.

Forgetting to set HOST

/usr/lib/gcc/x86_64-linux-gnu/15/include/limits.h:210:15: fatal error: limits.h: No such file or directory
  210 | #include_next <limits.h>                /* recurse down to the real one */

The solution is to properly cross-compile as shown above. This error will occur if the HOST=i686-elf environment variable is not set, which causes the build system to not cross-compile and instead invoke the system compiler directly. In this case, the build may partially succeed, but the compiler's limits.h header wrapper tries to include the real limits.h header from libc, but no such header exists in this example.

During the GCC build, it remembers whether a libc limits.h header exists, and wraps the libc header using #include_next if it does, and otherwise provides its own header. The i686-elf toolchain is built using --without-headers and it includes its own limits.h. You can provide your own limits.h header only when completing the OS Specific Toolchain tutorial in the future.

Moving Forward

Please note that this tutorial is an example. You are personally responsible for adopting the code to your needs and you need to fully understand everything. The example here is complex, but the complexity is very intentional, and is actually designed to make your design much simpler in the long run. There are a lot of subtleties to study, much of which is explained above, and other aspects that can be learned from the code. You are responsible for becoming an expert in the topics introduced in this tutorial. Welcome to osdev!

You should adapt this template to your needs. There's a number of things you should consider doing now:

Renaming MyOS to YourOS

Certainly you wish to name your operating system after your favorite flower, hometown, boolean value, or whatever marketing told you. Do a search and replace that replaces myos with whatever you wish to call it. Keep in mind that the name is deliberately lower-case in a few places for technical reasons.

Serial Line

The GRUB bootloader can be configured to use the serial line too, for a fully headless experience:

serial
terminal_input serial
terminal_output serial
menuentry "myos (serial)" {
	multiboot2 /boot/myos --console=ttyS0
}

Ideally the top-level makefile would invoke a script to dynamically generate the isodir/boot/grub/grub.cfg GRUB configuration file. This feature would have an option to enable the serial line by default. Designing this feature is left as an exercise.

Improving the Build System

Main article: Hard Build System

It is probably worth improving the build system.

It's worth considering how contributors will build your operating system. It's an easy trap to fall into thinking you can make a super script that does everything. This design will end up complex and insufficiently flexible; or it will be flexible and even more complex. It's better to document what the user should do to prepare a cross toolchain and what prerequisite programs to install. This tutorial shows an example hard build system that merely builds the operating system. You can complete it by documenting how to build a cross-compiler and how to use it.

Stack Smash Protector

Main article: Stack Smashing Protector

Early is not too soon to think about security and robustness. You can take advantage of the optional stack smash protector offered by modern compilers that detect stack buffer overruns rather than behaving unexpectedly (or nothing happening, if unlucky).

Going Further

Main article: Going Further on x86

This guide is meant as an overview of what to do, so you have a kernel ready for more features, without actually redesigning it radically when adding them.

User-Space

A later tutorial in this series will extend this template with a proper user-space and an OS Specific Toolchain that fully utilizes the system root.