<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Emil Friðriksson</title>
  <subtitle>Personal homepage and blog of Emil Friðriksson — systems programmer, EVE Online veteran database admin, decade-deep in hobby kernels and x86.</subtitle>
  <link href="https://emilf.dev/feed.xml" rel="self"/>
  <link href="https://emilf.dev/"/>
  <updated>2026-09-11T00:00:00Z</updated>
  <id>https://emilf.dev/</id>
  <author>
    <name>Emil Friðriksson</name>
  </author>
  <entry>
    <title>TheseusOS, part 1: before the kernel</title>
    <link href="https://emilf.dev/blog/theseusos-part-1-before-the-kernel/"/>
    <updated>2026-09-11T00:00:00Z</updated>
    <id>https://emilf.dev/blog/theseusos-part-1-before-the-kernel/</id>
    <content type="html">&lt;p&gt;This is the first post in a series about &lt;a href=&quot;https://emilf.dev/projects/theseusos/&quot;&gt;TheseusOS&lt;/a&gt;, my hobby operating system for &lt;a href=&quot;https://en.wikipedia.org/wiki/X86-64&quot;&gt;x86-64&lt;/a&gt; written in Rust. I&#39;ve been poking at OS development on and off since I was a teenager, and I&#39;m writing the series assuming very little systems-level background. Partly because that&#39;s the explanation I would have wanted when I started, and partly because writing it down forces me to check that I actually understand it.&lt;/p&gt;
