AxlImage — executable-image lifecycle
Backend-neutral wrapper for loading, starting, and unloading
executable images. On UEFI, maps to LoadImage / StartImage
/ UnloadImage; on a future Linux backend the same shape would
map to posix_spawn. Consumers operate on an opaque
AxlImage * handle — the underlying EFI_HANDLE never crosses
the public API.
Sibling to AxlSys — System Utilities (axl_driver_* for DXE-driver lifecycle,
which AxlImage delegates to internally for its load and unload
paths). The one place AxlImage diverges from AxlDriver is
axl_image_start, which captures the image’s exit code —
something axl_driver_start discards because drivers aren’t
expected to exit cleanly.
API Reference
Executable-image lifecycle: load, start, unload.
A backend-neutral abstraction for what UEFI calls LoadImage/StartImage/UnloadImage. On a future Linux backend the same shape would map to posix_spawn or execve-style entry; on coreboot stages, to their loader. Consumer code never references EFI_HANDLE or EFI_LOADED_IMAGE_PROTOCOL directly — the AxlImage handle is opaque.
AxlImage *img;
if (axl_image_load("fs0:\\boot\\hello.efi", &img) == 0) {
int exit_code = 0;
axl_image_start(img, &exit_code);
axl_image_unload(img);
axl_printf("hello.efi exited with %d\n", exit_code);
}
Typedefs
-
typedef struct AxlImage AxlImage
Opaque handle to a loaded executable image.
Created by axl_image_load(); released by axl_image_unload(). The struct is intentionally not defined here — consumers treat it as a pointer-only type.
-
typedef int (*AxlImageIterFn)(const AxlImageInfo *info, void *ctx)
Iterator callback for
axl_image_enumerate.- Return:
0 to continue iteration, non-zero to stop. The non-zero value is returned to the
axl_image_enumeratecaller.
Functions
-
int axl_image_load(const char *path, AxlImage **out)
Load an executable image from a path on a mounted volume.
Path syntax follows the UEFI Shell convention: a volume label, a colon, and a backslash-separated path. Forward slashes are accepted and normalized internally. The image is loaded but not yet started.
- Parameters:
path – image path (e.g. “fs0:\boot\hello.efi”)
out – [out] receives the image handle
- Returns:
AXL_OK on success, AXL_ERR if the file can’t be read or the image format is rejected.
-
int axl_image_set_load_options(AxlImage *img, const void *data, size_t size)
Set load options on a loaded image before starting it.
Mirrors axl_driver_set_load_options for the image-level API: installs
dataas the loaded image’sEFI_LOADED_IMAGE_PROTOCOL.LoadOptionsso the started image sees it via the same surface a shell launch would expose (theargc/argvthe loaded image’smainreceives, or the raw byte buffer via axl_driver_get_load_options_raw).The data is copied internally — caller’s buffer can be freed after. The copy is owned by AXL and released by axl_image_unload (or by a subsequent set on the same handle, which replaces the previous copy). Pass NULL data (or size == 0) to clear load options and free any previous copy. Call between axl_image_load and axl_image_start.
Encoding: pass-through. UEFI shells encode argv as UCS-2 strings; programmatic launchers can pass arbitrary bytes — the started image is responsible for interpreting the buffer.
- Parameters:
img – image handle from axl_image_load
data – option bytes (copied; NULL to clear)
size – option size in bytes
- Returns:
AXL_OK on success, AXL_ERR on bad arguments, alloc failure, or firmware protocol error.
-
int axl_image_start(AxlImage *img, int *exit_code)
Start a loaded image and wait for it to return.
Transfers control to the image’s entry point. Returns when the image calls Exit() or returns from its entry. The handle remains valid after start; the caller still owns it and must axl_image_unload() it.
The image’s exit code (low 32 bits of its EFI_STATUS) is reported in
*exit_code. For an image that callsExit(EFI_SUCCESS, ...)this is 0; for an explicitExit(7, ...)it is 7. UEFI’s Exit() and propagated-error channels share the same encoding, so callers that need to distinguish should treat any non-zero value as “image did not succeed” rather than rely on a specific code.- Parameters:
img – image handle from axl_image_load
exit_code – [out] image’s exit status (NULL allowed)
- Returns:
AXL_OK on successful start (regardless of the image’s exit code), AXL_ERR if the image could not be started at all.
-
int axl_image_unload(AxlImage *img)
Unload an image, releasing its memory.
Safe to call on a never-started image. Frees the handle.
- Parameters:
img – image handle from axl_image_load
- Returns:
AXL_OK on success, AXL_ERR if the firmware refuses (e.g. the image has installed protocols that aren’t released yet).
-
int axl_image_run(const char *path, const char *args, int *out_exit_code)
Load an image, run it to completion, and unload it.
The one-call form of the common “launch a foreground UEFI app and get
its exit code” pattern:
axl_image_load + (optionally) axl_image_set_load_options + axl_image_start (which blocks until the image returns) + axl_image_unload. Use it for any blocking UEFI application — a diagnostic tool, a vendor setup app, a recovery menu, or (via axl_shell_launch) the UEFI Shell.argsis a command-line string installed as the image’sLoadOptions, encoded to UCS-2 the way a shell launch encodes a command line; pass NULL (or “”) for none. The started image parses it as its arguments per the shell convention — whether to include a leading program name depends on that image’s argv parser. The encoding buffer is internal;argsmay be freed after the call.Pair this with axl_console_mirror_install to mirror the launched app’s console to a remote terminal.
- Parameters:
path – image path (UEFI shell syntax)
args – command-line / LoadOptions (UTF-8), or NULL
out_exit_code – [out] image’s exit code (NULL allowed)
- Returns:
AXL_OK if the image was started and has now returned (its exit code is in
out_exit_code); AXL_ERR ifpathis NULL or the image could not be loaded.
-
int axl_image_run_fv_file(const AxlGuid *name_guid, const char *args, int *out_exit_code)
Load + run an image embedded in a firmware volume, by file GUID.
The FV-embedded counterpart of axl_image_run(): instead of a path on a mounted volume, it locates the firmware file whose name GUID is
name_guidin a readable Firmware Volume (EFI_FV2_READ_STATUS),LoadImages it directly out of the FV (no file staged on any filesystem), installsargsasLoadOptions,StartImage(which blocks until the image returns), and unloads it. This is how a consumer runs a firmware-embedded tool — most notably the vendor-supplied UEFI Shell (see axl_shell_launch_fv) — with nothing staged on disk.name_guidis the FFS file name GUID — an AxlGuid (write it with the AXL_GUID macro), the same value the firmware’s file directory carries. Only files of typeEFI_FV_FILETYPE_APPLICATIONare matched. All readable FVs are searched and the first match wins; the order among multiple FVs carrying the same GUID is firmware-dependent and unspecified, so a consumer needing one specific FV’s build should not rely on it (harmless for the Shell — every instance is equivalent).argsis installed as the image’sLoadOptions, UTF-8 encoded to UCS-2 exactly as axl_image_run does; pass NULL (or “”) for none.Cleanup is atomic: on any failure after the image loads, it is unloaded before returning.
out_exit_codeis set to 0 up front, so it reads 0 on every failure path.- Parameters:
name_guid – FFS file name GUID (see AXL_GUID)
args – command-line / LoadOptions (UTF-8), or NULL
out_exit_code – [out] image’s exit code (NULL allowed)
- Returns:
AXL_OK if the file was found, started, and has now returned (its exit code is in
out_exit_code); AXL_ERR ifname_guidis NULL, no readable FV carries a matching application, or load/start failed.
-
int axl_image_enumerate(AxlImageIterFn cb, void *ctx)
Walk every currently-loaded image, invoking
cbonce per image.Backend-neutral abstraction over UEFI’s
LocateHandleBuffer(EFI_LOADED_IMAGE_PROTOCOL)+ per-handleHandleProtocol. The callback receives a layout-stableAxlImageInfo— consumer code never seesEFI_LOADED_IMAGE_PROTOCOL.info->pathmay be NULL for images whose firmware FilePath couldn’t be decoded (e.g. synthetic loads or in-memory images). Callers that use the path for display should fall back to a placeholder.- Returns:
AXL_OK if the walk completed, the callback’s non-zero value if it stopped early, or AXL_ERR on enumeration failure.
-
int axl_image_self_get_range(void **out_base, size_t *out_size)
Get the base address and size of the currently-running image.
Convenience over
axl_image_enumeratewhen the caller only wants the self image’s range — used for fault attribution and similar “where am I in memory” checks. Equivalent to walkingaxl_image_enumerateand matching the entry whose path equalsaxl_app_image_path(), but cheaper.- Parameters:
out_base – [out] image base load address
out_size – [out] image size in bytes (NULL allowed)
- Returns:
AXL_OK on success (
out_baseandout_sizepopulated); AXL_ERR if firmware doesn’t expose the loaded-image protocol for the current image (extremely unusual).
-
struct AxlImageInfo
- #include <axl-image.h>
Snapshot of a loaded image as visible to the firmware.
Returned by
axl_image_enumerate’s callback andaxl_image_self_get_range. The string fields point into runtime- owned storage that’s valid for the duration of the callback (or until the next call for_self_get_range); copy if you need it longer.
AxlImageVerify — Authenticode signature inspection
PE Authenticode signature inspection without launching the image —
two-axis check (presence + Secure-Boot-db validity) for offline
integrity-check tooling. See include/axl/axl-image-verify.h
for the side-effect contract on the consult_db path.
PE Authenticode signature inspection without launching the image.
axl_image_load runs the firmware’s PE-loader signature checks as a side-effect of loading, and only when Secure Boot is on. Tools that want to ask “is this PE file signed and does its signature
validate against the current Secure Boot db?” without committing to launching the image (incident-response triage, BIOS-update pre-flight, bootable-media verification) reach for axl_image_verify_signature.
The check has two orthogonal axes:
Presence (
has_signature): does the PE file’s Certificate Table data directory (PE/COFF spec §6.4) hold a non-empty WIN_CERTIFICATE blob? Detected by parsing file bytes only — works regardless of Secure Boot state and on any platform.Validity (
signature_valid,consulted_db): if the caller asks for db validation and Secure Boot is enabled, the firmware’s PE loader is asked to dry-run the same signature check it would perform on a real launch (viaLoadImage(SourceBuffer=...)+ immediateUnloadImage). The result isEFI_SECURITY_VIOLATIONfor a signature mismatch,EFI_SUCCESSfor a valid one. When the caller passesconsult_db = false, or Secure Boot is off, or the firmware refuses to load via SourceBuffer,consulted_dbis set false andsignature_validmirrorshas_signature(presence-only).
AxlImageSignatureInfo info = {0};
if (axl_image_verify_signature("fs0:\\boot.efi", true, &info) != 0) {
axl_print("could not read or parse PE\n");
} else if (!info.has_signature) {
axl_print("UNSIGNED\n");
} else if (info.consulted_db && !info.signature_valid) {
axl_print("SIGNATURE INVALID against current Secure Boot db\n");
} else {
axl_print("SIGNED%s\n",
info.consulted_db ? " (db-validated)" : " (presence only)");
}
axl_image_signature_info_free(&info);
Functions
-
int axl_image_verify_signature(const char *path, bool consult_db, AxlImageSignatureInfo *info)
Inspect a PE image’s signature without launching it.
Reads the file, locates the Certificate Table data-directory entry, and (optionally) asks the firmware to dry-run the signature check against the current Secure Boot db. See the file-level overview for the per-field contract.
- Parameters:
path – Image file path (e.g.
"fs0:\\boot.efi").consult_db – When true, ask the firmware to dry-run a full db validation via
LoadImage(SourceBuffer)immediate
UnloadImage. Has no effect beyond presence detection when Secure Boot is off. Side-effect note: the firmware’s PE loader allocates image memory, applies relocations, and invokes any registeredEFI_SECURITY2_ARCH_PROTOCOLhandlers as part of the dry-run. Production firmwares that hook those for audit logging, PCR measurement, ordbxupdate notifications WILL trigger those side effects on everyconsult_db = truecall;UnloadImagereverses the load but not the observability hooks. Passconsult_db = falsewhen those side effects are unacceptable.
info – [out] receives the inspection result. Must be non-NULL. Caller frees via axl_image_signature_info_free. When
infois non-NULL, every bool/pointer field is cleared to false / NULL before any further work — so on a -1 return the struct is in a defined “unknown / nothing detected” state, not arbitrary leftover bytes.
- Returns:
AXL_OK on success (with
infopopulated), AXL_ERR if the file is missing/unreadable, the bytes are not a recognizable PE image, orinfois NULL.
-
void axl_image_signature_info_free(AxlImageSignatureInfo *info)
Release any heap-allocated fields inside
info.Frees AxlImageSignatureInfo::subject_cn and AxlImageSignatureInfo::issuer_cn (each independently — either may be NULL) and clears the struct’s pointer fields back to NULL. NULL-safe on the
infopointer itself.
-
struct AxlImageSignatureInfo
- #include <axl-image-verify.h>
PE Authenticode signature inspection result.
Cleared with axl_image_signature_info_free, which is NULL-safe — callers that don’t pass a non-NULL info pointer to axl_image_verify_signature can skip the free.
Public Members
-
bool has_signature
PE Certificate Table directory entry holds a non-empty WIN_CERTIFICATE blob.
-
bool signature_valid
signature validates (db-validated when consulted_db, presence-only otherwise)
-
bool consulted_db
Secure Boot db was actually consulted (firmware LoadImage dry-run succeeded)
-
char *subject_cn
Subject CommonName from the first certificate in the PKCS#7 SignedData bundle. signtool.exe and most Authenticode signers emit the signer’s certificate first in practice, but the format does not require it — the formal way to identify the signer is via SignerInfo’s IssuerAndSerial. This field is best-effort, suitable for diagnostic output (“Signed by
`<cn>`”) but NOT for security decisions. NULL if has_signature is false, the cert can’t be parsed, no CN attribute is present, or the CN string uses an encoding the walker doesn’t support (T61String, BMPString, IA5String). Heap-allocated UTF-8; caller frees via axl_image_signature_info_free.
-
char *issuer_cn
Issuer CommonName from the same certificate as subject_cn, extracted by the same parser. Same “first cert in the bundle,
best-effort, diagnostic-only” caveats apply.
-
bool has_signature
AxlShell — launch a real UEFI Shell
Find a Shell.efi and run it as a foreground child image
(StartImage blocks until it exits), with -nostartup so a
child Shell launched from startup.nsh doesn’t recurse. The
AXL-ified form of the EDK2 ShellLauncher; pairs with
AxlConsoleMirror — mirror the firmware console to a remote terminal to host the real Shell over a remote terminal.
When no Shell.efi is staged, axl_shell_launch_fv runs the
firmware-embedded Shell straight out of a Firmware Volume (the
ShellPkg application; layered on the reusable
axl_image_run_fv_file FV-file loader above), and
axl_shell_locate reports where a Shell is available
(AXL_SHELL_FILE / AXL_SHELL_FIRMWARE / AXL_SHELL_NONE)
without launching one.
Locate and launch the real UEFI Shell as a foreground child.
Shell-specific policy over the generic foreground launcher axl_image_run (<axl/axl-image.h>
, the “run any blocking UEFI app
and get its exit code” mechanism): find a
Shell.efi across the conventional locations and run it with -nostartup (so a child Shell launched from startup.nsh doesn’t re-run startup.nsh and recurse). For any other blocking app — a diagnostic tool, a vendor setup app, a recovery menu — call axl_image_run directly with its path.
Companion to AxlConsoleMirror (the mirror wraps the console so a remote terminal can drive whatever runs in the foreground); this puts the real Shell there. StartImage blocks until the Shell exits.
// Host a real Shell while a background HTTP server keeps serving,
// pumped off a firmware timer (the resident-driver model):
axl_loop_attach_driver(loop, 10); // network pumped in the background
int exit_code = 0;
axl_shell_launch(&exit_code); // blocks until the Shell exits
axl_loop_detach_driver(loop);
Enums
-
enum AxlShellSource
Where a real UEFI Shell can be found, without launching it.
The availability query behind axl_shell_launch / axl_shell_launch_fv
— lets a consumer surface “is a shell available, and
from where” (e.g. enable a remote Terminal) without paying a blocking launch.
Values:
-
enumerator AXL_SHELL_NONE
neither a file nor a firmware-embedded Shell found
-
enumerator AXL_SHELL_FILE
a
Shell.efifile is locatable (launch via axl_shell_launch)
-
enumerator AXL_SHELL_FIRMWARE
no file, but a readable FV embeds the Shell (launch via axl_shell_launch_fv)
-
enumerator AXL_SHELL_NONE
Functions
-
int axl_shell_launch(int *out_exit_code)
Locate a real
Shell.efiand run it in the foreground.Searches for
Shell.efiusing axl_driver_locate (the running image’s own directory, itsdrivers/<arch>/, and other mounted volumes’drivers/<arch>/). When a consumer deploys the Shell to a non-standard location (e.g. an ESP layout like\\x64\\Shell.efi), call axl_image_run with the explicit path and"-nostartup".The Shell is started with the
-nostartupload option so that, when the launcher itself was started fromstartup.nsh, the child Shell does not re-runstartup.nshand recurse.StartImageblocks: this call returns only when the Shell exits.- Parameters:
out_exit_code – [out] Shell’s exit code (NULL allowed)
- Returns:
AXL_OK if a Shell was found, started, and has now exited (its exit code is reported in
out_exit_code); AXL_ERR if noShell.eficould be located or loaded.
-
int axl_shell_launch_fv(const char *load_options, int *out_exit_code)
Launch the firmware-embedded UEFI Shell out of a Firmware Volume.
The no-file-staged counterpart of axl_shell_launch(): rather than locating a
Shell.efifile, it finds the platform’s built-in UEFI Shell — the EDK2 ShellPkg application, FILE_GUIDEA4BB293-2D7F-4456-A681-1F22F42CD0BC— in a readable Firmware Volume and runs it in the foreground via axl_image_run_fv_file.StartImageblocks: this returns only when the Shell exits. Use it when a consumer runs from the host firmware’s own shell (e.g. mounted virtual media) with noShell.efistaged, or on a vendor firmware that embeds the Shell.Same
-nostartuprationale as axl_shell_launch applies; pass that (or any Shell command line) viaload_options, NULL for none. The launched instance is a fresh Shell, not the parent the consumer may itself have been started from (that parent is blocked inStartImageand cannot be reattached) — same binary and capabilities.out_exit_codeis set to 0 up front, so it reads 0 on every failure path.- Parameters:
load_options – Shell command line (UTF-8), or NULL
out_exit_code – [out] Shell’s exit code (NULL allowed)
- Returns:
AXL_OK once the Shell has been started and exited (its exit code is in
out_exit_code); AXL_ERR if no readable FV carries the Shell or it could not be loaded/started.
-
AxlShellSource axl_shell_locate(void)
Report where a real UEFI Shell can be launched from.
Checks for a staged
Shell.efifirst (the axl_shell_launch search path) and, failing that, for the ShellPkg Shell in a readable Firmware Volume (the axl_shell_launch_fv search). The file path is preferred because it is the cheaper launch and matches a consumer’s own staged copy.Read-only: it walks the firmware volumes and mounted volumes but loads nothing and has no side effects. The walk is not free (it touches mounted filesystems), so a consumer polling it for a UI flag should cache the result rather than call it on every refresh.
- Returns:
AXL_SHELL_FILEif aShell.efifile is locatable;AXL_SHELL_FIRMWAREif no file exists but the firmware embeds the Shell in a readable FV;AXL_SHELL_NONEif neither is available.