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!
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 🙂