Programming for EFI: Creating a "Hello, World" Program by Roderick W. Smith, [1]rodsmith@rodsbooks.com 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 PayPal - The safer, easier way to pay online! Donate with PayPal PayPal - The safer, easier way to pay online! Donate with PayPal PayPal - The safer, easier way to pay online! Donate with PayPal PayPal - The safer, easier way to pay online! Donate with PayPal PayPal - The safer, easier way to pay online! 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 [2]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 #include EFI_STATUS EFIAPI 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: * The program begins with two #include directives, which load EFI header files from the GNU-EFI package. You should not normally include regular C header files, such as stdlib.h, because most of these header files define data types and functions that are used by the C library. This library is not available in EFI, though, and because of differences in compilation options, you can't easily compile it into an EFI program. Instead, you must rely exclusively on your development package's library. Also note that if you use TianoCore's EDK II rather than GNU-EFI, you'll need to include different (and usually more) header files than shown here. * Instead of using an entry point function called main(), EFI programs written with GNU-EFI use an entry point called efi_main(). This function takes two arguments, as shown in the sample program, which point to the program's image and to the EFI system table, respectively. (The EFI system table is the key to accessing most EFI features, as described on the [3]Using EFI Services page.) If you use the TianoCore EDK II toolkit, you can give the program's entry point another name, but you must specify the entry point when linking the program. * The efi_main() function returns a value of type EFI_STATUS. Possible return values are summarized in the [4]Phoenix EFI documentation, among other places. The sample program always returns EFI_SUCCESS, but a more complex program might return another value. * The definition of EFIAPI varies between platforms. It tells the compiler to use EFI's calling conventions for the specified function. Unfortunately for Linux programmers, EFI uses Microsoft's application binary interface (ABI), rather than the System V (SysV) ABI used by Linux, and therefore used by GCC by default. Functions called from outside your own program, such as efi_main() and functions you pass to the EFI (as drivers are likely to do, and as you might do if you want to patch the EFI's service tables) must be so identified. If you use the TianoCore toolkit, it uses the Microsoft ABI internally, but GNU-EFI uses the SysV ABI internally. * The call to InitializeLib() sets up some critical global variables that are created by GNU-EFI and used to access the firmware's features. Most importantly, the ST variable identifies the system table, the BS variable refers to boot services, and RT refers to runtime services. If you use the TianoCore EDK II, this call doesn't exist, and the variables in question are generally referred to as gST, gBS, and gRT, respectively. * The EFI Print() call takes the place of the printf() function with which you're probably familiar. You can use the two in a very similar fashion, but this example program illustrates one exception: Strings in EFI use a 16-bit encoding, so you must precede string constants with the character L to denote a 16-bit representation. When using string variables, they must normally be defined as a CHAR16* type in order to display properly with Print() or to be passed to most EFI functions. More advanced programs can use common conversion specifiers with Print(), such as %s for strings and %d for decimal numbers. 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)_efi.lds CFLAGS = $(EFIINCS) -fno-stack-protector -fpic \ -fshort-wchar -mno-red-zone -Wall ifeq ($(ARCH),x86_64) CFLAGS += -DEFI_FUNCTION_WRAPPER endif LDFLAGS = -nostdlib -znocombreloc -T $(EFI_LDS) -shared \ -Bsymbolic -L $(EFILIB) -L $(LIB) $(EFI_CRT_OBJS) all: $(TARGET) hello.so: $(OBJS) ld $(LDFLAGS) $(OBJS) -o $@ -lefi -lgnuefi %.efi: %.so 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 elf_x86_64_efi.lds 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: * -fno-stack-protector--Stack protection isn't supported by EFI, so there's no point in building a binary with this feature active. * -fpic--EFI requires that code be position-independent, hence the use of this option. * -fshort-wchar--GCC defines the wchar_t type to be 32 bits by default, but EFI requires it to be 16 bits for 16-bit strings to work correctly. * -fmno-red-zone--On x86-64 systems, the red zone is an area that follows the stack pointer that can be used for temporary variables. The EFI may modify this area, though, so it's not safe to use, and you must compile EFI binaries with this option. * -Wall--When developing EFI applications, you might want to pay extra attention to compiler warnings, and this switch (which causes warnings to be treated as errors) can help. * -DEFI_FUNCTION_WRAPPER--This option is required on the x86-64 platform, but is not defined on the 32-bit x86 platform. It relates to the calling conventions for EFI functions, described on the [5]Using EFI Services page. Linker flags are defined in LDFLAGS, of course. They have the following effects: * -nostdlib--An EFI application should not be linked against standard libraries, and this argument accomplishes this goal. * -nocombreloc--This argument causes the linker to not combine relocation sections. * -T $(EFI_LDS)--To create an EFI binary, a non-standard linker script must be used, and this option tells ld where to find it. * -shared--Even with GNU-EFI's new linker script, ld can't create the final executable. Instead, it creates a shared library, which is subsequently turned into the final binary. * -Bsymbolic--This option causes references to global symbols to be bound to the definitions within the shared library. When ld finishes its work, the result is a file called hello.so, 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 [6]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 [7]64-bit (x86-64) and [8]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 [9]rEFInd and [10]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 } Note: If your computer boots with [11]Secure Boot active, you should disable Secure Boot for your initial tests, since launching your own applications on such a computer complicates matters. 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. __________________________________________________________________ [12]Go on to "Using EFI Services" [13]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 [14]rodsmith@rodsbooks.com. Thanks. [15]Return to my main Web page. References 1. mailto:rodsmith@rodsbooks.com 2. http://www.rodsbooks.com/efi-programming/index.html 3. http://www.rodsbooks.com/efi-programming/efi_services.html 4. http://wiki.phoenix.com/wiki/index.php/EFI_STATUS 5. http://www.rodsbooks.com/efi-programming/efi_services.html 6. http://www.rodsbooks.com/refind/ 7. https://edk2.svn.sourceforge.net/svnroot/edk2/trunk/edk2/EdkShellBinPkg/FullShell/X64/Shell_Full.efi 8. https://edk2.svn.sourceforge.net/svnroot/edk2/trunk/edk2/EdkShellBinPkg/FullShell/Ia32/Shell_Full.efi 9. http://www.rodsbooks.com/refind/ 10. http://freedesktop.org/wiki/Software/gummiboot 11. http://www.rodsbooks.com/efi-bootloaders/secureboot.html 12. http://www.rodsbooks.com/efi-programming/efi_services.html 13. http://www.rodsbooks.com/efi-programming/index.html 14. mailto:rodsmith@rodsbooks.com 15. http://www.rodsbooks.com/