Skip to main content
UEFI

OS Development on Windows – Part 1: Building a UEFI Application in NASM

This is a series of articles on developing your own operating system. We will be focusing on modern techniques, like UEFI booting and 64-bit assembly, and everything will be created from scratch. I will be going step-by-step, explaining every tool we use and every line of code, so that you have a thorough understanding of the process. You should be able to copy-paste these steps and get the same results I am describing.

In addition, we will be doing this on Windows and using the tools available on this platform only. OS development is almost exclusively done on UNIX-like systems because the tools are more readily available and documented. I hope to show you how easy this is to do on Windows too.

Requirements

The techniques described in these articles have been tested on Windows 10 only. They may work on Windows 7 or 8, but this has not been confirmed. Please install the Microsoft Visual Studio and NASM requirements before proceeding.

What is UEFI ?

UEFI is the firmware that loads up during a computer’s boot process. It provides runtime and boot services to the operating system and it’s loader. It is a replacement for the legacy BIOS that has been prevalent since CP/M first started using one in 1975. UEFI provides support for larger hard drives, more security features and the handling of mouse, text and graphics. Most modern computers ship with UEFI support and by 2020 this will be ubiquitous because Intel plans on ending their support for legacy BIOS systems. This means your OS will need to use UEFI booting techniques to stay relevant in the near future.

Creating a Virtual Hard Disk for UEFI

The UEFI specification allows you to create applications that the firmware can run during the boot process. This is what we will use to load the operating system. These applications must be stored on a FAT12, FAT16 or FAT32 file system and located on a hard disk with a GPT partition. So our first step is to create a virtual hard disk to store the application on.

Launch Visual Studio 2017 > x64 Native Tools Command Prompt for VS 2017 by right-clicking on it and choosing More > Run as administrator. This will allow you to run system commands without Windows asking for your permission every time.

  • Run the native, disk partitioning program in Windows.
Administrator: x64 Native Tools Command Prompt for VS 2017diskpart
  • Create a 300MB virtual hard disk. The minimum size for a GPT disk, with a EFI partition, is 100MB.
Administrator: x64 Native Tools Command Prompt for VS 2017 - diskpartcreate vdisk file=C:\kernel.vhd maximum=300
  • Select the virtual hard disk you created. All commands will now apply to this disk only.
Administrator: x64 Native Tools Command Prompt for VS 2017 - diskpartselect vdisk file=C:\kernel.vhd
  • Attach the virtual hard disk so that Windows can see it. This is similar to mounting a disk on other OSes.
Administrator: x64 Native Tools Command Prompt for VS 2017 - diskpartattach vdisk
  • Update the virtual hard disk to use a GPT partitioning scheme.
Administrator: x64 Native Tools Command Prompt for VS 2017 - diskpartconvert gpt
  • Create a 100MB partition for EFI.
Administrator: x64 Native Tools Command Prompt for VS 2017 - diskpartcreate partition efi size=100
  • Create a primary partition for the remaining space.
Administrator: x64 Native Tools Command Prompt for VS 2017 - diskpartcreate partition primary
  • Format the entire volume as FAT32. This is the most commonly supported file system for UEFI firmware.
Administrator: x64 Native Tools Command Prompt for VS 2017 - diskpartformat fs=fat32 quick
  • Assign a drive letter to the virtual hard disk so that we can copy files to it.
Administrator: x64 Native Tools Command Prompt for VS 2017 - diskpartassign letter=V
  • Exit and create the directory structure that UEFI requires when looking for a boot application.
Administrator: x64 Native Tools Command Prompt for VS 2017mkdir V:\EFI
mkdir V:\EFI\BOOT

You now have a virtual hard disk that is ready to accept a UEFI application.

Creating a UEFI Application

There are only a few UEFI libraries out there. They all are difficult to understand, add a lot of overhead and obscure what is really going on. Instead, we will write our own UEFI application in NASM using only the functionality we need.

kernel.asm; Copyright 2018-2019 Brian Otto @ https://hackerpulp.com
; 
; Permission to use, copy, modify, and/or distribute this software for any 
; purpose with or without fee is hereby granted, provided that the above 
; copyright notice and this permission notice appear in all copies.
; 
; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 
; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 
; AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 
; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 
; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 
; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 
; PERFORMANCE OF THIS SOFTWARE.

; generate 64-bit code
bits 64

; contains the code that will run
section .text

; allows the linker to see this symbol
global _start

; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G8.1001729
struc EFI_TABLE_HEADER
    .Signature    RESQ 1
    .Revision     RESD 1
    .HeaderSize   RESD 1
    .CRC32        RESD 1
    .Reserved     RESD 1
