Here is the same post rewritten in clean Markdown with no em dashes, ready to drop directly into your Pelican workflow on kaleb.au.


Building Bare Metal .NET: Exploring NativeAOT Patching

If you have ever wondered how far .NET can be pushed outside the traditional runtime model and down into kernels, bootloaders, and bare metal environments, then the project nativeaot-patcher is worth a serious look.

Repository: https://github.com/valentinbreiz/nativeaot-patcher

This project is essentially a proof of concept for a next generation Cosmos style toolchain that compiles C# directly into a bootable kernel using .NET NativeAOT and IL patching.

It demonstrates something many developers assume is impossible.

Using modern .NET tooling to build an operating system kernel without a traditional managed runtime.


The Big Idea

At a high level, the project combines:

  • .NET NativeAOT for ahead of time compilation
  • IL patching using Mono.Cecil
  • Custom build tooling
  • Low level assembly integration
  • Standard bootloader packaging

The result is a workflow that turns C# code into:

C# -> IL -> patched IL -> native objects -> ELF kernel -> bootable ISO

This pipeline is fully automated through MSBuild and custom tasks, ultimately producing a bootable kernel image that can run in a VM or on hardware.

In other words:

It treats C# as a systems programming language.


Why This Matters

Most developers think of .NET like this:

App -> CLR -> OS

But NativeAOT enables a different architecture:

App -> native binary -> hardware

Projects like this take that one step further:

Kernel -> native binary -> hardware

No runtime No JIT No operating system dependency

Just compiled code.


How the Build Pipeline Works

The build process is surprisingly structured and transparent.

1) Assembly Compilation

Low level routines are written in assembly and compiled into object files.

Examples include:

  • Hardware I/O
  • Interrupt handling
  • Runtime imports

These are assembled using:

yasm -felf64

2) C# Compilation

Standard .NET tooling compiles the kernel code into an IL assembly.

At this stage:

  • Roslyn compiles the code
  • Static analyzers validate plug rules

Nothing unusual yet.


3) IL Patching

This is where things get interesting.

The patcher:

  • Loads the compiled assembly
  • Finds methods marked with [Plug]
  • Replaces implementations with the methods marked with [Plug]

This allows:

  • Runtime methods to be overridden
  • Platform specific implementations
  • Missing runtime functionality to be injected

The patched assembly is then saved.


4) NativeAOT Compilation

The patched IL is compiled into native machine code.

This step performs:

  • Tree shaking
  • Dependency trimming
  • Native code generation

Output:

.obj / .o files

5) Linking

All native objects are linked into a single executable:

Kernel.elf

A linker script defines:

  • Memory layout
  • Entry point
  • Symbol resolution

6) ISO Creation

Finally, the system packages everything into a bootable image:

Kernel.iso

This includes:

  • Bootloader
  • Configuration
  • Kernel binary

The ISO can then be:

  • Booted in QEMU
  • Run in VirtualBox
  • Written to USB
  • Executed on hardware

All from a standard:

dotnet build

pipeline.


The Real Innovation: Plug Based Runtime Replacement

The most powerful concept in this project is the plug system.

Instead of relying on the standard runtime, the toolchain allows you to replace functionality directly at the IL level.

For example:

Console.WriteLine()

could be replaced with:

SerialPort.Write()

Or:

Memory allocation
Thread scheduling
Hardware access

All implemented manually.

This makes the runtime:

  • Minimal
  • Deterministic
  • Hardware aware
  • Fully customizable

Comparison to Traditional Cosmos

Traditional Cosmos uses:

IL -> custom compiler -> assembly

This project uses:

IL -> NativeAOT -> native objects

That shift matters.

NativeAOT provides:

  • Modern compiler optimizations
  • Better tooling compatibility
  • Smaller binaries
  • Faster startup
  • Predictable execution

And importantly:

It leverages the official .NET toolchain instead of a custom compiler stack.


Where This Gets Interesting for Real Systems

This is not just about hobby operating systems.

The same architecture could be used for:

Embedded Systems

  • Routers
  • IoT devices
  • Industrial controllers

Infrastructure Components

  • Hypervisors
  • Boot environments
  • Recovery systems

High Reliability Systems

  • Deterministic services
  • Minimal attack surface
  • Fast cold start workloads

Given the kind of infrastructure heavy environments many of us run, especially around virtualization and low level networking, this model is particularly relevant for:

  • custom appliance OS builds
  • secure boot environments
  • minimal control plane runtimes

Strengths

Uses the Official .NET Toolchain

No custom compiler required.

Fully Native Output

No runtime dependency.

Deterministic Behavior

No JIT or runtime variability.

Modern Build System

Everything integrates with:

dotnet build
MSBuild
NuGet

Limitations

This is still experimental.

You should expect:

Limited Runtime Support

Many standard APIs require manual implementation.

Architecture Constraints

Hardware support must be written explicitly.

Debugging Complexity

You are debugging:

native kernel code

not managed applications.


Why Developers Should Watch This Project

This is one of the clearest demonstrations that:

.NET is no longer just an application framework.

It can be:

  • a systems language
  • a kernel language
  • an embedded language

And that shift is happening now.


Getting Started

The easiest way to explore the project:

Install dependencies

.NET SDK 9+
LLVM (lld)
yasm
xorriso

Clone the repository

git clone https://github.com/valentinbreiz/nativeaot-patcher
cd nativeaot-patcher

Build

dotnet build

You will get:

Kernel.iso

Boot it in:

qemu-system-x86_64 -cdrom Kernel.iso

And you are running a C# kernel.


Final Thoughts

Projects like this challenge long held assumptions about what managed languages can do.

They show that:

  • modern compilers
  • aggressive trimming
  • IL rewriting
  • ahead of time compilation

can turn high level languages into true systems languages.

Not in theory.

In practice.