Author Topic: UEFI implementation in NASM  (Read 10012 times)

Offline JD9999

  • Jr. Member
  • *
  • Posts: 7
  • Country: au
UEFI implementation in NASM
« on: September 06, 2021, 07:56:32 AM »
Hi everyone!

With much help from previous implementations of a nasm uefi interface:
- BrianOtto's implementation: https://github.com/BrianOtto/nasm-uefi
- Charles AP's implementation: https://github.com/charlesap/nasm-uefi

I have written a basic UEFI application!
This looks much more like the second implementation than the first, because I wanted to build it as a single EFI file without a linker.

What does it do?
It displays the words "Wait a second..."
Then it waits a second
Then it displays the words "Hi JD9999!"

How do I run this code?
To compile this code, run the command:
Code: [Select]
nasm -f bin loader.asm -o BOOTX64.efi(change loader.asm to whatever you name the file)

This produces an EFI file, which you need to copy to a USB in the directory
Code: [Select]
F:\EFI\BOOT\BOOTX64.efi(change F: to the drive letter as necessary)

Then just boot off the USB device (or whatever type of device it is)

Where's the code?
Here it is:
Code: [Select]
BITS 64
org 0x00100000 ;Space for a small stack?

;**************
;*** HEADER ***
;**************

section .header
DOS_HEADER:
   dw 0x5a4d ;DOS Magic number
   times 29 dw 0 ;Zeroes
   dd 0x00000080 ;Address of PE header

DOS_STUB:
   ; I don't know how to write this as text, so you get zeroes
   times 32 dw 0

PE_HEADER:
   dd 0x00004550 ;PE Magic number
   dw 0x8664 ;Building for x86 architecture
   dw 2 ;Two sections (.text, .data)
   dd 0x5ed4b58 ;number of seconds between 00:00:00 1st January 1970 and 00:00:00 1st July 2021
   dd 0x0 ;No symbol table
   dd 0x0 ;No symbols in the non-existent symbol table!
   dw oSize ;The size of the entire optional header. See the OPTIONAL_HEADER_END label for the calculation.
   dw 0x1002 ;Is a valid image file, is a system file. No other fancy characteristics.

oSize equ OPTIONAL_HEADER_END - OPTIONAL_HEADER_STANDARD_FIELDS

OPTIONAL_HEADER_STANDARD_FIELDS: ;Not actually optional
   dw 0x020b ;PE32+ Executable. I want my 64-bit registers!
   dw 0x0 ;What linker?
   dd 1024 ;The size of the code segment
   dd 1024 ;The size of the data segment
   dd 0x0 ;No .bss section. All variables to be initialised.
   dd 1024 ;The program's entry point
   dd 1024 ;The program's first instruction. Same as the start of the code execution. Duh.

OPTIONAL_HEADER_WINDOWS_FIELDS: ;This is required for UEFI applications too. Trust me, plenty of debugging went into that discovery.
   dq 0x00100000 ;The entry point of the image
   dd 0x1024 ;The section alignment
   dd 0x1024 ;The file alignment
   dw 0x0 ;No operating system requirements
   dw 0x0 ;Stil no operating system requirements
   dw 0x0 ;Major image version number
   dw 0x1 ;Minor image version number
   dw 0x0 ;Major subsystem version. Doesn't matter, as long as it supports UEFI.
   dw 0x0 ;Minor subsystem version. Doesn't matter, as long as it supports UEFI.
   dd 0x0 ;A dedicated zero
   dd 3072 ;Image size
   dd 1024 ;Header size
   dd 0x0 ;Checksum //TODO ADD LATER
   dw 0x000A ;UEFI Subsystem number.
   dw 0x0 ;Not a DLL, so this can be zero

   ;Using PE32+ file type, so the following are dqs, not dds
   dq 0x8000 ;Amount of stack space to reserve
   dq 0x8000 ;Amount of stack space to commit immediately
   dq 0x8000 ;Amount of local heap space to reserve
   dq 0x0 ;Amount of local heap space to commit immediately. Hopefully not needed.
   dd 0x0 ;Another four bytes dedicated to being zeroes
   dd 0x0 ;Number of data dictionary entries

;OPTIONAL_HEADER_DATA_DIRECTORIES: ;We don't have any special sections, so we don't need this header!

OPTIONAL_HEADER_END: ;This label is required for calculating value of oSize

SECTION_TABLE: ;as if you don't have enough information already :\
.1: ;text section
   dq `.text` ;The name of the text section
   dd 1024 ;virtual size.
   dd 1024 ;virtual entry point address.
   dd 1024 ;actual size.
   dd 1024 ;actual entry point address.
   dd 0 ;No relocations
   dd 0 ;No line numbers
   dw 0 ;No relocations
   dw 0 ;No line numbers
   dd 0x60000020 ;Contains executable code, can be executed as code, can be read.

.2: ;data section
   dq `.data` ;The name of the data section
   dd 1024 ;virtual size.
   dd 2048 ;virtual entry point address.
   dd 1024 ;actual size.
   dd 2048 ;actual entry point address.
   dd 0 ;No relocations
   dd 0 ;No line numbers
   dw 0 ;No relocations
   dw 0 ;No line numbers
   dd 0xc0000040 ;Contains initialised data, can be read, can be written to.

times 1024 - ($-$$) db 0 ;alignment

;*****************
;*** MAIN CODE ***
;*****************

section .text follows=.header
vars:
   ;Function return codes.
   EFI_SUCCESS equ 0
   
   ;Offsets for loading function addresses
   OFFSET_TABLE_BOOT_SERVICES equ 96
   OFFSET_TABLE_ERROR_CONSOLE equ 80
   OFFSET_TABLE_OUTPUT_CONSOLE equ 64
   OFFSET_TABLE_RUNTIME_SERVICES equ 88
   OFFSET_BOOT_EXIT_PROGRAM equ 216
   OFFSET_BOOT_STALL equ 248
   OFFSET_CONSOLE_OUTPUT_STRING equ 8

   ;Numbers used in the program.
   waitTime equ 1000000 ;One million microseconds, equals one second

