EFI Boot Support on Intel Platforms

Project description

The aim of this project is to complete the implementation of EFI boot support on the amd64 and i386 platforms for both UEFI compliant as well as Apple machines.

Approach to solving the problem

An EFI boot service will provide the functionality of loader(8). Since EFI provides substantially more functionality, the boot0-2 stages of booting will not be necessary.

Modifications to the kernel may be necessary in order to allow it occupy non-contiguous sections of memory. The EFI interface provides functions for allocating memory, however, if the EFI loader allocates a block for each loadable ELF segment, there is no guarantee that the kernel will be contiguous in memory. It may be possible to work around this by allocating one block for the entire kernel

Some drivers may also need modification, if they rely on BIOS code that may not be present when booting from EFI.

On UEFI-compliant machines, there is a system partition, which contains a FAT filesystem, where the EFI loader resides.

On Apple machines, the system partition is used to install firmware updates, and is usually blank. The Apple EFI boot manager searches instead for an HFS+ partition, and loads /System/Library/CoreServices/boot.efi. Therefore, on an apple machine, there will need to be an HFS+ partition to hold the loader, as well as some way of installing it to the partition. The easiest solution is to create a tool which instantiates a filesystem containing the loader.

General approach is as follows:

Minimize modifications to the kernel, libstand, loader, etc. Use as much of what already exists as possible Do things in such a way that enables future development to take advantage of other EFI features. Document everything.

Current Status

Issues with getting loader.efi running were far worse than I anticipated. Fortunately, I have successfully gotten it to run on the TianoCore image.

Item Status Notes Kernel modifications to support non-contiguous kernel IN PROGRESS Initial investigations seem promising Build i386 loader.efi COMPLETE At present, only works with complete make buildworld Run i386 loader.efi on QEMU/TianoCore COMPLETE Boot i386 EFI kernel on QEMU/TianoCore IN PROGRESS Build amd64 loader.efi INCOMPLETE Linking fails, due to additional symbols needed by elf64_freebsd.c Run amd64 loader.efi on QEMU/TianoCore INCOMPLETE Boot amd64 EFI kernel on QEMU/TianoCore INCOMPLETE Implement tools to allow boot on Apple machines IN PROGRESS Run amd64 loader.efi on an Apple machine INCOMPLETE Boot amd64 EFI kernel on an Apple machine INCOMPLETE

Issues

I ran into a number of very low-level issues trying to get loader.efi to run. As I am developing a boot loader, finding root causes can be quite difficult, due to the very limited information available. This is a list of the more serious issues:

There was a bug in stdint.h which caused int64_t and uint64_t to be defined as 32-bit integers. This causes problems for EFI, because the API relies on the layout of structures that are compiled with a different compiler. Future EFI development should include this issue as a possible cause of errors.

For some reason, clang generates bad jump offsets in the resulting PE executable.

The EFI loader includes a simple relocation function, _reloc, which is run before starting the program. Prior to running this function, the program cannot access any global variables or constants. This caused problems with printf-style debugging of _reloc itself.

Also, _reloc only handles certain kinds of relocations, which are the only kind that occur in a program compiled with -Bsymbolic and no undefined symbols. Undefined symbols will actually get through the linking and conversion to PE silently, and will cause _reloc to fail at runtime.

Knowledge Base

This section has some specific points about how the EFI loader works, and is compiled.

An EFI environment loads a PE executable and executes at its starting address. It does not perform relocations.

PE code is supposed to be position-independent. Also, the ABI is slightly different.

The EFI loader, loader.efi is created by compiling C source to object files and static libraries, then creating an executable using a custom linker script that starts everything at address 0x1000 and creates relocations for any absolute addresses. The first 0x1000 bytes of the actual file contain all the PE metadata, the rest in the program. The idea is that we can relocate simply by adding the load address to all of the offsets. This is, in essence, a trick to get around the PIC requirement

The linker script appears to attach some kind of metadata onto the end of each function. I do not know what this does at this time.

The entry point is an assembly function, which calls _reloc, then calls efi_main if that succeeds.

The _reloc function performs a limited form of relocation. It appears to use ELF data structures, but these are fully compatible with the PE structures. Note: I don't know at this time if PE relocation entries are designed that way, or if we are actually creating relocation segments containing ELF relocation entries (which would technically be a hack). If the second is true, this could cause problems if any (non-standard) platform attempts to perform relocations when loading (Apple, maybe?)

The efi_main function initializes a heap and parses command line arguments, sets up the libstand API using EFI functions, then hands off control to main().

EDK II vs Current

Currently, we use a combination of linker scripts and objcopy to produce an EFI application. I was able to get the EDK II (EFI Development Kit) tools and the Microsoft IASL compiler to build on FreeBSD with some work. There is a case for using this to build the loader instead. There are advantages and disadvantages to this approach:

Advantages: EDK is Intel's official reference implementation. It's what people developing EFI firmware, loaders, drivers, etc. use to build and test their tools. Additionally, the source is available in subversion, so we could easily track and incorporate new versions. Also, we would avoid running into obscure ABI issues.

Disadvantages: EDK is designed for development on Windows, and follows certain Windows-specific conventions. It is marginally supported on some linux distros. There would probably be a considerable effort involved in incorporating it into the base system, particularly if we want to follow FreeBSD's conventions (at the minimum, the gmake-specific makefiles need rewriting). Also, this would require building the mingw32 gcc (and whatever would need to be built for clang) as part of world. It would also require a separate build of anything needed by loader.efi with the mingw32 compiler.

Deliverables

Be able to boot a kernel from EFI on a UEFI-compliant i386 machine

Be able to boot a kernel from EFI on a UEFI-compliant amd64 machine

Be able to boot a kernel from EFI on an Apple machine

Allow users the option of booting a given kernel from BIOS or EFI (meaning, if a host machine allows a choice of BIOS or EFI booting, the same kernel should load and boot properly regardless)

Allow loading kernels from both UFS and ZFS

Milestones

May 21: Start of coding

Late May: Evaluate state of existing code, be able to build for both i386 again as well as amd64

Early June: Investigate difficulty of allowing the kernel to be non-contiguous in memory

Mid June: Make any necessary kernel modifications

Late June: Be able to load a kernel and execute at least some portion of the startup procedures

Early-Mid July: Work towards complete boot

July 9-13: Mid-term Evaluations

Late July: Test on real hardware, write necessary tools for booting on Apple hardware

Early August: Identify and address device driver issues.

August 13: End of coding (soft)

August 20: End of coding (hard)

Test Plan

Primary testing will take place on qemu, using i386 and amd64 instances using the TianoCore EFI bios images. Once booting works and is stable, I will search for volunteers with UEFI-compliant machines to test booting on actual hardware. I will test booting on Apple using my own machines.

The Code

https://socsvn.freebsd.org/socsvn/soc2012/emc2/