Tuesday, February 18, 2025

Let us print text from the UEFI boot loader in assembly

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.
  • EFI_TABLE_HEADER – A common header used in UEFI tables, containing metadata like a signature, version, and checksum.
  • EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL – Defines function pointers for text output operations (e.g., printing text to the screen, clearing the screen, setting the cursor position).
  • EFI_SYSTEM_TABLE – The main UEFI system table, providing access to firmware services, console input/output, and runtime/boot services.
  • 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 :)



    No comments:

    Post a Comment

    Let us print text from the UEFI boot loader in assembly

    1. Raam Raam ji (greeting with god's name :) ). This is our second blog post in the operating system development series. If yo...