summaryrefslogtreecommitdiff
path: root/doc/www.rodsbooks.com_efi-programming_hello.txt
blob: 22609c4a63625c756d12b8d0076ef0d57394d38f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
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 <efi.h>
#include <efilib.h>

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/