Skip to main content
Kernel

OS Development on Windows – Part 2: Booting a 64-Bit Kernel from UEFI

This is Part 2 in a series of articles on developing your own operating system. Please read Part 1 before proceeding, as it will give you an introduction to the series. In addition, it details the software requirements and shows you how to get a UEFI application loaded onto a virtual hard disk and booted by a machine.

In this article, I have taken the previous steps and created a batch script to automate the entire process. This makes it very easy to experiment with changes and see the results immediately. At a high level, here is what the script does …

  • it verifies you are running the script in the x64 Native Tools Command Prompt for VS 2017
  • it verifies you are running the script as an Administrator
  • it lets you choose between different build options

We will be using option #4 which will …

  • build the virtual hard disk
  • mount the virtual hard disk
  • build the UEFI application with NASM and link
  • copy the UEFI application to the virtual hard disk
  • launch Qemu with the UEFI firmware and virtual hard disk enabled

This will boot a virtual machine that runs the UEFI application and loads our kernel.

This file, along with the rest in this article, can be download from GitHub under the ISC License.

build.bat@ECHO OFF

SET EXITCODE=0
SET EXTRAMSG=

SET MNT="V:\"
SET VHD="%CD%\kernel.vhd"
SET ASM="%CD%\kernel.asm"
SET OBJ="%CD%\kernel.obj"
SET EFI="%CD%\kernel.efi"

SET OVMF="%CD%\OVMF.fd"
SET NASM="C:\Program Files\NASM\nasm"
SET QEMU="C:\Program Files\Qemu\qemu-system-x86_64"

ECHO.
ECHO NASM UEFI Build Script
ECHO.

IF "%VSCMD_ARG_HOST_ARCH%" NEQ "x64" GOTO :ErrorX64
IF "%VSCMD_ARG_TGT_ARCH%" NEQ "x64" GOTO :ErrorX64

NET SESSION > NUL 2>&1
IF %ERRORLEVEL% NEQ 0 GOTO :ErrorAdmin

ECHO   1) Build Virtual Hard Disk
ECHO   2) Mount / Unmount VHD
ECHO   3) Build UEFI Application
ECHO   4) Build UEFI ^& Boot Machine
ECHO   5) Clean Files
ECHO.

SET /P CHOICE=Choose #

ECHO.

IF %CHOICE% == 1 GOTO :BuildVHD
IF %CHOICE% == 2 GOTO :MountVHD
IF %CHOICE% == 3 GOTO :BuildUEFI
IF %CHOICE% == 4 GOTO :Boot
IF %CHOICE% == 5 GOTO :Clean

ECHO Error: You have entered an invalid #

GOTO :EOF

:ErrorX64

ECHO Error: You must run this script from the x64 Native Tools Command Prompt for VS 2017

GOTO :EOF

:ErrorAdmin

ECHO Error: You must run this script as an Administrator

GOTO :EOF

:BuildVHD
IF EXIST %MNT% CALL :MountVHD
IF %EXITCODE% NEQ 0 GOTO :EOF

IF EXIST %VHD% DEL %VHD%

ECHO create vdisk file=%VHD% maximum=300 > build.tmp
ECHO select vdisk file=%VHD% >> build.tmp
ECHO attach vdisk >> build.tmp
ECHO convert gpt >> build.tmp
ECHO create partition efi size=100 >> build.tmp
ECHO create partition primary >> build.tmp
ECHO format fs=fat32 quick >> build.tmp
ECHO assign letter=V >> build.tmp
ECHO exit >> build.tmp

SET EXTRAMSG=create

GOTO :RunDiskPart

:MountVHD
IF EXIST %MNT% (
    IF EXIST %VHD% (
        ECHO select vdisk file=%VHD% > build.tmp
        ECHO detach vdisk >> build.tmp
        ECHO exit >> build.tmp
        
        SET EXTRAMSG=detach
    ) ELSE (
        ECHO Error: You must build the VHD first
        
        SET EXITCODE=1
        
        GOTO :EOF
    )
) ELSE (
    ECHO select vdisk file=%VHD% > build.tmp
    ECHO attach vdisk >> build.tmp
    ECHO select partition 3 >> build.tmp
    ECHO assign letter=V >> build.tmp
    ECHO exit >> build.tmp
    
    SET EXTRAMSG=attach
)

GOTO :RunDiskPart

:RunDiskPart
DISKPART /S build.tmp > build.err