start:
   sub rsp, 6*8+8 ; Copied from Charles AP's implementation, fix stack alignment issue (Thanks Charles AP!)

   ;Start moving handoff variables.
   mov [EFI_HANDLE], rcx
   mov [EFI_SYSTEM_TABLE], rdx
   mov [EFI_RETURN], rsp

   ;Set up necessary boot services functions
   add rdx, OFFSET_TABLE_BOOT_SERVICES ;get boot services table
   mov rcx, [rdx]
   mov [BOOT_SERVICES], rcx
   add rcx, OFFSET_BOOT_EXIT_PROGRAM ;get exit function from boot services table
   mov rdx, [rcx]
   mov [BOOT_SERVICES_EXIT], rdx
   mov rcx, [BOOT_SERVICES]
   add rcx, OFFSET_BOOT_STALL ;get stall function from boot services table
   mov rdx, [rcx]
   mov [BOOT_SERVICES_STALL], rdx

   ;Set up necessary console functions
   mov rdx, [EFI_SYSTEM_TABLE]
   add rdx, OFFSET_TABLE_ERROR_CONSOLE ;get error console table
   mov rcx, [rdx]
   mov [CONERR], rcx
   add rcx, OFFSET_CONSOLE_OUTPUT_STRING ;get output string function from console table
   mov rdx, [rcx]
   mov [CONERR_PRINT_STRING], rdx

   mov rdx, [EFI_SYSTEM_TABLE]
   add rdx, OFFSET_TABLE_OUTPUT_CONSOLE ;get output console table
   mov rcx, [rdx]
   mov [CONOUT], rcx
   add rcx, OFFSET_CONSOLE_OUTPUT_STRING ;get output string function from console table
   mov rdx, [rcx]
   mov [CONOUT_PRINT_STRING], rdx

   ;Set up necessary runtime services functions
   mov rdx, [EFI_SYSTEM_TABLE]
   add rdx, OFFSET_TABLE_RUNTIME_SERVICES ;get runtime services table
   mov rcx, [rdx]
   mov [RUNTIME_SERVICES], rcx

   ;Clear some registers for use.
   xor rcx, rcx
   xor rdx, rdx
   xor r8, r8

   ;Print a string
   mov rcx, [CONOUT]
   lea rdx, [waitString]
   call [CONOUT_PRINT_STRING]

   ;Wait a second so that the user can read the string
   mov rcx, waitTime
   call [BOOT_SERVICES_STALL]

   ;Print a string
   mov rcx, [CONOUT]
   lea rdx, [hello]
   call [CONOUT_PRINT_STRING]

   ;Return back to the UEFI with success!
   mov rcx, [EFI_HANDLE]
   mov rdx, EFI_SUCCESS
   mov r8, 1
   call [BOOT_SERVICES_EXIT]

   ret

times 1024 - ($-$$) db 0 ;alignment

;************
;*** DATA ***
;************

section .data follows=.text
dataStart:
   ;Handover variables
   EFI_HANDLE dq 0
   EFI_SYSTEM_TABLE dq 0
   EFI_RETURN dq 0

   ;Accessing functions of EFI system table
   BOOT_SERVICES dq 0
   BOOT_SERVICES_EXIT dq 0 ;This one exits the program, not just stop boot services!
   BOOT_SERVICES_STALL dq 0
   CONERR dq 0
   CONERR_PRINT_STRING dq 0
   CONOUT dq 0
   CONOUT_PRINT_STRING dq 0
   RUNTIME_SERVICES dq 0
   
   ;Strings used in the program.
   waitString db __utf16__ `Wait a second...\r\n\0`
   hello db __utf16__ `Hi JD9999!\r\n\0`

times 1024 - ($-$$) db 0 ;alignment

Things I've learned
I learned lots of things, but here are some things I wanted to share for people who want to do something similar in the future:
  • A relocation section is not required.
  • Section alignments are necessary. Sometimes you have to represent it as a hexadecimal, and something you have to reference it as a decimal. You can see above the difference between 'dd 1024' and 'dd 0x1024' above to know when to use which one.
  • Some optional things are not optional, including the "Windows-Specific Fields". They are required for UEFI (see https://docs.microsoft.com/en-us/windows/win32/debug/pe-format for a full description of the PE header)
  • The offsets from the EFI_SYSTEM_TABLE point to pointers, but are themselves not pointer addresses. This means don't use the [] for offsets. But use it for the combined value of the table and the offset to get the pointer to the function call you need!

Summary
If you have any improvements you would like to suggest, please do! I always like improving things to make them better.

Hope this helps someone!

Offline patrick666

  • New Member
  • Posts: 1
Re: UEFI implementation in NASM
« Reply #1 on: October 27, 2022, 10:09:05 AM »
dd 0x00000080 ;Address of PE header
change it to 0x00000040 and remove "times 32 dw 0". It will skip that part and go to file offset 0x40 directly to the PE signature instead of (0x40 + (32*sizeof dw)).

Offline JD9999

  • Jr. Member
  • *
  • Posts: 7
  • Country: au
Re: UEFI implementation in NASM
« Reply #2 on: November 01, 2022, 07:36:34 AM »
That would certainly work!

The reason I have kept it there is because Microsoft's PE Format document is keen on maintaining MS-DOS compatibility (see information on the MS-DOS header and stub: https://learn.microsoft.com/en-us/windows/win32/debug/pe-format).

However, on most machines, your code will run perfectly fine :)