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
[ ]
--verboseflag 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 --netflag to link net module with extra GUIDs[ ]
axl-cc --type driver|runtimefor 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 |
|
gBS/gST/gRT |
BS/ST/RT |
Memory |
|
AllocatePool/FreePool |
BS->AllocatePool/FreePool |
Console |
|
gST->ConOut->OutputString |
ST->ConOut->OutputString |
Time |
|
gRT->GetTime |
RT->GetTime |
File I/O |
|
ShellLib functions |
EFI_SHELL_PROTOCOL calls |
Wide-string |
|
BaseLib (StrLen etc.) |
gnu-efi RtStrLen etc. |
Self-contained replacements (no backend needed):
AsciiStriCmp→axl_strcasecmp(new portable function)AsciiStrHexToUint64S→ self-implemented hex parserInterlockedCompareExchange64→__sync_val_compare_and_swap(GCC builtin)CpuPause/MemoryFence→ GCC__builtin_ia32_pause/__sync_synchronizeZeroMem→ 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 sourceEDK2 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#ifdefor 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)
objcopydoes NOT work as a fallback (fails on PIE relocations)
Option 2: Clang + LLD (recommended)
Clang compiles to COFF with
-target x86_64-unknown-windowslld-linkproduces PE/COFF.efidirectlyNo 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 |
|
Build environment, base libraries |
libaxl |
|
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:
Build from source (developer workflow):
git clone axl-sdk ./scripts/install.sh # produces out/ with everything needed
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, intools/): works for simple binaries but has edge cases with BSS/section sizinggnu-efilinker script + objcopy: incompatible with EDK2-compiled libs (-fpievs-fPICrelocation mismatch)llvm-objcopy: doesn’t supportefi-app-*output targets