AmigaOS Hunk Overlays

An overlaid executable splits into a small root node that stays resident for the lifetime of the program, plus one or more overlay nodes that are loaded on demand and released when no longer needed. This lets large programs fit in memory budgets that would not admit them if loaded in full. Overlay management is implemented inside the root node, not in dos.library; the loader only knows the file-format conventions.

dos.library provides three overlay managers:

Manager Linkers Notes
Hierarchical ALink, BLink, SLink (SAS/C) Tree-structured; a path from the root to one leaf must be in memory at a time
MANX (flat) Aztec C Independent flat resources; uses self-modifying code
Custom Application code Anything implementing the OverlayHeader protocol

The MANX overlay manager is documented as non-functional on 68030 and later: it patches instruction opcodes without flushing the instruction cache. It is preserved here as a historical reference; do not use it on modern hardware.

File layout

An overlaid binary is a sequence of nodes, each of which is a Hunk format sub-file:

[ HUNK_HEADER ]                              ┐
[ HUNK_CODE | HUNK_DATA | HUNK_BSS … ]       │ root node
[ HUNK_RELOC32 … ]                           │
[ HUNK_END … ]                               │
[ HUNK_OVERLAY ]                             ┘  ← loader stops at the root

[ HUNK_HEADER ]                              ┐
[ HUNK_CODE | HUNK_DATA | HUNK_BSS … ]       │ overlay node 1
[ HUNK_RELOC32 … ]                           │
[ HUNK_END … ]                               │
[ HUNK_BREAK ]                               ┘  ← loader stops

[ HUNK_HEADER ]                              ┐
[ … ]                                        │ overlay node 2
[ HUNK_BREAK ]                               ┘
… until EOF

The loader distinguishes "root" from "overlay node" purely by whether it encounters HUNK_OVERLAY (root boundary) or HUNK_BREAK (overlay boundary) at the end of a node. Both terminate loading; only the root contains the overlay metadata.

The OverlayHeader

When the loader finishes a root node, it injects a small structure at the start of the first segment of the root node. C and assembler programs that include an overlay manager therefore begin their root segment with two long-words that are not code:

struct OverlayHeader {
    UWORD         oh_Jump[2];      /* branch around the header (valid 68K opcode) */
    LONG          oh_Magic;        /* must be 0x0000abcd for UnLoadSeg() */
    BPTR          oh_FileHandle;   /* loader fills: BPTR to the open filehandle */
    struct OVTab *oh_OVTab;        /* loader fills: pointer into HUNK_OVERLAY payload */
    BPTR          oh_Segments;     /* loader fills: BPTR to the segment table */
    BPTR          oh_GV;           /* loader fills: BPTR to dos.library global vector */
};
Field Filled by Meaning
oh_Jump Application A branch opcode that skips over the rest of the header. CPU begins execution at offset 0 of the first segment; without a jump it would land in the data and crash.
oh_Magic = 0xabcd Application Marker. UnLoadSeg() detects overlaid executables from this value and additionally calls Close() on oh_FileHandle. Do not include this magic in non-overlaid binaries — it will confuse their unloader.
oh_FileHandle Loader BPTR to the file from which the root was loaded (or the first argument of InternalLoadSeg()). Used by the overlay manager to load subsequent nodes. Must remain open for the life of the program.
oh_OVTab Loader Pointer to the runtime form of the HUNK_OVERLAY payload (see below).
oh_Segments Loader BPTR to the segment table (an array of BPTRs). Indexed by segment number starting at 0.
oh_GV Loader BCPL Global Vector for BCPL overlay managers; unused by C or assembler.

Hierarchical overlay manager — HUNK_OVERLAY payload

For ALink, BLink, and SLink, the HUNK_OVERLAY payload starts with l (size indicator), then od (number of tree levels including the root), then od−1 zero long-words (placeholders for the currently loaded ordinate at each level), then a symbol table.

struct OVTab {
    ULONG ot_TreeDepth;                    /* levels including root (the 'od' field) */
    ULONG ot_Ordinate[od-1];              /* currently loaded ordinate, indexed by level-1 */
    /* followed by the symbol table: */
};

struct SymTab {
    ULONG ot_FilePosition;   /* absolute byte offset of the node's HUNK_HEADER in the file */
    ULONG ot_Reserved[2];
    ULONG ot_Level;          /* tree level (root = 0) */
    ULONG ot_Ordinate;       /* ordinate within the level (1-based) */
    ULONG ot_FirstSegment;   /* index of the first segment of the overlay node */
    ULONG ot_SymbolSegment;  /* segment containing the referenced symbol */
    ULONG ot_SymbolOffset;   /* byte offset of the symbol within that segment */
} [];

