x86-64 · UEFI · NativeAOT · CC0

An operating system written entirely in C#.

Bootloader, kernel, garbage collector, drivers, shell and applications. The kernel, DOOM and the shell are pure C#: not a single .c, .h or .asm file. Stock PowerShell 7 runs on top of that through an optional CoreCLR fork, which is the one C++ component and lives in a separate submodule. All of it, including CoreCLR, boots and runs on a real laptop (HP 255 G10, Ryzen 3 7320U, AMI firmware), not only in QEMU.

COM1 — qemu-system-x86_64
BdsDxe: loading Boot0002 "UEFI QEMU QEMU USB HARDDRIVE 1-0000:00:02.0-3"
BdsDxe: starting Boot0002 "UEFI QEMU QEMU USB HARDDRIVE 1-0000:00:02.0-3"
[uefi] image protection: base=0x7CBBB000 size=0xD07000
[info] boot info ready, switching to the kernel stack
[info] on the kernel stack, installing the IDT
[info] SharpOS 0.1
[info] build: 931671e+dirty.1014-2355-pipe-dev
[info] boot: UEFI  fw: EDK II / rev 65536
[info] caps: TextOutput Shutdown MemoryMap ExternalElf KeyboardInput
[info] idt installed
[info] memory regions: 83
[info] usable pages: 509110
[info] heap init ok
[info] iface dispatch bridge installed
[info] throw-ex shellcode: bytes=150 head=0x000000007D7A6358
[info] call-catch-funclet shellcode installed
[info] gc collect hook installed (precise once the walker is up)
[info] coff-pdata: records=25800 managed=0x00C50000..0x00CF6684
[cr3] readstub iced=legacy OK len=0x4
[cr3] writestub iced=legacy OK len=0x7
[info] pager root activated (clone CR3 live)
[info] VA 0 unmapped (null-deref trap)
[info] tss: double fault on its own stack (IST1)
[info] framebuffer mapped: 1280x800 va=0x80000000 modes=30
[info] acpi madt: lapic=0xFEE00000 cpus=1 ioapics=1
[info] hpet: freq=100000000 Hz comparators=3 64bit=yes
[serial] COM1 own 16550: PRESENT (loopback OK)
[gop] 1280x800 base=0x80000000 stride=1280 fmt=1
[pci] devs=6 ahci=0:31.2 id=0x8086:0x2922 PASS
[usb] xHCI(3.x) at 0:2.0 vid=0x1B36 did=0xD mmio=0xC000000000
[ebs] ExitBootServices OK -- POST-EBS substrate LIVE
[apic] id=0 ver=0x14 timer=1563 kHz
[xhci] port 2 connected speed=4 (usb3) slot=1 functions=storage
[xhci] port 4 connected speed=3 (usb2) slot=2 functions=keyboard
[xhci] usb keyboard attached as system input
[fat] mount=Y FAT32 bps=512 spc=8 lfn:CoreLib.dll sz=16003072 PASS
=== CoreClrProbe ===
[host] GC heap limit 994 MiB of 1988 MiB usable
[host] FileOpen path="\sharpos\System.Private.CoreLib.dll"
[fsread] done sz=16003072 read_ms=38 total_ms=69
[r2r] System.Private.CoreLib: Ready to Run initialized successfully
coreclr_initialize hr=0x0
=== S_OK — CoreCLR initialized ===
[info] pe launcher start
[info] config: \config\system.env read
[info] app run start: \bin\LAUNCHER.EXE

SharpOS shell — 'help' for what exists, Tab completes, Up/Down for history, 'exit' to leave
\home $ pipegen 100 | KQL 'summarize n = count() by Level'
[kql] start ms: declared=0 bind1=0 bind2=1 first=5 total=6
Expando { Level = 0, n = 20 }
Expando { Level = 1, n = 20 }
Expando { Level = 2, n = 20 }
Expando { Level = 3, n = 20 }
Expando { Level = 4, n = 20 }
\home $ DOOM.EXE
Last commit · build 931671e · commit history · discussions
Invariant #1

