efipack

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 9, 2026 License: BSD-3-Clause Imports: 9 Imported by: 0

README

go-coff/efipack

CI Go Reference

Pure-Go library that compresses PE32+/EFI binaries into a self-extracting PE32+/EFI image — the UPX-equivalent that does not exist anywhere else for this format. Designed to mitigate the EDK2 OVMF amd64 CpuPageTableLib #GP that fires on LoadImage / StartImage of sufficiently large EFI binaries (cloud-boot M6.2 milestone).

Status — PR1 (library skeleton)

This revision ships the host side only:

  • compression API (Pack, Options, PackResult),
  • PE32+/EFI envelope assembly (DOS stub + PE signature + COFF + optional header + section table + .stub placeholder + .payload body),
  • arch detection (amd64, arm64, riscv64, loong64),
  • a swappable body compressor (default Flate; LZFSE, LZ4 reserved),
  • round-trip tests proving the bytes in .payload decompress back to the original input.

The output is not yet a runnable EFI: the .stub section contains a TODO_STUB placeholder. The runtime per-arch decompressor stub lands in PR2; the pectl pack CLI subcommand and the cross-arch boot-smoke matrix land in PR3.

API surface

import "github.com/go-coff/efipack"

// Detect the architecture of an input PE32+ from its COFF header.
arch, err := efipack.InferArch(peBytes)
// arch ∈ {AmdArch, ArmArch, RiscvArch, LoongArch}

// Pack a PE32+ binary into a self-extracting envelope.
in, _ := os.Open("BOOTX64.EFI")
out, _ := os.Create("BOOTX64-packed.EFI")
res, err := efipack.Pack(in, out, efipack.Options{
    Compressor: efipack.Flate, // default; zero stub cost
    Level:      0,             // 0 → codec default
})
// res.OriginalSize, res.CompressedSize, res.PackedSize, res.Arch, res.Compressor
Type / constant Purpose
Pack(in, out, opts) (PackResult, error) host-side compress + envelope
Options{Compressor, Level} knobs; zero value = Flate at default level
PackResult{OriginalSize, CompressedSize, PackedSize, Compressor, Arch} summary
Compressor (Flate / LZFSE / LZ4) algorithm switch
Arch (AmdArch / ArmArch / RiscvArch / LoongArch) PE machine
InferArch(pe) (Arch, error) read COFF.Machine without debug/pe (works on loong64)
ReadPayload(pe) (algo, uncompressedSize, body, err) inverse of Pack's envelope; used by host tests and by PR2's runtime stub
ErrCompressorNotImplemented sentinel for codecs that aren't wired yet — LZ4 only as of M6.2 PR4
.payload wire format
.payload section body:
  magic         [4]byte   "CBP0"   — cloud-boot pack v0
  algo          [4]byte   "FLAT" | "LZFS" | "LZ4 "
  uncompressed  uint64    little-endian — host-input size in bytes
  compressed    uint64    little-endian — body size in bytes
  body          [N]byte   exactly N=compressed bytes; codec-specific stream

PR2's runtime stub reads this header, allocates exactly the right number of EfiBootServicesCode pages, decompresses, then chain-loads via gBS->LoadImage + gBS->StartImage.

Why Flate as the default?

The original M6.2 design picked LZFSE on raw ratio (40.13% vs 39.11%). After review we pivoted: cloud-boot binaries already link compress/gzip via M6.1 embeds and M7 OCI manifest handling, so the Flate-based stub adds zero bytes vs LZFSE's ~100-200 KiB cost. The 1-point ratio gap is negligible against the ~5 MiB binaries we are compressing. LZFSE remains pluggable via the Compressor enum for cases where the host has a larger budget than the cloud-boot baseline.

LZFSE — host-side only (M6.2 PR4)

Compressor = LZFSE was wired in v0.2.0 via github.com/go-compressions/lzfse. It works end-to-end on the host side: Pack produces a structurally valid PE32+ envelope whose .payload decodes back to the original input byte-for-byte. However, the embedded per-arch runtime decompressor stubs (stub/blobs/<arch>.efi.bin) are still Flate-only — a packed binary produced with Compressor = LZFSE will NOT boot under firmware because the stub it inherits doesn't know how to decode LZFS. Rebuilding the runtime stubs with an LZFSE inflate (and re-embedding them) is a deferred follow-up; meanwhile use Compressor = Flate for runnable packed EFIs.

Dependencies

No CGO, no vendoring, stdlib-only otherwise.

License

BSD 3-Clause.

Documentation

Overview

