UEFI App Bare Bones: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content deleted Content added
m →‎Prerequisites: Added link to GNU-EFI page
m Bot: Replace deprecated source tag with syntaxhighlight
 
(19 intermediate revisions by 7 users not shown)
Line 10: Line 10:
== Prerequisites ==
== Prerequisites ==


Note that [[GNU-EFI]] prefers to use your host native compiler, and then convert the ELF to PE as a last step. Otherwise developers will need a [[GCC Cross-Compiler]] targeting the '''x86_64-w64-mingw32''' target (for PE output), and the [https://sourceforge.net/projects/gnu-efi/ '''gnu-efi'''] package to compile the actual kernel. If they already have a copy of LLVM/clang, it will too work as a cross-compiler. To build the EFI filesystem image, developers can use [[MTools]] or [https://github.com/jncronin/mkgpt '''mkgpt'''] to create a hard disk image. To build a CD image, '''xorriso''' (in [[mkisofs]] emulation mode) will be needed. To run under an emulator, it is best to use '''qemu-system-x86_64''' coupled with the [http://tianocore.sourceforge.net/wiki/OVMF '''x64 OVMF firmware'''].
Developers will need a [[GCC Cross-Compiler]] or Clang targeting the '''x86_64-w64-mingw32''' target (for [[PE]] output), and the [https://sourceforge.net/projects/gnu-efi/ '''gnu-efi'''] package (for UEFI headers). Most Linux distros provide cross-compilers for this target, so it's usually not necessary to build it yourself. This example does not link against [[GNU-EFI]] or follow its build process; only the headers are used.

Under an apt-based system (e.g. Debian/Ubuntu), developers can run <source lang="bash">sudo apt-get install qemu binutils-mingw-w64 gcc-mingw-w64 xorriso mtools
To build the EFI filesystem image, developers can use [[MTools]] or [https://github.com/jncronin/mkgpt '''mkgpt'''] to create a hard disk image. To build a CD image, '''xorriso''' (in [[mkisofs]] emulation mode) will be needed. To run under an emulator, it is best to use '''qemu-system-x86_64''' coupled with the [http://tianocore.sourceforge.net/wiki/OVMF '''x64 OVMF firmware'''].
wget http://www.tysos.org/files/efi/mkgpt-latest.tar.bz2

tar jxf mkgpt-latest.tar.bz2
Under an apt-based system (e.g. Debian/Ubuntu), developers can run: <syntaxhighlight lang="bash">sudo apt-get install qemu ovmf gnu-efi binutils-mingw-w64 gcc-mingw-w64 xorriso mtools</syntaxhighlight>
cd mkgpt && ./configure && make && sudo make install && cd ..</source> and then separately download OVMF and extract the OVMF.fd file somewhere, as well as gnu-efi.

To install mkgpt you can run these commands:<syntaxhighlight lang="bash">git clone https://github.com/jncronin/mkgpt.git
cd mkgpt
automake --add-missing
autoreconf
./configure
make
sudo make install</syntaxhighlight>


== Testing the emulator ==
== Testing the emulator ==
Line 20: Line 28:
Now is a good time to check the emulator is working successfully with the OVMF firmware.
Now is a good time to check the emulator is working successfully with the OVMF firmware.


<source lang="bash">qemu-system-x86_64 -L OVMF_dir/ -bios OVMF.fd</source>should launch qemu and launch a UEFI shell prompt.
<syntaxhighlight lang="bash">qemu-system-x86_64 -L OVMF_dir/ -pflash OVMF.fd</syntaxhighlight>should launch qemu and launch a UEFI shell prompt.


== Preparing the files ==
== Preparing the files ==
Line 27: Line 35:


Next, create a file with the following:
Next, create a file with the following:
<source lang="c">#include <efi.h>
<syntaxhighlight lang="c">#include <efi.h>
#include <efilib.h>
#include <efilib.h>


Line 58: Line 66:


return Status;
return Status;
}</source>
}</syntaxhighlight>


=== gnu-efi/lib/data.c ===
=== gnu-efi/lib/data.c ===
Line 71: Line 79:


To build, use the cross-compiler:
To build, use the cross-compiler:
<source lang="bash">
<syntaxhighlight lang="bash">
# compile: (flags before -o become CFLAGS in the Makefile)
# compile: (flags before -o become CFLAGS in the Makefile)
x86_64-w64-mingw32-gcc -ffreestanding -Ipath/to/gnu-efi/inc -Ipath/to/gnu-efi/inc/x86_64 -Ipath/to/gnu-efi/inc/protocol -c -o hello.o hello.c
x86_64-w64-mingw32-gcc -ffreestanding -Ipath/to/gnu-efi/inc -Ipath/to/gnu-efi/inc/x86_64 -Ipath/to/gnu-efi/inc/protocol -c -o hello.o hello.c
x86_64-w64-mingw32-gcc -ffreestanding -Ipath/to/gnu-efi/inc -Ipath/to/gnu-efi/inc/x86_64 -Ipath/to/gnu-efi/inc/protocol -c -o data.o path/to/gnu-efi/lib/data.c
x86_64-w64-mingw32-gcc -ffreestanding -Ipath/to/gnu-efi/inc -Ipath/to/gnu-efi/inc/x86_64 -Ipath/to/gnu-efi/inc/protocol -c -o data.o path/to/gnu-efi/lib/data.c
# link: (flags before -o become LDFLAGS in the Makefile)
# link: (flags before -o become LDFLAGS in the Makefile)
x86_64-w64-mingw32-gcc -nostdlib -Wl,-dll -shared -Wl,--subsystem,10 -e efi_main -o BOOTX64.EFI hello.o data.o -lgcc
x86_64-w64-mingw32-gcc -nostdlib -Wl,-dll -shared -Wl,--subsystem,10 -e efi_main -o BOOTX64.EFI hello.o data.o
</syntaxhighlight>
</source>
Note here that '--subsystem 10' specifies an EFI application for ld.
Note here that '--subsystem 10' specifies an EFI application for ld.


Line 83: Line 91:
The build sequence under LLVM/clang is essentially the same, although there is the advantage of having ''all'' targets installed by default:
The build sequence under LLVM/clang is essentially the same, although there is the advantage of having ''all'' targets installed by default:


<source lang="bash">
<syntaxhighlight lang="bash">
CFLAGS='-target x86_64-unknown-windows
CFLAGS='-target x86_64-unknown-windows
-ffreestanding
-ffreestanding
Line 97: Line 105:
clang $CFLAGS -c -o data.o path/to/gnu-efi/lib/data.c
clang $CFLAGS -c -o data.o path/to/gnu-efi/lib/data.c
clang $LDFLAGS -o BOOTX64.EFI hello.o data.o
clang $LDFLAGS -o BOOTX64.EFI hello.o data.o
</syntaxhighlight>
</source>


Passing '--target x86_64-unknown-windows' to clang tells it to compile for x86_64 "Windows". This is quite not the same as 64-bit UEFI PE yet, but as before the "freestanding" part makes it a good kernel image. An example of this toolchain is found in the [https://github.com/c-util/c-efi c-efi] project.
Passing '--target x86_64-unknown-windows' to clang tells it to compile for x86_64 "Windows". This is quite not the same as 64-bit UEFI PE yet, but as before the "freestanding" part makes it a good kernel image. An example of this toolchain is found in the [https://github.com/c-util/c-efi c-efi] project.
Line 107: Line 115:


Next, create a FAT filesystem image.
Next, create a FAT filesystem image.
<source lang="bash">
<syntaxhighlight lang="bash">
dd if=/dev/zero of=fat.img bs=1k count=1440
dd if=/dev/zero of=fat.img bs=1k count=1440
mformat -i fat.img -f 1440 ::
mformat -i fat.img -f 1440 ::
Line 113: Line 121:
mmd -i fat.img ::/EFI/BOOT
mmd -i fat.img ::/EFI/BOOT
mcopy -i fat.img BOOTX64.EFI ::/EFI/BOOT
mcopy -i fat.img BOOTX64.EFI ::/EFI/BOOT
</syntaxhighlight>
</source>


=== Running as a USB stick image ===
=== Running as a USB stick image ===
Line 119: Line 127:
The FAT image can either be written directly to a USB stick and used in in a UEFI machine, or it can be run directly in QEMU:
The FAT image can either be written directly to a USB stick and used in in a UEFI machine, or it can be run directly in QEMU:


<source lang="bash">qemu-system-x86_64 -L OVMF_dir/ -bios OVMF.fd -usb -usbdevice disk::fat.img</source>
<syntaxhighlight lang="bash">qemu-system-x86_64 -L OVMF_dir/ -pflash OVMF.fd -usb -usbdevice disk::fat.img</syntaxhighlight>


=== Creating and running the HD image ===
=== Creating and running the HD image ===
Line 125: Line 133:
The HD image is a disk image in the [[GPT]] format, with the FAT image specially identified as a 'EFI System Partition'.
The HD image is a disk image in the [[GPT]] format, with the FAT image specially identified as a 'EFI System Partition'.


<source lang="bash">mkgpt -o hdimage.bin --image-size 4096 --part fat.img --type system
<syntaxhighlight lang="bash">mkgpt -o hdimage.bin --image-size 4096 --part fat.img --type system
qemu-system-x86_64 -L OVMF_dir/ -bios OVMF.fd -hda hdimage.bin</source>
qemu-system-x86_64 -L OVMF_dir/ -pflash OVMF.fd -hda hdimage.bin</syntaxhighlight>


=== Creating and running the CD image ===
=== Creating and running the CD image ===
The ISO image is a standard ISO9660 image which contains the FAT image as a file. A special El Torito option (-e) then points EFI aware systems to this image to be loaded. The CD image can either be burned to a CD and ran in a UEFI machine, or run directly in QEMU:
The ISO image is a standard ISO9660 image which contains the FAT image as a file. A special El Torito option (-e) then points EFI aware systems to this image to be loaded. The CD image can either be burned to a CD and ran in a UEFI machine, or run directly in QEMU:
<source lang="bash">mkdir iso
<syntaxhighlight lang="bash">mkdir iso
cp fat.img iso
cp fat.img iso
xorriso -as mkisofs -R -f -e fat.img -no-emul-boot -o cdimage.iso iso
xorriso -as mkisofs -R -f -e fat.img -no-emul-boot -o cdimage.iso iso
qemu-system-x86_64 -L OVMF_dir/ -bios OVMF.fd -cdrom cdimage.iso</source>
qemu-system-x86_64 -L OVMF_dir/ -pflash OVMF.fd -cdrom cdimage.iso</syntaxhighlight>


== What to do next? ==
== What to do next? ==


Developers may want to try using some more of the EFI boot services, e.g., to read more files from the FAT image, manage memory, etc. (see the [http://www.uefi.org/specifications UEFI Specifications] page for further documentation of this).
Developers may want to try using some more of the EFI boot services, e.g., to [[Loading files under UEFI|read more files]] from the FAT image, manage memory, set up [[GOP|graphical frame buffer]] etc. (see the [http://www.uefi.org/specifications UEFI Specifications] page for further documentation of this).


There is also a finished app bare bone which supports both Linux and Windows (Visual Studio), see [https://github.com/pbatard/uefi-simple uefi-simple].
There is also a finished app bare bone which supports both Linux and Windows (Visual Studio), see [https://github.com/pbatard/uefi-simple uefi-simple].
Line 150: Line 158:
* [[UEFI ISO Bare Bones]]
* [[UEFI ISO Bare Bones]]
* [[GNU-EFI]]
* [[GNU-EFI]]
* [[POSIX-UEFI]]


[[Category:Bare bones tutorials]]
[[Category:Bare bones tutorials]]

Latest revision as of 05:44, 9 June 2024

WAIT! Have you read Getting Started, Beginner Mistakes, and some of the related OS theory?
Difficulty level

Medium

In this tutorial, developers will create a hard drive or ISO image containing a bare bones UEFI application for the x86-64 platform.

It is recommended to have read and fully understood the Bare Bones tutorial first. The UEFI page provides some background to the UEFI boot process and should also be consulted first.

This tutorial uses the header files and GUID definitions from the GNU-EFI project, but does not use the gnu-efi build system, but rather the MinGW-w64 or LLVM/Clang toolchain.

Prerequisites

Developers will need a GCC Cross-Compiler or Clang targeting the x86_64-w64-mingw32 target (for PE output), and the gnu-efi package (for UEFI headers). Most Linux distros provide cross-compilers for this target, so it's usually not necessary to build it yourself. This example does not link against GNU-EFI or follow its build process; only the headers are used.

To build the EFI filesystem image, developers can use MTools or mkgpt to create a hard disk image. To build a CD image, xorriso (in mkisofs emulation mode) will be needed. To run under an emulator, it is best to use qemu-system-x86_64 coupled with the x64 OVMF firmware.

Under an apt-based system (e.g. Debian/Ubuntu), developers can run:

sudo apt-get install qemu ovmf gnu-efi binutils-mingw-w64 gcc-mingw-w64 xorriso mtools

To install mkgpt you can run these commands:

git clone https://github.com/jncronin/mkgpt.git
cd mkgpt
automake --add-missing
autoreconf
./configure
make
sudo make install

Testing the emulator

Now is a good time to check the emulator is working successfully with the OVMF firmware.

qemu-system-x86_64 -L OVMF_dir/ -pflash OVMF.fd

should launch qemu and launch a UEFI shell prompt.

Preparing the files

hello.c

Next, create a file with the following:

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

EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
{
    EFI_STATUS Status;
    EFI_INPUT_KEY Key;

    /* Store the system table for future use in other functions */
    ST = SystemTable;

    /* Say hi */
    Status = ST->ConOut->OutputString(ST->ConOut, L"Hello World\r\n"); // EFI Applications use Unicode and CRLF, a la Windows
    if (EFI_ERROR(Status))
        return Status;

    /* Now wait for a keystroke before continuing, otherwise your
       message will flash off the screen before you see it.

       First, we need to empty the console input buffer to flush
       out any keystrokes entered before this point */
    Status = ST->ConIn->Reset(ST->ConIn, FALSE);
    if (EFI_ERROR(Status))
        return Status;

    /* Now wait until a key becomes available.  This is a simple
       polling implementation.  You could try and use the WaitForKey
       event instead if you like */
    while ((Status = ST->ConIn->ReadKeyStroke(ST->ConIn, &Key)) == EFI_NOT_READY) ;

    return Status;
}

gnu-efi/lib/data.c

Developers will also need bring in the data.c file from the gnu-efi distribution, as this contains many predefined GUIDs for the various UEFI services. To avoid bloat and unnecessary dependencies on the rest of gnu-efi, it will need to be edited to remove the references to 'LibStubStriCmp', 'LibStubMetaiMatch', and 'LibStubStrLwrUpr' (simply set all the members of the LibStubUnicodeInterface structure be NULL).

gnu-efi/lib/lib.h

data.c includes this file. It must be copied as-is to the source directory.

Building

To build, use the cross-compiler:

# compile: (flags before -o become CFLAGS in the Makefile)
x86_64-w64-mingw32-gcc -ffreestanding -Ipath/to/gnu-efi/inc -Ipath/to/gnu-efi/inc/x86_64 -Ipath/to/gnu-efi/inc/protocol -c -o hello.o hello.c
x86_64-w64-mingw32-gcc -ffreestanding -Ipath/to/gnu-efi/inc -Ipath/to/gnu-efi/inc/x86_64 -Ipath/to/gnu-efi/inc/protocol -c -o data.o path/to/gnu-efi/lib/data.c
# link: (flags before -o become LDFLAGS in the Makefile)
x86_64-w64-mingw32-gcc -nostdlib -Wl,-dll -shared -Wl,--subsystem,10 -e efi_main -o BOOTX64.EFI hello.o data.o

Note here that '--subsystem 10' specifies an EFI application for ld.

Under LLVM/clang

The build sequence under LLVM/clang is essentially the same, although there is the advantage of having all targets installed by default:

CFLAGS='-target x86_64-unknown-windows 
        -ffreestanding 
        -fshort-wchar 
        -mno-red-zone 
        -Ipath/to/gnu-efi/inc -Ipath/to/gnu-efi/inc/x86_64 -Ipath/to/gnu-efi/inc/protocol'
LDFLAGS='-target x86_64-unknown-windows 
        -nostdlib 
        -Wl,-entry:efi_main 
        -Wl,-subsystem:efi_application 
        -fuse-ld=lld-link'
clang $CFLAGS -c -o hello.o hello.c
clang $CFLAGS -c -o data.o path/to/gnu-efi/lib/data.c
clang $LDFLAGS -o BOOTX64.EFI hello.o data.o

Passing '--target x86_64-unknown-windows' to clang tells it to compile for x86_64 "Windows". This is quite not the same as 64-bit UEFI PE yet, but as before the "freestanding" part makes it a good kernel image. An example of this toolchain is found in the c-efi project.

Note the '-mno-red-zone' part used here as well -- it is a bad idea to use a red zone for kernel code if interrupts are to be implemented. It should be done with GCC as well, but read Libgcc without red zone for the extra work needed to be done.

Creating the FAT image

Main article: Bootable Disk

Next, create a FAT filesystem image.

dd if=/dev/zero of=fat.img bs=1k count=1440
mformat -i fat.img -f 1440 ::
mmd -i fat.img ::/EFI
mmd -i fat.img ::/EFI/BOOT
mcopy -i fat.img BOOTX64.EFI ::/EFI/BOOT

Running as a USB stick image

The FAT image can either be written directly to a USB stick and used in in a UEFI machine, or it can be run directly in QEMU:

qemu-system-x86_64 -L OVMF_dir/ -pflash OVMF.fd -usb -usbdevice disk::fat.img

Creating and running the HD image

The HD image is a disk image in the GPT format, with the FAT image specially identified as a 'EFI System Partition'.

mkgpt -o hdimage.bin --image-size 4096 --part fat.img --type system
qemu-system-x86_64 -L OVMF_dir/ -pflash OVMF.fd -hda hdimage.bin

Creating and running the CD image

The ISO image is a standard ISO9660 image which contains the FAT image as a file. A special El Torito option (-e) then points EFI aware systems to this image to be loaded. The CD image can either be burned to a CD and ran in a UEFI machine, or run directly in QEMU:

mkdir iso
cp fat.img iso
xorriso -as mkisofs -R -f -e fat.img -no-emul-boot -o cdimage.iso iso
qemu-system-x86_64 -L OVMF_dir/ -pflash OVMF.fd -cdrom cdimage.iso

What to do next?

Developers may want to try using some more of the EFI boot services, e.g., to read more files from the FAT image, manage memory, set up graphical frame buffer etc. (see the UEFI Specifications page for further documentation of this).

There is also a finished app bare bone which supports both Linux and Windows (Visual Studio), see uefi-simple.

Common problems

Some UEFI hardware implementations require that the FAT image is in the FAT32 format (rather than FAT12 or FAT16). OVMF does not have this limitation, so developers will not see such a problem in QEMU. However, the minimum size of a FAT32 filesystem is around 32 MiB, so developers will need to generate a much larger image and pass the '-F' option to mformat.

See also