amiga-bootcamp/03_loader_and_exec_format/hunk_format.md
2026-04-26 14:46:18 -04:00

27 KiB
Raw Blame History

← Home · Loader & HUNK Format

HUNK Binary Format — Complete Specification

Overview

The HUNK format is the binary container format used throughout AmigaOS. It is not a single file type — it covers two very different kinds of file that happen to share the same record structure:

File kind Extension First longword Can be executed?
Executable — program, shared library, device driver (none), .library, .device $000003F3 (HUNK_HEADER) Yes — loaded directly by dos.library LoadSeg()
Object file — compiler/assembler output, needs linking .o $000003E7 (HUNK_UNIT) No — must be linked first to produce an executable
Static library archive — collection of object files .lib $000003FA (HUNK_LIB) No — linker input only

An object file (.o) is intermediate output from a compiler. It contains relocatable code and unresolved external references. A linker (slink, vlink) combines one or more .o files with library archives into a final executable.

The format is a linear stream of hunk records, each identified by a 32-bit type word followed by type-specific data.


Magic Number — All Valid First Longword Values

Tools and the OS identify a HUNK file by reading its first 32-bit longword. There are exactly three valid opening values:

First longword Hex Dec Constant File type Who reads it
$000003F3 0x3F3 1011 HUNK_HEADER Loadable executable — program, .library, .device dos.library InternalLoadSeg()
$000003E7 0x3E7 999 HUNK_UNIT Relocatable object file (.o) — compiler/assembler output Linker (slink, vlink)
$000003FA 0x3FA 1018 HUNK_LIB Static library archive (.lib) — collection of .o files Linker only

Any other first longword means the file is not a valid HUNK file. InternalLoadSeg will return an error.

Note

Only HUNK_HEADER files can be passed to LoadSeg(). Passing a .o object file or a .lib archive to LoadSeg() will fail — those are consumed exclusively by the linker at build time, never at runtime.

What $000003F3 means exactly

The value $000003F3 = decimal 1011 = the constant HUNK_HEADER. Nothing about this value is arbitrary — it is the hunk type code for the header record, used as the magic number because the header is always the first hunk in an executable.

What $000003E7 means exactly

The value $000003E7 = decimal 999 = HUNK_UNIT. This marks the start of one relocatable compilation unit. A .o file may contain multiple HUNK_UNIT records, one per independently-compiled module (though most compilers emit exactly one per file).

Checking the magic yourself

# Check file type from the command line:
python3 -c "
import struct, sys
data = open(sys.argv[1], 'rb').read(4)
tag  = struct.unpack('>I', data)[0]
names = {0x3F3:'HUNK_HEADER (executable)', 0x3E7:'HUNK_UNIT (object file)', 0x3FA:'HUNK_LIB (library archive)'}
print(f'{sys.argv[1]}: {names.get(tag, f\"UNKNOWN ({tag:#010x})\")}')
" mybinary

Hunk Type Codes

Source header: dos/doshunks.h (NDK 3.9). Every hunk record starts with one of these 32-bit tag values. The file is a linear stream — the loader reads tag → payload → next tag, until the file ends.


Terminology

Term Meaning
longword 32-bit (4-byte) value — the native word size of the 68000
BPTR BCPL pointer — byte address right-shifted by 2 (always longword-aligned). Dereference: real_addr = bptr_value << 2
ULONG Unsigned 32-bit integer
UBYTE Unsigned 8-bit byte
size in longs Content length as a count of 4-byte longwords. Bytes = longs × 4
Exec Appears in loadable executables only (starts with HUNK_HEADER)
Obj Appears in relocatable object files only (starts with HUNK_UNIT)
Both Valid in either context

Group 1 — Object File Framing

These two tags appear only in .o files. Never in a final linked executable.

