Programming in assembly without an operating system

notes.

Programming in assembly without an operating system

Source: Programming in Assembly without an Operating System, Inkbox, 26:26, uploaded 2025-10-03, playlist index 170.

Inkbox opens by booing Windows, Linux, and macOS. The joke is that an operating system keeps a serious gamer away from the hardware, so the answer is to write a classic arcade game in x64 assembly and boot it without an operating system. The project becomes a tour of the services that an operating system usually hides: firmware entry, memory and procedure conventions, timing, graphics, parallel work, and input.

The result is a UEFI application called Space Game for x64, a recreation of Zaxxon. That qualification matters. The program runs without Windows or Linux, yet it still uses the UEFI environment and its protocols. The video treats that layer as close enough to the metal for the experiment, then shows how much work remains when familiar system services disappear.

The firmware layer between hardware and program

The video begins with the BIOS as the old name for firmware that sits between hardware and software. The machine exposes low-level services through an abstraction layer, which lets an operating system work with a family of hardware without knowing every chip beneath it. The computer on screen uses UEFI, the Unified Extensible Firmware Interface, which grew out of Intel’s EFI work in the late 1990s and early 2000s.

Intel later released its implementation as open source. The TianoCore community maintains it and publishes tools such as the EFI Development Kit II, which targets C. Inkbox chooses assembly instead. UEFI supplies boot services for memory management and loading other UEFI applications, along with runtime services for tasks such as reading the time and resetting the machine. The project will use both kinds.

On power-up or reset, the x86_64 processor starts in real mode and jumps to the reset vector at 0xFFFFFFF0. Firmware runs its initialisation code, enters protected mode, checks itself, initialises memory, loads drivers for storage, input, and graphics, and starts the UEFI boot manager. The boot manager follows the boot-order list and loads an EFI application. That application might be firmware settings, Windows Boot Manager, GRUB, or Inkbox’s game.

The program therefore enters after a substantial amount of software has already prepared the machine. “Without an operating system” describes the missing general-purpose layer. It does not describe a computer with no firmware or abstractions.

A UEFI program in two sections

Inkbox starts with a blank assembly file containing .DATA and .CODE sections and an EFI main entry point. A RET instruction is enough to make a valid UEFI program, although a program that exits at once offers a thin kind of freedom. The first useful task is text output.

UEFI passes the entry point two parameters. The first is the image handle for the current application. The second is a pointer to the UEFI system table, which contains pointers to boot services, runtime services, and the simple text input and output protocols. The x64 calling convention places those parameters in the RCX and RDX registers, so the program saves them for later use.

The simple text output protocol is a table of function pointers. Inkbox takes the pointer at the seventh entry, which sits at byte 64 after accounting for the preceding entries, and stores it in ConOut. The OutputString procedure lives eight bytes into that protocol. It expects the protocol pointer and a pointer to a string of 16-bit Unicode characters in RCX and RDX. Basic Latin occupies the same character range as ASCII, with each character using two bytes, so a call prints a message to the firmware console.

The program also needs to wait for a key before it returns. The simple text input protocol exposes a WaitKeyEvent. That value is an EFI event rather than a procedure, so the program retrieves WaitForEvent from the boot services table and passes it one event handle plus a pointer for the returned data. The UEFI application can then pause instead of immediately handing control to the next boot option or opening firmware settings.

UEFI applications use PE/COFF images, the same executable format used by Windows. Inkbox assembles the object file with Microsoft’s 64-bit Macro Assembler and links it with the Microsoft Visual C++ linker, setting the subsystem to EFI application and the entry point to EFI main. The result is a .EFI file that firmware can load.

A flash drive and an actual boot path

The first version runs. Inkbox eventually gets QEMU to load it, although testing still depends on external hardware. The practical boot medium is a flash drive with a GUID Partition Table and an EFI System Partition. The application goes at EFI/BOOT/BOOTX64.EFI, the path UEFI expects when it searches removable media.

For the hardware test, GEEKOM supplies an A9 MAX mini PC with an AMD Ryzen AI 9 HX370, 32 GB of dual-channel DDR5 memory, and a 2 TB SSD. Inkbox checks the machine’s internals, plugs in the flash drive and a keyboard, disables Secure Boot, and makes USB the first boot option. The computer then loads the assembly program directly from the drive.

The machine is part of a sponsored segment, so its specifications and performance are presented by the manufacturer and the video’s creator rather than through an independent test. The hardware matters here because it provides the x86_64 UEFI environment on which the rest of the demonstration runs.

Timing without a useful clock

The game needs a timer before it needs enemies or scenery. UEFI’s runtime services include GetTime, which returns an EFI time structure and a time-capability structure. Inkbox first extracts the month and converts September’s value of 9 into the Unicode character 9 by adding 48. A loop that compares successive seconds then produces a one-hertz clock.

The same approach fails for a 60 Hz game loop. Inkbox expects the nanosecond field of the time structure to provide sub-second resolution, yet the machine reports a resolution of one second. The code can wait for the next second, although it cannot use that service to measure the 16,666,667 nanoseconds between frames. A game running at one frame per second is technically a game, though the demonstration leaves that possibility on the table for roughly as long as it takes to reject it.

