1. Raam Raam ji (greeting with god's name :) ). This is our second blog post in the operating system development series. If you haven't, you can read the first blog post about creating our first UEFI boot loader in assembly here.
Today, we will code and run what the title of the post says.
struc UINT32 {
align 4 ; ensure 4-byte alignment
. dd ? ; reserve 4 bytes (32-bit) of uninitialized space
}
struc void {
align 8
. dq ? ; reserve 8 bytes (64-bit) of uninitialized space
}
macro struct name {
virtual at 0
name name
end virtual
}
struc EFI_TABLE_HEADER {
.Signature void
.Revision UINT32
.HeaderSize UINT32
.CRC32 UINT32
.Reserved UINT32
}
struct EFI_TABLE_HEADER
struc EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL {
.Reset void
.OutputString void
.TestString void
.QueryMode void
.SetMode void
.SetAttribute void
.ClearScreen void
.SetCursorPosition void
.EnableCursor void
.Mode void
}
struct EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
struc EFI_SYSTEM_TABLE {
.Hdr EFI_TABLE_HEADER
.FirmwareVendor void
.FirmwareRevision UINT32
.ConsoleInHandle void
.ConIn void
.ConsoleOutHandle void
.ConOut void
.StandardErrorHandle void
.StdErr void
.RuntimeServices void
.BootServices void
.NumberOfTableEntries void
.ConfigurationTable void
}
struct EFI_SYSTEM_TABLE
format pe64 efi
entry start
section '.text' code executable readable
start:
; store the image handle and the system table pointer passed by the
; firmware
mov [image_handle], rcx
mov [system_table], rdx
push rbp
sub rsp,0x20 ; allocate stack space
mov rax,[system_table]
mov rdx,test_ok_text
mov rcx,[rax+EFI_SYSTEM_TABLE.ConOut]
mov rcx,[rcx+EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.OutputString]
call rcx
add rsp,0x20 ; restore stack
; hang here
jmp $
section '.data' data readable writeable
image_handle dq ?
system_table dq ?
test_ok_text: du 'Test OK',0
Save the above program in a file named `boot.asm` and assemble it as:
`fasm boot.asm BOOTx64.EFI`
2. Now prepare a pen drive with the boot loader image and test it on your real machine by following the procedure given in the point #2 from the previous blog post.
Here is the output:
3. Now let us understand the above code a little bit:
struc UINT32 {
align 4 ; ensure 4-byte alignment
. dd ? ; reserve 4 bytes (32-bit) of uninitialized space
}
struc void {
align 8
. dq ? ; reserve 8 bytes (64-bit) of uninitialized space
}
These two structure definitions create standard data types with proper alignment.
These structures are used when defining UEFI system tables and structures where proper alignment is necessary.
macro struct name {
virtual at 0
name name
end virtual
}
This macro defines a struct-like alias, allowing us to create named structures without allocating memory immediately. It
allows a named structure to be used as a structure reference.
The next three structures above define key UEFI system tables used for interacting with the firmware. You can check the UEFI specification document for the structures of these tables.
These structures allow the boot loader to interface with UEFI firmware, enabling operations like printing text on the screen.
Now beginning from our entry point, first we store the image handle and the system table pointer passed by the firmware in the `rcx` and `rdx` registers respectively.
The subsequent code prints a test message using UEFI’s text output protocol and goes into an infinite loop. First, we setup the stack by saving the base pointer (`rbp`) and allocating stack space (0x20 bytes) for function calls. Then we get UEFI conout (console output interface) interface and finally get `OutputString` function and print text by loading the string address into `rdx`.
Then we restore stack and execute the infinite loop.
Please try the above code and let me know if you have any comments or suggestions :)