IF %ERRORLEVEL% NEQ 0 (
    ECHO Error: The diskpart command failed with the following output
    ECHO.
    ECHO ------
    
    TYPE build.err
    DEL build.err
    
    SET EXITCODE=1
) ELSE (
    ECHO VHD Success! ^(%EXTRAMSG%^)
)

DEL build.err
DEL build.tmp

GOTO :EOF

:BuildUEFI
%NASM% -f win64 %ASM% -o %OBJ% > NUL 2> build.err

IF %ERRORLEVEL% NEQ 0 (
    ECHO Error: The nasm command failed with the following output
    ECHO.
    ECHO ------
    ECHO.
    
    TYPE build.err
    DEL build.err
    
    SET EXITCODE=1
    
    GOTO :EOF
)

DEL build.err

link /subsystem:EFI_APPLICATION /entry:start /out:%EFI% %OBJ% > build.err

IF %ERRORLEVEL% NEQ 0 (
    ECHO Error: The link command failed with the following output
    ECHO.
    ECHO ------
    ECHO.
    
    TYPE build.err
    DEL build.err
    
    SET EXITCODE=1
    
    GOTO :EOF
)

DEL build.err

ECHO EFI Success!

GOTO :EOF

:Boot
IF NOT EXIST %VHD% CALL :BuildVHD && ECHO.
IF %EXITCODE% NEQ 0 GOTO :EOF

IF NOT EXIST %MNT% CALL :MountVHD && ECHO.
IF %EXITCODE% NEQ 0 GOTO :EOF

CALL :BuildUEFI
IF %EXITCODE% NEQ 0 GOTO :EOF

ECHO.

IF NOT EXIST %MNT%EFI MKDIR %MNT%EFI
IF NOT EXIST %MNT%EFI\BOOT MKDIR %MNT%EFI\BOOT

COPY /Y %EFI% %MNT%EFI\BOOT\BOOTX64.EFI > build.err

IF %ERRORLEVEL% NEQ 0 (
    ECHO Error: The copy command failed with the following output
    ECHO.
    ECHO ------
    ECHO.
    
    TYPE build.err
    DEL build.err
    
    SET EXITCODE=1
    
    GOTO :EOF
)

DEL build.err

CALL :MountVHD
IF %EXITCODE% NEQ 0 GOTO :EOF

%QEMU% -cpu qemu64 -bios %OVMF% -drive file=%VHD%,format=raw

GOTO :EOF

:Clean
IF EXIST %MNT% (CALL :MountVHD)

DEL %VHD%
DEL %OBJ%
DEL %EFI%

ECHO Clean!

GOTO :EOF

:EOF
EXIT /B %EXITCODE%

Creating a UEFI Library

In order to boot a kernel, we will need to extend our support of the UEFI specification. I have done this below in a small library that defines the error codes, signatures, GUIDs and structures needed to support the UEFI protocols and services. I have also written some wrappers to make the UEFI function calls easier to understand and manage.

This code uses the same calling conventions used by UEFI on x64 platforms.

The caller passes the first four integer arguments in registers. The integer values are passed from left to right in Rcx, Rdx, R8, and R9 registers. The caller passes arguments five and above onto the stack.

The registers Rax, Rcx Rdx R8, R9, R10, R11, and XMM0-XMM5 are volatile and are, therefore, destroyed on function calls.

The registers RBX, RBP, RDI, RSI, R12, R13, R14, R15, and XMM6-XMM15 are considered nonvolatile and must be saved and restored by a function that uses them.
kernel-efi.asm; the UEFI definitions we use in our application
; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G45.1342862
EFI_SUCCESS                       equ 0
EFI_LOAD_ERROR                    equ 0x8000000000000001 ; 9223372036854775809
EFI_INVALID_PARAMETER             equ 0x8000000000000002 ; 9223372036854775810
EFI_UNSUPPORTED                   equ 0x8000000000000003 ; 9223372036854775811
EFI_BAD_BUFFER_SIZE               equ 0x8000000000000004 ; 9223372036854775812
EFI_BUFFER_TOO_SMALL              equ 0x8000000000000005 ; 9223372036854775813
EFI_NOT_READY                     equ 0x8000000000000006 ; 9223372036854775814
EFI_NOT_FOUND                     equ 0x8000000000000014 ; 9223372036854775828

; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G8.1001773
EFI_SYSTEM_TABLE_SIGNATURE        equ 0x5453595320494249

; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G16.1018812
EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID db 0xde, 0xa9, 0x42, 0x90, 0xdc, 0x23, 0x38, 0x4a
                                  db 0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a

; the UEFI data types with natural alignment set
; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G6.999718
%macro UINTN 0
    RESQ 1
    alignb 8
%endmacro

%macro UINT32 0
    RESD 1
    alignb 4
%endmacro

%macro UINT64 0
    RESQ 1
    alignb 8
%endmacro

%macro EFI_HANDLE 0
    RESQ 1
    alignb 8
%endmacro

%macro POINTER 0
    RESQ 1
    alignb 8
%endmacro

; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G8.1001729
struc EFI_TABLE_HEADER
    .Signature  UINT64
    .Revision   UINT32
    .HeaderSize UINT32
    .CRC32      UINT32
    .Reserved   UINT32
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       POINTER
    .FirmwareRevision     UINT32
    .ConsoleInHandle      EFI_HANDLE
    .ConIn                POINTER
    .ConsoleOutHandle     EFI_HANDLE
    .ConOut               POINTER
    .StandardErrorHandle  EFI_HANDLE
    .StdErr               POINTER
    .RuntimeServices      POINTER
    .BootServices         POINTER
    .NumberOfTableEntries UINTN
    .ConfigurationTable   POINTER
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             POINTER
    .OutputString      POINTER
    .TestString        POINTER
    .QueryMode         POINTER
    .SetMode           POINTER
    .SetAttribute      POINTER
    .ClearScreen       POINTER
    .SetCursorPosition POINTER
    .EnableCursor      POINTER
    .Mode              POINTER
endstruc

; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G8.1001843
struc EFI_BOOT_SERVICES
    .Hdr                                 RESB EFI_TABLE_HEADER_size
    .RaiseTPL                            POINTER
    .RestoreTPL                          POINTER
    .AllocatePages                       POINTER
    .FreePages                           POINTER
    .GetMemoryMap                        POINTER
    .AllocatePool                        POINTER
    .FreePool                            POINTER
    .CreateEvent                         POINTER
    .SetTimer                            POINTER
    .WaitForEvent                        POINTER
    .SignalEvent                         POINTER
    .CloseEvent                          POINTER
    .CheckEvent                          POINTER
    .InstallProtocolInterface            POINTER
    .ReinstallProtocolInterface          POINTER
    .UninstallProtocolInterface          POINTER
    .HandleProtocol                      POINTER
    .Reserved                            POINTER
    .RegisterProtocolNotify              POINTER
    .LocateHandle                        POINTER
    .LocateDevicePath                    POINTER
    .InstallConfigurationTable           POINTER
    .LoadImage                           POINTER
    .StartImage                          POINTER
    .Exit                                POINTER
    .UnloadImage                         POINTER
    .ExitBootServices                    POINTER
    .GetNextMonotonicCount               POINTER
    .Stall                               POINTER
    .SetWatchdogTimer                    POINTER
    .ConnectController                   POINTER
    .DisconnectController                POINTER
    .OpenProtocol                        POINTER
    .CloseProtocol                       POINTER
    .OpenProtocolInformation             POINTER
    .ProtocolsPerHandle                  POINTER
    .LocateHandleBuffer                  POINTER
    .LocateProtocol                      POINTER
    .InstallMultipleProtocolInterfaces   POINTER
    .UninstallMultipleProtocolInterfaces POINTER
    .CalculateCrc32                      POINTER
    .CopyMem                             POINTER
    .SetMem                              POINTER
    .CreateEventEx                       POINTER
endstruc

; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G8.1002011
struc EFI_RUNTIME_SERVICES
    .Hdr                       RESB EFI_TABLE_HEADER_size
    .GetTime                   POINTER
    .SetTime                   POINTER
    .GetWakeupTime             POINTER
    .SetWakeupTime             POINTER
    .SetVirtualAddressMap      POINTER
    .ConvertPointer            POINTER
    .GetVariable               POINTER
    .GetNextVariableName       POINTER
    .SetVariable               POINTER
    .GetNextHighMonotonicCount POINTER
    .ResetSystem               POINTER
    .UpdateCapsule             POINTER
    .QueryCapsuleCapabilities  POINTER
    .QueryVariableInfo         POINTER
endstruc

; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G16.1018812
struc EFI_GRAPHICS_OUTPUT_PROTOCOL
    .QueryMode POINTER
    .SetMode   POINTER
    .Blt       POINTER
    .Mode      POINTER
endstruc

; see Errata A page 494
struc EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE
    .MaxMode         UINT32
    .Mode            UINT32
    .Info            POINTER
    .SizeOfInfo      UINTN
    .FrameBufferBase UINT64
    .FrameBufferSize UINTN
endstruc

; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G11.1004788
; the Related Definitions section
struc EFI_MEMORY_DESCRIPTOR
    .Type          UINT32
    .PhysicalStart POINTER
    .VirtualStart  POINTER
    .NumberOfPages UINT64
    .Attribute     UINT64
endstruc

; ---- Function Wrappers for the Protocols / Services

; we use the same calling conventions as UEFI
; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G6.1000069

; EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.OutputString
; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G16.1016966
efiOutputString:
    ; set the 2nd argument to the passed in string
    mov rdx, rcx
    
    ; get the EFI_SYSTEM_TABLE
    mov rcx, [ptrSystemTable]
    
    ; set the 1st argument to EFI_SYSTEM_TABLE.ConOut
    ; which is pointing to EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
    mov rcx, [rcx + EFI_SYSTEM_TABLE.ConOut]
    
    ; run EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.OutputString
    call [rcx + EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.OutputString]
    
    ; display any errors
    cmp rax, EFI_SUCCESS
    jne errorCode
    
    ret

; EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.ClearScreen
; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G16.1017390
efiClearScreen:
    ; get the EFI_SYSTEM_TABLE
    mov rcx, [ptrSystemTable]
    
    ; set the 1st argument to EFI_SYSTEM_TABLE.ConOut
    ; which is pointing to EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
    mov rcx, [rcx + EFI_SYSTEM_TABLE.ConOut]
    
    ; run EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.ClearScreen
    call [rcx + EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.ClearScreen]
    
    ; display any errors
    cmp rax, EFI_SUCCESS
    jne errorCode
    
    ret

; EFI_BOOT_SERVICES.LocateProtocol
; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G11.1006649
efiLocateProtocol:
    ; get the EFI_SYSTEM_TABLE
    mov rax, [ptrSystemTable]
    
    ; get the EFI_SYSTEM_TABLE.BootServices
    ; which is pointing to EFI_BOOT_SERVICES
    mov rax, [rax + EFI_SYSTEM_TABLE.BootServices]
    
    ; set the 1st argument to the GUID for the graphics protocol
    mov rcx, EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID
    
    ; set the 2nd argument to NULL
    mov rdx, 0
    
    ; set the 3rd argument to a variable that holds 
    ; the returned EFI_GRAPHICS_OUTPUT_PROTOCOL
    lea r8, [ptrInterface]
    
    ; run EFI_BOOT_SERVICES.LocateProtocol
    call [rax + EFI_BOOT_SERVICES.LocateProtocol]
    
    ; display any errors
    cmp rax, EFI_SUCCESS
    jne errorCode
    
    ret

; EFI_BOOT_SERVICES.GetMemoryMap
; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G11.1004788
efiGetMemoryMap:
    ; get the EFI_SYSTEM_TABLE
    mov rax, [ptrSystemTable]
    
    ; get the EFI_SYSTEM_TABLE.BootServices
    ; which is pointing to EFI_BOOT_SERVICES
    mov rax, [rax + EFI_SYSTEM_TABLE.BootServices]
    
    ; set the 1st argument to the memory map buffer size
    lea rcx, [intMemoryMapSize]
    
    ; set the 2nd argument to the memory map buffer
    lea rdx, [bufMemoryMap]
    
    ; set the 3rd argument to a variable that holds 
    ; the returned memory map key
    lea r8, [ptrMemoryMapKey]
    
    ; set the 4th argument to a variable that holds 
    ; the returned EFI_MEMORY_DESCRIPTOR size
    lea r9, [ptrMemoryMapDescSize]
    
    ; set the 5th argument to a variable that holds 
    ; the returned EFI_MEMORY_DESCRIPTOR version
    lea r10, [ptrMemoryMapDescVersion]
    
    ; save the top of the stack so we can return here
    mov rbx, rsp
    
    ; save the 5th argument to the stack
    push r10
    
    ; run EFI_BOOT_SERVICES.GetMemoryMap
    call [rax + EFI_BOOT_SERVICES.GetMemoryMap]
    
    ; display any errors
    cmp rax, EFI_SUCCESS
    jne errorCode
    
    ; return to the previous stack location
    mov rsp, rbx
    
    ret

