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/
Useful links
TianoCore Project, a bios image providing EFI on QEMU
EFI Boot Process Description, explains differences in Apple's implementation
Description of Apple EFI implementation, seems to be out of date
Linux EFI support for MacBooks, explains how linux got it working on macs
HFS+ disk format description, necessary for booting on Apple EFI implementations