SDK Design

AXL SDK Design

Part of the AximCode project. AXL = AximCode Library. Pronounced “axle.”

Vision

A developer writes a standard C file with #include <axl.h> and int main(int argc, char **argv), runs axl-cc app.c -o app.efi, and gets a working UEFI application. No EDK2 source tree, no .inf files, no .dsc files, no PascalCase, no UEFI headers.

Architecture

The SDK is a packaging layer on top of libaxl. It takes pre-built static libraries from an EDK2 build of libaxl and bundles them with headers, a linker script, an entry point stub, and build tooling into a self-contained distributable.

┌─────────────────────────────────────────────┐
│  Consumer Application (hello.c)             │
│    #include <axl.h>                         │
│    int main(int argc, char **argv) { ... }  │
├─────────────────────────────────────────────┤
│  axl-crt0.c                                 │
│    _AxlEntry → _axl_init → main → cleanup  │
├─────────────────────────────────────────────┤
│  axl-autogen.o                              │
│    ProcessLibraryConstructorList             │
│    UEFI GUID definitions                     │
├─────────────────────────────────────────────┤
│  Pre-built Static Libraries                  │
│    AXL: AxlMemLib, AxlLogLib, AxlDataLib,   │
│         AxlIOLib, AxlFormatLib, AxlAppLib,   │
│         AxlNetLib, AxlLoopLib, AxlTaskLib,   │
│         AxlUtilLib                           │
│    EDK2: BaseLib, BaseMemoryLib, PrintLib,    │
│          UefiLib, ShellLib, ...               │
├─────────────────────────────────────────────┤
│  UEFI Firmware (target system)               │
└─────────────────────────────────────────────┘

Entry Point Flow

UEFI firmware
  └→ _ModuleEntryPoint()           [UefiApplicationEntryPoint.lib]
       ├→ ProcessLibraryConstructorList()  [axl-autogen.o]
       │    ├→ UefiBootServicesTableLibConstructor()  → sets gBS
       │    ├→ UefiRuntimeServicesTableLibConstructor() → sets gRT
       │    ├→ UefiLibConstructor()
       │    └→ ShellLibConstructor()       → Shell protocol init
       └→ ProcessModuleEntryPointList()    [axl-autogen.o]
            └→ _AxlEntry()                 [axl-crt0.c]
                 ├→ _axl_init()            → streams, memory
                 ├→ _axl_get_args()        → argc/argv from Shell
                 ├→ main(argc, argv)       → user code
                 └→ _axl_cleanup()         → teardown

Build Flow

axl-cc hello.c -o hello.efi
  │
  ├─ gcc -ffreestanding -nostdlib -fpie ...
  │    -c axl-crt0.c → axl-crt0.o
  │    -c hello.c → hello.o
  │    link: axl-crt0.o + axl-autogen.o + hello.o
  │           + all .lib files (--start-group/--end-group)
  │    → hello.dll (ELF)
  │
  └─ GenFw -e UEFI_APPLICATION hello.dll → hello.efi (PE/COFF)
     (or: objcopy -O efi-app-x86_64 hello.dll hello.efi)

Phases

Phase 1: Core (DONE)

  • [x] install.sh: build libaxl, package SDK

  • [x] axl-crt0.c: entry point stub

  • [x] axl-cc: command-line build wrapper

  • [x] axl.cmake: CMake integration

  • [x] hello.c example: verified in QEMU

  • [x] X64 architecture support

Phase 2: Polish

  • [ ] AARCH64 cross-build support

  • [x] Test CMake build end-to-end (verified in QEMU)

  • [ ] Verify net module works (TCP/HTTP/DNS GUIDs)

  • [ ] Better error messages in axl-cc

  • [ ] --verbose flag for axl-cc

  • [ ] axl-cc --version / axl-cc --help

Phase 3: Distribution

  • [ ] GitHub Actions: build SDK on push, publish release tarballs

  • [ ] Versioned releases: axl-sdk-0.1.0-x64-linux.tar.gz

  • [ ] Version stamp in axl-cc output

  • [ ] Release notes automation