A node can be loaded only if it forms a path from the root. To load node l at level n, the manager first unloads the currently loaded node at level n and all of its descendants (in the table, all entries ot_Ordinate[i] ≥ n are cleared), then loads the ancestors of the requested node up to the leaf.

The size l is n_long_words − 1, not n_long_words — an unorthogonal quirk the loader depends on. A single-symbol overlay table is therefore encoded as l = 9, ten long-words total.

Loading an overlay node

seglist = LoadSeg(NULL, oh->oh_Segments, oh->oh_FileHandle);   /* non-standard prototype */

LoadSeg() with a NULL first argument enters its overlay-loading code path. The official include files do not expose the additional arguments; they must be passed via a hidden interface (or a small assembler wrapper).

Before the call, the file pointer of oh_FileHandle must be Seek()-ed to the byte offset of the overlay node's HUNK_HEADER — ot_FilePosition for the hierarchical manager. LoadSeg() does not reposition the file pointer; the caller must.

LoadSeg() does not release the segments already occupying the table entries that the new node will populate. The overlay manager must FreeVec() (or equivalent) the segments whose entries fall inside the range [ot_FirstSegment, ot_FirstSegment + segment_count) before invoking LoadSeg(). The sample implementation below shows the technique.

Unloading an overlay node

void UnloadOverlayNode(struct OverlayHeader *oh, ULONG i)
{
    BPTR *segtbl = (BPTR *)BADDR(oh->oh_Segments);
    BPTR *prev   = (BPTR *)BADDR(segtbl[i - 1]);

    *prev = 0;                          /* unlink the chain */

    while ((prev = (BPTR *)BADDR(segtbl[i++])) != NULL) {
        FreeVec(prev);
        segtbl[i - 1] = 0;              /* clear the table entry */
    }
}

The previous segment (i − 1) is guaranteed to exist because the root node populates at least entry 0.

Unloading the whole program

Calling UnLoadSeg(oh->oh_Segments) (or, more commonly, on the first segment of the root) is sufficient. UnLoadSeg() detects the overlaid case from oh_Magic == 0xabcd, then closes oh_FileHandle, frees the segment table, and walks the segments.

If the program was loaded through InternalLoadSeg() (custom I/O), InternalUnLoadSeg() must be used. Be aware that InternalUnLoadSeg() always calls Close() on oh_FileHandle, even if that handle is not a BPTR to a real file handle. For custom-I/O loads, close the underlying stream manually first, zero oh_FileHandle, then call InternalUnLoadSeg().

Trampolines — calling overlay code from the root

The linker replaces calls to symbols in descendant nodes with trampolines in the root:

jsr  @ovlyMgr
dc.w symbId

@ovlyMgr is the entry point of the overlay manager. On entry, the manager reads the return address (which points at symbId), indexes into oh_OVTab's symbol table to find the target symbol's (level, ordinate, segment, offset), ensures the path is loaded, then rewrites the return address to the real function and RTS-es into it.

Trampolines are therefore how the root can call overlay code: every cross-node call goes through the manager. Direct data references from a parent to a child are not possible; expose data via accessor functions.

MANX (flat) overlay manager

Historical only. Flat, independent resources loaded by segload(), unloaded by freeseg(). The HUNK_OVERLAY payload has a different shape:

struct MANXOVTab {
    ULONG ot_NodeCount;
    struct OvNode {
        ULONG ot_FilePosition;
        UWORD ot_TrampolineOff;   /* offset to first trampoline relative to __H1_org */
        UWORD ot_SymbolOffset;    /* offset to symbol table relative to &ot_NodeCount+1 */
    } ot_Nodes[od-1];
};

followed by a flat symbol table of MANXSymTab { UWORD ot_Segment; UWORD ot_Count; } pairs.

Trampolines start with bsr.w (0x6100) to the overlay manager and are patched to jmp.abs (0x4ef9) when the symbol is loaded, then patched back to bsr.w when unloaded. This self-modification breaks on 68030+ caches.

See Also


Sources: Thomas Richter, "ROM Kernel Reference Manual: AmigaDOS" (First Edition, 2024), Chapter 11.4 — Overlays; Ralph Babel, "The Amiga Guru Book" (1993), Chapter 22. Raw: raw/rkm/rkm-dos-book.md; raw/rkm/guru-book.md Updated: 2026-10-02