Hex Dec Constant Wire format Description
$3E7 999 HUNK_UNIT [tag] [name_len_longs] [name_bytes…] Start of a relocatable object unit. Always the very first record in a .o file — the object-file equivalent of HUNK_HEADER. The name field names the compilation unit (e.g. "main.o"). A single .o file may contain multiple HUNK_UNIT records.
$3E8 1000 HUNK_NAME [tag] [name_len_longs] [name_bytes…] Section name label. Optional; assigns a human-readable name to the following section. The linker uses it for map files and diagnostics.

Group 2 — Content Sections

Carry actual program data. Valid in both executables and object files. The type longword may have HUNKF_CHIP / HUNKF_FAST ORed into its upper bits — see Memory Placement Flags.

Hex Dec Constant Payload Description
$3E9 1001 HUNK_CODE [tag] [size_longs] [code_bytes × size×4] Machine-code section. The loader allocates RAM, copies the bytes, then applies any HUNK_RELOC32 that follows. Holds 68k instructions — never data.
$3EA 1002 HUNK_DATA [tag] [size_longs] [data_bytes × size×4] Initialized read/write data. Global variables with non-zero values, string literals, jump tables, etc. Any embedded pointers to other hunks require HUNK_RELOC32 fixups.
$3EB 1003 HUNK_BSS [tag] [size_longs] (no data bytes) Uninitialized data (zero-fill). Only the size is stored — no bytes in the file. The loader calls AllocMem(..., MEMF_CLEAR). A 64 KB zero array costs 4 bytes on disk.

Group 3 — Relocation Records

Tell the loader which longwords inside the current hunk need to be patched with the actual load address of another hunk. Without relocation, all cross-hunk pointers would point to wrong addresses after the OS places code at a non-zero address.

Hex Dec Constant Alias Field width Description
$3EC 1004 HUNK_RELOC32 HUNK_ABSRELOC32 LONG (32-bit) Absolute 32-bit fixup — the most common type. Wire format: [tag] { [count] [hunk_idx] [offset_0] … [offset_n] } … [0]. Each offset points to a longword in the current hunk; *(ULONG*)(base+offset) += target_hunk_base. Terminated by count=0.
$3ED 1005 HUNK_RELOC16 HUNK_RELRELOC16 LONG (32-bit) 16-bit absolute fixup. Same format as above but patches a UWORD. Rare — 68k branch displacements are PC-relative and need no reloc.
$3EE 1006 HUNK_RELOC8 HUNK_RELRELOC8 LONG (32-bit) 8-bit fixup. Patches a UBYTE. Essentially unused — no 68k instruction has an 8-bit absolute address field.
$3F7 1015 HUNK_DREL32 WORD (16-bit) Compact 32-bit reloc. Same semantics as HUNK_RELOC32 but count, hunk index, and offsets are stored as 16-bit WORDs, halving the table size. Valid only when all hunk offsets fit in 16 bits (hunk < 64 KB). Generated by BLink.
$3F8 1016 HUNK_DREL16 WORD (16-bit) Compact 16-bit reloc with WORD-sized fields. Very rare.
$3F9 1017 HUNK_DREL8 WORD (16-bit) Compact 8-bit reloc with WORD-sized fields. Essentially unused.
$3FC 1020 HUNK_RELOC32SHORT WORD (16-bit) Compact absolute 32-bit reloc with WORD offsets. Semantically identical to HUNK_RELOC32 with WORD fields. Default output of vasm/vlink when all offsets fit in 16 bits. Preferred over HUNK_DREL32 in OS 3.x-era tools.
$3FD 1021 HUNK_RELRELOC32 LONG (32-bit) PC-relative 32-bit reloc. Patch: *(LONG*)(base+off) += target_base (base+off+4). Used by GCC -fPIC and PIC shared libraries.
$3FE 1022 HUNK_ABSRELOC16 LONG (32-bit) Absolute 16-bit fixup. Patches a UWORD with the low 16 bits of the target's absolute address. Required for MOVE.W #abs_addr,Dn patterns. Rare.

Group 4 — External Symbol Table

Object files only — never present in a linked executable.

