Skip to main content
Hero Arm

A Bionic Arm That Can Grow With You

Open Bionics is a company that is developing affordable, assistive devices that enhance the human body. Last year they created Hero Arm. The world’s first clinically tested, medically certified and FDA registered 3D-printed bionic arm.

  • A powered bionic hand controlled by your muscles and formed perfectly for you.
  • Breathable, easily cleanable and dynamically fitted socket.
  • Long battery life gives you all day usage without needing to plug in and charge.
  • Has multiple, easy-to-select grips giving great user control. These can be reconfigured to your preferences.
  • Freeze mode – The hand can be held in a static position for no fuss, reliable grip e.g. Useful for holding a glass.
  • Lights, sounds and vibrations – A full suite of tools that give you feedback on the status of your bionic hand.
  • Custom covers – Show your style and have different looks for different outfits.
  • Posable wrist – Allows you to hold objects at odd angles.
  • Posable thumb – Allows you to pick up tiny objects.
  • Proportional control – Control the speed of fingers for delicate tasks.
  • Ages 8 and up – For the first time, a multi-grip myoelectric is available for kids too!

 

 

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 🙂

Cloudpunk

Cloudpunk: A Story-Driven Cyberpunk Game

Cloudpunk is a story-driven Cyberpunk game with a beautiful aesthetic. You are a courier, working for a semi-legal delivery company, based in the sprawling city of Nivalis. While making deliveries, you meet a diverse range of characters and slowly unravel a mysterious plot of corporate conspiracy, hackers, androids and rogue AI.

It is developed by Marko Dieckmann at ION LANDS and is expected to be released this year.

MS-DOS Viruses

A Dive Into the World of MS-DOS Viruses

Ben Cox takes a nostalgic and in-depth look at MS-DOS viruses

Once you have got an infected file on your system and run it, the malware will either actively search or install syscall hooks to programs you run after. It will often do this in a subtle and non visible way to avoid detection. The importance of subtlety is important since to spread this malware need to either be given to another system through media (floppy disk) copy, or uploaded to another distribution point like a BBS.

Autonomous

Autonomous

Autonomous
by Annalee Newitz

Break open a bottle of Club-Mate and settle in for a world where Big Pharma controls our lives and indentured services are the norm. Autonomous shows us how important open access to science really is. It describes a world where we lose our freedoms when the patent system is used to claim property over life saving therapies and drugs, allowing only the rich to prosper and survive.

The book touches on gender and sexual identity and explores what love may be like in this not-too-distant future. In addition, it exemplifies what it means to have privacy and how truly important that is. The tech is inspiring and the molecular engineering even more so …

A dull green glow emerged in streaks on the walls as bacterial colonies awoke to illuminate her way. Jack came to a stop beneath a coil of ceiling ducts. A command line window materialized helpfully at eye level, its photons organized into the shape of a screen by thousands of projectors circulating in the air. With a swipe, she pulled up the navigation system …

Here is a great review by NPR.

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.

Dune

Dune Soundtrack

Cryo Interactive’s Dune (1992) has the most beautiful soundtrack ever made for a game of its era.

I stand in the sacred human presence. As I do now, so should you stand someday. I pray to your presence that this be so. The future remains uncertain and so it should, for it is the canvas upon which we paint our desires. Thus always the human condition faces a beautifully empty canvas. We possess only this moment in which to dedicate ourselves continuously to the sacred presence which we share and create. — Frank Herbert, Children of Dune, 1976

Omno

OMNO: An Atmospheric Exploration and Adventure Game

OMNO is an atmospheric adventure game. It is a journey of discovery through an ancient world of wonders. The game takes players through lush forests, across a sun blasted desert and over a frigid tundra. The power of a lost civilization will even carry the hero to the clouds. Along the way there will be creatures great and small to observe and interact with – shy rock-like crabs, helpful turtles, maybe even a friendly dinosaur to ride?

The world of OMNO is a game filled with puzzles, platforming challenges and hidden secrets. The player’s magic staff is the key to powering forgotten relics, and will allows players to dash lighting fast across platforms, glide over land, sail above the clouds and more.

It is developed by StudioInkyfox (Jonas Manke), and he recently completed funding on Kickstarter.

The game is about 60% complete and a playable demo is available.

Elowan - A Plant-Robot Hybrid

Elowan: A Plant-Robot Hybrid

Elowan is a cybernetic lifeform, a plant in direct dialogue with a machine. Using its own internal electrical signals, the plant is interfaced with a robotic extension that drives it toward light.

Elowan is an attempt to demonstrate what augmentation of nature could mean. Elowan’s robotic base is a new symbiotic association with a plant. The agency of movement rests with the plant based on its own bio-electrochemical signals, the language interfaced here with the artificial world.

These in turn trigger physiological variations such as elongation growth, respiration, and moisture absorption. In this experimental setup, electrodes are inserted into the regions of interest (stems and ground, leaf and ground). The weak signals are then amplified and sent to the robot to trigger movements to respective directions.

Such symbiotic interplay with the artificial could be extended further with exogenous extensions that provide nutrition, growth frameworks, and new defense mechanisms.

Author / Credit: Harpreet Sareen
License: CC-BY 4.0