Stivale2 CSharp BareBones: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content deleted Content added
No edit summary
mNo edit summary
 
(8 intermediate revisions by 2 users not shown)
Line 26: Line 26:
* Xorriso (To build the iso)
* Xorriso (To build the iso)
* Qemu-system-x86 (To run the OS)
* Qemu-system-x86 (To run the OS)
If you are using debian based system you can install most of them with this command:
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
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
sudo dpkg -i tysila2.deb


Line 48: Line 49:


limine.cfg
limine.cfg
TIMEOUT=0
VERBOSE=yes
SERIAL=yes
# Kernel entry name in bootloader menu
# Kernel entry name in bootloader menu
:Stivale2
:Stivale2
Line 63: Line 68:


Makefile:
Makefile:
<source lang="make">
<syntaxhighlight lang="make">
KERNELDIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
KERNELDIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))


Line 71: Line 76:


limine:
limine:
git clone https://github.com/limine-bootloader/limine.git --single-branch --branch=latest-binary --depth=1
git clone https://github.com/limine-bootloader/limine.git --branch=v2.0-branch-binary --depth=1
$(MAKE) -C $(KERNELDIR)/limine
$(MAKE) -C $(KERNELDIR)/limine


Line 81: Line 86:
rm -rf $(KERNELDIR)/limine
rm -rf $(KERNELDIR)/limine
rm -rf $(KERNELDIR)/iso_root
rm -rf $(KERNELDIR)/iso_root
</syntaxhighlight>
</source>


source/Makefile:
source/Makefile:
<source lang="make">
<syntaxhighlight lang="make">
SOURCEDIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
SOURCEDIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))


Line 143: Line 148:
run:
run:
$(QEMU) $(QEMUFLAGS) -cdrom $(ISO)
$(QEMU) $(QEMUFLAGS) -cdrom $(ISO)
</syntaxhighlight>
</source>
====Linker script====
====Linker script====
Script that ld.lld will use to link relocatable elf objects
Script that ld.lld will use to link relocatable elf objects


source/linker.ld
source/linker.ld
<source lang="lscript">
<syntaxhighlight lang="text">
OUTPUT_FORMAT(elf64-x86-64)
OUTPUT_FORMAT(elf64-x86-64)
OUTPUT_ARCH(i386:x86-64)
OUTPUT_ARCH(i386:x86-64)
Line 196: Line 201:
} :data
} :data
}
}
</syntaxhighlight>
</source>


====Kernel====
====Kernel====
Line 202: Line 207:


source/kernel.asm
source/kernel.asm
<source lang="asm">
<syntaxhighlight lang="asm">
[EXTERN _ZN6kernel6Kernel7ProgramM_0_8RealMain_Rv_P1PV26stivale2#2Bstivale2_struct]
global sthrow

; C# entry point
extern _ZN6kernel6Kernel7ProgramM_0_8RealMain_Rv_P1PV26stivale2#2Bstivale2_struct


section .data
section .data

stivale2_smp_tag:
stivale2_smp_tag:
dq 0x1ab015085f3273df
dq 0x1ab015085f3273df
Line 215: Line 216:
dq 0
dq 0


; Framebuffer
stivale2_framebuffer_tag:
dq 0x3ecc1bc43d0f7971
dq stivale2_smp_tag
dw 0
dw 0
dw 32

; Text mode
stivale2_any_video_tag:
stivale2_any_video_tag:
dq 0xc75c9fa92a44c4db
dq 0xc75c9fa92a44c4db
Line 229: Line 221:
dq 1
dq 1


[SECTION .bss]
; Kernel stack
section .bss
align 16
align 16
stack_bottom:
stack_bottom:
Line 236: Line 227:
stack_top:
stack_top:


[SECTION .stivale2hdr]
; Stivale2 header
section .stivale2hdr
align 4
align 4
stivale_hdr:
stivale_hdr:
Line 243: Line 233:
dq stack_top
dq stack_top
dq (1 << 1)
dq (1 << 1)
; Replace this with "stivale2_framebuffer_tag" to enable framebuffer
dq stivale2_any_video_tag
dq stivale2_any_video_tag


section .text
[SECTION .text]
kmain:
kmain:
push rdi
push rdi
Line 254: Line 243:
hlt
hlt
jmp sthrow
jmp sthrow
[GLOBAL sthrow]
</source>

_ZN6kernel6Kernel7ProgramM_0_4halt_Rv_P0:
hlt
jmp _ZN6kernel6Kernel7ProgramM_0_4halt_Rv_P0
[GLOBAL _ZN6kernel6Kernel7ProgramM_0_4halt_Rv_P0]
</syntaxhighlight>


source/kernel.cs
source/kernel.cs
<source lang="csharp">
<syntaxhighlight lang="csharp">
using System.Runtime.CompilerServices;

namespace Kernel
namespace Kernel
{
{
Line 267: Line 264:
public static void Main()
public static void Main()
{
{
// These lines of code are required
// These line is required
// Without them compiler will remove RealMain from kernel
// Without it compiler will remove RealMain from kernel
stivale2.stivale2_struct* test = null;
RealMain(null);
RealMain(test);
}
}


public static stivale2.stivale2_struct_tag_smp *smp_tag;
public static stivale2.stivale2_struct_tag_smp *smp_tag;

[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void halt();


// Real entry
// Real entry
Line 279: Line 278:
{
{
// If Stivale2 struct was not found halt
// If Stivale2 struct was not found halt
if (stiv == null) while (true);
if (stiv == null) halt();


// Example on how to get Stivale2 structure tags
// Example on how to get Stivale2 structure tags
Line 288: Line 287:


// Halt
// Halt
while (true);
halt();
}
}
}
}
}
}
</syntaxhighlight>
</source>
====Console====
====Console====
This is an example code that provides console write and writeline functions:
This is an example code that provides console write and writeline functions:
source/Console.cs
source/Console.cs
<source lang="csharp">
<syntaxhighlight lang="csharp">
namespace Kernel
namespace Kernel
{
{
Line 331: Line 330:
public static void Write(string s)
public static void Write(string s)
{
{
foreach(char c in s)
foreach (char c in s)
{
{
PutChar(c, BackgroundColour, ForegroundColour);
PutChar(c, BackgroundColour, ForegroundColour);
Line 339: Line 338:
public static void WriteLine(string s)
public static void WriteLine(string s)
{
{
foreach(char c in s)
foreach (char c in s)
{
{
PutChar(c, BackgroundColour, ForegroundColour);
PutChar(c, BackgroundColour, ForegroundColour);
Line 384: Line 383:
}
}
}
}
</syntaxhighlight>
</source>


====Stivale2.cs====
====Stivale2.cs====
Line 407: Line 406:
[[Category:Bare bones tutorials]]
[[Category:Bare bones tutorials]]
[[Category:CSharp]]
[[Category:CSharp]]
[[Category:Stivale]]

Latest revision as of 09:47, 9 June 2024

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

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:
    dq 0x1ab015085f3273df
    dq 0
    dq 0

stivale2_any_video_tag:
    dq 0xc75c9fa92a44c4db
    dq stivale2_smp_tag
    dq 1

[SECTION .bss]
align 16
stack_bottom:
resb 8192
stack_top:

[SECTION .stivale2hdr]
align 4
stivale_hdr:
    dq kmain
    dq stack_top
    dq (1 << 1)
    dq stivale2_any_video_tag

[SECTION .text]
kmain:
    push rdi
    call _ZN6kernel6Kernel7ProgramM_0_8RealMain_Rv_P1PV26stivale2#2Bstivale2_struct

sthrow:
    hlt
    jmp sthrow
[GLOBAL sthrow]

_ZN6kernel6Kernel7ProgramM_0_4halt_Rv_P0:
    hlt
    jmp _ZN6kernel6Kernel7ProgramM_0_4halt_Rv_P0
[GLOBAL _ZN6kernel6Kernel7ProgramM_0_4halt_Rv_P0]

source/kernel.cs

using System.Runtime.CompilerServices;

namespace Kernel
{
    // Class is unsafe so we can use pointers
    public unsafe class Program
    {
        // Fake kernel entry point
        // Do not remove
        public static void Main()
        {
            // These line is required
            // Without it compiler will remove RealMain from kernel
            RealMain(null);
        }

        public static stivale2.stivale2_struct_tag_smp *smp_tag;

        [MethodImpl(MethodImplOptions.InternalCall)]
        public static extern void halt();

        // Real entry
        public static void RealMain(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

namespace Kernel
{
    // Console colours
    public enum ConsoleColour
    {
        Black,
        Blue,
        Green,
        Cyan,
        Red,
        Purple,
        Brown,
        Grey,
        DarkGrey,
        LightBlue,
        LightGreen,
        LightCyan,
        LightRed,
        LightPurple,
        Yellow,
        White,
    };
    public unsafe static class Console
    {
        // VGA text mode address
        public static ushort* vga = (ushort*)0xB8000;
        // Cursor positions
        public static int x = 0, y = 0, lastx = 0;
        // Text colours
        public static ConsoleColour ForegroundColour = ConsoleColour.White;
        public static ConsoleColour BackgroundColour = ConsoleColour.Black;

        public static void Write(string s)
        {
            foreach (char c in s)
            {
                PutChar(c, BackgroundColour, ForegroundColour);
            }
        }

        public static void WriteLine(string s)
        {
            foreach (char c in s)
            {
                PutChar(c, BackgroundColour, ForegroundColour);
            }
            PutChar('\n', BackgroundColour, ForegroundColour);
        }

        static void PutChar(char c, ConsoleColour bgcolour, ConsoleColour fgcolour)
        {
            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