Building the Firmware
Steps to set up your environment, compile, and run QemuQ35Pkg and QemuSbsaPkg.
Developer Environment
This is a Stuart-based platform, so the default environment requirements come from the How to Build with Stuart instructions.
QEMU is used to run the locally compiled firmware on a virtual platform.
- Windows: No action is needed. An external dependency in the repository provides the necessary QEMU binaries.
- Linux: Install QEMU.
This build uses edk2-pytools for its functionality. On most Linux distros, mono and nuget support require an extra step. See: A note on nuget on Linux.
CLANGPDB Toolchain
Both QemuQ35Pkg and QemuSbsaPkg use the CLANGPDB toolchain exclusively. This enables development of both
architectures on Linux and Windows, native PE/COFF image generation, and PDBs that work with the
uefi_debug_tools WinDbg infrastructure or
lldb.
LLVM version 21 or greater is recommended for use with EDK II and related projects. It can be downloaded from
LLVM itself. Add the directory containing the clang executable to
your PATH (restarting the terminal if necessary) and follow the steps below to build with Stuart.
By default, Hafnium and TF-A (used in the QemuSbsaPkg build) are pulled in as precompiled binaries. Passing
HAF_TFA_BUILD=TRUE on the stuart_build command line recompiles these components. This is only supported on Linux
as those projects do not build natively on Windows. They still use clang to compile.
Building with Pytools
-
[Optional] Create a Python virtual environment. Generally once per workspace.
python -m venv <name of virtual environment> -
[Optional] Activate the virtual environment. Each time a new shell is opened.
-
Linux
source <name of virtual environment>/bin/activate -
Windows
<name of virtual environment>/Scripts/activate.bat
-
-
Install Pytools. Generally once per virtual env or whenever
pip-requirements.txtchanges.pip install --upgrade -r pip-requirements.txt -
Initialize and update submodules. Only when submodules have updated.
First time setup:
stuart_setup -c Platforms/<Package>/PlatformBuild.pySubsequent submodule updates:
git submodule update --recursive -
Initialize and update dependencies. Only as needed when
ext_deps change.stuart_update -c Platforms/<Package>/PlatformBuild.py -
Compile firmware.
stuart_build -c Platforms/<Package>/PlatformBuild.pyUse
stuart_build -c Platforms/<Package>/PlatformBuild.py -hto see additional options like--clean. -
Running QEMU.
-
Append
--FlashRomto the build command and QEMU will run after the build completes, booting the new firmware. -
Or use
--FlashOnlyto skip the build and launch QEMU with the last built firmware:stuart_build -c Platforms/<Package>/PlatformBuild.py --FlashOnly
-
- QEMU is provided on Windows via an external dependency located at
QemuPkg/Binaries. QEMU must be manually downloaded on Linux. - QEMU on Linux requires at least version 9.0.2 when booting an operating system. If you are only booting to shell, matching the version of the Windows external dependency is acceptable.
- To override the external dependency on Windows, or the installed version on Linux, use
QEMU_PATH=<path>on the command line.
Custom Build Options
| Option | Effect |
|---|---|
SHUTDOWN_AFTER_RUN=TRUE | Outputs a startup.nsh file to the location mapped as fs0 with reset -s as the final line. Used in CI in combination with --FlashOnly to run QEMU to the UEFI shell and then execute the contents of startup.nsh. |
QEMU_PATH=<path> | Use a specific QEMU binary. |
QEMU_HEADLESS=TRUE | Run QEMU with no display. CI servers run headless and require this; locally it is not needed. |
GDB_SERVER=<TCP port> | Enables the QEMU GDB server at the provided TCP port. Connect a GDB client for hardware-level debugging. |
SERIAL_PORT=<TCP port> | Enables the specified serial port. Primarily used to connect to the software debugger when enabled. |
ENABLE_NETWORK=TRUE | Enables networking. Currently only supported on the Q35 platform. |
PATH_TO_OS=<path> | Boots an OS image (VHDX or QCOW2) instead of stopping at the UEFI shell. |
SERIAL_PORT defaults:
- Q35: defaults to
Noneto avoid unintended port conflicts in the pipeline. The build_and_run_rust_binary.py script defaults to port50001. - SBSA: only has a single serial port for normal world. By default, this is unset so it can send serial output to stdio. Setting it for SBSA prevents logs from coming over stdio and instead routes them to the TCP port.
Passing Build Defines
To pass build defines through stuart_build, prepend BLD_*_ to the define name and pass it on the command line.
stuart_build requires values to be assigned, so add a =1 suffix for bare defines.
For example, to enable E1000 network support, instead of the traditional -D E1000_ENABLE:
stuart_build -c Platforms/<Package>/PlatformBuild.py BLD_*_E1000_ENABLE=1