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/
|