endstruc

; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G8.1001773
struc EFI_SYSTEM_TABLE
    .Hdr                  RESB EFI_TABLE_HEADER_size
    .FirmwareVendor       RESQ 1
    .FirmwareRevision     RESD 1
    .ConsoleInHandle      RESQ 1
    .ConIn                RESQ 1
    .ConsoleOutHandle     RESQ 1
    .ConOut               RESQ 1
    .StandardErrorHandle  RESQ 1
    .StdErr               RESQ 1
    .RuntimeServices      RESQ 1
    .BootServices         RESQ 1
    .NumberOfTableEntries RESQ 1
    .ConfigurationTable   RESQ 1
endstruc

; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G16.1016807
struc EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
    .Reset             RESQ 1
    .OutputString      RESQ 1
    .TestString	       RESQ 1
    .QueryMode	       RESQ 1
    .SetMode	       RESQ 1
    .SetAttribute      RESQ 1
    .ClearScreen       RESQ 1
    .SetCursorPosition RESQ 1
    .EnableCursor      RESQ 1
    .Mode              RESQ 1
endstruc

loopForever:
    jmp loopForever

_start:
    ; reserve space for 4 arguments
    sub rsp, 4 * 8

    ; rdx points to the EFI_SYSTEM_TABLE structure
    ; which is the 2nd argument passed to us by the UEFI firmware
    ; adding 64 causes rcx to point to EFI_SYSTEM_TABLE.ConOut
    mov rcx, [rdx + 64]

    ; load the address of our string into rdx
    lea rdx, [rel strHello]

    ; EFI_SYSTEM_TABLE.ConOut points to EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
    ; call OutputString on the value in rdx
    call [rcx + EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.OutputString]
    
    ; loop forever so we can see the string before the UEFI application exits
    jmp loopForever

codesize equ $ - $$

; contains nothing - but it is required by UEFI
section .reloc

; contains the data that will be displayed
section .data
    ; this must be a Unicode string
    strHello db __utf16__ `Hello World !\0`

datasize equ $ - $$
  • Save this to C:\kernel.asm and assemble it with NASM. This will create a C:\kernel.obj file.
Administrator: x64 Native Tools Command Prompt for VS 2017nasm -f win64 kernel.asm
  • Link the object file as a UEFI application and set its entry point.
  • This will create a C:\kernel.efi file, which is a PE32+ DLL with a EFI subsystem.
Administrator: x64 Native Tools Command Prompt for VS 2017link /subsystem:EFI_APPLICATION /entry:_start kernel.obj
  • Copy the UEFI application to the virtual hard disk. It must be named BOOTX64.EFI for the firmware to find it.
Administrator: x64 Native Tools Command Prompt for VS 2017copy kernel.efi V:\EFI\BOOT\BOOTX64.EFI
  • Detach the virtual hard disk so that other applications can use it.
Administrator: x64 Native Tools Command Prompt for VS 2017 - diskpartdiskpart
select vdisk file=C:\kernel.vhd
detach vdisk
exit

Running the UEFI Application

You have two options to run your UEFI application. You can emulate a UEFI device using Qemu or you can write the virtual hard disk to a USB drive and boot it using a real computer. Using Qemu is the preferred method, since it can be scripted and has a fast boot time. This will allow you to test and debug changes quickly.

Running the UEFI Application with Qemu

  • Install Qemu for Windows (64 bit)
  • Download a UEFI firmware image for OVMF x64
    • TianoCore provides an edk2.git-ovmf-x64-XXX.noarch.rpm image
    • Open the RPM using 7zip and extract the following file …
      edk2.git-ovmf-x64-XXX.noarch.cpio\.\usr\share\edk2.git\ovmf-x64\OVMF-pure-efi.fd
      to C:\OVMF.fd
  • Run Qemu using the UEFI firmware and your virtual hard disk
"C:\Program Files\Qemu\qemu-system-x86_64" -cpu qemu64 -bios C:\OVMF.fd -drive file=C:\kernel.vhd,format=raw

Running the UEFI Application with USB

  • Install Rufus, or any application that can create a bootable USB drive
  • Select the C:\kernel.vhd file and write it to a USB drive
  • Reboot your computer and enable USB booting in your BIOS
  • Reboot again and the UEFI application should launch

Next Steps

In the next article, we will automate the build / launch process with a batch script and add more support for the UEFI spec. The UEFI application will also be updated to load a kernel and draw to the screen.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.