&lt;p&gt;This post is about the &lt;strong&gt;bootloader&lt;/strong&gt;: the code that runs after the machine wakes up and before any of the kernel exists. The next post picks up at the handoff, when the kernel takes over.&lt;/p&gt;
&lt;h2&gt;The chain from the power button&lt;/h2&gt;
&lt;p&gt;Your operating system isn&#39;t the first thing that runs. Between the power button and the &lt;a href=&quot;https://en.wikipedia.org/wiki/Kernel_(operating_system)&quot;&gt;kernel&lt;/a&gt; sits &lt;a href=&quot;https://en.wikipedia.org/wiki/Firmware&quot;&gt;firmware&lt;/a&gt; — software baked into the motherboard. On a modern PC that firmware implements &lt;a href=&quot;https://en.wikipedia.org/wiki/UEFI&quot;&gt;UEFI&lt;/a&gt;, the Unified Extensible Firmware Interface. Its job is to bring the hardware up, find something bootable, and run it.&lt;/p&gt;
&lt;p&gt;For a normal machine, the thing it runs is a &lt;a href=&quot;https://en.wikipedia.org/wiki/Bootloader&quot;&gt;bootloader&lt;/a&gt; such as &lt;a href=&quot;https://en.wikipedia.org/wiki/GNU_GRUB&quot;&gt;GRUB&lt;/a&gt;, which then loads Linux or Windows. For TheseusOS I skipped the middleman: the bootloader and the kernel are built as a single program that UEFI launches directly. I&#39;ll get to why later, because the reason is less noble than it sounds.&lt;/p&gt;
&lt;p&gt;UEFI gets a bad reputation, and some of it is deserved. But compared to the old way — a &lt;a href=&quot;https://en.wikipedia.org/wiki/Boot_sector&quot;&gt;512-byte boot sector&lt;/a&gt; dropped into 16-bit &lt;a href=&quot;https://en.wikipedia.org/wiki/Real_mode&quot;&gt;real mode&lt;/a&gt; with no memory management at all — it&#39;s a picnic. You start in a flat 64-bit environment, the firmware has already configured the machine, and it hands you structured ways to ask questions instead of making you probe hardware blind.&lt;/p&gt;
&lt;p&gt;The one thing that genuinely hurts is display output. UEFI has no text mode, so you get no usable text on screen until you build your own font rendering and write characters to a &lt;a href=&quot;https://en.wikipedia.org/wiki/Framebuffer&quot;&gt;framebuffer&lt;/a&gt; yourself. Until that exists, your only window into the machine is a serial line.&lt;/p&gt;
&lt;h2&gt;Why you leave the firmware behind&lt;/h2&gt;
&lt;p&gt;UEFI gives your boot program a set of helpers called &lt;strong&gt;boot services&lt;/strong&gt;: read a file, allocate memory, enumerate devices, and so on. They are genuinely useful, and while they exist you&#39;d be foolish not to lean on them.&lt;/p&gt;
&lt;p&gt;So why does every OS eventually call &lt;code&gt;ExitBootServices&lt;/code&gt; and throw them away?&lt;/p&gt;
&lt;p&gt;Because useful isn&#39;t the same as &lt;em&gt;yours&lt;/em&gt;. While boot services are live, the firmware still owns the machine. It owns the &lt;a href=&quot;https://en.wikipedia.org/wiki/Page_table&quot;&gt;page tables&lt;/a&gt; that translate virtual addresses to physical ones, and you don&#39;t get to redesign them. It owns the &lt;a href=&quot;https://en.wikipedia.org/wiki/Global_Descriptor_Table&quot;&gt;Global Descriptor Table&lt;/a&gt; and the &lt;a href=&quot;https://en.wikipedia.org/wiki/Interrupt&quot;&gt;interrupt&lt;/a&gt; tables, and you can&#39;t install your own. It manages memory on its own terms and expects to be the only one doing so. If you tried to bring up your own &lt;a href=&quot;https://en.wikipedia.org/wiki/Device_driver&quot;&gt;device drivers&lt;/a&gt; and take over interrupt handling while the firmware was still working underneath you, the two of you would be fighting over the same hardware and the same data structures.&lt;/p&gt;
&lt;p&gt;To run an operating system you need to own the address space, own the descriptor tables, own interrupt routing, and drive devices yourself. That only becomes possible once the firmware steps away. Exiting boot services is the act of taking the machine over: after the call, there are no more helpers, and everything the kernel needs has to already be in hand. You build your own page tables and GDT, install your own handlers, and go from there.&lt;/p&gt;
&lt;p&gt;A small set of &lt;strong&gt;runtime services&lt;/strong&gt; survive the transition — reading the hardware clock, reading and writing UEFI variables — but they&#39;re limited, and getting at them after you&#39;ve replaced the page tables is its own piece of work. That belongs to the next post.&lt;/p&gt;
&lt;h2&gt;Reading the machine before the window closes&lt;/h2&gt;
&lt;p&gt;Here&#39;s the shape of the whole pre-handoff job: the firmware&#39;s description of this machine is readable for a short while, and then it isn&#39;t. So the bootloader reads everything it might need and writes it down.&lt;/p&gt;
&lt;p&gt;In TheseusOS the bootloader begins at &lt;code&gt;efi_main&lt;/code&gt;, the entry point UEFI calls. The first orders of business are an output path (so failures are visible) and a temporary allocator that forwards to UEFI, so Rust code can use strings and vectors before a real allocator exists.&lt;/p&gt;
&lt;p&gt;Then it goes shopping, in order:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Graphics.&lt;/strong&gt; Through UEFI&#39;s Graphics Output Protocol it asks for the screen resolution, the pixel format, and the address of the framebuffer — the block of memory that &lt;em&gt;is&lt;/em&gt; the display. Write a colour to the right offset and a pixel appears. The kernel can&#39;t draw anything without that address.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The physical memory map.&lt;/strong&gt; The firmware hands over a list describing every region of the machine&#39;s &lt;a href=&quot;https://en.wikipedia.org/wiki/Memory_map&quot;&gt;physical memory&lt;/a&gt;: which parts are usable RAM, which are reserved for hardware, which are off-limits. The kernel needs this to know where it&#39;s allowed to place anything, so the bootloader copies the whole list into memory it controls, where the kernel can read it once firmware is gone.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ACPI tables.&lt;/strong&gt; &lt;a href=&quot;https://en.wikipedia.org/wiki/ACPI&quot;&gt;ACPI&lt;/a&gt; is the standard that describes the machine&#39;s layout in machine-readable form: how many CPUs, where the interrupt controllers live, and so on. The bootloader locates the root table and records its address.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;System information.&lt;/strong&gt; Firmware vendor, a boot timestamp, CPU feature flags, and the UEFI system table — the top-level structure of the whole firmware world.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hardware inventory.&lt;/strong&gt; UEFI exposes devices as handles, and the bootloader walks them, sorting what it finds into a small inventory: this is a PCI device, this is USB, this is a serial port. It&#39;s the kernel&#39;s first look at the actual hardware.&lt;/p&gt;
&lt;p&gt;Every one of these stages writes its results into the same place.&lt;/p&gt;
&lt;h2&gt;Packing the handoff&lt;/h2&gt;
&lt;p&gt;All of that lands in a single structure with a fixed layout, called the &lt;strong&gt;handoff&lt;/strong&gt;. It&#39;s a plain C-style struct: a known sequence of fields at known offsets, so the kernel can read it with no parsing and no discovery of its own. The bootloader is the only thing that ever fills it in, and it does so exactly once.&lt;/p&gt;
&lt;p&gt;Before the jump, the bootloader finishes the paperwork. It works out where the kernel image sits in memory — its physical base, its size, and the virtual address the kernel intends to run at once it has built its own page tables. It reserves a small temporary heap, deliberately placed so it doesn&#39;t overlap the kernel image, to carry the kernel through its first moments. Then it copies the finished handoff into &lt;code&gt;LOADER_DATA&lt;/code&gt;, a memory type that survives what&#39;s coming.&lt;/p&gt;
&lt;p&gt;Then comes the point of no return. The bootloader calls &lt;code&gt;ExitBootServices&lt;/code&gt;, marks the copied handoff as passed that boundary, and jumps into the kernel&#39;s entry point with a plain function call.&lt;/p&gt;
&lt;p&gt;That jump is the handoff, so this post stops there.&lt;/p&gt;
&lt;h2&gt;Why one binary&lt;/h2&gt;
&lt;p&gt;Most operating systems keep the bootloader and the kernel as two separate programs: a small loader reads a kernel image off disk, parses it, and jumps in. TheseusOS originally did exactly that.&lt;/p&gt;
&lt;p&gt;I merged them because the AI helper that writes this project&#39;s code could not reliably get the kernel&#39;s &lt;a href=&quot;https://en.wikipedia.org/wiki/Executable_and_Linkable_Format&quot;&gt;ELF&lt;/a&gt; parsing right. Rather than keep fighting it, I removed the step: one binary, no image format to parse, no loader logic to get wrong.&lt;/p&gt;
&lt;p&gt;The cost is on the debugging side. &lt;a href=&quot;https://en.wikipedia.org/wiki/GNU_Debugger&quot;&gt;GDB&lt;/a&gt; is built around ELF symbol tables, and loading symbols for a unified UEFI binary is a headache I&#39;d rather not have. A separate kernel ELF would make source-level debugging far less painful, which is a good enough reason to split the two apart again later. For now, the single binary stays.&lt;/p&gt;
&lt;h2&gt;What the bootloader really is&lt;/h2&gt;
&lt;p&gt;Strip away the protocol calls and the memory juggling and the bootloader comes down to one job: the machine can only describe itself for a little while, so read all of it, record it, and get out before the door shuts.&lt;/p&gt;
&lt;p&gt;Next time: the other side. The kernel wakes up holding a pointer to a structure it has never seen, in a machine that has just stopped helping it, and has to decide what to do first.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;TheseusOS is open source — you can follow along at &lt;a href=&quot;https://github.com/emilf/TheseusOS&quot;&gt;https://github.com/emilf/TheseusOS&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Hello, world. Again.</title>
    <link href="https://emilf.dev/blog/hello-world/"/>
    <updated>2026-09-09T00:00:00Z</updated>
    <id>https://emilf.dev/blog/hello-world/</id>
    <content type="html">&lt;p&gt;For the longest time, this domain was a single line of HTML. &lt;code&gt;1337 html skillz&lt;/code&gt;. It was a joke about a page that was almost nothing — and honestly, it served me fine for years. But I&#39;ve been meaning to actually &lt;em&gt;use&lt;/em&gt; it for a while, and today&#39;s the day.&lt;/p&gt;
&lt;p&gt;What&#39;s it going to be? A home base. Somewhere to point people when they ask what I do. A place to write when the mood strikes — probably too much about hobby OS development, database internals, and the occasional reverse-engineering puzzle.&lt;/p&gt;
&lt;h2&gt;The stack&lt;/h2&gt;
&lt;p&gt;Since I&#39;m the sort of person who likes his tools boring and reproducible, this site is a static &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;Eleventy&lt;/a&gt; build. No database, no server to patch, no build step I&#39;ll regret in six months. Just templates and Markdown, compiled to flat HTML and shipped through GitHub Actions.&lt;/p&gt;
&lt;p&gt;The whole thing is versioned on GitHub. If I break it, I can see exactly what I broke.&lt;/p&gt;
&lt;h2&gt;What to expect&lt;/h2&gt;
&lt;p&gt;Irregular posts, mostly in the weeds. If you&#39;re here, you probably already know who I am — consider this the permanent answer to &amp;quot;so what do you do, anyway?&amp;quot;&lt;/p&gt;
&lt;p&gt;Thanks for stopping by. Set a while.&lt;/p&gt;
</content>
  </entry>
</feed>
