Compiling your first UEFI program is not exactly effortless. Environment setup can take time, linker errors are common, and a .EFI program does not have the same direct edit-and-run experience as an ordinary desktop application.
This article organizes the topic from a beginner’s perspective: if you only want to compile your first UEFI program, where should you start, which concepts matter first, and which pitfalls are most likely to appear?
What Is a UEFI Program?
A UEFI program is usually a .EFI file.
It is not an ordinary .exe that you double-click in Windows. It is a PE/COFF executable that runs inside the UEFI firmware environment. Common use cases include:
- Boot managers
- Hardware initialization tools
- Firmware update tools
- Pre-boot diagnostic tools
- Custom boot flows
Many functions you see early in the system boot process may be related to UEFI applications, drivers, or firmware services.
For beginners, there is no need to understand full firmware development immediately. The first goal is simple: compile a .EFI file that can be loaded by a UEFI Shell or emulator.
Why Not Start with EDK II?
Real UEFI development often involves EDK II.
EDK II is complete and closer to real firmware engineering, but it is not very friendly for beginners:
- The project structure is complex
- The build system has a learning curve
- Environment variables and toolchain setup involve many details
- Compiler errors are not always easy to understand
- It is easy to get stuck on the environment before writing any code
If the goal is simply to get a minimal UEFI program running, a lightweight example is a better starting point.
pbatard/uefi-simple is one such project. Its goal is straightforward: provide a simple UEFI Hello World example so you can compile a .EFI file first.
What Is uefi-simple Good For?
uefi-simple is a good first stepping stone for UEFI beginners.
It solves three practical problems:
- It gives you a minimal compilable UEFI application structure
- It avoids the complexity of large firmware projects at the beginning
- It lets you verify that compiling, linking, and running all work
The project supports multiple build methods, including Visual Studio 2022 and MinGW/gcc. It can also be tested with QEMU and OVMF.
In other words, you do not have to repeatedly reboot a real machine for early experiments. Running the program in an emulator first is much safer.
What to Prepare Before Starting
You need at least a few categories of tools.
The first category is the compiler toolchain.
On Windows, you can start with:
- Visual Studio 2022
- Or MinGW/gcc
The second category is a UEFI runtime environment.
There are two common options:
- Run the
.EFIfile in a real machine’s UEFI Shell - Test it in a virtual environment with QEMU + OVMF
The third category is an example project.
Beginners should not start by writing build scripts from an empty directory. Using a minimal example such as uefi-simple helps avoid many build-system problems.
Basic Workflow
A minimal UEFI program workflow can be understood like this.
First, get the example project.
|
|
Second, choose a build toolchain.
If you use Visual Studio, build with the Visual Studio solution in the project.
If you use MinGW/gcc, follow the Makefile or instructions provided by the project.
Third, generate the .EFI file.
The key point here is to confirm the target architecture. A common PC is usually x86_64, meaning a 64-bit UEFI environment.
Fourth, put the .EFI file somewhere the UEFI Shell can access.
On a real machine, this usually means preparing a FAT32 partition or USB drive.
With QEMU, you can mount a directory or disk image.
Fifth, run it in the UEFI Shell.
The result is usually a minimal output, such as a Hello World-style message.
Where Beginners Usually Get Stuck
The hardest part of compiling a UEFI program is usually not the C language itself, but the environment and linking process.
Common issues include:
- Wrong compiler architecture
- Wrong target format
- Incomplete linker parameters
- Missing UEFI entry point
- Generating an ordinary executable instead of a UEFI-loadable
.EFI - QEMU or OVMF not configured correctly
- Secure Boot on a real machine blocking an unsigned program
Linker errors are especially easy to misread as code problems.
In many cases, the real issue is the entry function, subsystem, target architecture, or linker script.
So in the first stage, do not rush into complex logic. Make sure the original example can compile and run, then change the output little by little.
Why Use QEMU + OVMF for Testing?
Testing UEFI programs on a real machine is possible, but it is not convenient at the beginner stage.
You may have to repeat this cycle:
- Compile
- Copy to a USB drive
- Reboot
- Enter the UEFI Shell
- Run the program
- Record the error
- Return to the system and modify the code
That loop is slow.
QEMU + OVMF lets you simulate a UEFI environment directly inside the operating system. You can verify whether a .EFI file loads more quickly, and it is less likely to affect your real boot entries.
Once the program basically works, testing it on a real machine is much more manageable.
What Should Beginners Modify First?
If you have already compiled your first .EFI with the example project, do not jump into complex features immediately.
A better order is:
- Change the output text first to confirm that recompilation really takes effect.
- Try reading simple information provided by UEFI.
- Understand the entry function, output protocol, and basic services.
- Then consider more complex features such as file systems, graphical output, or boot entry management.
This approach makes every step verifiable.
If you change too much at once, it becomes difficult to tell whether the issue is in the code, the build process, or the runtime environment.
How Is It Different from an Ordinary C Program?
Although UEFI programs can be written in C, their runtime environment is completely different from ordinary C programs.
An ordinary C program usually runs inside an operating system and can rely on the standard library, file system, process model, and system calls.
A UEFI program runs before the operating system boots. It relies on services provided by UEFI firmware. Many things you are used to in normal programs are not automatically available here.
When writing UEFI programs, you need to adapt to several differences:
- The entry function is different
- Output works differently
- Available libraries are different
- Memory and file access work differently
- Debugging works differently
This is why starting from a minimal example is better than writing code as if it were a normal C program.
A Practical Learning Path
For beginners, a realistic path looks like this:
- Step 1: Compile
uefi-simple - Step 2: Run it with QEMU + OVMF
- Step 3: Modify the Hello World output
- Step 4: Understand how the UEFI Shell loads
.EFI - Step 5: Learn the UEFI entry function and basic output protocol
- Step 6: Then read EDK II or more complete UEFI development material
The point of this path is to build a working feedback loop first.
Once you can generate a .EFI from source and see output in a UEFI environment, you have already crossed the hardest first threshold.
References
Final Thought
The hard part of compiling your first UEFI program is usually not writing a bit of C code, but connecting the toolchain, link format, and runtime environment.
Do not rush into complex features.
Start with a minimal example such as uefi-simple, get a runnable .EFI first, and then gradually understand UEFI entry points, protocols, and build methods.