LoadSeg and Segment Lists¶
LoadSeg() turns a file in the Hunk format into a segment list in memory: a chain of separately allocated segments, one per code, data or BSS hunk, relocated to their load addresses. The segment list is what CreateNewProc(), RunCommand() and the resident-command list consume. This article covers the in-memory layout, the four loader functions with their version-specific bugs, the InternalLoadSeg() call-back interface, unloading, and how overlays use the same entry points.
The segment list in memory¶
Each segment is one memory block laid out as:
/* not in any NDK header */
struct SegmentList {
BPTR NextSegment; /* BPTR to next segment, ZERO for the last */
ULONG Data[1]; /* payload: the hunk's code/data/BSS */
};
LoadSeg()returns a BPTR to the first node; followNextSegmentfor the rest.- The long word immediately before
NextSegmentholds the byte size of the whole allocation including overhead — theAllocVec()/FreeVec()convention. It is handy for finding a segment's size. - Processes and commands using C or assembler startup begin execution at the first byte of the first segment's payload (
&Data[0]of the first node). BCPL handlers (GLOBVEC0 or -3) are started through the BCPL runtime binder instead.
/* Walk a segment list and print each segment's address and size */
void ShowSegments(BPTR seglist)
{
while (seglist) {
ULONG *seg = (ULONG *)BADDR(seglist); /* -> NextSegment */
ULONG size = seg[-1]; /* AllocVec-style size */
Printf("segment at 0x%08lx, %lu bytes\n", (ULONG)&seg[1], size);
seglist = (BPTR)seg[0];
}
}
Do not confuse three different things:
| Name | What it is |
|---|---|
| Segment list (this article) | The NextSegment chain returned by LoadSeg() |
struct Segment (dos/dosextens.h) |
A node of the resident-command list (seg_Next, seg_UC, seg_Seg, seg_Name). Its seg_Seg is a segment list |
pr_SegList (struct Process) |
A BCPL array: a count followed by BPTRs to segment lists. See Process Creation and Structure |
LoadSeg()¶
BPTR LoadSeg(CONST_STRPTR name);
Opens name with Open(name, MODE_OLDFILE) (so the usual path rules apply), loads every hunk, and returns the segment list or ZERO. On failure any blocks already loaded are freed and IoErr() holds an error code (for example ERROR_BAD_HUNK for an unknown hunk type); on success IoErr() is undefined. Release the result with UnLoadSeg().
Loader bugs by version¶
LoadSeg(), NewLoadSeg() and InternalLoadSeg() share one implementation and therefore all of these defects:
| Version | Defect | Workaround |
|---|---|---|
| V34 and below | BSS segments larger than 256K are not fully cleared | Avoid BSS hunks over 256K; allocate such memory with exec |
| V34 and below | Space beyond the payload of code/data hunks (l < ms[i]) is not cleared |
Clear it yourself |
| V36+ | That trailing space is cleared only if the hunk has payload (l > 0) |
Clear it yourself, or avoid it |
| V36+ | At most 2¹⁶ entries per relocation array | Split long lists into several arrays for the same target hunk |
| V36–V38 | HUNK_RELOC32SHORT recognised only under the wrong ID 0x3f7 |
Use 0x3f7, which V39+ still accepts |
| V39+ | HUNK_RELRELOC32 fields are read as 16 bits |
Do not use HUNK_RELRELOC32 |
| all | Odd relocation offsets crash on 68000/68010 | Linkers must emit even offsets |
| all | HUNK_NAME/HUNK_SYMBOL names over 124 characters fail |
Keep names ≤ 31 long-words |
Neither short relocation form loads on V34 and below; binaries for those systems must use HUNK_RELOC32. See AmigaDOS Hunk Binary Format for the hunk layouts.
NewLoadSeg()¶
BPTR NewLoadSeg(CONST_STRPTR file, CONST struct TagItem *tags); /* V36 */
BPTR NewLoadSegTagList(CONST_STRPTR file, CONST struct TagItem *tags);
BPTR NewLoadSegTags(CONST_STRPTR file, ULONG tag1type, ...);
All three reach the same library entry. No tags are defined, so it does nothing LoadSeg() cannot. Version and stack information come from the $VER: and $STACK: cookies instead. Unload with plain UnLoadSeg(). Same bugs and IoErr() behaviour as LoadSeg().
InternalLoadSeg()¶
Loads through caller-supplied functions instead of Read() and AllocMem(). Use it to load a binary without a file system, or into special memory — for example from ROM, or from the Rigid Disk Block of a boot partition.
seglist = InternalLoadSeg(fh, table, funcs) /* V36 */
D0 D0 A0 A1
BPTR InternalLoadSeg(BPTR fh, BPTR table, struct LoadSegFuncs *funcs);
fhis an opaque value: it is never interpreted, only passed toReadFunc()ind1. It need not be a real file handle.tablemust beZEROfor a normal binary or an overlay root; a non-ZEROsegment table selects overlay-node loading (see below).funcspoints to three function pointers. The structure is not officially documented:
struct LoadSegFuncs {
LONG __asm (*ReadFunc)(register __d1 BPTR fh,
register __d2 APTR buffer,
register __d3 ULONG size,
register __a6 struct DosLibrary *DOSBase);
APTR __asm (*AllocFunc)(register __d0 ULONG size,
register __d1 ULONG flags,
register __a6 struct ExecBase *SysBase);
void __asm (*FreeFunc)(register __a1 APTR mem,
register __d0 ULONG size,
register __a6 struct ExecBase *SysBase);
};
| Function | Inputs | Result | Compatible system call |
|---|---|---|---|
ReadFunc |
d1 = fh, d2 = buffer, d3 = byte count, a6 = DOSBase |
d0 = bytes read, negative on error |
Read() |
AllocFunc |
d0 = size, d1 = MEMF_* flags, a6 = SysBase |
d0 = memory or NULL |
AllocMem() |
FreeFunc |
a1 = memory, d0 = size, a6 = SysBase |
— | FreeMem() |
InternalLoadSeg() stores each block's size in the block itself (the AllocVec() convention), so the allocator and the release function do not need to track sizes: plain AllocMem()/FreeMem() already fit the interface.
Note: the NDK 3.2 autodoc documents the
ReadFuncregisters asd1(handle),a0(buffer),d0(length); the manual givesd1/d2/d3as above. TheReadFuncarguments are identical to those ofRead(), which takesd1/d2/d3.Note: the NDK 3.2
clib/dos_protos.hand autodoc declare a fourth argumentLONG *stackina2that receives a stack size found in the file, and typefuncsasLONG *(an array of three function pointers). The manual documents three arguments; on V47 useScanStackToken()on the loaded seglist instead.
IoErr() is not set consistently, unless your functions set it. Call SetIoErr(0) beforehand if you want to distinguish errors.
Note: the NDK 3.2 autodoc states that for an overlaid file the result is the negated seglist,
-(seglist), and that the result is not returned ind1.
UnLoadSeg()¶
success = UnLoadSeg(seglist)
D0 D1
BOOL UnLoadSeg(BPTR seglist);
Frees every segment of a list loaded by LoadSeg() or NewLoadSeg(). For an overlaid binary — recognised by the magic 0xabcd in the first segment — it also closes the overlay file handle and frees the segment table (see AmigaOS Hunk Overlays).
- The only error is passing
ZERO: it then returns 0 and leavesIoErr()alone. Everything else is reported as success; the result of closing the overlay file is not checked. - The code must no longer be running. AmigaDOS has no protocol to learn when a process started from a seglist has finished; use
NP_FreeSeglistwithCreateNewProc()so the process frees its own code. - Segment lists from
InternalLoadSeg()should be freed withInternalUnLoadSeg().
Note: the NDK 3.2
clib/dos_protos.hdeclaresUnLoadSeg()as returningVOID.
InternalUnLoadSeg()¶
success = InternalUnLoadSeg(seglist, FreeFunc) /* V36 */
D0 D1 A1
BOOL InternalUnLoadSeg(BPTR seglist,
void __asm (*FreeFunc)(register __a1 APTR mem,
register __d0 ULONG size,
register __a6 struct ExecBase *SysBase));
FreeFunc should be the one from the LoadSegFuncs used for loading (or at least able to free what its AllocFunc allocated). For overlaid binaries it always calls Close() on the root's file handle, so it can only clean up overlays loaded from a real Open() handle. Returns 0 only for a ZERO seglist; the result of Close() is not checked and IoErr() is not set consistently.
If an overlaid program was loaded through custom I/O, close the stream yourself, set oh_FileHandle to ZERO (closing ZERO is a harmless no-op) and then call InternalUnLoadSeg().
Loading overlay nodes¶
The same entry points load overlay nodes. LoadSeg() actually takes two hidden arguments when its first argument is NULL:
seglist = LoadSeg(name, table, fh)
D0 D1 D2 D3
/* name = NULL selects overlay-node mode
table = oh_Segments from the root's OverlayHeader
fh = oh_FileHandle from the root's OverlayHeader */
The official prototype does not expose table and fh; call it through an assembler stub or a custom pragma. Rules:
Seek()first, every time. Positionfhat the node'sHUNK_HEADER(ot_FilePositionfor the hierarchical overlay manager). AfterLoadSeg()returns, the file position is undefined.LoadSeg()does not free the segments already in the table entries the node will fill. The overlay manager must unload them first.- On success it links the new segments, fills the table and returns the first segment;
IoErr()is undefined. On failure it returnsZEROwithIoErr()set.
InternalLoadSeg(fh, table, funcs) with a non-ZERO table does the same through call-backs, but its use for overlays is discouraged because the unloaders always Close() the overlay handle. Details are in AmigaOS Hunk Overlays.
Using a segment list¶
| Consumer | How the seglist is used |
|---|---|
CreateNewProc() with NP_Seglist |
New process starts at the first segment. NP_FreeSeglist (default FALSE) makes the process UnLoadSeg() it at exit; on creation failure it is never freed |
CreateProc() 1.3+ |
Same, but the seglist is never freed by dos |
RunCommand(seg, stack, args, len) |
Runs the seglist as a command on the calling process with its own stack and arguments; it does not load anything. The caller unloads the seglist afterwards |
Resident list (AddSegment()) |
Keeps the seglist in a struct Segment for later RunCommand() |
LONG RunCommand(BPTR seg, LONG stack, CONST_STRPTR paramptr, LONG paramlen); /* V36 */
LONG ScanStackToken(BPTR seg, LONG defaultstack); /* V47 */
RunCommand() does not look at the binary to choose a stack: the caller decides. The Shell uses cli_DefaultStack × 4 raised by the $STACK: cookie, which on V47 is exactly what ScanStackToken() computes. The argument string should end with a newline for ReadArgs(). RunCommand() returns -1 (with IoErr() = ERROR_NO_FREE_STORE) if it cannot allocate the stack or input buffer.
#include <string.h>
#include <dos/dos.h>
#include <proto/dos.h>
/* Load and run a command on the current (shell) process */
LONG RunFile(STRPTR file, STRPTR args) /* args must end in "\n" */
{
LONG rc = -1;
BPTR seg;
if ((seg = LoadSeg(file))) {
LONG stack = ScanStackToken(seg, 4096); /* V47 */
SetProgramName(file);
rc = RunCommand(seg, stack, args, strlen(args));
UnLoadSeg(seg);
}
return rc;
}
A seglist that is never passed to UnLoadSeg() can be faked for CreateProc(); see Process Creation and Structure.
See Also¶
- AmigaDOS Hunk Binary Format
- AmigaOS Hunk Overlays
- Hunk Version and Stack Cookies
- Process Creation and Structure
- BCPL Runtime Binder
- dos.library Reference
Sources: Thomas Richter, Amiga ROM Kernel Reference Manual: AmigaDOS (2024, v1.27), §11 intro, §11.2, §11.3–11.4 (pp. 162–183), §15.2.3 (pp. 328–329); NDK 3.2 R4 clib/dos_protos.h, dos/dosextens.h, Autodocs/dos.doc Updated: 2026-11-13