From aab647a0cc135a0510ded3b65b812dfd110321a5 Mon Sep 17 00:00:00 2001 From: Andreas Baumann Date: Sun, 14 Jan 2024 19:40:25 +0100 Subject: updated links and documentation --- doc/www.rodsbooks.com_efi-programming_hello.txt | 307 ++++++++++++++++++++++++ 1 file changed, 307 insertions(+) create mode 100644 doc/www.rodsbooks.com_efi-programming_hello.txt (limited to 'doc/www.rodsbooks.com_efi-programming_hello.txt') diff --git a/doc/www.rodsbooks.com_efi-programming_hello.txt b/doc/www.rodsbooks.com_efi-programming_hello.txt new file mode 100644 index 0000000..22609c4 --- /dev/null +++ b/doc/www.rodsbooks.com_efi-programming_hello.txt @@ -0,0 +1,307 @@ + 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/ -- cgit v1.2.3-54-g00ecf