UNDER CONSTRUCTION!!
UNDER CONSTRUCTION!!
UNDER CONSTRUCTION!!
UNDER CONSTRUCTION!!
(Updated April 1,2023)
Overview
Compared to the BIOS version of Write Your Own OS, this tutorial is a bit more involved. So, we will start with installing anything we need right away.
sudo apt update && sudo apt install build-essential qemu-system-x86 mtools git
This includes the OVMF (Open Virtual Machine Firmware), which supports UEFI for virtual machines through QEMU (Quick EMUlator). QEMU will help to facilitate running a virtual machine without all of the complex VM setups in VMware or VirtualBox. This way, it is all self-contained within the same environment to try and keep us focused on what we are building rather than jumping around with an assortment of tools. (We can always make that happen later!)
QEMU
The Quick Emulator (QEMU) is a form of virtualization and emulation. We can launch a virtual machine with QEMU running on native hardware, like Intel. We can also launch a VM intended to run on an ARM processor but still have the QEMU program running on Intel, emulating the ARM CPU.
This product provides a simple environment for testing our code base before moving to a live environment.
OVMF
The OVMF package is installed as a dependency to QEMU for x86. We do not need to go and build anything for UEFI support – we already have it. This is a rather important component since UEFI is generally available in hardware. Since we are doing things in a virtualized world (QEMU), we need software to emulate those features.
Testing The Waters
The following will confirm that QEMU can launch a 64-bit Intel VM with OVMF providing UEFI support. The complexity of the command has to do with protecting the CODE (read-only) while allowing the VARS to be read-write. This is the recommended way with QEMU >=1.6.
qemu-system-x86_64 -cpu qemu64 \ -drive if=pflash,format=raw,unit=0,file=/usr/share/OVMF/OVMF_CODE.fd,readonly=on \ -drive if=pflash,format=raw,unit=1,file=/usr/share/OVMF/OVMF_VARS.fd \ -net none
If all is successful, you would be sitting at a UEFI shell something like the following:
EFI API Library
The EFI API is extensive – and complicated. Let’s begin by pointing out that this environment is nothing like BIOS/MBR. Then let’s further say there are three ways to do EFI programming. One is POSIX-UEFI, another is GNU-EFI, and finally, EDK2. The first two are much easier to write code for.
POSIX-UEFI is a highly lightweight environment that follows a known standard which includes a C runtime library – making it very easy to write code for UEFI. This is the simplest to work with.
GNU-EFI is also a lightweight environment but requires more coding as it also contains a wrapper to assist with different ABI (application binary interface) models. This is relatively easy to work with but requires some setup and study to understand what you are building.
EDK2 is a heavyweight environment with a complete build system. The complexity is such that we will end the discussion here and not provide any examples for this environment.
Setting Up the Build Environment
The build environment is where you will create your code for the EFI boot, along with the supporting libraries needed to make it all happen. We will show how to do basic builds in POSIX and GNU.
POSIX-UEFI
The POSIX environment is pretty easy to get started with, and it is introduced first. We start with the one-time setup of the POSIX library. This assumes you will store your build environment in a directory called posix-uefi
. Further, your current project is a directory called os
.
mkdir posix-uefi cd posix-uefi git clone https://gitlab.com/bztsrc/posix-uefi.git mkdir os cd os ln -s ../posix-uefi/uefi
You should be in the os
directory for building the EFI application. Create a main.c
file.
#include <uefi.h>
/**
* List directory contents
*/
int main(int argc, char **argv)
{
(void)argc;
(void)argv;
DIR *dh;
struct dirent *de;
if((dh = opendir("\\04_dirent"))) {
while ((de = readdir(dh)) != NULL) {
printf("%c %04x %s\n", de->d_type == DT_DIR ? 'd' : '.', de->d_type, de->d_name);
}
closedir(dh);
} else
fprintf(stderr, "Unable to open directory\n");
return 0;
}
Now create a Makefile
, which is a set of rules on how to compile and link main.c
.
TARGET = dirent.efi
#USE_GCC=1
include uefi/Makefile
make
Then you can use the make
command to build it.
root@U22-UEFI-Dev:~/posix-uefi/os# make
gcc -fshort-wchar -fno-strict-aliasing -ffreestanding -fno-stack-protector -fno-stack-check -I. -I./uefi -I/usr/include -I/usr/include/efi -I/usr/include/efi/protocol -I/usr/include/efi/x86_64 -D__x86_64__ -DHAVE_USE_MS_ABI -mno-red-zone -maccumulate-outgoing-args -Wno-builtin-declaration-mismatch -fpic -fPIC -c main.c -o main.o
ld -nostdlib -shared -Bsymbolic -Luefi uefi/crt_x86_64.o main.o -o main.efi.so -luefi -T uefi/elf_x86_64_efi.lds
objcopy -j .text -j .sdata -j .data -j .dynamic -j .dynsym -j .rel -j .rela -j .rel.* -j .rela.* -j .reloc --target efi-app-x86_64 --subsystem=10 main.efi.so main.efi || echo target: efi-app-x86_64
root@U22-UEFI-Dev:~/posix-uefi/os#
GNU-EFI
git clone https://git.code.sf.net/p/gnu-efi/code gnu-efi
cd gnu-efi
make
Create Bootable Disk
These next steps can be turned into a bash script.
#!/bin/bash
# create an empty image file of 48MiB
dd if=/dev/zero of=uefi.img bs=512 count=93750
# create a bootable GPT FAT32 partition that starts at 2048
parted uefi.img -s -a minimal mklabel gpt
parted uefi.img -s -a minimal mkpart EFI FAT32 2048s 93716s
parted uefi.img -s -a minimal toggle 1 boot
# create partition image that fits inside the allocated partition.
dd if=/dev/zero of=/tmp/part.img bs=512 count=91669
# format the partition to 64MB (32 heads, 32 cylinders, 64 sectors, cluster size 1)
mformat -i /tmp/part.img -h 32 -t 32 -n 64 -c 1
# make dirs and copy the boot file to the partition image
mmd -i /tmp/part.img ::/EFI
mmd -i /tmp/part.img ::/EFI/BOOT
mcopy -i /tmp/part.img main.efi ::/EFI/BOOT/bootx64.efi
# overlay the partition image onto the parition we created earlier
dd if=/tmp/part.img of=uefi.img bs=512 count=91669 seek=2048 conv=notrunc
Now that the disk has been created, we can boot the environment and see how things go!
qemu-system-x86_64 -cpu qemu64 -bios /usr/share/ovmf/OVMF.fd -drive file=uefi.img,if=ide,format=raw
Some Examples
References and Further Reading
https://wiki.osdev.org/Getting_Started
https://wiki.osdev.org/UEFI
https://uefi.org/specifications
https://wiki.osdev.org/Bootable_Disk
https://gitlab.com/bztsrc/posix-uefi
https://wiki.osdev.org/POSIX-UEFI
https://wiki.osdev.org/GNU-EFI
https://wiki.osdev.org/EDK2
https://github.com/tianocore/tianocore.github.io/wiki/OVMF
https://pubs.opengroup.org/onlinepubs/9699919799/ (POSIX)
https://www.qemu.org/ (QEMU emulation software)