Hex Dec Constant Description
$3EF 1007 HUNK_EXT Import + export symbol table for a compilation unit. A single stream encodes both sides: exports declare symbols defined in this hunk (type EXT_DEF, EXT_ABS, EXT_RES); imports list unresolved references the linker must satisfy from other objects (type EXT_REF32, EXT_REF16, EXT_REF8, EXT_COMMON). The linker resolves all imports and emits HUNK_RELOC32 records in the output executable. Wire format: [tag] { [type_and_namelen] [name_bytes…] [value_or_refcount] [ref_offsets…] } … [0]. See hunk_ext_deep_dive.md for sub-type encoding.

Group 5 — Debug and Metadata

Completely ignored by the OS loader. Strip with slink NODBG or m68k-amigaos-strip --strip-debug to reduce file size.

Hex Dec Constant Payload Description
$3F0 1008 HUNK_SYMBOL [tag] { [namelen_longs] [name_bytes…] [value] } … [0] Local symbol table. Maps label names → offsets within this hunk. Consumed by MonAm, wack, IDA Pro. Terminated by namelen=0.
$3F1 1009 HUNK_DEBUG [tag] [size_longs] [format_tag] [data_bytes…] Opaque debug block. The leading format_tag longword identifies the format: $3D415053 = SAS/C stabs; $3D474343 = GCC stabs; $3D574152 = Warp/Storm C. See hunk_debug_info.md.

Group 6 — Structural Records

Hex Dec Constant Payload Description
$3F2 1010 HUNK_END [tag] only — no payload Required end-of-hunk marker. Every code/data/BSS hunk (and all reloc/symbol records that follow it) must close with HUNK_END. The loader advances to the next segment slot on reading it.
$3F3 1011 HUNK_HEADER [tag] [0] [num_hunks] [first_hunk] [last_hunk] [size_longs × n] Executable magic number and segment size table. Must be the very first longword in a loadable executable. The zero longword is the resident-library list (always 0 in practice). num_hunks = total hunks; first_hunk/last_hunk = inclusive range; followed by one size-in-longs per hunk.
$3F5 1013 HUNK_OVERLAY [tag] [size_longs] [overlay_table_data…] Overlay descriptor table. Follows the resident hunks; describes groups of code swapped in from disk on demand. Allows programs larger than available RAM. Obsolete — prefer OpenLibrary().
$3F6 1014 HUNK_BREAK [tag] only — no payload End of overlay tree sentinel. InternalLoadSeg needs this to know where the overlay descriptor ends and the per-overlay hunk data begins.

Note

Value $3F4 (decimal 1012) is unused — the numbering skips it intentionally.


Group 7 — Static Library Archive

Linker input only. Never loaded by LoadSeg() at runtime.

Hex Dec Constant Description
$3FA 1018 HUNK_LIB Static library archive container. A sequence of embedded HUNK_UNIT object files, each preceded by its size in longwords. Produced by ar68k or the AmigaOS join command. The linker extracts only the units needed to resolve outstanding HUNK_EXT imports.
$3FB 1019 HUNK_INDEX Symbol index for HUNK_LIB. A packed string table plus a per-unit map of exported symbol names → unit byte offsets. Lets the linker locate a function without scanning every object in the archive. Always immediately follows the HUNK_LIB it describes.

Memory Placement Flags

The type longword for these three hunks can encode a memory placement request in its upper bits. The loader passes the corresponding MEMF_* flags to AllocMem.

Bit layout of the type longword:

  31      30      29      28 ............. 0
  ┌───┐  ┌────┐  ┌────┐  ┌─────────────────┐
  │ 0 │  │CHIP│  │FAST│  │  Hunk type code  │
  └───┘  └────┘  └────┘  └─────────────────┘
