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.