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_enumerate caller.

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 data as the loaded image’s EFI_LOADED_IMAGE_PROTOCOL.LoadOptions so the started image sees it via the same surface a shell launch would expose (the argc / argv the loaded image’s main receives, 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 calls Exit(EFI_SUCCESS, ...) this is 0; for an explicit Exit(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.

args is a command-line string installed as the image’s LoadOptions, 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; args may 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 if path is 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_guid in a readable Firmware Volume (EFI_FV2_READ_STATUS), LoadImages it directly out of the FV (no file staged on any filesystem), installs args as LoadOptions, 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_guid is 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 type EFI_FV_FILETYPE_APPLICATION are 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).

args is installed as the image’s LoadOptions, 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_code is 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 if name_guid is 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 cb once per image.

Backend-neutral abstraction over UEFI’s LocateHandleBuffer(EFI_LOADED_IMAGE_PROTOCOL) + per-handle HandleProtocol. The callback receives a layout-stable AxlImageInfo — consumer code never sees EFI_LOADED_IMAGE_PROTOCOL.

info->path may 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_enumerate when the caller only wants the self image’s range — used for fault attribution and similar “where am I in memory” checks. Equivalent to walking axl_image_enumerate and matching the entry whose path equals axl_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_base and out_size populated); 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 and axl_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.

Public Members

void *base

image load address in memory

size_t size

image size in bytes

const char *path

UTF-8 path (e.g. “fs0:\drivers\foo.efi”), or NULL if the firmware doesn’t expose it

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 (via LoadImage(SourceBuffer=...) + immediate UnloadImage). The result is EFI_SECURITY_VIOLATION for a signature mismatch, EFI_SUCCESS for a valid one. When the caller passes consult_db = false, or Secure Boot is off, or the firmware refuses to load via SourceBuffer, consulted_db is set false and signature_valid mirrors has_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 registered EFI_SECURITY2_ARCH_PROTOCOL handlers as part of the dry-run. Production firmwares that hook those for audit logging, PCR measurement, or dbx update notifications WILL trigger those side effects on every consult_db = true call; UnloadImage reverses the load but not the observability hooks. Pass consult_db = false when those side effects are unacceptable.

  • info – [out] receives the inspection result. Must be non-NULL. Caller frees via axl_image_signature_info_free. When info is 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 info populated), AXL_ERR if the file is missing/unreadable, the bytes are not a recognizable PE image, or info is 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 info pointer 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.

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.efi file 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)

Functions

int axl_shell_launch(int *out_exit_code)

Locate a real Shell.efi and run it in the foreground.

Searches for Shell.efi using axl_driver_locate (the running image’s own directory, its drivers/<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 -nostartup load option so that, when the launcher itself was started from startup.nsh, the child Shell does not re-run startup.nsh and recurse. StartImage blocks: 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 no Shell.efi could 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.efi file, it finds the platform’s built-in UEFI Shell — the EDK2 ShellPkg application, FILE_GUID EA4BB293-2D7F-4456-A681-1F22F42CD0BC — in a readable Firmware Volume and runs it in the foreground via axl_image_run_fv_file. StartImage blocks: 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 no Shell.efi staged, or on a vendor firmware that embeds the Shell.

Same -nostartup rationale as axl_shell_launch applies; pass that (or any Shell command line) via load_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 in StartImage and cannot be reattached) — same binary and capabilities.

out_exit_code is 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.efi first (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_FILE if a Shell.efi file is locatable; AXL_SHELL_FIRMWARE if no file exists but the firmware embeds the Shell in a readable FV; AXL_SHELL_NONE if neither is available.