Bit Constant Value Meaning
30 HUNKF_CHIP 1L<<30 Hunk must be in Chip RAM — required for anything the custom chips DMA from (bitmaps, audio, copper lists, sprites)
29 HUNKF_FAST 1L<<29 Hunk prefers Fast RAM — use for pure CPU data where DMA is not needed; avoids Chip RAM bus contention
30+29 both set (extended) 0x60000000 Next longword in the file contains full MEMF_* flags for AllocMem — allows any combination
neither (default) 0 MEMF_PUBLIC — any available memory

Additional helper constants:

Constant Value Meaning
HUNKB_CHIP 30 Bit number (use with bset/btst)
HUNKB_FAST 29 Bit number

MEMF_* flags used in extended mode (from exec/memory.h):

Constant Value Meaning
MEMF_ANY 0 No preference — any accessible memory
MEMF_PUBLIC 1<<0 Must be accessible by all tasks and hardware
MEMF_CHIP 1<<1 Chip RAM — reachable by DMA controllers
MEMF_FAST 1<<2 Fast RAM — CPU-only, no chip DMA contention
MEMF_CLEAR 1<<16 Zero-fill on allocation
MEMF_LARGEST 1<<17 Return the single largest contiguous free block
MEMF_REVERSE 1<<18 Allocate from the top of the region (high addresses first)
MEMF_TOTAL 1<<19 AvailMem: report total installed rather than current free

Example: force a code hunk into Chip RAM:

type longword = HUNK_CODE | HUNKF_CHIP
             = 0x000003E9 | 0x40000000
             = 0xC00003E9

Why would code go in Chip RAM? Rare, but needed on an A500 with no Fast RAM — everything including code must fit in the 512 KB Chip RAM.


Quick Reference Table

Hex Dec Constant Alias Context Purpose
$3E7 999 HUNK_UNIT Obj Start of relocatable object unit
$3E8 1000 HUNK_NAME Obj Name label for the following section
$3E9 1001 HUNK_CODE Both Machine-code section
$3EA 1002 HUNK_DATA Both Initialized read/write data
$3EB 1003 HUNK_BSS Both Uninitialized data (size only, no bytes)
$3EC 1004 HUNK_RELOC32 HUNK_ABSRELOC32 Both Absolute 32-bit address fixup list
$3ED 1005 HUNK_RELOC16 HUNK_RELRELOC16 Obj 16-bit address fixup list
$3EE 1006 HUNK_RELOC8 HUNK_RELRELOC8 Obj 8-bit fixup list
$3EF 1007 HUNK_EXT Obj Import + export symbol table
$3F0 1008 HUNK_SYMBOL Both Local debug symbol table
$3F1 1009 HUNK_DEBUG Both Opaque debug data (stabs / DWARF)
$3F2 1010 HUNK_END Both End-of-hunk marker — required
$3F3 1011 HUNK_HEADER Exec Executable magic number + size table
(none) 1012 (unused) Gap in the numbering
$3F5 1013 HUNK_OVERLAY Exec Overlay group descriptor
$3F6 1014 HUNK_BREAK Exec End of overlay tree
$3F7 1015 HUNK_DREL32 Both Compact 32-bit reloc (WORD-width fields)
$3F8 1016 HUNK_DREL16 Obj Compact 16-bit reloc
$3F9 1017 HUNK_DREL8 Obj Compact 8-bit reloc
$3FA 1018 HUNK_LIB Obj Static library archive
$3FB 1019 HUNK_INDEX Obj Symbol index for HUNK_LIB
$3FC 1020 HUNK_RELOC32SHORT Both Compact abs 32-bit reloc (WORD offsets)
$3FD 1021 HUNK_RELRELOC32 Both PC-relative 32-bit reloc
$3FE 1022 HUNK_ABSRELOC16 Both Absolute 16-bit address patch

Context key: Exec = loadable executable · Obj = object file (HUNK_UNIT stream) · Both = either

/* dos/doshunks.h — NDK 3.9 */

#define HUNK_UNIT           999   /* 0x3E7 — start of relocatable object (.o) */
#define HUNK_NAME          1000   /* 0x3E8 — name of this object unit/section */
#define HUNK_CODE          1001   /* 0x3E9 — machine code section */
#define HUNK_DATA          1002   /* 0x3EA — initialized data section */
#define HUNK_BSS           1003   /* 0x3EB — uninitialized data (zero-fill) */
#define HUNK_RELOC32       1004   /* 0x3EC — 32-bit absolute relocation table */
#define HUNK_ABSRELOC32    HUNK_RELOC32   /* alias */
#define HUNK_RELOC16       1005   /* 0x3ED — 16-bit relocation table */
#define HUNK_RELRELOC16    HUNK_RELOC16   /* alias */
#define HUNK_RELOC8        1006   /* 0x3EE — 8-bit relocation table */
#define HUNK_RELRELOC8     HUNK_RELOC8    /* alias */
#define HUNK_EXT           1007   /* 0x3EF — external symbol table (obj only) */
#define HUNK_SYMBOL        1008   /* 0x3F0 — local debug symbol table */
#define HUNK_DEBUG         1009   /* 0x3F1 — arbitrary debug data (stabs etc.) */
#define HUNK_END           1010   /* 0x3F2 — end-of-hunk marker */
#define HUNK_HEADER        1011   /* 0x3F3 — executable file header (magic) */
                                  /* 0x3F4 — unused */
#define HUNK_OVERLAY       1013   /* 0x3F5 — overlay tree descriptor */
#define HUNK_BREAK         1014   /* 0x3F6 — end of overlay tree */
#define HUNK_DREL32        1015   /* 0x3F7 — compact 32-bit reloc (WORD fields) */
#define HUNK_DREL16        1016   /* 0x3F8 — compact 16-bit reloc */
#define HUNK_DREL8         1017   /* 0x3F9 — compact 8-bit reloc */
#define HUNK_LIB           1018   /* 0x3FA — static library archive */
#define HUNK_INDEX         1019   /* 0x3FB — symbol index for HUNK_LIB */
#define HUNK_RELOC32SHORT  1020   /* 0x3FC — compact 32-bit absolute reloc */
#define HUNK_RELRELOC32    1021   /* 0x3FD — PC-relative 32-bit reloc */
#define HUNK_ABSRELOC16    1022   /* 0x3FE — absolute 16-bit reloc */

/* Memory placement flags — OR'd into HUNK_CODE/DATA/BSS type longword */
#define HUNKB_FAST    29           /* bit number for Fast RAM flag */
#define HUNKB_CHIP    30           /* bit number for Chip RAM flag */
#define HUNKF_FAST    (1L<<29)    /* request Fast RAM for this hunk */
#define HUNKF_CHIP    (1L<<30)    /* request Chip RAM for this hunk */

Terminology used in this document

  • longword — a 32-bit (4-byte) value; the natural word size of the 68k
  • BPTR — a BCPL pointer: the byte address right-shifted by 2 (always longword-aligned). Convert: byte_addr = BPTR_value << 2
  • ULONG — unsigned 32-bit integer (unsigned long on the 68k)
  • UBYTE — unsigned 8-bit byte
  • size in longs — the hunk content length expressed as a count of 4-byte longwords (multiply by 4 to get bytes)

Quick Reference Table