Package efipack compresses PE32+/EFI binaries into a self-extracting PE32+/EFI image. PR1 (this revision) ships the host-side compression API, the PE32+ envelope assembly, and tests; the per-arch runtime decompressor stub lands in PR2 and the pectl CLI integration in PR3.

Index

Constants

This section is empty.

Variables

View Source
var ErrCompressorNotImplemented = errors.New("efipack: compressor not implemented in this build")

ErrCompressorNotImplemented is returned by switchCompressor for algorithms that are valid Compressor constants but not yet wired in this PR. PR2 implements LZFSE; LZ4 is reserved for later.

Functions

func ReadPayload

func ReadPayload(pe []byte) (algo string, uncompressedSize uint64, body []byte, err error)

ReadPayload locates the .payload section in a packed PE32+ image, strips the wire header, and returns the raw compressed body plus the metadata stamped at pack time. It is the inverse of Pack's envelope assembly, used by host tests today and by the runtime stub (PR2) tomorrow. It does NOT decompress — the caller pairs it with a bodyCodec.Decode for the appropriate algorithm.

Types

type Arch

type Arch int

Arch enumerates the four PE32+/EFI target machines efipack ships (or will ship, once PR2 lands the per-arch decompressor stubs).

const (
	// AmdArch is amd64 / x86_64. PE COFF Machine = 0x8664.
	AmdArch Arch = iota
	// ArmArch is arm64 / aarch64. PE COFF Machine = 0xaa64.
	ArmArch
	// RiscvArch is riscv64. PE COFF Machine = 0x5064.
	RiscvArch
	// LoongArch is loongarch64. PE COFF Machine = 0x6264.
	LoongArch
)

func InferArch

func InferArch(pe []byte) (Arch, error)

InferArch reads the PE/COFF header of pe and returns the detected Arch. It does the minimum amount of parsing required to read the Machine field — no debug/pe round-trip, so it accepts machines such as loong64 (0x6264) that debug/pe rejects.

func (Arch) String

func (a Arch) String() string

String returns a short stable name for the architecture. Used in error messages and in the per-arch stub blob name (see bodyCodec.StubBlobName).

type Compressor

type Compressor int

Compressor is the body-compression algorithm used by Pack.

The default is Flate; it costs zero additional bytes in the decompressor stub because cloud-boot binaries already link compress/gzip via M6.1's embedded payloads and M7's OCI manifest handling. Alternatives (LZFSE, LZ4) can be wired in by adding a constant + case in switchCompressor.

const (
	// Flate uses stdlib compress/flate. Default; zero stub cost.
	Flate Compressor = iota
	// LZFSE uses github.com/go-compressions/lzfse. Optional; the
	// decompressor stub grows by ~100-200 KiB. Not yet wired (PR2).
	LZFSE
	// LZ4 is reserved for a future tiny-payload optimisation.
	LZ4
)

func (Compressor) String

func (c Compressor) String() string

String returns a short stable name for the compressor.

type Options

type Options struct {
	Compressor Compressor // defaults to Flate
	Level      int        // compression level passed to the underlying codec; 0 = codec default
}

Options controls Pack. The zero value is sensible (Flate at the stdlib default level).

type PackResult

type PackResult struct {
	OriginalSize   int64
	CompressedSize int64 // size of the compressed body inside .payload (excludes the 24-byte header)
	PackedSize     int64 // size of the output PE32+ on disk
	Compressor     Compressor
	Arch           Arch
}

PackResult summarises a successful Pack call. Sizes are in bytes.

PR1 leaves StubSize at 0 because the .stub section is the placeholder; PR2 will add a StubSize field carrying the real per-arch blob length.

func Pack

func Pack(in io.Reader, out io.Writer, opts Options) (PackResult, error)

Pack reads a PE32+/EFI image from in, compresses it, and writes a self-extracting PE32+/EFI envelope to out.

PR1 acceptance: the output is structurally a valid PE32+ image (correct DOS stub, PE signature, COFF header with the input arch's Machine field, optional header, section table with `.stub` and `.payload`), but the `.stub` section is a placeholder containing only the sentinel "TODO_STUB". Firmware will load the image, jump to the entry point, and fault — by design. PR2 replaces the .stub body with the real per-arch decompressor blob, after which the output becomes a runnable self-extracting EFI.

Round-trip is guaranteed today: the bytes inside .payload, when fed back through the matching bodyCodec.Decode, reproduce the input byte-for-byte. This is what the host tests exercise.

Directories

Path Synopsis
Package stub embeds the per-arch self-extracting decompressor blobs produced by github.com/cloud-boot/tamago-uefi/cmd/efipackstub.
Package stub embeds the per-arch self-extracting decompressor blobs produced by github.com/cloud-boot/tamago-uefi/cmd/efipackstub.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL