Getting Started with Compiling UEFI Programs: From uefi-simple to Your First .EFI

A beginner-friendly guide to compiling your first UEFI .EFI program: what a UEFI program is, why uefi-simple is a good starting point, which tools to prepare, and where beginners usually get stuck.

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 .EFI file 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.

1
git clone https://github.com/pbatard/uefi-simple.git

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:

  1. Change the output text first to confirm that recompilation really takes effect.
  2. Try reading simple information provided by UEFI.
  3. Understand the entry function, output protocol, and basic services.
  4. 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.

记录并分享
Built with Hugo
Theme Stack designed by Jimmy