Hex Dec Name Alias Context Purpose
$3E7 999 HUNK_UNIT Obj Start of relocatable object unit
$3E8 1000 HUNK_NAME Obj Name string for this unit/section
$3E9 1001 HUNK_CODE Both Executable machine code section
$3EA 1002 HUNK_DATA Both Initialized read/write data section
$3EB 1003 HUNK_BSS Both Uninitialized data — size only, no bytes
$3EC 1004 HUNK_RELOC32 HUNK_ABSRELOC32 Both Absolute 32-bit address patch list
$3ED 1005 HUNK_RELOC16 HUNK_RELRELOC16 Obj 16-bit address patch list
$3EE 1006 HUNK_RELOC8 HUNK_RELRELOC8 Obj 8-bit patch list
$3EF 1007 HUNK_EXT Obj Import + export symbol table
$3F0 1008 HUNK_SYMBOL Both Local debug symbol table
$3F1 1009 HUNK_DEBUG Both Opaque debug data (stabs, DWARF)
$3F2 1010 HUNK_END Both End of this hunk — required
$3F3 1011 HUNK_HEADER Exec Executable magic + size table
$3F5 1013 HUNK_OVERLAY Exec Overlay group descriptor
$3F6 1014 HUNK_BREAK Exec End of overlay tree
$3F7 1015 HUNK_DREL32 Both Compact 32-bit reloc (WORD-sized fields)
$3F8 1016 HUNK_DREL16 Obj Compact 16-bit reloc
$3F9 1017 HUNK_DREL8 Obj Compact 8-bit reloc
$3FA 1018 HUNK_LIB Obj Static library archive (.lib)
$3FB 1019 HUNK_INDEX Obj Symbol index for HUNK_LIB
$3FC 1020 HUNK_RELOC32SHORT Both Compact abs 32-bit reloc (WORD offsets)
$3FD 1021 HUNK_RELRELOC32 Both PC-relative 32-bit reloc
$3FE 1022 HUNK_ABSRELOC16 Both Absolute 16-bit address patch

Context key: Exec = loadable executable only · Obj = object file (HUNK_UNIT stream) only · Both = valid in either

Memory Type Flags on HUNK_CODE / HUNK_DATA / HUNK_BSS

The type longword for code, data, and BSS hunks can carry memory placement hints in its upper two bits:

Bits 31..0 of the type longword:
  bit 31: unused (always 0)
  bit 30: HUNKF_CHIP  — loader must use AllocMem(..., MEMF_CHIP)
  bit 29: HUNKF_FAST  — loader prefers AllocMem(..., MEMF_FAST)
  bits 28..0: the hunk type constant (e.g. 0x3E9 for HUNK_CODE)
Bit 30 Bit 29 Meaning AllocMem flags used
0 0 Any memory (default) MEMF_PUBLIC
1 0 Chip RAM required MEMF_CHIP
0 1 Fast RAM preferred MEMF_FAST
1 1 Extended — next longword holds full MEMF_* flags caller reads extra longword

MEMF_* flag constants (from exec/memory.h, used in extended mode):

/* exec/memory.h — NDK39 */
#define MEMF_PUBLIC   (1L<<0)   /* any accessible memory */
#define MEMF_CHIP     (1L<<1)   /* Chip RAM (DMA-reachable by custom chips) */
#define MEMF_FAST     (1L<<2)   /* Fast RAM (CPU-only; faster than Chip) */
#define MEMF_VIRTUAL  (1L<<3)   /* not used on classic AmigaOS */
#define MEMF_CLEAR    (1L<<16)  /* zero-fill the allocation */
#define MEMF_LARGEST  (1L<<17)  /* return the single largest free block */
#define MEMF_REVERSE  (1L<<18)  /* allocate from top of list (high address) */
#define MEMF_TOTAL    (1L<<19)  /* AvailMem: return total, not largest */
#define MEMF_ANY      0L        /* no preference — equivalent to MEMF_PUBLIC */

Example: HUNK_CODE forced into Chip RAM:

type longword = HUNK_CODE | HUNKF_CHIP
             = 0x000003E9 | 0x40000000
             = 0xC00003E9

HUNK_HEADER — Executable Header

Appears at the very start of an executable file:

$000003F3           HUNK_HEADER magic
$00000000           Resident library list (always 0 for loadable executables)
<num_hunks>         Number of hunks in the executable (longword)
<first_hunk>        Index of first loadable hunk (usually 0)
<last_hunk>         Index of last loadable hunk (= num_hunks - 1)
<size_0>            Size of hunk 0 in longwords
<size_1>            Size of hunk 1 in longwords
...
<size_n>            Size of last hunk in longwords