; EFI_BOOT_SERVICES.ExitBootServices
; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G11.1007244
efiExitBootServices:
    ; get the EFI_SYSTEM_TABLE
    mov rax, [ptrSystemTable]
    
    ; get the EFI_SYSTEM_TABLE.BootServices
    ; which is pointing to EFI_BOOT_SERVICES
    mov rax, [rax + EFI_SYSTEM_TABLE.BootServices]
    
    ; set the 1st argument to the stored EFI_HANDLE
    mov rcx, [hndImageHandle]
    
    ; set the 2nd argument to the memory map key
    mov rdx, [ptrMemoryMapKey]
    
    ; run EFI_BOOT_SERVICES.ExitBootServices
    call [rax + EFI_BOOT_SERVICES.ExitBootServices]
    
    ; display any errors
    cmp rax, EFI_SUCCESS
    jne errorCode
    
    ret

In our previous article we were accessing protocols by adding bytes until we called the correct memory location.

mov rcx, [rdx + 64]

We now do this properly by using the defined structures for this.

mov rcx, [ptrSystemTable]
mov rcx, [rcx + EFI_SYSTEM_TABLE.ConOut]

You will also notice we use EFI_BOOT_SERVICES.LocateProtocol to locate a graphics device. This returns the first device it finds, but to do this properly you should really be using LocateHandleBuffer / HandleProtocol / QueryMode instead. You can then search for a display with the desired resolution and pixel format you need.

Finally, I have added error handling throughout the functions and an error code will display any time one of them fails.

Creating a High-Level API

In addition, I have created a higher level API that uses the UEFI library. This is done to keep the UEFI application logic simpler and easier to read. The API is also dependent on a few utility functions that can be downloaded from the GitHub repository. I didn’t include the source code here to keep the article length shorter.

kernel-api.asmapiVerifySignature:
    ; get the signature for the loaded EFI_SYSTEM_TABLE
    mov rcx, [ptrSystemTable]
    mov rcx, [rcx + EFI_SYSTEM_TABLE.Hdr + EFI_TABLE_HEADER.Signature]
    
    ; get the signature defined in the UEFI spec
    ; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G8.1001773
    mov rdx, EFI_SYSTEM_TABLE_SIGNATURE
    
    ; set our error code
    mov rax, EFI_LOAD_ERROR
    
    ; compare the signatures and return the error code when they don't match
    cmp rdx, rcx
    jne error
    
    mov rax, EFI_SUCCESS
    
    ret

apiOutputHeader:
    ; clear the screen
    call efiClearScreen
    
    ; write the header
    mov rcx, strHeader
    call efiOutputString
    
    ; write the firmware vendor
    mov rcx, [ptrSystemTable]
    mov rcx, [rcx + EFI_SYSTEM_TABLE.FirmwareVendor]
    call efiOutputString
    
    ; write the v
    mov rcx, strHeaderV
    call efiOutputString
    
    ; write the firmware revision
    mov rcx, [ptrSystemTable]
    mov rcx, [rcx + EFI_SYSTEM_TABLE.FirmwareRevision]
    call funIntegerToAscii
    
    ret

apiGetFrameBuffer:
    ; locate the first EFI_GRAPHICS_OUTPUT_PROTOCOL
    ; and allocate a frame buffer
    call efiLocateProtocol
    
    ; get the base address of the frame buffer 
    mov rcx, [ptrInterface]
    mov rcx, [rcx + EFI_GRAPHICS_OUTPUT_PROTOCOL.Mode]
    mov rcx, [rcx + EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE.FrameBufferBase]
    
    mov [ptrFrameBuffer], rcx
    
    ret

apiExitUEFI:
    call efiGetMemoryMap
    call efiExitBootServices
    
    ret

The New UEFI Application

We are now at a point where we can re-write the UEFI application from our previous article. The new application includes our functions above and then proceeds to do the following …

  • save the data passed to us by the UEFI firmware
  • verify we are working with UEFI firmware by validating its signature
  • display the OS header and firmware version number
  • locate a graphics device and allocate a frame buffer
  • get a memory map from UEFI and exit boot services
  • and finally load our kernel
