972
edits
[unchecked revision] | [unchecked revision] |
m (Fix typing errors of OVMF (as OVFM)) |
m (Fix lint errors) |
||
(10 intermediate revisions by 5 users not shown) | |||
Line 17:
'''Debian / Ubuntu'''
<
# apt-get install ovmf
</syntaxhighlight>
'''RedHat / CentOS'''
<
# yum install ovmf
</syntaxhighlight>
'''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
====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 123:
==Developing with 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 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.
<
#include <uefi.h>
Line 137:
return 0;
}
</syntaxhighlight>
Makefile looks like this:
<
TARGET = main.efi
include uefi/Makefile
</syntaxhighlight>
Run make to build it. The result of this process is a PE executable file ''main.efi''.
==Developing with 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 161:
The traditional "Hello, world" UEFI program is shown below.
<
#include <efi.h>
#include <efilib.h>
Line 173:
return EFI_SUCCESS;
}
</syntaxhighlight>
A few notes:
Line 181:
This program is compiled and linked as below.
<
$ gcc main.c \
-c \
Line 216:
main.so \
main.efi
</syntaxhighlight>
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 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.
<
$ 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>
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 241:
===Creating disk images===
:
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
<
$ dd if=/dev/zero of=/path/to/uefi.img bs=512 count=93750
</syntaxhighlight>
====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.
<
$ uefi-run -b /path/to/OVMF.fd -q /path/to/qemu app.efi -- <extra_qemu_args>
</syntaxhighlight>
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 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]].
<
$ gdisk /path/to/uefi.img
GPT fdisk (gdisk) version 0.8.10
Line 293:
The new table will be used at the next reboot.
The operation has completed successfully.
</syntaxhighlight>
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.
<
losetup --offset 1048576 --sizelimit 46934528 /dev/loop0 /path/to/uefi.img
</syntaxhighlight>
(If /dev/loop0 is already in use you will need to select a different loopback device.)
Format the partition for FAT32 with mkdosfs.
<
mkdosfs -F 32 /dev/loop0
</syntaxhighlight>
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.
<
mount /dev/loop0 /mnt
</syntaxhighlight>
Copy any UEFI applications you want to test to the file system.
<
$ cp /path/to/main.efi /mnt/
$ ...
</syntaxhighlight>
Finally, unmount the partition and free the loopback device.
<
$ umount /mnt
$ losetup -d /dev/loop0
</syntaxhighlight>
''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 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.
<
$ 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>
Now create a new temporary image file that will contain the EFI partition data and use mformat to format it with FAT16.
<
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>
Use mcopy to copy any UEFI applications you want to test to the file system.
<
$ mcopy -i /tmp/part.img /path/to/main.efi ::
$ ...
</syntaxhighlight>
Finally, write the partition image into the main disk image.
<
$ dd if=/tmp/part.img of=/path/to/uefi.img bs=512 count=91669 seek=2048 conv=notrunc
</syntaxhighlight>
''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 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.
<
$ mdconfig -f /path/to/uefi.img
md0
</syntaxhighlight>
In this example the new block device is ''md0''. Now create the empty primary and secondary GPT tables on the device.
<
$ gpart create -s GPT md0
md0 created
</syntaxhighlight>
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.
<
$ gpart add -t efi md0
md0p1 added
</syntaxhighlight>
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.
<
$ 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>
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.
<
$ mount_msdosfs /dev/md0p1 /mnt
</syntaxhighlight>
Copy any UEFI applications you want to test to the file system.
<
$ cp /path/to/main.efi /mnt/
$ ...
</syntaxhighlight>
Finally, unmount the partition and free the block device.
<
$ umount /mnt
$ mdconfig -d -u md0
</syntaxhighlight>
''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 407:
First, let's create a temporary folder that will contains all files and folders required for booting UEFI.
<
$ mkdir -p diskImage/EFI/BOOT
</syntaxhighlight>
Secondly, let's copy the boot application to the required location:
<
$ cp bootx64.efi diskImage/EFI/BOOT/BOOTX64.EFI
</syntaxhighlight>
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:
<
$ hdiutil create -fs fat32 -ov -size 48m -volname NEWOS -format UDTO -srcfolder diskImage uefi.cdr
</syntaxhighlight>
uefi.cdr should be ready to be used by QEMU.
Line 427:
===Launching UEFI applications===
Once your disk image is ready, you can invoke QEMU as below.
<
$ qemu-system-x86_64 -cpu qemu64 -bios /path/to/OVMF.fd -drive file=uefi.disk,if=ide
</syntaxhighlight>
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 475:
* Phoenix (SecureCore, TrustedCore, AwardCore).
* Insyde (InsydeH20).
== UEFI applications in detail ==
Line 530 ⟶ 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
==== Memory ====
Line 543 ⟶ 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<
Its output is quite long. Among other things, it shows the '''subsystem''', that is the type of the UEFI image, mentioned earlier.
Line 564 ⟶ 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).
<
EFI_STATUS EFIAPI efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
{
Line 573 ⟶ 570:
return status;
}
</syntaxhighlight>
=== 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.
<
format pe64 dll efi
entry main
Line 601 ⟶ 598:
section '.reloc' fixups data discardable
</syntaxhighlight>
As a UEFI application contains normal x86 or x86-64 machine code, inline assembly is also an option in compilers that support it.
Line 612 ⟶ 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 <
=== My bootloader hangs if I use user defined EFI_MEMORY_TYPE values ===
|