A W.I.P. template project for UEFI OS development.
<project_name> // Project root directory
├── files // Files to be copied to the root of the OS's image (there's a README inside)
├── gnu-efi // Development package for creating UEFI applications (do not touch)
├── LICENSE // Project's license (you're free to do whatever with it)
├── Makefile // Project's main Makefile (use only this one)
├── ovmf-bins // UEFI BIOS images (needed for QEMU)
├── README.md // Project's README (modify it to your heart's content)
├── src // Your OS's source code folder
│ ├── bootloader // - Bootloader source code and Makefile
│ ├── kernel // - Kernel source code and Makefile
│ └── libc // - C library source code
├── tools // Scripts to set up the development environment (see below for more info)
└── Vagrantfile // File needed by Vagrant to set up the VM (you can delete this if you don't plan on using it)
If you're using GitHub you can just create a new repository using this one as a template by clicking on the green button that says 'Use this template', located in the top right of the repository page.
Otherwise, open your git client and clone this repository. If you're using a terminal you can just type:
git clone --recurse-submodules https://github.com/SkrapeProjects/uefi-os-template.git <project_name>
This will clone the repository in a directory named <project_name>
located in the same directory you ran the command.
This template provides a Vagrant Ubuntu 20.04 LTS VM to use, in order to make the build environment consistent across all computers. The VM will automatically set up X11 forwarding to the host (check note under this paragraph), so if you have an XServer running on your host you should be able to run GUI applications (such as QEMU) from the virtual machine.
If you're a Windows user you'll need to install an XServer (such as VcXsrv and GWSL) and disable the following features:
- Virtual Machine Platform
- Windows Hypervisor Platform
- Hyper-V (if available)
Note: As of right now X11 forwarding works only on Windows and Linux hosts.
Before you get started make sure you have virtualization enabled.
Head over to the Vagrant website and download the appropriate version for your system.
Note: all the following commands are to be run in your terminal emulator or Windows console inside the project root directory or one of its subdirectories.
To start the VM type vagrant up
.
The initial startup will take a while, since it will upgrade the system and download all the required dependencies for building the project.
Once the VM is up and running type vagrant ssh
to login.
You can also SSH manually into the machine; the address is 127.0.0.1:2222
or localhost:2222
. The default username and password are both vagrant
.
Your project directory will be mounted at /workspace
and you should be dropped inside this directory automatically at login.
Note: if you're using Linux as your host and want to use X11 forwarding, you'll need to run the following command to ssh into the VM:
vagrant ssh -- -X
Once you're done working inside the VM you can type logout
or exit
to return to your host's terminal/console.
Finally, type vagrant halt
to shut down the VM.
Tip: If you just need to restart your VM you can use vagrant reload
.
In the unlikely event your VM breaks type vagrant destroy
to reset the VM.
Note: the next time you type vagrant up
it will do all the initial set up once again, so don't worry if it takes a while.
I provided scripts, stored in the tools
directory, to automate some tasks. They're made for Ubuntu (they also work under Ubuntu WSL).
Here's a list of all the scripts, brief explanation of what they do and the order they should be run in:
download_deps.sh
Downloads the required dependencies for building the project. Must be run as root. Not needed if using Vagrant.setup_crosscompiler.sh
Downloads and compiles BinUtils and GCC forx86_64-elf
cross compilation and installs them intools/x86_64-elf-cross
. May take a while to execute.get_latest_ovmf_bins.sh
Downloads and extracts (in theovmf-bins
folder) the latest precompiled OVMF firmware from https://www.kraxel.org/repos.
Note: all the following commands are to be run in the root directory of the project.
To build the project for the first time run make all
, to build gnu-efi
and the image.
After that a normal make
will build only the bootloader and the kernel and update the image with the new files.
The Makefile offers 2 clean options:
make clean
Cleans all built objects, libraries, ELFs and EFI files in the build directory.make clean-all
Does what clean does but also cleans thegnu-efi
project and completely removes the build directory. After running this command it's necessary to re-runmake all
. To run and debug the project you can usemake run
andmake debug
respectively.
From the root directory of your project, the following make
targets are available:
make all
Builds everything. Required aftermake clean-all
and when building the project for the first time.make
Defaults tomake partial
. Only builds the bootloader and the kernel and updates the already existing image.make clean
Removes all built targets that end with *.o, *.elf, *.so, *.efi and *.efi.debugmake clean-all
Cleangnu-efi
project and completely deletes the build directory.make run
Runs OS in QEMUmake debug
Runs OS in QEMU with GDB attached
When running make all
in the root directory:
- Create an empty image.
- Create
startup.nsh
script. - Build
gnu-efi
submodule. - Build the bootloader in
BUILD_DIR/bootloader
. - Build the kernel and C library in
BUILD_DIR/kernel
andBUILD_DIR/libc
respectively. - Format the image as FAT32.
- Copy the bootloader's EFI executable in the image's /EFI/BOOT folder.
- Copy the
startup.nsh
script in the image's root. - Copy the kernel's elf file in the image's root.
- Copy all files and folders recursively from
files/
to the OS image's root.
Note: the startup.nsh
script contains the search path for the EFI file. The script looks through all the drives detected up to FS7
and checks for the EFI executable in FSX:\EFI\BOOT\
. If the executable is found the script will execute it, otherwise the script will just quit.
Note: all modifications are to be made in the main Makefile (see overview).
Available parameters:
OS_NAME
: name of the imageBUILD_DIR
: path of the build directorySOURCE_DIR
: path of the source directory containing the kernel, bootloader and libc source directoriesDATA_DIR
: path of the directory containing the files that are to be copied inside the OS imageOVMF_BINARIES_DIR
: path of the directory containing the UEFI BIOS imagesGNU_EFI_DIR
: path of the directory to the GNU-EFI development package.EFI_TARGET
: has to be the name of your bootloader's main source file (default isloader.efi
so the bootloader's main file isloader.c
)ELF_TARGET
: name of your compiled kernel ELF fileEMU
: emulator's executableDBG
: debugger's executableCC
: C compiler's executable (does not apply to bootloader)AC
: assembly compiler's executable (does not apply to bootloader)LD
: linker's executable (does not apply to bootloader)LDS
: path to the linker script to use (does not apply to bootloader)EMU_BASE_FLAGS
: emulator flags to be applied when testing the OSEMU_DBG_FLAGS
: emulator flags to be applied when debugging the OSDBG_FLAGS
: debugger's flagsCFLAGS
: C compiler's flags (does not apply to bootloader)ACFLAGS
: assembly compiler's flags (does not apply to bootloader)LDFLAGS
: linker's flags (does not apply to bootloader)
Ensure that the path to the project folder and any of the files in it do not contain spaces.
Check if the folder tools/x86_64-elf-cross
exists in the project folder. If it doesn't, run tools/setup_crosscompiler.sh
to build the default cross-compiler. If you already have set up a cross-compiler you want to use, ensure it's in the global path and is set in the main Makefile (more info here).
Try using uefi_call_wrapper
to call those functions. The syntax is:
EFI_STATUS Status = uefi_call_wrapper(function, number_of_paramters, ...);
where ...
are the function's parameters.
An example:
EFI_STATUS Status = SystemTable->BootServices->AllocatePool(EfiLoaderData, sizeof(UINTN), (void **) &Buffer);
becomes
EFI_STATUS Status = uefi_call_wrapper(BS->AllocatePool, 3, EfiLoaderData, sizeof(UINTN), (void **) &Buffer);
If the compilation fails with one of the following errors
ld: cannot find ../gnuefi/crt0-efi-x86_64.o: No such file or directory
ld: cannot find -lgnuefi
ensure you're not using a -j
argument when running make all
.
If the linking fails with undefined references, try running make clean-all && make all
. Sometimes make "gets out of sync" or doesn't actually recompile files. The command above forces make to recompile everything. If this doesn't work, then it's probably either your code or your linker script. Also check issue below.
Before this commit, there was an error in the main Makefile that prevented all the *.o files from being deleted. Try updating the main Makefile and see if that fixes it.
Before this commit, there was a missing compiler flag in the main Makefile that prevented the inclusion of debugging symbols in the executable. To fix this either update the main Makefile or add the -g
flag to the CFLAGS
variable (check this out).
MadMark: original creator.
GitHub: @SkrapeProjects