The replacement is the x86 RDTSC instruction. It reads a 64-bit timestamp counter tied to the processor clock. Inkbox says the Ryzen system’s invariant counter advances at roughly two billion ticks per second even when the processor boosts towards 5 GHz. To make the frame rate adapt to the machine, the program waits for a new second with GetTime, records RDTSC, waits another second, records it again, and divides the difference by the target frame rate. The game can then wait for the right number of CPU cycles after each frame.

Inkbox chooses 64 frames per second because 64 divides cleanly, then pushes the target to 128. The idle loop has around 15.5 million cycles per frame at that rate, yet the timing loop itself uses more than five million. The budget gets smaller as soon as the game begins to draw.

From a framebuffer to a tile-based game

UEFI’s Graphics Output Protocol, or GOP, provides the framebuffer. Inkbox locates it through the boot-services LocateProtocol procedure and the protocol’s globally unique identifier. GOP can query the available video modes, report their resolutions and pixel formats, select a mode, and transfer blocks of pixels to the video buffer.

The machine offers a 1280 by 1024 mode, which suits the game’s 256 by 256 internal screen because each pixel can become a 4 by 4 square. GOP stores the 24-bit colour values in BGR order rather than RGB. A normal PNG or bitmap carries extra file data, so Inkbox converts the artwork into raw BGR pixels, includes the bytes in an assembly include file, and passes the buffer to GOP’s Blt procedure.

The game renderer acts like a small picture-processing unit. It draws two tile-map layers with independent X and Y offsets. The upper layer treats its background colour as transparent, and sprite objects sit between the two layers. Inkbox takes the original Zaxxon tiles and sprites, then renders the first background layer into the small output buffer.

Scaling that buffer exposes the first serious bottleneck. The program calls Blt for each of roughly 65,000 pixels to expand the 256 by 256 image to the display. The picture looks right, yet the 128-frame counter collapses because the firmware call overhead has consumed the frame budget.

Twenty-four processors and one shared buffer

Inkbox briefly returns to Windows Task Manager to show the Ryzen system’s 24 logical processors. The UEFI application itself runs on one core. UEFI’s MP Services protocol can start a procedure on the other application processors, called APs, whilst the bootstrap processor waits. All processors share the application’s memory, so they can work on the same output data.

The APs cannot call UEFI services. Inkbox therefore moves the expensive scaling step into ordinary memory work. Each processor finds an unrendered line, expands its 256 pixels into a 1024-pixel line by repeating each pixel four times, and copies that line to the next three rows. The bootstrap processor resumes after the APs finish and makes one Blt call for the completed 1024 by 1024 buffer.

With 23 other cores acting as graphics workers, each one handles around 11 or 12 lines per frame. The counter returns to roughly 128 frames per second, with the qualification that the speed is approximate and the cooling fan has become audible. Parallel rendering solves the repeated firmware-call problem by keeping UEFI at the edge of the process and doing the bulk of the work in shared memory.

The second tile layer then lands on top of the first. Zero tiles and pure black pixels become transparent, the title reads “Space Game for x64”, and side columns recreate the framing of the arcade display. Sixty-four sprites move between the two layers so that the ship can respond to input.

Keyboard input and the mouse workaround

UEFI’s input model is poorly suited to a game. A key-down event reports a press and starts an expiration timer. Holding a key produces one event, waits for a while, and then produces more events. Inkbox lengthens the first timer to make movement possible, which leaves the ship moving long after a tap. The program needs a different source of input.

The direct route would be the USB stack. A PS/2 keyboard would offer a simpler route through ports 60 and 64, although the test computer has no PS/2 port. Inkbox decides that the USB stack looks too involved and searches the UEFI documentation again. The simple pointer protocol provides a GetState procedure that returns relative mouse movement and the left and right button states.

The game turns the mouse into a joystick. The left button unlocks the stick, horizontal and vertical movement controls the ship, and the right button fires. Inkbox adds two small sprites at the bottom right to show the controls. The result is far more responsive than the keyboard version, although the ship still crashes the program when it leaves the bounds because the code contains no memory controls. The later movement looks smooth at the target frame rate.

At this point Inkbox calls the project the “J90” stage: about 90 percent of the work is new, which means roughly half of the effort remains before the project is finished. The scrolling backgrounds, enemy placement, hitboxes, and the rest of the game loop still need work. The remaining code consists largely of familiar game-programming tasks with a smaller set of unusual firmware tricks.

What the experiment calls freedom

The finished demonstration shows a scrolling Zaxxon-like scene with the two background layers, sprites, input, and the accelerated renderer. The title’s freedom comes from removing a general-purpose operating system from the boot path. That removal exposes the services an operating system normally gathers into a stable programming environment.

The program still depends on UEFI for time access, graphics modes, boot services, event handling, and processor startup. It also depends on the firmware’s particular support for input and the machine’s processor features. The experiment therefore gives direct control inside a narrower contract. The trade is visible in the failed sub-second clock, the unusable keyboard events, the out-of-bounds crash, and the need to build the renderer around a firmware framebuffer.

The source’s claim that the program runs on any x86_64 machine is presented as a property of the project rather than a tested compatibility survey. The video demonstrates one AMD system after disabling Secure Boot and selecting the required boot path. Its handling of RDTSC, GOP modes, MP Services, and pointer input assumes UEFI features that other machines may expose differently.

Further reading / references

35 paragraphs2,174 words13,259 characters