kernel.asm; generate 64-bit code
bits 64

; use relative addresses
default rel

; contains the code that will run
section .text

; allows the linker to see the entry symbol
global start

%include "kernel-functions.asm"
%include "kernel-efi.asm"
%include "kernel-api.asm"

start:
    ; save the location of UEFI
    mov [ptrUEFI], rsp
    
    ; reserve space for 4 arguments
    sub rsp, 4 * 8
    
    ; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G6.1000024
    ; the "2.3.4.1 Handoff State" section
    
    ; rcx is the 1st argument passed to us by the UEFI firmware
    ; it will contain the EFI_HANDLE
    mov [hndImageHandle], rcx
    
    ; rdx is the 2nd argument passed to us by the UEFI firmware
    ; it points to the EFI_SYSTEM_TABLE
    mov [ptrSystemTable], rdx
    
    ; verify the EFI_TABLE_HEADER.Signature
    call apiVerifySignature
    
    ; output the OS and Firmware versions
    call apiOutputHeader
    
    ; locate a graphics device and allocate a frame buffer
    call apiGetFrameBuffer
    
    ; get a memory map and exit UEFI boot services
    call apiExitUEFI
    
    ; load our kernel
    call apiLoadKernel
    
    add rsp, 4 * 8
    mov rax, EFI_SUCCESS
    ret

error:
    ; move to the location of UEFI and return
    mov rsp, [ptrUEFI]
    ret

errorCode:
    ; save our error code
    push rax
    
    ; display the message
    mov rcx, strErrorCode
    call efiOutputString
    
    ; grab our error code and write it
    ; see the UEFI definitions in kernel-efi.asm
    mov rcx, [rsp]
    call funIntegerToAscii
    
    ; restore the error code
    pop rax
    
    jmp error

codesize equ $ - $$

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

; contains the data being stored
section .data
    ; UEFI requires we use Unicode strings
    strHeader               db   __utf16__ `Hacker Pulp OS v0.1\r\nRunning on \0`
    strHeaderV              db   __utf16__ ` v\0`
    strErrorCode            db   __utf16__ `\r\n\nError Code #\0`
    strDebugText            db   __utf16__ `\r\n\nDebug: \0`
    
    ; stores the location of UEFI
    ptrUEFI                 dq   0
    
    ; stores the EFI_HANDLE
    hndImageHandle          dq   0
    
    ; stores the EFI_SYSTEM_TABLE
    ptrSystemTable          dq   0
    
    ; stores the EFI_GRAPHICS_OUTPUT_PROTOCOL
    ptrInterface            dq   0
    
    ; stores the memory map data
    intMemoryMapSize        dq   EFI_MEMORY_DESCRIPTOR_size * 1024
    bufMemoryMap            resb EFI_MEMORY_DESCRIPTOR_size * 1024
    ptrMemoryMapKey         dq   0
    ptrMemoryMapDescSize    dq   0
    ptrMemoryMapDescVersion dq   0
    
    ; stores the frame buffer base
    ptrFrameBuffer          dq   0

datasize equ $ - $$

Loading the Kernel

One thing I left out of the API is our kernel, because I wanted to touch on this last. In order to gain complete control over the machine you need to exit the UEFI boot services. To do this we have to get a memory map key to verify we have not modified any memory before exiting. This is what we do when calling apiExitUEFI and afterwards we can load our kernel.

kernel-api.asmapiLoadKernel:
    ; set the 1st argument to our frame buffer
    mov rcx, [ptrFrameBuffer]
    
    ; set the 2nd argument to our start position
    mov rdx, 0
    
    ; set the 3rd argument to our end position
    ; this should be in multiples of 4
    mov r8, 1024 * 100
    
    call funDrawLine
    
    ret

At this point, the training wheels are off and our kernel has control. UEFI will only provide a small set of Runtime Services and everything else is up to us. The machine is also in the following state …

  • 64-bit long mode
  • paging is enabled
  • interrupts are enabled

This means we no longer have access to the text output protocol and so debugging becomes much more difficult. So, to verify our kernel is loaded and has control, we use the frame buffer we previously allocated and write pixels directly to it. This is the band of red you see at the top of the screen.

Congratulations, all your base are belong to us!

Kernel
 

Next Steps

In the next article, we will introduce a higher level language and start doing traditional kernel development. Well, not all of it will be traditional. I do want to try a few fun things along the way 🙂

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.