Stivale2 CSharp BareBones

From OSDev Wiki
Jump to navigation Jump to search

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

Difficulty level
Difficulty 2.png
Medium

What is Stivale?

stivale means "boot" in Italian. It is a boot protocol designed to overcome shortcomings of common boot protocols used by hobbyist OS developers, such as Multiboot.

There are 2 revisions of the stivale boot protocol, namely: stivale, and stivale2. Stivale2 makes use of tags for bootloader writers' and kernel writers' convenience, and to make future expandability and revisioning easier. We will use second version.

Stivale GitHub repository

What is Limine?

Limine is a modern, advanced x86 bootloader for BIOS and UEFI, with support for cutting edge features such as 5-level paging, 64-bit Long Mode, and direct higher half loading thanks to the stivale boot protocol.

Limine GitHub repository

Getting started

It is recommended to check out this project repository until getting started

Install required packages

To compile and run the kernel you will need following programs installed:

  • Dotnet (C# compiler) should be installed in /usr/share/dotnet/
  • Tysila2 (Debian package included in repository)
  • Mono (To run tysila2)
  • Make (To run everything)
  • Nasm (Assembly compiler)
  • ld.lld (Linker)
  • Xorriso (To build the iso)
  • Qemu-system-x86 (To run the OS)

If you are using debian based system you can install most of them with these commands:

sudo apt install nasm ld.lld xorriso qemu-system-x86 mono-runtime
// First download tysila2.deb from project's repository
sudo dpkg -i tysila2.deb

Actual code

First of all you need to create following files and directories:

├── limine.cfg
├── Makefile
└── source
 ├── Console.cs
 ├── kernel.asm
 ├── kernel.cs
 ├── linker.ld
 ├── Makefile
 └── stivale2.cs

limine.cfg

This is Limine configuration file which tells bootloader what to do:

limine.cfg

TIMEOUT=0
VERBOSE=yes
SERIAL=yes
# Kernel entry name in bootloader menu
:Stivale2
# Boot protocol to use
PROTOCOL=stivale2
# Kernel elf file path on iso
KERNEL_PATH=boot:///kernel.elf
# Disable KASLR
KASLR=no

Makefile

These are simple makefile that will be excecuted when you run make command:

Note: Make sure to use tabs instead of 8 spaces

Makefile:

KERNELDIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
all: limine
	mkdir -p $(KERNELDIR)/iso_root
	$(MAKE) -s -C $(KERNELDIR)/source
limine:
	git clone https://github.com/limine-bootloader/limine.git --branch=v2.0-branch-binary --depth=1
	$(MAKE) -C $(KERNELDIR)/limine
clean:
	$(MAKE) -s -C $(KERNELDIR)/source clean
distclean:
	$(MAKE) -s -C $(KERNELDIR)/source clean
	rm -rf $(KERNELDIR)/limine
	rm -rf $(KERNELDIR)/iso_root

source/Makefile:

SOURCEDIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
KERNEL := $(SOURCEDIR)/kernel.elf
KERNELEXE := $(KERNEL:.elf=.exe)
KERNELO := $(KERNEL:.elf=.o)
ISO = $(SOURCEDIR)/../image.iso
LIMINE = $(SOURCEDIR)/../limine/limine-install
QEMU = qemu-system-x86_64
QEMUFLAGS = -enable-kvm -M q35 -cpu max -smp 2 -m 512M -boot d -rtc base=localtime -serial stdio
XORRISO = xorriso
XORRISOFLAGS = -as mkisofs -b limine-cd.bin \
		-no-emul-boot -boot-load-size 4 -boot-info-table \
		--efi-boot limine-eltorito-efi.bin -efi-boot-part \
		--efi-boot-image --protective-msdos-label
LD = ld.lld
AS = nasm
AOT = tysila2
DOTNET = /usr/share/dotnet/dotnet
CSC = /usr/share/dotnet/sdk/*/Roslyn/bincore/csc.dll
ASMFLAGS = -f elf64
AOTFLAGS = --arch x86_64-elf64-tysos -fno-rtti -fno-exceptions
CSCFLAGS = -unsafe -target:exe -platform:x86 -nostdlib /r:/usr/share/tysila2/mscorlib.dll
LDFLAGS = -T $(SOURCEDIR)/linker.ld -m elf_x86_64 -z max-page-size=0x1000
CSFILES = $(shell find $(SOURCEDIR)/ -type f -name '*.cs')
ASMFILES = $(shell find $(SOURCEDIR)/ -type f -name '*.asm')
OBJ = $(ASMFILES:.asm=_asm.o)
.PHONY: all
all: $(KERNEL)
	$(MAKE) iso
	$(MAKE) clean run
$(KERNEL): $(OBJ)
	$(DOTNET) $(CSC) $(CSCFLAGS) $(CSFILES) -out:$(KERNELEXE)
	$(AOT) $(AOTFLAGS) $(KERNELEXE) -o $(KERNELO)
	$(LD) $(LDFLAGS) $(INTERNALLDFLAGS) $(OBJ) $(KERNELO) -o $@
%_asm.o: %.asm
	$(AS) $(ASMFLAGS) $^ -o $@
iso:
	cp $(KERNEL) $(SOURCEDIR)/../limine.cfg $(SOURCEDIR)/../limine/limine.sys \
		$(SOURCEDIR)/../limine/limine-cd.bin $(SOURCEDIR)/../limine/limine-eltorito-efi.bin $(SOURCEDIR)/../iso_root/
	$(XORRISO) $(XORRISOFLAGS) $(SOURCEDIR)/../iso_root -o $(ISO)
	$(LIMINE) $(ISO)
clean:
	rm -rf $(KERNEL) $(OBJ) $(KERNELEXE) $(KERNELO) $(SOURCEDIR)/../iso_root/*
run:
	$(QEMU) $(QEMUFLAGS) -cdrom $(ISO)

Linker script

Script that ld.lld will use to link relocatable elf objects

source/linker.ld

OUTPUT_FORMAT(elf64-x86-64)
OUTPUT_ARCH(i386:x86-64)
ENTRY(_start)
PHDRS
{
 null PT_NULL FLAGS(0) ;
 text PT_LOAD FLAGS((1 << 0) | (1 << 2)) ;
 rodata PT_LOAD FLAGS((1 << 2)) ;
 data PT_LOAD FLAGS((1 << 1) | (1 << 2)) ;
 dynamic PT_DYNAMIC FLAGS((1 << 1) | (1 << 2)) ;
}
SECTIONS
{
 . = 0xffffffff80200000;
 .text : {
 *(.text*)
 } :text
 . += 0x1000;
 .stivale2hdr : {
 KEEP(*(.stivale2hdr))
 } :rodata
 .rodata : {
 *(.rodata*)
 } :rodata
 . += 0x1000;
 .data : {
 *(.data*)
 } :data
 .dynamic : {
 *(.dynamic)
 } :data :dynamic
 .bss : {
 *(COMMON)
 *(.bss*)
 } :data
}

Kernel

These files contain code that will be executed when kernel runs:

source/kernel.asm

[EXTERN_ZN6kernel6Kernel7ProgramM_0_8RealMain_Rv_P1PV26stivale2#2Bstivale2_struct]
section.data
stivale2_smp_tag:
dq0x1ab015085f3273df
dq0
dq0
stivale2_any_video_tag:
dq0xc75c9fa92a44c4db
dqstivale2_smp_tag
dq1
[SECTION.bss]
align16
stack_bottom:
resb8192
stack_top:
[SECTION.stivale2hdr]
align4
stivale_hdr:
dqkmain
dqstack_top
dq(1<<1)
dqstivale2_any_video_tag
[SECTION.text]
kmain:
pushrdi
call_ZN6kernel6Kernel7ProgramM_0_8RealMain_Rv_P1PV26stivale2#2Bstivale2_struct
sthrow:
hlt
jmpsthrow
[GLOBALsthrow]
_ZN6kernel6Kernel7ProgramM_0_4halt_Rv_P0:
hlt
jmp_ZN6kernel6Kernel7ProgramM_0_4halt_Rv_P0
[GLOBAL_ZN6kernel6Kernel7ProgramM_0_4halt_Rv_P0]

source/kernel.cs

usingSystem.Runtime.CompilerServices;
namespaceKernel
{
// Class is unsafe so we can use pointers
publicunsafeclassProgram
{
// Fake kernel entry point
// Do not remove
publicstaticvoidMain()
{
// These line is required
// Without it compiler will remove RealMain from kernel
RealMain(null);
}
publicstaticstivale2.stivale2_struct_tag_smp*smp_tag;
[MethodImpl(MethodImplOptions.InternalCall)]
publicstaticexternvoidhalt();
// Real entry
publicstaticvoidRealMain(stivale2.stivale2_struct*stiv)
{
// If Stivale2 struct was not found halt
if(stiv==null)halt();
// Example on how to get Stivale2 structure tags
smp_tag=(stivale2.stivale2_struct_tag_smp*)stivale2.get_tag(stiv,stivale2.STIVALE2_STRUCT_TAG_SMP_ID);
// Print text
Console.WriteLine("Hello, World!");
// Halt
halt();
}
}
}

Console

This is an example code that provides console write and writeline functions: source/Console.cs

namespaceKernel
{
// Console colours
publicenumConsoleColour
{
Black,
Blue,
Green,
Cyan,
Red,
Purple,
Brown,
Grey,
DarkGrey,
LightBlue,
LightGreen,
LightCyan,
LightRed,
LightPurple,
Yellow,
White,
};
publicunsafestaticclassConsole
{
// VGA text mode address
publicstaticushort*vga=(ushort*)0xB8000;
// Cursor positions
publicstaticintx=0,y=0,lastx=0;
// Text colours
publicstaticConsoleColourForegroundColour=ConsoleColour.White;
publicstaticConsoleColourBackgroundColour=ConsoleColour.Black;
publicstaticvoidWrite(strings)
{
foreach(charcins)
{
PutChar(c,BackgroundColour,ForegroundColour);
}
}
publicstaticvoidWriteLine(strings)
{
foreach(charcins)
{
PutChar(c,BackgroundColour,ForegroundColour);
}
PutChar('\n',BackgroundColour,ForegroundColour);
}
staticvoidPutChar(charc,ConsoleColourbgcolour,ConsoleColourfgcolour)
{
switch(c)
{
// Newline
case'\n':
lastx=x;
x=0;
y++;
break;
// Backspace
case'\b':
if(x>0)
{
x--;
vga[y*80+x]=(ushort)(((byte)bgcolour<<12)|((byte)fgcolour<<8)|' ');
}
else
{
x=lastx;
y--;
}
break;
// Everything else
default:
vga[y*80+x]=(ushort)(((byte)bgcolour<<12)|((byte)fgcolour<<8)|c);
if(x<80)x++;
else
{
lastx=x;
x=0;
y++;
}
break;
}
}
}
}

Stivale2.cs

Because file is too long its recommended to download it and place under source/

Building and running the kernel

To compile and run the kernel go to main directory (Where limine.cfg, Makefile and source/ are) and run:

make -j$(nproc --all)

See Also

Articles

External Links

Retrieved from "https://wiki.osdev.org/index.php?title=Stivale2_CSharp_BareBones&oldid=27028"