UEFI: Difference between revisions

504 bytes removed ,  28 days ago
m
Fix lint errors
[unchecked revision][unchecked revision]
m (added EDK2 link)
m (Fix lint errors)
 
(13 intermediate revisions by 6 users not shown)
Line 17:
 
'''Debian / Ubuntu'''
<sourcesyntaxhighlight lang="bash">
# apt-get install ovmf
</syntaxhighlight>
</source>
 
'''RedHat / CentOS'''
<sourcesyntaxhighlight lang="bash">
# yum install ovmf
</syntaxhighlight>
</source>
 
'''MacOS'''
Line 45:
 
====Boot mechanism====
A legacy BIOS loads a 512 byte flat binary blob from the MBR of the boot device into memory at physical address 7C00 and jumps to it. The bootloader cannot return back to BIOS. UEFI firmware loads an arbitrary sized UEFI application (a relocatable PE executable file) from a FAT partition on a GPT- or MBR partitioned boot device to some address selected at run-time. Then it calls that application's main entry point. The application can return control to the firmware, which will continue searching for another boot device or bring up a diagnostic menu.
 
====System discovery====
Line 62:
[[EDK2]] is a large and complex, yet feature filled environment with its own build system. It can be configured to use GCC, LLVM, MinGW, Microsoft Visual C++, etc. as a cross-compiler. Not only can it be used to compile UEFI applications, but it can also be used to compile UEFI firmware to be flashed to a BIOS ROM.
 
[[GNU-EFI]] is a set of libraries and headers for compiling UEFI applications with a system's native GCC (does not work with LLVM CLang). It can't be used to compile UEFI firmware. Since it's just a couple of libraries against which a UEFI application can be linked, it is much easier to use than TianoCore ( EDK2 ).
 
[[POSIX-UEFI]] is very similar to GNU-EFI, but it is distributed mainly as a source, not as a binary library, has ANSI C like names and works with GCC as well as LLVM CLang. It's shipped with a Makefile that sets up the compiler flags for you.
Line 114:
 
An easy way out to use a loader that is signed by Microsoft, and allows you to load another binary signed by a key and certificate owned by you (called MOK, Machine Owner's Key). Such a loader is [https://github.com/rhboot/shim shim], used by RedHat, Fedora, Suse, Ubuntu, Arch and many other distros to load GRUB. The filename of the EFI executable is hardwired in shim, but if you rename your loader to GRUBX64.EFI (or GRUBIA32.EFI), you sign it with your MOK key and certificate using [https://github.com/imedias/sbsigntool sbsigntool], then you can load any loader in Secure Boot you want.
 
Although some firmware has no options for turning Secure Boot off, there is a huge security flaw in Win8's bootmgr.efi which allows loading of policies that [https://www.xda-developers.com/microsofts-debug-mode-flaw-and-golden-key-leak-allows-disabling-of-secure-boot/ turns off Secure Boot on any computer]. This flaw cannot be patched, because anybody can simply replace a fixed Win10 bootmgr.efi with the original Win8 bootmgr.efi without problems (they are signed with the same Microsoft key).
 
===How to use UEFI===
Line 125 ⟶ 123:
 
==Developing with POSIX-UEFI==
:''{{Main article: [[|POSIX-UEFI]]}}
 
One option to compile UEFI applications on POSIX like systems is POSIX-UEFI. It provies a [[libc]]-like API for your EFI application, and ships with a Makefile that can detect and set up the toolchain for you. It can use GCC or LLVM, and defaults to using the host compiler, but a cross compiler is still recommended.
Line 131 ⟶ 129:
It uses POSIX style typedefs (like ''uintn_t'' instead of ''UINTN''), and it does not ship with the standard EFI headers. You can still get interfaces not covered by POSIX-UEFI (such as GOP) by installing the EFI headers from GNU-EFI or EDK2. Also, it compiles with the MS ABI, meaning that UEFI services can be called natively (i.e., without uefi_call_wrapper) so long as your apps are compiled with it as well.
The traditional "Hello, world" UEFI program goes like this.
<sourcesyntaxhighlight lang="c">
#include <uefi.h>
Line 139 ⟶ 137:
return 0;
}
</syntaxhighlight>
</source>
Makefile looks like this:
<sourcesyntaxhighlight lang="make">
TARGET = main.efi
include uefi/Makefile
</syntaxhighlight>
</source>
Run make to build it. The result of this process is a PE executable file ''main.efi''.
 
