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
- Windows 10
- Microsoft Visual Studio 17
- NASM 2.14 (win64) or higher
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.