Every line of source is C#, even the parts you would normally write in assembly.

MSBuild and PowerShell only orchestrate the build. Interrupt handlers, write barriers, interface dispatch trampolines, reading CR3: each is solved one of three ways.

Tiers 0 and 1, the kernel and every native app, contain zero lines of C, C++ or assembly. The only exception is Tier 2: stock PowerShell needs a JIT and reflection, so it runs on an optional CoreCLR fork, which is C++ and is not required to build or boot the system.

As far as we know, no other managed OS holds this invariant: Cosmos drops to X#, its own assembler dialect, for the lowest layer, and Singularity kept its bootstrap and HAL in C++ and assembly. If a task looks impossible in C#, it has been framed wrong.

1 · The language itself

Plain C#

[RuntimeExport], [UnmanagedCallersOnly], delegate* unmanaged, fixed, pointer arithmetic.

2 · Machine code from C#

Iced assembler, at build time and at runtime

A source generator bakes 16 early-boot stubs into byte templates. Once the heap is up, Iced is called directly.

var a = new Assembler(64);
a.mov(rax, rcx);
a.Assemble(writer, rip);
3 · Symbols from attributes

COFF objects without a line of C

When the linker needs a symbol ILC does not emit, an MSBuild task generates the .obj itself.

[BootAsm.CoffDataSymbol(
    "__security_cookie",
    Section = ".data")]
public static ulong SecurityCookie
    = 0x2B992DDFA232UL;
Architecture

Three execution tiers

From bare metal up to stock .NET assemblies loaded byte-for-byte. A unikernel: one address space, its own scheduler, its own GC.

Tier 0

Kernel-AOT

Kernel, boot, drivers, scheduler. Direct access to CR3, PCI, MMIO, IDT, local APIC.

Lives in
OS/
Built with
NativeAOT + NoStdLib + MinimalRuntime
Tier 1

PE app

Freestanding apps: launcher, shell, DOOM, NES emulators. Each one gets its own collector and heap.

Lives in
apps_native/
Built with
NativeAOT + NoStdLib + shared SDK
Tier 2

CoreCLR-hosted

Stock .NET DLLs, byte-for-byte: JIT, reflection, the full BCL. This is how PowerShell runs.

Lives in
\sharpos\*.dll on FAT
Built with
optional CoreCLR fork (C++), linked into the kernel
Demos

Not a hello world: PowerShell and DOOM on bare metal.

The toughest tests of the whole stack at once: TPL, exceptions, reflection, GC, FAT32, ANSI console, framebuffer.

PowerShell 7.6.5 running on SharpOS: Get-ChildItem, tab completion, variables

PowerShell 7.6.5

Stock pwsh boots from FAT32 to an interactive prompt and runs real cmdlets. PSReadLine works fully: colors, Tab completion, history, Ctrl+C.

Get-ChildItem · pipelines · [Math]::Sqrt · PSReadLine · XtermSharp

DOOM

ManagedDoom built as a PE app on our own std. The WAD is read from FAT32, frames go out via GOP at 2× scale, PS/2 keyboard input, 35 Hz off the HPET.

ManagedDoom · FAT32 · GOP · HPET · PS/2
SharpOS launcher and fetch.exe output

A whole environment

A Terminal.Gui launcher, a bash-syntax shell with history, up to 16 processes via Process.Start, a FAT32 driver that writes, an xHCI USB stack, NES emulators. The whole set, kernel, shell, apps, pipes and CoreCLR with PowerShell, is tested on a real laptop, an HP 255 G10 with a Ryzen 3 7320U, 8 GB of RAM and AMI firmware, and the full probe suite passes there.

Terminal.Gui · bash-like shell · xHCI USB · FAT32 r/w · NES
Pipes

Programs pass objects, not bytes.

PIPEGEN | PIPEFILT 3 | PIPECNT works just like in bash, but every stage receives a real object with fields. It lives in shared exchange memory: no parsing, no copying into the receiver's heap.

