14 KiB
Executable Load Pipeline
Overview
This document traces the complete path from user request to a running process: LoadSeg() → memory allocation → relocation → CreateProc() → execution.
Entry Points
| Function | Library | Description |
|---|---|---|
LoadSeg(name) |
dos.library | Load named file, return segment list |
InternalLoadSeg(fh, table, funcarray, stack) |
dos.library | Low-level load from open file handle |
NewLoadSeg(name, tags) |
dos.library (3.1+) | Tagged version of LoadSeg |
UnLoadSeg(seglist) |
dos.library | Free segment list |
CreateNewProc(tags) |
dos.library | Create process from segment list |
RunCommand(seg, stack, args, len) |
dos.library | Run segment in current process context |
Phase 1: Parsing HUNK_HEADER
InternalLoadSeg opens the file and reads the header:
1. Read magic word — must be $000003F3 (HUNK_HEADER)
2. Read resident library list (always 0 for standard executables)
3. Read num_hunks, first_hunk, last_hunk
4. Read size table: num_hunks longwords
Each longword: bits[31:30] = memory type, bits[29:0] = size in longs
The loader allocates one memory block per hunk using AllocMem():
- Memory type from size longword bits →
MEMF_CHIP,MEMF_FAST, orMEMF_ANY - Size = longword_count × 4 bytes
Phase 2: Memory Allocation
For each hunk (from first_hunk to last_hunk):
ULONG size_longs = size_table[i] & ~0xC0000000;
ULONG mem_type = (size_table[i] >> 30) & 3;
ULONG memf;
switch (mem_type) {
case 0: memf = MEMF_PUBLIC; break;
case 1: memf = MEMF_CHIP; break;
case 2: memf = MEMF_FAST; break;
case 3: /* extended: read additional longword for MEMF_ flags */
}
APTR seg_mem = AllocMem(size_longs * 4 + sizeof(BPTR), memf | MEMF_CLEAR);
Each allocation is 4 bytes larger than the hunk data to hold the BPTR link to the next segment.
Phase 3: Loading Hunk Data
For each hunk, the loader reads hunks sequentially from the file:
while not HUNK_END:
switch (hunk_type):
HUNK_CODE, HUNK_DATA:
read num_longs × 4 bytes into segment memory
HUNK_BSS:
already zero-filled by AllocMem(MEMF_CLEAR)
HUNK_RELOC32:
store for Phase 4 (apply after all hunks loaded)
HUNK_SYMBOL, HUNK_DEBUG:
read and discard (or pass to debugger hook)
HUNK_END:
advance to next hunk
Phase 4: Relocation Pass
After all hunks are loaded and their base addresses are known:
for each HUNK_RELOC32 in hunk H:
for each (target_hunk, offsets[]):
base = segment_base[target_hunk]
for each offset:
patch = (ULONG *)(segment_base[H] + offset)
*patch += base /* add actual load address */
This two-pass approach (load all, then relocate) is required because HUNK_RELOC32 entries may reference any hunk, including ones not yet loaded when the reloc entry is encountered.
Phase 5: Segment List Construction
The segments are chained as a BPTR list:
Segment 0 memory:
[BPTR → seg1] (4 bytes)
[code data...]
Segment 1 memory:
[BPTR → 0] NULL = end of list
[data...]
LoadSeg() returns MKBADDR(seg0_mem) — a BPTR to the first segment (the memory address right-shifted by 2, as required by the BCPL pointer convention).
Converting BPTR to real address:
APTR addr = BADDR(seglist); /* = seglist << 2 */
Phase 6: Process Creation
CreateNewProc() (or the old CreateProc()) takes the segment list and creates a new AmigaOS process:
struct Process *proc = CreateNewProcTags(
NP_Seglist, seglist,
NP_Name, "MyProgram",
NP_StackSize, 8192,
NP_Priority, 0,
NP_CommandName, cmd_string,
TAG_DONE);
Internally this calls exec.library MakeNode() and initializes:
Process->pr_SegList— the segment list BPTR- Stack: allocated via
AllocMem(NP_StackSize, MEMF_PUBLIC), stored inpr_Stack tc_SPLower/tc_SPUpper— stack boundstc_SPReg— initial stack pointer (top of stack)pr_GlobVec— global vector (BCPL compat, not used by C programs)pr_CLI— CLI structure if launched from Shell
Phase 7: Entry Point
The process starts executing at the first word of hunk 0 (the first loaded segment). This is not main() — it is the startup code (_start / c.o):
; c.o (SAS/C startup):
_start:
MOVE.L 4.W, A6 ; SysBase
MOVE.L A0, _CommandStr ; raw command line from dos.library
BSR __main ; C runtime init
...
MOVE.L _ExitCode, D0 ; return value
RTS
CLI vs Workbench Launch
| Parameter | CLI Launch | WBStartup |
|---|---|---|
pr_CLI |
Non-NULL, points to CLI struct | NULL |
pr_WBenchMsg |
NULL | Pointer to WBStartup message |
| A0 at entry | Command string pointer | NULL |
| A1 at entry | NULL | Pointer to WBStartup message |
| Return | dos.library handles exit |
Must Forbid(); ReplyMsg(wb_msg) |
Startup code detects the launch type:
if (pr->pr_CLI) {
/* CLI launch: use command string */
} else {
/* WB launch: wait for and reply to WBenchMsg */
WaitPort(&pr->pr_MsgPort);
WBMsg = (struct WBStartup *)GetMsg(&pr->pr_MsgPort);
}
State Machine Diagram
stateDiagram-v2
[*] --> ParseHeader : LoadSeg(name)
ParseHeader --> AllocHunks : HUNK_HEADER valid
AllocHunks --> LoadData : AllocMem() per hunk
LoadData --> Relocate : all hunks read
Relocate --> BuildSegList : all patches applied
BuildSegList --> CreateProc : BPTR chain complete
CreateProc --> Running : AddTask() + Dispatch
Running --> [*] : process exits, UnLoadSeg()
UnLoadSeg — Freeing Memory
UnLoadSeg(seglist);
/* Walks the BPTR chain, FreeMem() each segment block */
The 4-byte BPTR header in each segment block records the size (stored by the loader before the data):
seg_mem - 4 : size of this allocation in bytes
seg_mem + 0 : BPTR to next segment
seg_mem + 4 : hunk data
Hunk Types Reference
Complete enumeration of all hunk type codes as defined in NDK39: dos/doshunks.h. Types are listed in numeric order. The Context column indicates where each hunk may legally appear: Exec = loadable executable, Obj = relocatable object file (HUNK_UNIT stream), Both = either.
| Hex | Dec | Name | Context | Purpose | Typical Content | Notes / Limits |
|---|---|---|---|---|---|---|
$3E7 |
999 | HUNK_UNIT |
Obj | Marks the start of a relocatable object file unit | 1 longword = name length in longs, then the unit name string (padded to longword boundary) | Must be the first record in every .o file. Not present in final executables. |
$3E8 |
1000 | HUNK_NAME |
Obj | Names a section within a HUNK_UNIT | 1 longword = name length, then the section name string | Optional; precedes the code/data/BSS hunk it names. Linker uses for diagnostics. |
$3E9 |
1001 | HUNK_CODE |
Both | Machine-code section | 1 longword = size in 32-bit longs; then size×4 bytes of 68k opcodes | Loaded into RAM. Bits 30–29 of the type word select memory type (see HUNKF_* flags). Max size: ~1 GB (29-bit field = 512 M longs). |
$3EA |
1002 | HUNK_DATA |
Both | Initialized read/write data section | 1 longword = size in longs; then size×4 bytes of raw data | Same memory-type flags as HUNK_CODE. Pointer tables here require HUNK_RELOC32 fixups. |
$3EB |
1003 | HUNK_BSS |
Both | Uninitialized (zero-filled) data section | 1 longword = size in longs | No data follows — the loader's AllocMem(MEMF_CLEAR) zeroes the block. Bitmap/audio buffers use this. |
$3EC |
1004 | HUNK_RELOC32 / HUNK_ABSRELOC32 |
Both | Absolute 32-bit address fixup table | Pairs of (num_offsets, target_hunk) followed by num_offsets longword byte-offsets; terminated by num_offsets=0 |
Each patch site: *(ULONG*)(hunk_base+offset) += target_base. Offsets must be longword-aligned. Unlimited entries. Most common reloc type. |
$3ED |
1005 | HUNK_RELOC16 / HUNK_RELRELOC16 |
Obj | PC-relative or absolute 16-bit fixup | Same structure as HUNK_RELOC32 but patches a UWORD | Rarely generated. The 68k only supports 16-bit displacements in Bcc/BSR; linkers prefer PC-relative code instead. |
$3EE |
1006 | HUNK_RELOC8 / HUNK_RELRELOC8 |
Obj | 8-bit fixup | Same structure, patches a UBYTE | Extremely rare. Only useful for short-branch offsets inside a single hunk. |
$3EF |
1007 | HUNK_EXT |
Obj | External symbol table (imports + exports) | Sequence of (type_namelen, name, value/refs) entries; terminated by longword $00000000 |
Not present in executables — linker resolves all externals into HUNK_RELOC32 at link time. See EXT_DEF, EXT_REF32, EXT_COMMON sub-types in hunk_ext_deep_dive.md. |
$3F0 |
1008 | HUNK_SYMBOL |
Both | Local (non-exported) symbol table for debugging | Pairs of (name_len, name, value); terminated by name_len=0 |
Ignored by the OS loader; used only by debuggers (MonAm, wack, IDA). Strip with slink NODBG or m68k-amigaos-strip --strip-debug. No limit on entry count. |
$3F1 |
1009 | HUNK_DEBUG |
Both | Arbitrary debugger data block | 1 longword = size in longs; then size×4 bytes of opaque data | First longword is often a format tag: $3D415053 = SAS/C stabs, $3D474343 = GCC stabs. Ignored by loader. Can hold DWARF, stabs, or proprietary data. |
$3F2 |
1010 | HUNK_END |
Both | Marks the end of one logical hunk | No data — bare type longword only | Required after every code/data/BSS + its reloc/symbol records. Loader advances to the next segment slot when this is seen. |
$3F3 |
1011 | HUNK_HEADER |
Exec | Executable file header — magic number + segment size table | Resident-lib list (always 0), num_hunks, first_hunk, last_hunk, then one size longword per hunk | Must be the very first record in a loadable executable. Object files use HUNK_UNIT instead. Each size longword: bits 31–30 = memory type, bits 29–0 = size in longs. |
$3F5 |
1013 | HUNK_OVERLAY |
Exec | Overlay table — describes on-demand swap-in groups | 1 longword = overlay data size; then the overlay tree structure (node count, per-node hunk counts and sizes) | Follows the resident hunks in the file. Obsolete in modern Amiga software; replaced by OpenLibrary(). AmigaOS InternalLoadSeg supports it but it is rarely seen after 1990. |
$3F6 |
1014 | HUNK_BREAK |
Exec | End-of-overlay-tree sentinel | No data | Immediately follows the overlay tree data. Required for InternalLoadSeg to know where the overlay definition ends. |
$3F7 |
1015 | HUNK_DREL32 |
Both | Compact 32-bit base-relative relocation (word-sized fields) | Pairs of (num_offsets:WORD, target_hunk:WORD) followed by num_offsets WORDs; terminated by WORD 0 |
Used by BLink and some third-party linkers. More compact than HUNK_RELOC32 for small programs (all offsets fit in 16 bits, hunks < 64 KB). Supported by InternalLoadSeg. |
$3F8 |
1016 | HUNK_DREL16 |
Obj | Compact 16-bit base-relative relocation | Same word-field structure as HUNK_DREL32, patches UWORD | Very rare; primarily in object files from BLink-family toolchains. |
$3F9 |
1017 | HUNK_DREL8 |
Obj | Compact 8-bit base-relative relocation | Same word-field structure, patches UBYTE | Essentially unused in practice. |
$3FA |
1018 | HUNK_LIB |
Obj | Static library archive container | Sequence of embedded HUNK_UNIT blocks (each a full .o) preceded by their individual sizes |
Output of ar-equivalent tools (ar68k, AmigaOS join). The linker extracts individual units from this container as needed. Not executed directly. |
$3FB |
1019 | HUNK_INDEX |
Obj | Symbol index for a HUNK_LIB archive | String table + per-unit symbol-name / hunk-number mappings | Allows the linker to locate a specific symbol without scanning all units. Immediately follows HUNK_LIB. |
$3FC |
1020 | HUNK_RELOC32SHORT |
Both | Compact 32-bit absolute relocation (word-sized offsets) | Same compact word-field structure as HUNK_DREL32 but semantically identical to HUNK_RELOC32 | Added in AmigaOS 3.x era. Reduces reloc-table file size when all patch offsets fit in 16 bits. Some linkers (e.g., vasm/vlink) emit this by default. |
$3FD |
1021 | HUNK_RELRELOC32 |
Both | PC-relative 32-bit relocation | Same longword-field structure as HUNK_RELOC32; patches a 32-bit displacement rather than an absolute address | Used by GCC -fPIC output and shared-library position-independent code. Patch: *(LONG*)(base+offset) += target_base − (base+offset+4). |
$3FE |
1022 | HUNK_ABSRELOC16 |
Both | Absolute 16-bit relocation | Same longword-field structure as HUNK_RELOC32, patches a UWORD with the lower 16 bits of the target address | Required when code uses MOVE.W #abs_addr, Dn with a truncated 16-bit address constant. Rare in well-structured programs. |
Memory-Type Flags (Bits 30–29 of Hunk Type Word)
These flags may be ORed into the type longword of HUNK_CODE, HUNK_DATA, and HUNK_BSS to control memory placement:
| Bit 30 | Bit 29 | Meaning | AllocMem flag |
|---|---|---|---|
| 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 specifies full MEMF_* flags |
See note |
Example: HUNK_CODE forced into Chip RAM = $C00003E9 (HUNK_CODE | HUNKF_CHIP).
HUNKB_ADVISORY (Bit 29 of the type word itself)
When bit 29 is set in an otherwise-unknown hunk type, AmigaOS InternalLoadSeg treats it like HUNK_DEBUG (reads and discards the block) instead of failing with an error. This allows future hunk types to be added without breaking older loaders.
References
- NDK39:
dos/dos.h,dos/dosextens.h— Process, CLI structures - ADCD 2.1 Autodocs:
LoadSeg,InternalLoadSeg,CreateNewProc - http://amigadev.elowar.com/read/ADCD_2.1/Libraries_Manual_guide/node0150.html
- Amiga ROM Kernel Reference Manual: Libraries — AmigaDOS chapter