==Developing with GNU-EFI==
:''{{Main article: [[|GNU-EFI]]}}
 
GNU-EFI can be used to develop both 32-bit and 64-bit UEFI applications. This section will address 64-bit UEFI applications only, and assumes that the development environment itself is running on an x86_64 system, so that no cross-compiler is needed. For a more thorough walk-through of a proper (non-gnu-efi) development environment, see [[UEFI App Bare Bones]].
Line 163 ⟶ 161:
 
The traditional "Hello, world" UEFI program is shown below.
<sourcesyntaxhighlight lang="c">
#include <efi.h>
#include <efilib.h>
Line 175 ⟶ 173:
return EFI_SUCCESS;
}
</syntaxhighlight>
</source>
 
A few notes:
Line 183 ⟶ 181:
 
This program is compiled and linked as below.
<sourcesyntaxhighlight lang="bash">
$ gcc main.c \
-c \
Line 218 ⟶ 216:
main.so \
main.efi
</syntaxhighlight>
</source>
 
The result of this process is a 44 kB PE executable file ''main.efi''. On a real project you'll probably want to use make or another build tool, and may need to build a cross-compiler. The way GNU-EFI works is a bit contrived: you are wrapping an ELF file built by your normal compiler into PE.
Line 227 ⟶ 225:
The recommended way to use OVMF (for QEMU 1.6 or newer) is with a <code>pflash</code> parameter. The instructions below assume you have an OVMF image split into separate CODE and VARS sections.
 
<sourcesyntaxhighlight lang="bash">
$ qemu-system-x86_64 -cpu qemu64 \
-drive if=pflash,format=raw,unit=0,file=path_to_OVMF_CODE.fd,readonly=on \
-drive if=pflash,format=raw,unit=1,file=path_to_OVMF_VARS.fd \
-net none
</syntaxhighlight>
</source>
 
If you prefer to work on a terminal without a display, or via SSH/telnet, you will want to run QEMU without graphics support, using the <code>-nographic</code> flag.
Line 243 ⟶ 241:
 
===Creating disk images===
:''{{Main article: [[|Bootable Disk]]}}
 
To launch a UEFI application you will need to create a disk image and present it to QEMU. UEFI firmware expects UEFI applications to be stored in a FAT12, FAT16, or FAT32 file system (calledon a [[EFI System PartitionGPT]]) on aor [[GPTMBR]]-partitioned disk. Many firmwares only support FAT32, so that's what you'll want to use. Depending on your platform, there are several different ways to create a disk image containing your UEFI application, but they all start by creating a zeroed disk image file. The minimum FAT32 partition size is 33,548,800 bytes, plus you will need space for the primary and secondary GPT tables, plus some slack space so the partition can be aligned correctly. Throughout these examples we will be creating a 48,000,000 byte (93750 512-byte sectors, or 48 MB) disk image.
<sourcesyntaxhighlight lang="bash">
$ dd if=/dev/zero of=/path/to/uefi.img bs=512 count=93750
</syntaxhighlight>
</source>
 
====uefi-run helper application====
The uefi-run application is useful for quick testing. It creates a temporary FAT image containing your EFI application and starts qemu.
 
<sourcesyntaxhighlight lang="bash">
$ uefi-run -b /path/to/OVMF.fd -q /path/to/qemu app.efi -- <extra_qemu_args>
</syntaxhighlight>
</source>
 
uefi-run is not currently packaged for any distribution. You can install it using cargo (the Rust package manager) though ("cargo install uefi-run").
Line 261 ⟶ 259:
====Linux, root required====
This approach requires root privileges and uses '''gdisk''', '''losetup''', and '''mkdosfs'''. First, use gdisk to create a GPT partition table with a single [[EFI System Partition]].
<sourcesyntaxhighlight lang="bash">
$ gdisk /path/to/uefi.img
GPT fdisk (gdisk) version 0.8.10
Line 295 ⟶ 293:
The new table will be used at the next reboot.
The operation has completed successfully.
</syntaxhighlight>
</source>
 
