From Power-On to 'Oh No'
Dear Diary: MinGW, M1, and the Sweet Taste of Actually Working Code
January 2, 2026 - ChronOS Development Log
Dear diary, today I discovered that building an operating system kernel is like trying to assemble IKEA furniture while blindfolded, using instructions written in ancient Sumerian, and the Allen wrench is actually a spoon.
It's 3:20 PM on a gray Tuesday, and I'm sitting in my home office staring at a WSL terminal that's mocking me with a permission denied error. The coffee has gone cold – the third cup today – and somewhere in the back of my mind, a small voice is suggesting that perhaps normal people spend their afternoons watching Netflix instead of wrestling with GNU toolchains.
But here we are.
The Permission Paradox
The day started innocently enough. I had ChronOS building perfectly in my WSL environment yesterday, producing a lovely little UEFI bootloader that should, in theory, transform my ancient Aorus laptop into a portal to kernel development glory. All I needed was to install a couple of build dependencies and push forward to the M1 milestone.
"Just run apt install," I told myself. "What could go wrong?"
The terminal responded with the digital equivalent of a patronizing cough: permission denied. No sudo access on this machine. Of course. Because the universe has a sense of humor, and that sense of humor involves making sure that every simple task in OS development becomes a three-act tragedy.
I spent twenty minutes googling "WSL sudo without admin rights" before accepting the uncomfortable truth: I was fighting the wrong battle. This is the moment in every debugging story where you realize you've been trying to push a door that clearly says "pull."
The MinGW Revelation
Here's the thing about building UEFI applications that I somehow forgot in my quest for Linux purity: UEFI executables are PE32+ files. Windows executables, essentially. And what do you know, MinGW-w64 produces Windows executables natively. No conversion needed. No elaborate toolchain gymnastics. Just... build the thing with the tools designed to build Windows things.
Sometimes the solution is so obvious you feel personally insulted by your own oversight.
I fired up MSYS2 and watched pacman cheerfully install gcc without asking for a single password. No drama. No permissions. Just tools, installing themselves like civilized software should. It was almost suspicious how smooth it went.
"This is going too well," I muttered, and the universe heard me.
The Makefile Archaeology
Creating a MinGW-compatible build system turned out to be an exercise in cross-platform archaeology. My existing Makefile was a beautiful work of Linux-centric art, complete with objcopy incantations to convert ELF files to PE format. Elegant, sophisticated, and completely useless for a toolchain that already speaks PE natively.
So I created Makefile.mingw – a parallel universe version where things just work without the ceremonial file format conversion dance. The key insight, which took me longer to grasp than I care to admit, is that MinGW produces PE executables because it's designed to target Windows. UEFI uses PE format because it's essentially Windows Boot Manager's cousin. The math was always there; I just needed to stop trying to make it more complicated than necessary.
The missing piece was a replacement for the GNU-EFI library functions that my code was cheerfully calling. So I wrote efi_support.c – a minimal shim that implements just enough EFI library functionality to keep my kernel from having an existential crisis at link time. It's not pretty, but it's honest work.
The Build That Actually Worked
At 3:30 PM, I typed make -f Makefile.mingw with the kind of cautious hope you reserve for situations that have failed spectacularly multiple times before. The compiler churned through six source files, the linker did its mysterious linking dance, and then...
[STRIP] deploy/BOOTX64.EFI
==========================================
Build complete.
==========================================
I stared at the screen for a moment, waiting for the other shoe to drop. When you've been fighting toolchains all day, success feels like a trap. But there it was: a 21KB UEFI executable, properly formatted, sitting in my deploy directory like it had every right to exist.
file deploy/BOOTX64.EFI confirmed what I hardly dared believe: "PE32+ executable for EFI (application), x86-64." The computer had finally agreed with my vision of reality.
The USB Image Ritual
Creating a bootable USB image involves a series of incantations that feel like digital witchcraft. You create an empty file, format it as FAT32, create the sacred directory structure (EFI/BOOT/), and copy your executable to the precise location where UEFI expects to find it. Get any step wrong, and your laptop will stare at you with the blank indifference of a machine that has no idea what you wanted it to do.
But mtools played along nicely, and soon I had a 64MB image file containing my bootloader, properly nested in its directory hierarchy like a Russian doll of boot-time hopes and dreams.
The Moment of Truth
Here's where the story takes a turn toward the genuinely terrifying. Up until now, everything had been theoretical. Text on screens, files being shuffled around, compilers producing output that looked plausible. But now came the moment where I had to find out if any of this actually worked on real hardware.
I copied the image to a USB drive, walked over to my 2013 Aorus laptop – a machine old enough to have actual character – and held down F12 like I was making a wish on a digital birthday cake.
The boot menu appeared. My USB drive was listed there, looking innocent and harmless. I selected it, pressed Enter, and watched the screen go black.
This is the moment in every OS development story where time stops. The black screen could mean anything. Success, failure, or the kind of spectacular hardware confusion that requires a hard power cycle. You wait, holding your breath, hoping that somewhere in the depths of UEFI firmware, your code is doing what you told it to do instead of what you accidentally told it to do.
Then the text appeared.
The M1 Miracle
White text on black background, crisp and clear on the laptop's 3200x1800 display. My bootloader was not only running – it was chattering away happily, logging every detail of its interaction with the hardware:
"Framebuffer base: 0x00000000D0000000"
"Width: 3200, Height: 1800"
"Display initialized"
I pressed Enter, and it responded. I was having a conversation with code I had written, running on bare metal, without an operating system anywhere in sight. It's a feeling that's hard to describe – like hearing your own voice echoed back from the void, but the void is a graphics card and it's being remarkably cooperative about the whole thing.
The bootloader created a log file on the USB drive, documenting its brief existence with the bureaucratic thoroughness of a kernel that takes its paperwork seriously. When I plugged the drive back into my development machine, there was BOOTLOG.TXT, a UTF-16 testament to the fact that this insane project actually worked.
The Aftermath
It's now 4:15 PM, and I'm looking at a successful M1 milestone. The bootloader builds cleanly on MinGW, boots on real hardware, initializes the display, accepts keyboard input, and writes logs to persistent storage. All the basic building blocks of an operating system, working together in harmony.
There's something deeply satisfying about solving a problem by stepping back and choosing the right tool instead of fighting with the wrong one. WSL wanted sudo permissions I didn't have, but MinGW just wanted to build Windows executables. UEFI wanted PE32+ files, and MinGW produces PE32+ files. Sometimes the universe aligns, and you remember why you started this project in the first place.
Of course, this is just M1. The bootloader is currently about as sophisticated as a digital "Hello, World" with delusions of grandeur. Real operating systems have memory management, task scheduling, device drivers, filesystems, and a thousand other complexities that I haven't even begun to contemplate yet.
But today, for the first time in weeks, I have a kernel that actually boots and responds to input. It's a small victory in the grand scheme of things, but in the world of hobby OS development, small victories are worth celebrating.
Tomorrow I'll start working on M2, which will undoubtedly introduce new and creative ways for everything to break. But tonight, I'm going to enjoy the fact that I have 21KB of code that successfully convinced a modern laptop to do exactly what I asked it to do.
That's not nothing. That's actually pretty remarkable.
Now, where did I put that fourth cup of coffee?
Next up: M2 milestone, where we'll discover exciting new ways for hardware to ignore our carefully crafted instructions. The adventure continues.
Top comments (0)