Size longword bit encoding:

bits 31-30: Memory type (00=ANY, 10=CHIP, 01=FAST, 11=extended)
bits 29-0:  Size in 32-bit longwords

Example header (2 hunks: code + data):

Offset  Bytes           Meaning
$00:    00 00 03 F3     HUNK_HEADER
$04:    00 00 00 00     no resident library list
$08:    00 00 00 02     2 hunks
$0C:    00 00 00 00     first hunk index = 0
$10:    00 00 00 01     last hunk index = 1
$14:    00 00 00 50     hunk 0: 0x50 longwords = 0x140 bytes (code)
$18:    00 00 00 10     hunk 1: 0x10 longwords = 0x40 bytes (data)

HUNK_CODE / HUNK_DATA

<type>              HUNK_CODE ($3E9) or HUNK_DATA ($3EA)
<num_longs>         Size in longwords
<data...>           Raw code or data bytes (num_longs × 4 bytes)
HUNK_END ($3F2)     End of this hunk

HUNK_BSS

<type>              HUNK_BSS ($3EB)
<num_longs>         Size in longwords (allocate this many bytes, zero-filled)
HUNK_END ($3F2)     End of this hunk

No data follows — BSS is zero-initialized by the loader.


HUNK_RELOC32

32-bit absolute relocation records. These patch addresses in the code/data that reference other hunks.

<HUNK_RELOC32>      $000003EC
<num_offsets>       Number of offsets to patch for the next target hunk
<target_hunk>       Index of the hunk whose base address is added
<offset_0>          Byte offset within current hunk to patch (longword)
<offset_1>
...
<num_offsets=0>     Terminator — end of relocation table
HUNK_END ($3F2)

Example: Code hunk references data hunk. Two addresses need patching:

$000003EC           HUNK_RELOC32
$00000002           2 offsets to patch
$00000001           target = hunk 1 (data hunk)
$00000010           patch at offset $10 in code hunk
$00000024           patch at offset $24 in code hunk
$00000000           end of reloc list
$000003F2           HUNK_END

At load time: *(ULONG *)(code_base + 0x10) += data_base


HUNK_SYMBOL

Optional local symbol table for debugging:

<HUNK_SYMBOL>       $000003F0
<name_len>          String length in longwords (1N)
<name...>           Symbol name (padded to longword boundary)
<value>             Symbol value (offset within hunk)
...
<0>                 Zero name_len terminates
HUNK_END ($3F2)

Complete Executable Structure (Annotated)

[File start]
HUNK_HEADER
  num_hunks = 3         ; code, data, BSS
  sizes: [0x200, 0x80, 0x100]

--- Hunk 0: Code ---
HUNK_CODE
  0x200 bytes of machine code
HUNK_RELOC32
  Patches in code referencing hunk 1 (data)
  Patches in code referencing hunk 2 (BSS)
HUNK_SYMBOL              (optional)
  _main at offset 0
  _foo  at offset 0x40
HUNK_END

--- Hunk 1: Data ---
HUNK_DATA
  0x80 bytes of initialized data
HUNK_RELOC32
  Patches in data (e.g., pointer tables) referencing code hunk
HUNK_END

--- Hunk 2: BSS ---
HUNK_BSS
  0x100 longwords = 1024 bytes (zero-filled at load time)
HUNK_END

[File end]

File Format Diagram

block-beta
    columns 1
    header["HUNK_HEADER\n(sizes + memory types)"]
    code["HUNK_CODE\n(machine code bytes)"]
    reloc0["HUNK_RELOC32\n(patch list for code)"]
    sym["HUNK_SYMBOL\n(optional debug names)"]
    end0["HUNK_END"]
    data["HUNK_DATA\n(initialized data)"]
    reloc1["HUNK_RELOC32\n(patch list for data)"]
    end1["HUNK_END"]
    bss["HUNK_BSS\n(zero-fill size)"]
    end2["HUNK_END"]

References