Programming for EFI:
Creating a "Hello, World" Program

by Roderick W. Smith,

Originally written: 5/3/2013

I'm a technical writer and consultant specializing in Linux technologies. This Web page is provided free of charge and with no annoying outside ads; however, I did take time to prepare it, and Web hosting does cost money. If you find this Web page useful, please consider making a small donation to help keep this site up and running. Thanks!

Donate $1.00 Donate $2.50 Donate $5.00 Donate $10.00 Donate another value
Donate with PayPal
Donate with PayPal
Donate with PayPal
Donate with PayPal
Donate with PayPal

Note: This page is a sub-page of my Programming for EFI document. If a Web search has brought you here, you may want to start at the introductory page.

The traditional first program for a new compiler or environment is "Hello, World." I therefore present such a program for EFI, including the program itself, a Makefile for the program, and instructions on how to compile and run it.

Creating the Program File

A "Hello, World" program for EFI demonstrates some of the unique features of EFI programming. To begin, consider the program itself:

#include <efi.h>
#include <efilib.h>

efi_main (EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) {
   InitializeLib(ImageHandle, SystemTable);
   Print(L"Hello, world!\n");

   return EFI_SUCCESS;

If you want to try compiling the program itself, cut-and-paste the preceding lines into a file called main.c; the Makefile presented shortly assumes this name. Many of this program's features are similar to those of a similar program for Linux, Windows, or other OSes and environments; however, there are some significant differences, too:

More complex programs will of course expose additional differences between a C program written for most OSes and one written for EFI. Most of these differences relate to library differences between EFI and other environments.

Creating the Makefile

If you were building a "Hello, World" program for Linux in a Linux environment, you could compile it without a Makefile. Building the program in Linux for EFI, though, is essentially a cross-compilation operation. As such, it necessitates using unusual compilation and linker options, as well as a post-linking operation to convert the program into a form that the EFI will accept. Although you could type all the relevant commands by hand, a Makefile helps a lot. Such a file to build the preceding program file looks like this:

ARCH            = $(shell uname -m | sed s,i[3456789]86,ia32,)

OBJS            = main.o
TARGET          = hello.efi

EFIINC          = /usr/include/efi
EFIINCS         = -I$(EFIINC) -I$(EFIINC)/$(ARCH) -I$(EFIINC)/protocol
LIB             = /usr/lib64
EFILIB          = /usr/lib64/gnuefi
EFI_CRT_OBJS    = $(EFILIB)/crt0-efi-$(ARCH).o
EFI_LDS         = $(EFILIB)/elf_$(ARCH)

CFLAGS          = $(EFIINCS) -fno-stack-protector -fpic \
		  -fshort-wchar -mno-red-zone -Wall 
ifeq ($(ARCH),x86_64)

LDFLAGS         = -nostdlib -znocombreloc -T $(EFI_LDS) -shared \
		  -Bsymbolic -L $(EFILIB) -L $(LIB) $(EFI_CRT_OBJS) 

all: $(TARGET) $(OBJS)
	ld $(LDFLAGS) $(OBJS) -o $@ -lefi -lgnuefi

	objcopy -j .text -j .sdata -j .data -j .dynamic \
		-j .dynsym  -j .rel -j .rela -j .reloc \
		--target=efi-app-$(ARCH) $^ $@

If you cut-and-paste this code into a text editor, be sure to convert stretches of eight characters to tabs! Also, be aware that you may need to adjust the EFIINC, LIB, and EFILIB variables to point to the relevant portions of your GNU-EFI installation directory. EFIINC should, of course, point to your GNU-EFI include files; LIB should point to the directory that holds the libefi.a and libgnuefi.a files; and EFILIB should point to the directory that holds the crt0-efi-x86_64.o and files (or equivalents for another architecture). The Makefile shown here works on a Fedora 18 installation. On Ubuntu 13.04, both LIB and EFILIB must be set to /usr/lib; and on Gentoo, they must both be set to /usr/lib64.

The CFLAGS line sets a number of options that are important for getting a working EFI binary. Although some of these could be omitted or changed for the simple "Hello, World" demonstration, they can be important for larger programs:

Linker flags are defined in LDFLAGS, of course. They have the following effects:

When ld finishes its work, the result is a file called, which is technically a shared library. To create an EFI executable, the Makefile calls the objcopy program, which copies the code needed from the library to create an EFI application.

If you use TianoCore EDK II, many of these options will be different. One extremely important difference is the inclusion of the -DEFIAPI=__attribute__((ms_abi)) GCC flag, which causes the binary to be built using Microsoft's ABI. The linking process is also different; the TianoCore EDK II includes its own program, called GenFw, that builds the final binary instead of objcopy. TianoCore is designed around a build process that doesn't use make, so you must either use TianoCore's own build process or design a Makefile to mimic it. I don't describe either approach here, so you should consult TianoCore's documentation for the first option. If you want to use TianoCore with make, check rEFInd, which uses this approach, as a model.

Compiling and Running the Program

Compiling the program is simple: Type make. The result should be generation of intermediate files and the final hello.efi program file of about 46KiB on a 64-bit system. If you encounter errors, you'll have to fix them yourself. Be sure that your Makefile uses tabs where necessary, and check the locations of the header and library files, as already described. Note that the build process described here results in a program file for the architecture you're using. If you build on a 64-bit system, the binary won't work on a 32-bit computer, and vice-versa.

The best way to run the program is likely to be from an EFI shell. You can download a binary from the TianoCore site, for both 64-bit (x86-64) and 32-bit (x86) platforms. Rename these files as shellx64.efi or shellia32.efi, respectively, and place them in the root directory of your EFI System Partition (ESP). Some ESPs and boot managers, such as rEFInd and gummiboot, recognize this name and location as special, and will enable you to launch a shell when one exists there. Other boot managers, such as GRUB, may require explicit configuration to launch an EFI shell. Treat the shell like an OS's boot loader. For instance, in GRUB 2 you might create an entry in /etc/grub.d/40_custom like the following:

menuentry "EFI shell" {
        insmod part_gpt
        insmod chain
        set root='(hd0,gpt1)'
        chainloader /shellx64.efi

Details will vary depending on your installation, though. Once you've made these changes, use update-grub or grub-mkconfig to re-create your grub.cfg file with the new entry to launch the EFI shell.

When you launch your EFI shell, you should type fs0: to change to the first filesystem, which is normally the ESP. You can then type hello.efi to launch the program. You should see its output, as in:

Shell> fs0:

fs0:\> hello.efi
Hello, world!

fs0:\> exit

If the program hangs or otherwise misbehaves, you may need to review the code and build process. Unfortunately, debugging EFI applications can be tedious, because the usual debugging tools don't work with them. I find that using a virtual machine can help. VirtualBox, for instance, supports EFI, so it's possible to install Linux under EFI on VirtualBox and use it for testing EFI applications compiled in the host environment. This procedure at least obviates the need to re-start your editor or IDE after every test of your program. Ubuntu and Linux Mint work well in this role because they both boot very quickly, which can speed things up if you need to make a small but quick change to be tested immediately.

Go on to "Using EFI Services"

Return to the "Programming for EFI" main page

copyright © 2013 by Roderick W. Smith

If you have problems with or comments about this Web page, please e-mail me at Thanks.

Return to my main Web page.