I've been doing OS development in NASM on and off for years.
Recently I decided to restart an x86-64 hobby OS project from scratch, with one deliberate difference: I'm using an LLM as a development partner.
This is not an "AI generated an OS in five minutes" project.
The entire development environment currently runs on Windows 11, using NASM and LLVM's lld-link to produce PE32+ UEFI executables.
No Linux host is involved in the current build process.
The idea is to develop the system incrementally and treat the specifications and the actual hardware as the final authority.
My basic rules are:
- Hardware-facing structures, offsets and bitfields must be checked against specifications or reliable references.
- Each development step must actually work before moving on.
- The primary target is physical hardware, not only an emulator.
- Generated code is not accepted simply because it compiles.
- When something fails, the cause gets investigated and corrected before continuing.
- I want to avoid giant generated code dumps containing half a dozen unfinished subsystems.
Current hardware
The current physical development machine is:
Code: Select all
CPU: AMD Ryzen 5 9600X
Zen 5 / Granite Ridge
Motherboard: ASUS TUF GAMING B850-PLUS WIFI
RAM: 32 GiB
Graphics: AMD integrated graphics
NVIDIA GeForce RTX 5050
Host OS: Windows 11
Boot: Native UEFI
A note about programming languages
The current EFI bootstrap is written in NASM.
I used to be very attached to the idea of writing an OS entirely in assembly, but over the years I reached the point where I was writing increasingly sophisticated NASM macros to imitate higher-level language constructs.
At some point, when you start implementing IF/ELSE constructs, elaborate calling abstractions and pseudo-objects inside an assembler preprocessor, it becomes reasonable to ask why you are still pretending to write assembly.
So my current rule is:
NASM will therefore remain important for CPU state, special instructions, entry code, interrupt stubs, context switching, certain MMIO operations and other places where the exact machine operations matter.Use assembly when explicitly seeing the machine improves understanding.
Use a higher-level language when the instructions themselves become administrative noise around a more abstract problem.
For more structurally complex subsystems later on, I am no longer imposing an "assembly at all costs" rule.
The language choice will be made module by module.
Milestone 1 — UEFI GOP framebuffer
Status: PASS on physical hardware
The first stage is now working on the actual machine.
Current boot path:
Code: Select all
ASUS UEFI
|
v
\EFI\BOOT\BOOTX64.EFI
|
v
EFI Boot Services
|
v
LocateProtocol()
|
v
Graphics Output Protocol
|
v
Framebuffer
|
v
Direct rendering
It currently displays a dark gray framebuffer with a small fixed white software cursor.
The cursor is intentionally primitive. Its purpose is simply to prove that:
- the firmware loads the PE32+ image;
- our x86-64 NASM entry point executes;
- EFI_BOOT_SERVICES is accessible;
- LocateProtocol() succeeds;
- GOP is located;
- the framebuffer information is usable;
- and the framebuffer can be written directly.
Booting it on real hardware
There is currently no installer.
Nothing is installed on or written to the internal Windows disk.
The OS is booted as a standard UEFI removable-media application from a dedicated USB flash drive.
1. Format a spare USB flash drive as FAT32.
2. Create the standard x86-64 UEFI removable-media path:
Code: Select all
\EFI\BOOT\
Code: Select all
\EFI\BOOT\BOOTX64.EFI
Code: Select all
mkdir X:\EFI\BOOT
copy /Y build\BOOTX64.EFI X:\EFI\BOOT\BOOTX64.EFI
Code: Select all
USB
└── EFI
└── BOOT
└── BOOTX64.EFI
No Legacy BIOS boot path is used.
The firmware discovers BOOTX64.EFI through the standard UEFI removable-media path.
Firmware configuration
My current development configuration is:
Code: Select all
UEFI ON
CSM OFF
Secure Boot OFF
TPM ON, but unused by the OS
Secure Boot is disabled because the development EFI executable is currently unsigned.
ASUS Secure Boot example
On my ASUS TUF GAMING B850-PLUS WIFI, the relevant setting is:
Code: Select all
UEFI Setup
-> Boot
-> Secure Boot
-> OS Type
Code: Select all
Windows UEFI mode
Other OS
Code: Select all
Windows UEFI mode
-> Secure Boot enabled
Other OS
-> Secure Boot disabled
Code: Select all
Other OS
Windows System Information (msinfo32) reports:
Code: Select all
BIOS Mode: UEFI
Secure Boot State: Off
Code: Select all
UEFI ON
CSM OFF
Secure Boot OFF
TPM ON
ASUS documents this behaviour here:
ASUS — How to Enable/Disable Secure Boot
There is also no need to clear the Secure Boot key database merely to test this unsigned hobby OS.
Why USB first?
Until the boot path is stable, I prefer keeping the development OS completely separate from the Windows EFI System Partition.
Breaking a disposable USB flash drive is considerably funnier than breaking Windows Boot Manager.
What you should see
If the current build is working correctly, the firmware display should be replaced by a full-screen dark gray framebuffer with a small white triangular cursor near the upper-left corner.
Approximately:
Code: Select all
┌──────────────────────────────┐
│ dark gray background │
│ │
│ ◢ small white cursor │
│ │
│ │
└──────────────────────────────┘
At this stage the cursor is fixed and there is no keyboard or mouse input yet.
So the expected result is simply:
Code: Select all
dark gray framebuffer + small fixed white ◢
Code: Select all
UEFI
-> BOOTX64.EFI
-> EFI Boot Services
-> LocateProtocol()
-> GOP
-> framebuffer
-> direct pixel writes
I've attached the complete source code for this stage
This is the exact project used for the successful physical-hardware test, including:
- NASM source
- UEFI definitions
- Windows build script
- PE32+ EFI generation
- GOP framebuffer access
- fixed software cursor rendering
The next target is:
Code: Select all
GetMemoryMap()
|
v
Prepare boot information
|
v
ExitBootServices()
|
v
Continue executing independently
|
v
Keep rendering to the existing framebuffer
Roadmap
The current direction is approximately:
Code: Select all
UEFI
|
+-- GOP / framebuffer [PASS]
|
+-- GetMemoryMap / ExitBootServices
|
+-- CPU / memory / interrupts
|
+-- ACPI
|
+-- RSDP
+-- XSDT
+-- MCFG
|
+-- PCI Express
|
+-- ECAM
+-- bus/device/function enumeration
+-- Vendor ID / Device ID
+-- Class / Subclass / ProgIF
+-- BAR / MMIO discovery
|
+-- review PCIe foundation
|
+-- xHCI
|
+-- USB
+-- HID
+-- keyboard
+-- mouse
|
+-- movable software cursor
|
+-- eventually, a graphical environment
Current status
Code: Select all
PASS on physical hardware
AMD Ryzen 5 9600X
ASUS TUF GAMING B850-PLUS WIFI
Windows 11
-> NASM
-> lld-link
-> PE32+ EFI
-> FAT32 USB
-> physical UEFI boot
-> GOP
-> framebuffer
-> direct rendering
Next:
GetMemoryMap() -> ExitBootServices()