Now you have disk image with [[GPT|GUID partition table]] on it and an unformatted EFI partition starting at sector 2048. Unless you deviated from the commands shown above, the disk image will use 512-byte sectors, so the EFI partition starts at byte 1,048,576 and is 46,934,528 bytes in length. Use losetup to present the partition to Linux on a loopback device.
 
<sourcesyntaxhighlight lang="bash">
losetup --offset 1048576 --sizelimit 46934528 /dev/loop0 /path/to/uefi.img
</syntaxhighlight>
</source>
 
(If /dev/loop0 is already in use you will need to select a different loopback device.)
 
Format the partition for FAT32 with mkdosfs.
<sourcesyntaxhighlight lang="bash">
mkdosfs -F 32 /dev/loop0
</syntaxhighlight>
</source>
 
The partition can now be mounted, so that we can copy files to it. In this example we use the "/mnt" directory, but you could also create a local directory for temporary use.
<sourcesyntaxhighlight lang="bash">
mount /dev/loop0 /mnt
</syntaxhighlight>
</source>
 
Copy any UEFI applications you want to test to the file system.
<sourcesyntaxhighlight lang="bash">
$ cp /path/to/main.efi /mnt/
$ ...
</syntaxhighlight>
</source>
 
Finally, unmount the partition and free the loopback device.
<sourcesyntaxhighlight lang="bash">
$ umount /mnt
$ losetup -d /dev/loop0
</syntaxhighlight>
</source>
 
''uefi.img'' is now a disk image containing primary and secondary GPT tables, containing a single partition of type EFI, containing a FAT32 file system, containing one or more UEFI applications.
Line 331 ⟶ 329:
====Linux, root not required====
This approach uses '''parted''', '''mformat''', and '''mcopy''' and can be performed with user privileges. First, use parted to create primary and secondary GPT headers, and a single EFI partition spanning the same range as the approach above.
<sourcesyntaxhighlight lang="bash">
$ parted /path/to/uefi.img -s -a minimal mklabel gpt
$ parted /path/to/uefi.img -s -a minimal mkpart EFI FAT16 2048s 93716s
$ parted /path/to/uefi.img -s -a minimal toggle 1 boot
</syntaxhighlight>
</source>
 
Now create a new temporary image file that will contain the EFI partition data and use mformat to format it with FAT16.
<sourcesyntaxhighlight lang="bash">
dd if=/dev/zero of=/tmp/part.img bs=512 count=91669
mformat -i /tmp/part.img -h 32 -t 32 -n 64 -c 1
</syntaxhighlight>
</source>
 
Use mcopy to copy any UEFI applications you want to test to the file system.
<sourcesyntaxhighlight lang="bash">
$ mcopy -i /tmp/part.img /path/to/main.efi ::
$ ...
</syntaxhighlight>
</source>
 
Finally, write the partition image into the main disk image.
<sourcesyntaxhighlight lang="bash">
$ dd if=/tmp/part.img of=/path/to/uefi.img bs=512 count=91669 seek=2048 conv=notrunc
</syntaxhighlight>
</source>
 
''uefi.img'' is now a disk image containing primary and secondary GPT tables, containing a single partition of type EFI, containing a FAT16 file system, containing one or more UEFI applications.
Line 358 ⟶ 356:
====FreeBSD, root required====
This approach requires root privileges and uses '''mdconfig''', '''gpart''', '''newfs_msdos''', and '''mount_msdosfs'''. First, create a device node that presents the zeroed disk image as a block device. This will let us work on it using standard partitioning and formatting tools.
<sourcesyntaxhighlight lang="bash">
$ mdconfig -f /path/to/uefi.img
md0
</syntaxhighlight>
</source>
 
In this example the new block device is ''md0''. Now create the empty primary and secondary GPT tables on the device.
<sourcesyntaxhighlight lang="bash">
$ gpart create -s GPT md0
md0 created
</syntaxhighlight>
</source>
 