Phase 4: Advanced Features

  • [ ] Multi-file projects in axl-cc (already works, untested)

  • [ ] axl-cc --net flag to link net module with extra GUIDs

  • [ ] axl-cc --type driver|runtime for DXE/runtime driver targets

  • [ ] axl-cc --entry <name> for custom driver entry points

  • [ ] Debug build support (axl-cc –debug)

  • [ ] Strip/optimize for release (axl-cc –release)

  • [ ] QEMU test runner integration (axl-cc –run)

  • [ ] CMake find_package(axl) for consumer project integration

Phase 5: Backend Abstraction (EDK2 + gnu-efi)

Add a backend abstraction layer inside the library so it can be compiled against either EDK2 or gnu-efi, selected at build time. The public API (axl.h) is unchanged — only internal implementations switch between backends.

Architecture:

A new internal header AxlBackend.h defines backend-agnostic functions for memory allocation, console output, time, file I/O, and wide-string operations. Two implementation files provide the backend-specific code:

src/backend/axl-backend.h              ← backend API (internal)
src/backend/axl-backend-edk2.c         ← EDK2 implementation
src/backend/axl-backend-gnuefi.c       ← gnu-efi implementation

Backend selection at compile time via macro:

  • -DAXL_BACKEND_EDK2 (default) — uses EDK2 libraries

  • -DAXL_BACKEND_GNUEFI — uses gnu-efi headers/libraries

Backend API categories:

Category

Backend Functions

EDK2

gnu-efi

Table access

axl_bs(), axl_st(), axl_rt() macros

gBS/gST/gRT

BS/ST/RT

Memory

axl_backend_alloc/free

AllocatePool/FreePool

BS->AllocatePool/FreePool

Console

axl_backend_console_write/set_attr

gST->ConOut->OutputString

ST->ConOut->OutputString

Time

axl_backend_get_time

gRT->GetTime

RT->GetTime

File I/O

axl_backend_file_open/read/write/close

ShellLib functions

EFI_SHELL_PROTOCOL calls

Wide-string

axl_backend_wcslen/wcscmp/wcscpy

BaseLib (StrLen etc.)

gnu-efi RtStrLen etc.

Self-contained replacements (no backend needed):

  • AsciiStriCmpaxl_strcasecmp (new portable function)

  • AsciiStrHexToUint64S → self-implemented hex parser

  • InterlockedCompareExchange64__sync_val_compare_and_swap (GCC builtin)

  • CpuPause/MemoryFence → GCC __builtin_ia32_pause/__sync_synchronize

  • ZeroMem → local zeroing loop

Sub-phases:

Phase

Scope

Outcome

5a

Backend header + EDK2 impl + migrate core modules

All library code uses AxlBackend.h; EDK2 deps concentrated in one .c file; 346 tests pass

5b

gnu-efi backend impl + Makefile + CRT0

Library compiles with gnu-efi; hello.c runs in QEMU

5c

Event loop + networking migration

axl-loop.c, axl-tcp.c, axl-net-util.c use backend API

5d

Task pool + full test suite

axl-task-pool.c migrated; all tests pass on both backends

Benefits:

  • SDK consumers can choose EDK2 or gnu-efi at build time

  • gnu-efi path: gcc + gnu-efi-devel (distro package), no EDK2 source

  • EDK2 path: unchanged, full backward compatibility

  • All EDK2 dependencies concentrated in one file (AxlBackendLib)

