AI-Assisted Debugging Practices for UEFI Graphics Programs
This article presents a practical workflow for debugging UEFI graphics programs with an AI coding assistant. It covers the constraints of firmware development, how to review unfamiliar code, how to fix a common startup failure caused by an invalid frame buffer pointer after mode switching, how to diagnose a blank screen caused by an incorrect protocol GUID, and how serial logs help validate execution. It also explains how to move from a runnable prototype to a more polished UEFI application through small, verifiable iterations.
1. Why Graphics Development in Firmware Is Different
Before discussing the practical workflow, it is important to clarify a background that is easy to overlook: UEFI programs run in the firmware stage, before the operating system starts. This means they do not have the conveniences provided by an operating system. There is no ready-made window system, no dependable general-purpose file system, and no graphics library. All hardware interaction must be completed through protocols defined by the UEFI specification.
To draw something on the screen, such as a simple game, a developer must work directly with the Graphics Output Protocol, commonly abbreviated as GOP.
For this reason, writing a small game in a firmware environment is a useful test of low-level programming skills. It examines several abilities at once: memory lifecycle management, protocol location, mode setting, frame buffer operations, and key input handling. When this kind of task is handed to an AI coding assistant, the problems it exposes are often highly representative. Many of them are classic pitfalls that firmware developers have encountered before.
2. Preparation: Giving the AI Code That Does Not Run
A common practical scenario is taking over a project that was left by someone else, or by another tool, where the code is reasonably structured but cannot run.
For example, the project may be a modular UEFI application split into seven files, such as an entry point, main loop, game logic, rendering module, and input handling module. The directory organization may look reasonable, and responsibilities may be clearly divided, but the program fails immediately on startup.
In this situation, the correct approach is not to ask the AI to rewrite everything at once. The first step is to build an overall understanding of the code.
The mindset matters here. When using an AI coding assistant, the goal is usually not to compare models or decide which assistant is better. The more useful goal is to explore how far AI can be applied to engineering tasks. Once this is clear, each later prompt can be written with more focus.
3. First Round: Make the Program Run
3.1 Start With a Structured Code Review
The first instruction in the first round should be restrained and focused. For example:
Read through this code, summarize the responsibilities of each module, and list possible reasons why the program cannot run.
A good AI coding assistant will analyze the entry function, main loop, game logic, rendering module, and input handling module one by one. It can then produce a prioritized list of suspicious points.
The value of this list is that it turns a vague feeling that “something is wrong” into a set of hypotheses that can be verified one by one. This gives later debugging a clear direction.
3.2 Problem 1: Startup Crash Caused by an Invalid Frame Buffer Pointer
The first typical problem is that the program exits immediately after printing a startup log. On the surface, this looks like a crash, but the real issue is invalid memory access.
The root cause is a subtle trap in the UEFI graphics mode acquisition flow. The mode information pointer obtained through the QueryMode interface may point to a memory block that has already been reclaimed or overwritten by the firmware after SetMode is called. If the program continues to use the pointer obtained before the mode switch, it may access invalid memory, commonly referred to as a dangling pointer, and trigger an exception.
The correct fix is to query the mode information again after SetMode. This ensures that the program obtains a frame buffer description that matches the currently active mode.
The following example illustrates the idea:
#include <Uefi.h>#include <Protocol/GraphicsOutput.h>EFI_STATUS DrawScreen(EFI_HANDLE ImageHandle){EFI_STATUS Status;EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop = NULL;EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info = NULL;UINTN InfoSize;UINT32 ModeIndex;// 1. Locate the GOP protocol. Without the protocol, graphics cannot be output.Status = gBS->LocateProtocol(&gGraphicsOutputProtocolGuid, NULL, (VOID **)&Gop);if (EFI_ERROR(Status) || Gop == NULL)return EFI_NOT_FOUND;// 2. Query candidate mode information to obtain the mode index.Status = Gop->QueryMode(Gop, 0, &InfoSize, &Info);if (EFI_ERROR(Status)) return Status;ModeIndex = Info->Mode;// 3. Switch to the target mode.Status = Gop->SetMode(Gop, ModeIndex);if (EFI_ERROR(Status)) return Status;// 4. Key fix: query again after SetMode to refresh the Info pointer,// avoiding continued use of invalidated memory.Status = Gop->QueryMode(Gop, ModeIndex, &InfoSize, &Info);if (EFI_ERROR(Status)) return Status;// 5. Draw to the frame buffer through the Blt interface.// Filling the whole screen with black is used here as an example.return Gop->Blt(Gop, NULL, EfiBltVideoFill, 0, 0, 0,Info->HorizontalResolution,Info->VerticalResolution, 0, EFI_BLACK);}
This kind of problem, where memory becomes invalid after a cross-interface call, is one of the easiest issues to overlook in bare-metal development. It is also a good example of where an AI coding assistant can help. AI can identify this class of issue because it focuses on who owns the memory and when it may expire, rather than only checking syntax on a single line.
3.3 Problem 2: The Program Runs but the Screen Is Blank Because of an Incorrect GUID
After the crash is fixed, the program no longer exits, but the screen remains blank.
After this phenomenon is fed back to the AI, it can reason from the runtime logs and conclude that the program probably did not obtain the GOP correctly.
The UEFI specification defines multiple protocol GUIDs for graphics-related capabilities. If LocateProtocol is called with the wrong GUID, the program will not obtain the real graphics output protocol. In that case, all later drawing calls become no-ops.
This type of issue is subtle because the code can compile, run, and avoid crashing, but nothing happens on the screen. The key to troubleshooting is to verify whether the protocol GUID strictly matches the target protocol and whether the protocol is available in the current firmware environment.
4. Debugging With Serial Logs
A firmware environment does not have an operating-system-level graphics debugger. This makes logs the most important observation tool.
A practical approach is to ask the AI to insert serial print statements at key points, such as the entry point, main loop, protocol location, mode setting, and rendering calls. Then the execution flow can be monitored continuously: where the program reaches, and what return values are produced.
Once the logs show something like “protocol location succeeded but the drawing call did not return” or “mode setting returned an error,” the problem scope can be narrowed quickly to a specific stage.
Feeding the runtime phenomena back to the AI and asking it to re-infer the root cause is the core closed loop of this workflow. AI is not expected to produce the correct result in one attempt. It is gradually calibrated through a loop of running, feedback, and correction.
5. Second Round: From “Can Run” to “Can Be Used”
Getting the program to run in the first round is only the minimum acceptable result. The second round should focus on quality and experience.
This includes robustness at boundary conditions, such as fallback behavior when a resolution is not supported, input responsiveness, rendering smoothness, and complete resource release.
As before, the optimization goals should be split into small instructions and handed to the AI step by step. For example, saying “add fallback logic for unsupported high resolutions” is more likely to produce a usable result than simply saying “help me optimize.”
6. General Lessons for UEFI Graphics Development
- Memory lifetime is critical: Any pointer returned across interface calls should be treated carefully because it may become invalid after a state change.
- Confirm protocol availability before use: Before calling a protocol, confirm that the protocol exists, that the GUID is correct, and that return values are fully checked.
- Logs are the eyes of firmware development: Serial logs are almost the only tool that can reflect execution status in real time, so they should be placed densely along critical paths.
- Validate step by step: Instead of asking for the entire feature to be completed at once, use progressive goals: locate the protocol, set the mode, draw a single point, and then draw the full screen.
7. Methodology Summary
Looking back at the whole practice, truly efficient AI-assisted firmware development follows a reusable methodology: first perform a code review to build understanding, then iterate in two rounds, first making the program run and then refining it, use serial logs to establish runtime feedback, feed the observed phenomena back accurately, and replace vague requirements with concrete and verifiable instructions.
If this workflow is followed carefully, it can improve both development efficiency and success rates in high-barrier scenarios such as bare-metal and firmware development, whether the task is taking over legacy code or starting from scratch.