›PIPEGEN 1000LogEntry × 1000
?PIPESPY a@ 0x7F3A2000
?PIPESPY b@ 0x7F3A2000
ƒKQL 'where Level >= 3 | summarize n = count() by Level'

Both PIPESPY stages report the same address: one object flows through four programs without being copied.

  • Zero allocations per messageCopy, forward, read in place. On a 100-object graph it is 5–6× faster than BinaryWriter + BinaryReader.
  • Queries right in the pipelineKusto Query Language over a message stream: where, summarize, extend, project. The query binds before the first data arrives.
  • One attribute and a type can travelMark a type with [Message] and it can be read, filtered and written back without a copy.
    Pipe.Read<LogEntry>()
        .Where(e => e.Level >= min)
        .WriteTo();  // zero-copy
  • JSON, JSON Lines, CBORCONVERT is fully streaming with one message in memory: a 100 MiB file passes through a 64 MiB heap.
Status

What works today

Every “works” mark is confirmed by in-kernel probe runs. The full matrix with notes lives in the README.

FeatureKernel-AOTPE appCoreCLR
Managed heap, GC with precise stack scanningworksworksworks
try / catch / finally / when filtersworksworksworks
async / await, yield return, lambdasworksworksworks
BCL collections, LINQworksworksworks
Task.Run, lock, multithreaded processesworksworksworks
Correctly rounded Math (SharpLibm, CORE-MATH)worksworkspartial
dynamicnot yetpartialworks
Reflectionnot yetnot yetworks
USB (xHCI): keyboard, flash drives, U2F hardware keypartialpartialnot applicable
Preemptive multitaskingpartialpartialpartial
SMPplannedplannedplanned
Networkingnot yetnot yetnot yet
works partial planned not yet not applicable
Quick start

Build it yourself

It builds with a plain dotnet publish -r win-x64. mise or nix installs the toolchain, and the image boots in QEMU.

  1. Install mise or nix. Every tool version is pinned in mise.toml and flake.lock.
  2. Clone with submodules. The CoreCLR fork is optional; skip it with -SkipCoreClr.
  3. Build and run. run_build.ps1 builds the kernel, makes the image and launches QEMU.
  4. Drop in payloads. DOOM1.WAD, .nes cartridges and PowerShell go into payloads/.
# macOS / Linux / Windows
# or on Windows: winget install jdx.mise
brew install mise

git clone --recurse-submodules \
  https://github.com/daniilvaino/SharpOS.git
cd SharpOS
mise trust && mise bootstrap
mise exec -- pwsh

# then, in PowerShell
./build.ps1 launcher
$env:SHARPOS_GUI = 1
./run_build.ps1 -UsbOnly -SkipCoreClr
# NixOS or any Linux with flakes enabled
git clone --recurse-submodules \
  https://github.com/daniilvaino/SharpOS.git
cd SharpOS
nix develop --command pwsh

# then, in PowerShell
./build.ps1 launcher
$env:SHARPOS_GUI = 1
./run_build.ps1 -UsbOnly -SkipCoreClr
Credits

Libraries and inspiration

Third-party code that ships in the image, and the projects SharpOS learned from.

  • zerosharporigin
    A UEFI hello world on NativeAOT, where it all began.
  • dotnet/runtimeMIT
    NativeAOT toolchain, the CoreCLR fork, hundreds of BCL ports.
  • IcedMIT
    x86-64 encoder: every bit of assembly in the project.
  • ManagedDoomGPL-2.0
    DOOM in C#, isolated in its own app.
  • Terminal.GuiMIT
    Text UI toolkit the launcher is built on.
  • XtermSharpMIT
    Terminal emulator engine: ANSI/VT, scrollback.
  • Kusto.LanguageApache-2.0
    KQL parsing and binding for pipeline queries.
  • Cosmosidea
    The very idea of a managed operating system. Its lowest layer is written in X#, an assembler dialect, so it does not hold the all-C# invariant.

C# from the first instruction to DOOM.

SharpOS is public domain under CC0. Use it, change it, send pull requests.