Now we can add a partition to the disk. We'll specify an "EFI" partition, which just means that GPT will set that partition's GUID to the special "EFI" type. Not all BIOSs require this, and the partition will still be able to be mounted and browsed normally on Linux, FreeBSD, and Windows.
<sourcesyntaxhighlight lang="bash">
$ gpart add -t efi md0
md0p1 added
</syntaxhighlight>
</source>
 
Next, create a FAT16 file system on the new partition. You can specify various parameters for the file system if you'd like, but it isn't necessary. Ideally you would create a FAT32 partition for best firmware compatibility but FreeBSD seems to create FAT32 partitions that OVMF can't read.
<sourcesyntaxhighlight lang="bash">
$ newfs_msdos -F 16 md0p1
newfs_msdos: trim 2 sectors to adjust to a multiple of 9
/dev/md2p1: 93552 sectors in 11694 FAT16 clusters (4096 bytes/cluster)
BytesPerSec=512 SecPerClust=8 ResSectors=1 FATs=2 RootDirEnts=512 Media=0xf0 FATsecs=46 SecPerTrack=9 Heads=16 HiddenSecs=0 HugeSectors=93681
</syntaxhighlight>
</source>
 
The partition can now be mounted, so that we can copy files to it. In this example we use the ''/mnt'' directory, but you could also create a local directory for temporary use.
<sourcesyntaxhighlight lang="bash">
$ mount_msdosfs /dev/md0p1 /mnt
</syntaxhighlight>
</source>
 
Copy any UEFI applications you want to test to the file system.
<sourcesyntaxhighlight lang="bash">
$ cp /path/to/main.efi /mnt/
$ ...
</syntaxhighlight>
</source>
 
Finally, unmount the partition and free the block device.
<sourcesyntaxhighlight lang="bash">
$ umount /mnt
$ mdconfig -d -u md0
</syntaxhighlight>
</source>
 
''uefi.img'' is now a disk image containing primary and secondary GPT tables, containing a single partition of type EFI, containing a FAT16 file system, containing one or more UEFI applications.
Line 409 ⟶ 407:
First, let's create a temporary folder that will contains all files and folders required for booting UEFI.
<sourcesyntaxhighlight lang="bash">
$ mkdir -p diskImage/EFI/BOOT
</syntaxhighlight>
</source>
 
Secondly, let's copy the boot application to the required location:
 
<sourcesyntaxhighlight lang="bash">
$ cp bootx64.efi diskImage/EFI/BOOT/BOOTX64.EFI
</syntaxhighlight>
</source>
 
Finally, let's create a disk image partitioned with GPT, formatted with fat32 (-fs fat32), overriding destination file if needed (-ov), define disk size (-size 48m), define volume name (-volname NEWOS), the file format which the disk will be encoded (-format UDTO - the same used for DVDs/CDs) and the source folder containing the files that will be copied to the new disk:
 
<sourcesyntaxhighlight lang="bash">
$ hdiutil create -fs fat32 -ov -size 48m -volname NEWOS -format UDTO -srcfolder diskImage uefi.cdr
</syntaxhighlight>
</source>
 
uefi.cdr should be ready to be used by QEMU.
Line 429 ⟶ 427:
===Launching UEFI applications===
Once your disk image is ready, you can invoke QEMU as below.
<sourcesyntaxhighlight lang="bash">
$ qemu-system-x86_64 -cpu qemu64 -bios /path/to/OVMF.fd -drive file=uefi.disk,if=ide
</syntaxhighlight>
</source>
 
When OVMF drops into the UEFI shell, you will see an additional entry in the "Mapping table", labeled "FS0". This indicates that the firmware detected the disk, discovered the partition, and was able to mount the file system. You can explore the file system by switching to it using the DOS-style syntax "FS0:", as illustrated below.
Line 477 ⟶ 475:
* Phoenix (SecureCore, TrustedCore, AwardCore).
* Insyde (InsydeH20).
 