Challenges:

  • Must recompile library for each backend (different headers/types)

  • gnu-efi missing: DNS4, IP4_CONFIG2, MP_SERVICES protocols (define from UEFI spec or make optional)

  • Wide-string formatting (UnicodeVSPrint) has no gnu-efi equivalent (keep EDK2-only behind #ifdef or deprecate)

Key Technical Decisions

Why no LTO in pre-built libraries?

EDK2 builds with -flto by default. LTO objects contain GCC IR (intermediate representation), not native code. When a consumer links these LTO objects, GCC’s LTO plugin re-compiles them — but with potentially different optimization context than the original EDK2 build. This caused StackCheckLib’s guard value to be constant-folded to zero, replacing all stack-protected functions with ud2 (intentional crash).

Fix: libaxl’s AxlPkg.dsc adds -fno-lto to [BuildOptions], producing native .o files that link correctly in any context.

Why StackCheckLib instead of StackCheckLibNull?

GCC with -fstack-protector emits code that loads a “stack cookie” from __stack_chk_guard and checks it on function return. StackCheckLibNull sets __stack_chk_guard = 0, which means every non-zero cookie fails the check. With LTO disabled, this manifests as runtime crashes rather than compile-time optimization issues.

StackCheckLib provides a proper non-zero guard value (0xA5F2997A15F7A350), making stack protection functional.

Why _ModuleEntryPoint as linker entry?

EDK2’s UefiApplicationEntryPoint.lib provides _ModuleEntryPoint, which calls ProcessLibraryConstructorList before the app’s entry point. This initializes critical globals: gBS (Boot Services), gRT (Runtime Services), and the Shell protocol. Without this, any call to axl_printf, axl_fopen, or axl_malloc would crash because they ultimately call gBS->AllocatePool or gST->ConOut->OutputString.

Two toolchain options: GCC+GenFw or Clang+LLD

Option 1: GCC + GenFw (current default)

  • GCC compiles to ELF, links with GNU ld

  • GenFw converts ELF → PE/COFF

  • Requires GenFw binary (from EDK2 BaseTools)

  • objcopy does NOT work as a fallback (fails on PIE relocations)

Option 2: Clang + LLD (recommended)

  • Clang compiles to COFF with -target x86_64-unknown-windows

  • lld-link produces PE/COFF .efi directly

  • No GenFw, no conversion step

  • Both tools are part of standard LLVM distribution

  • Requires building libaxl with EDK2’s CLANGPDB toolchain

The clang path is simpler for consumers — they install clang and everything works. The GCC path requires shipping GenFw, which is an EDK2 build artifact.

Verified: Both paths produce working UEFI applications that run correctly in QEMU.

Dependencies

Build-time (install.sh)

Dependency

Location

Purpose

EDK2

~/projects/edk2

Build environment, base libraries

libaxl

./ (local, same repo)

AXL library source

GCC or Clang

system

Compiler

aarch64-linux-gnu-gcc

system

AARCH64 cross-compiler

Consumer-time

Clang path (recommended):

Dependency

Purpose

Clang + LLD

Compile + link → .efi directly

GCC path:

Dependency

Purpose

GCC

Compile + link → ELF

GenFw

ELF → PE/COFF conversion

No EDK2 source tree. No Python. No Java. No Make.

Distribution Model

No binaries in git. The axl-sdk repo contains only source code and build scripts. All build artifacts (pre-built .lib files, headers, GenFw binary, axl-cc, axl.cmake) are produced by install.sh into the out/ directory, which is gitignored.

Two ways to get the SDK:

  1. Build from source (developer workflow):

    git clone axl-sdk
    ./scripts/install.sh
    # produces out/ with everything needed
    
  2. Download a release tarball (consumer workflow):

    # GitHub Releases attach pre-built tarballs as assets
    tar xf axl-sdk-0.1.0-x64-linux.tar.gz
    

Release tarballs are built by GitHub Actions and attached to GitHub Releases — they are downloadable assets, not committed to the repository.

GenFw: For the GCC toolchain path, GenFw (EDK2’s ELF→PE/COFF converter) is built from EDK2’s BaseTools source during install.sh and included in the out/ directory. It is a build artifact, not a checked-in binary. The clang toolchain path does not need GenFw at all.

Investigated alternatives to GenFw:

  • objcopy --target efi-app-x86_64: fails on PIE ELF relocations (debug section relocations reference non-existent symbol indices)

  • axl-elf2efi.c (custom tool, in tools/): works for simple binaries but has edge cases with BSS/section sizing

  • gnu-efi linker script + objcopy: incompatible with EDK2-compiled libs (-fpie vs -fPIC relocation mismatch)

  • llvm-objcopy: doesn’t support efi-app-* output targets