===Apple systems===
Apple systems implement EFI 1.0, as opposed to UEFI, with the distinction that UEFI applications are loaded from HFS+ file systems instead of FAT12/16/32. Additionally, those UEFI applications must be "blessed" (either directly, or by residing in a blessed directory) to be loaded. Blessing sets flags within the HFS+ file system that Apple's firmware checks before loading an application. The open-source '''hfsutils''' package includes support for blessing files within HFS file systems, but not directories nor HFS+.
 
== UEFI applications in detail ==
Line 532 ⟶ 527:
Applications must either load an OS and exit boot services, or return from the main function (in which case the boot loader will look for the next app to load).
 
Drivers must initialize and then return 0 on success, or an error code. A computer might fail tooto boot if a required driver fails to load.
 
==== Memory ====
Line 545 ⟶ 540:
However, memory used by the runtime drivers must '''never''' be touched - the runtime drivers stay active and loaded for as long as the computer runs.
 
One way to see a breakdown of a PE file containing a UEFI application is by<sourcesyntaxhighlight lang="bash">$ objdump --all-headers /path/to/main.efi</sourcesyntaxhighlight>
Its output is quite long. Among other things, it shows the '''subsystem''', that is the type of the UEFI image, mentioned earlier.
 
Line 566 ⟶ 561:
 
For developer convenience, both POSIX-UEFI and GNU-EFI provides the "EFIAPI" macro, which expands to "cdecl" when targeting x86 and "__attribute__(ms_abi))" when targeting x86-64. Additionally, the "uefi_call_wrapper" thunk will simply pass the call through on x86. This allows the same source code to target x86 and x86-64. For example, the following main function will compile with the correct calling convention on both x86 and x86-64 and the call through the "uefi_call_wrapper" thunk will select the correct calling convention to use when calling the UEFI function (in this case, printing a string).
<sourcesyntaxhighlight lang="c">
EFI_STATUS EFIAPI efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
{
Line 575 ⟶ 570:
return status;
}
</syntaxhighlight>
</source>
 
=== Language binding ===
UEFI applications are typically written in C, although bindings could be written for any other language that compiles to machine code. Assembly is also an option; a [[uefi.inc]] is available for [[FASM]] that allows UEFI applications to be written as below.
<sourcesyntaxhighlight lang="asm">
format pe64 dll efi
entry main
Line 603 ⟶ 598:
 
section '.reloc' fixups data discardable
</syntaxhighlight>
</source>
 
As a UEFI application contains normal x86 or x86-64 machine code, inline assembly is also an option in compilers that support it.
Line 614 ⟶ 609:
=== My UEFI application hangs/resets after about 5 minutes ===
 
When control is handed to your UEFI application by firmware, it sets a watchdog timer for 5 minutes, after which the firmware is reinvoked as it assumes your application has hung. The firmware in this case will normally try to reset the system (although the OVMF firmware in VirtualBox simply causes the screen to go black and hang). To counteract this, you are required to refresh the watchdog timer before it times out. Alternatively, you can disable it completely with code like <sourcesyntaxhighlight lang="C">SystemTable->BootServices->SetWatchdogTimer(0, 0, 0, NULL);</sourcesyntaxhighlight>Obviously this is not a problem for most bootloaders, but can cause an issue if you have an interactive loader which waits for user input. Also note that you are required to disable the watchdog timer if you exit back to the firmware.
 
=== My bootloader hangs if I use user defined EFI_MEMORY_TYPE values ===
 
For the memory management functions in EFI, an OS is meant to be able to use "memory type" values above 0x80000000 for its own purposes. In the OVFMOVMF EFI firmware release "r11337" (for Qemu, etc) there is a bug where the firmware assumes the memory type is within the range of values defined for EFI's own use, and uses the memory type as an array index. The end result is an "array index out of bounds" bug; where the higher memory type values (e.g. perfectly legal values above 0x80000000) cause the 64-bit version of the firmware to crash (page fault), and cause incorrect "attribute" values to be reported by the 32-bit version of the firmware. This same bug is also present in whatever version of the EFI firmware VirtualBox uses (which looks like an older version of OVFMOVMF); and I suspect (but don't know) that the bug may be present in a wide variety of firmware that was derived from the tianocore project (not just OVFMOVMF).
 
== See also ==
Line 625 ⟶ 620:
* [[EFI System Partition]]
* [[PE]] file format
* [[TianoCore]]
* [[EDK2]]
* [[POSIX-UEFI]]
Line 646 ⟶ 640:
[[Category:x86]]
[[Category:x86-64]]
[[Category:IA-64]]
[[Category:ARM]]
[[Category:Firmware]]