LLX > Neil Parker > Apple II > Loader Secrets

Undocumented Secrets of the Apple IIGS System Loader

This article is a collection of undocumented information about the IIGS System Loader, the component of the operating system responsible for bringing executable code into memory and relocating to run at an arbirtrary address. These tidbits do not stand alone - the reader is assumed to be familiar with the Loader documentation in Apple IIGS GS/OS Reference, and possibly its predecessor Apple IIGS ProDOS 16 Reference.

Loader Versions

Since much of the information below varies depending on which version of the Loader is available, it's necessary to test the Loader version before using this information. Testing the major version number (the part to the left of the decimal point) is sufficient - as far as I can tell, none of information below changed when a minor version number changed.

A machine-language program can easily find the Loader major version like this (assuming 16-bit native mode):

        pha            ;Space for result
        ldx #$0411     ;LoaderVersion
        jsl $E10000
        pla
        and #$7F00     ;Mask off minor version and prototype bit
        xba            ;Get major version into low byte

Getting the GS/OS (or ProDOS 16) version number also works, since its version parallels the Loader version, but calling LoaderVersion is easier, because no parameter buffer needs to be set up.

Loader Version System Software Version Notes
1 1.0 through 3.2 Original version; ProDOS 16
2 4.0 GS/OS; new global area layout; new Pathname table format
3 5.0 through 5.0.4 ExpressLoad introduced as an optional component
4 6.0, 6.0.1 New global area layout; ExpressLoad folded into main Loader

New Loader Calls Introduced in System 6

Two new Loader calls were introduced in System 6. These are available if the major version number returned from LoaderVersion is 4.

Tool call $1E11: Get Loader globals

Stack before call:

|          |
+----------+
|          |  
+          +  Longword result space
|          |
+----------+
|          |  <-- SP

Stack after call:

|          |
+----------+
|          |  
+          +  Pointer to Loader globals
|          |
+----------+
|          |  <-- SP

This call returns a pointer to the Loader's global variable area. The locations of some useful fields in this area are described below.

Tool call $1F11: Save/restore Loader direct page

Stack before call:

|          |
+----------+
|          |  Word command code: 0 = save, non-0 = restore
+----------+
|          |  <-- SP

Stack after call:

|          |
+----------+
|          |  <-- SP

This call saves (if the command word is 0) or restores (if the command word is non-zero) the Loader's direct page.

A Save operation copies the first 234 ($EA) bytes from the Loader's direct page into a buffer in the Loader's global variable area, and disposes the direct page's handle.

A Restore operation allocates a new handle for the Loader's direct page, and copies 234 ($EA) bytes from a buffer in the Loader's global area into it.

There is only one save buffer, so save/restore calls are not nestable.

This call appears to be intended for purposes such as preserving Loader information across a switch to Prodos 8, and probably should not be called by applications.

Load Global Variables

The Loader keeps a block of data set aside for global variables. The location and layout of this block are different in different System Software versions.

System 1.0 through System 3.2 (ProDOS 16)

In ProDOS 16 (all System Software versions up to 3.2; LoaderVersion returns major version 1), the global variable block starts at $01/E700. Here are some useful entries in it:

Location Type Contents
$01/E700 word Loader busy flag
$01/E702 long Handle to Memory Segment Table
$01/E706 long Handle to Jump Table Directory
$01/E70A long Handle to Pathname Table
$01/E70E word User ID
$01/E71C long Address of jump table load function. A JSL to this address is patched into each entry of every jump table segment loaded from disk.

System 4.0 through System 5.0.4

The first two major releases of GS/OS moved the Loader global block to $01/FB00, and rearranged its contents. This is the layout if LoaderVersion returns major version 2 or 3:

Location Type Contents
$01/FB00 long Handle to Memory Segment Table
$01/FB04 long Handle to Jump Table Directory
$01/FB08 long Handle to Pathname Table
$01/FB0C word User ID
$01/FB1A long Address of jump table load function. A JSL to this address is patched into each entry of every jump table segment loaded from disk.

System 6.x

In System 6, the global block moved again, and was rearranged again. I've never seen it at any address other than $01/A68C, but that shouldn't be assumed - instead of hardwiring that address, check that LoaderVersion returns major version 4, and if so, use the new tool call $1E11 to find the global block, and use the offsets shown below to index into it.

Offset Type Contents
+0 word "Loader not initialized" flag
+2 long Handle to Loader direct page
+6 word Pointer to Loader direct page (in bank 0, of course)
+$1E long Handle to Memory Segment Table
+$22 long Handle to Jump Table Directory
+$26 long Handle to Pathname Table
+$2A word User ID
+$11E $EA bytes Direct page save buffer
+$20A pstring "EXPRESSLOAD" (without the quotes; Pascal-style string with length byte)
+$216 pstring "~EXPRESSLOAD" (without the quotes; Pascal-style string with length byte)
+$223 long JSL to jump table load instruction. This is patched into each entry of every jump table segment loaded from disk.

Loader Lists

This section shows the layouts of the three lists referenced in the Loader global area (the Memory Segment Table, the Jump Table Directory, and the Pathname Table). These three lists contain all the information you need to find every loadabie file currently known to the Loader, and every segment that has been loaded from those files.

"Known to the Loader" means that InitialLoad (or InitialLoad2) has been called to load the file (or the file is a run-time library referenced by an InitialLoad'ed file), and that UserShutDown has not been called to remove the file from memory. But note that files handled by ExpressLoad are not entered into these lists - ExpressLoad keeps its own lists elsewhere in a different format.

Actually, most of the information in this section is documented (in Apple IIGS ProDOS 16 Reference), but is included here anyway for completeness, to accompany the undocumented new format of the Pathname Table.

Memory Segment Table

The Memory Segment Table is a doubly-linked list containing one entry for every segment that has been loaded from a load file (static segments are added to this list at InitialLoad time; dynamic segments are added the first time they are actually brought into memory). The handle of the list's first entry can be found in the Loader global area as described above.

Here is the layout of a Memory Segment Table entry:

Offset Type Contents
+0 long Handle to next entry (0 = end of list)
+4 long Handle to previous entry (0 = beginning of list)
+8 word User ID for file segment was loaded from
+$A long Handle to segment contents
+$E word Load file number (essentially a serial number, starting at 1, that distinguishes different files loaded with the same User ID)
+$10 word Segment number of segment in load file (the first segment is number 1)
+$12 word KIND field from segment header

Jump Table Directory

The Jump Table Directory is a doubly-linked list containing one entry for every jump table segment currently known to the Loader. The handle of the list's first entry can be found in the Loader global area.

Here is the layout of a Jump Table Directory entry:

Offset Type Contents
+0 long Handle to next entry (0 = end of list)
+4 long Handle to previous entry (0 = beginning of list)
+8 word User ID for file segment was loaded from
+$A long Handle to jump table image in memory

Pathname Table (ProDOS 16 format)

The Pathname Table is a doubly-linked list containing one entry for every loadable file currently known to the Loader. The handle of the list's first entry can be found in the Loader global area.

Here is the layout of a Pathname Table entry under ProDOS 16 (LoaderVersion returns major version 1):

Offset Type Contents
+0 long Handle to next entry (0 = end of list)
+4 long Handle to previous entry (0 = beginning of list)
+8 word User ID for file
+$A word Load file number (essentially a serial number, starting at 1, that distinguishes files loaded with the same User ID)
+$C word File modification date (in two-byte ProDOS format)
+$E word File modification time (in two-byte ProDOS format)
+$10 word Direct page/stack address (if the file contains a direct page/stack segment, 0 if not)
+$12 word Direct page/stack length (if the file contains a direct page/stack segment, 0 if not)
+$14 word Jump-table-loaded flag (1 for InitialLoad files, initially 0 for run-time libraries)
+$16 pstring Full pathname of file (Pascal-style string with length byte)

Pathname Table (GS/OS format)

The Pathname Table changed under GS/OS, because the date/time fields and the file path could not accommodate the new formats that GS/OS uses for such fields. Here is the new layout under GS/OS (LoaderVersion returns major version 2, 3, or 4):

Offset Type Contents
+0 long Handle to next entry (0 = end of list)
+4 long Handle to previous entry (0 = beginning of list)
+8 word User ID for file
+$A word Load file number (essentially a serial number, starting at 1, that distinguishes files loaded with the same User ID)
+$C 8 bytes File modification date and time (in 8-byte GS/OS/ReadTimeHex format)
+$14 word Direct page/stack address (if the file contains a direct page/stack segment, 0 if not)
+$16 word Direct page/stack length (if the file contains a direct page/stack segment, 0 if not)
+$18 word Jump-table-loaded flag (1 for InitialLoad files, initially 0 for run-time libraries)
+$1A long Pointer to entry point
+$1E GS/OS string Full pathname of file (GS/OS-style string with length word)

ExpressLoad

ExpressLoad was introduced in System 5.0, and is an optional feature through System 5.0.4. It's always present in System 6. If a loadable file begins with a special ExpressLoad segment, ExpressLoad takes over, and using information in the ExpressLoad segment, loads the file much more rapidly than the Loader could.

The ExpressLoad Segment on Disk

An ExpressLoad-able file begins with an ExpressLoad segment, which is an ordinary segment with VERSION = 2 (OMF version 1 files cannot be ExpressLoaded) and KIND = $8001 (a dynamic data segment, so it will be skipped during InitialLoad if ExpressLoad is not available), and SEGNUM = 1 (it must be the file's first segment). The segment name is EXPRESSLOAD or ~EXPRESSLOAD (the latter is preferred, and the match is case-insensitive). The segment's body consists of a single LCONST record, with the following contents:

Offset Type Contents
+0 word Flags (always 0 on disk)
+2 word Offset to pathname. Always 0 on disk. In memory, this holds the offset from the beginning of the segment data to a GS/OS string (with length word) containing the file's full pathname.
+4 word Number of segments in the file, minus 2 (note that an ExpressLoad-able file always has at least two segments - the ExpressLoad segment and one or more others)
+6 8*N bytes Segment list. An array of 8-byte elements, one for each segment (except the ExpressLoad segment), each with the following contents:
Offset Type Contents
+0 word Self-relative offset to this segment's entry in the segment header info array
+2 word flags (always 0 on disk)
+4 long Handle to segment's data in memory (always 0 on disk)
(varies) 2*N bytes Segment remapping list. An array of 2-byte elements, indexed by the "old" segment number (the number the segment would have if the ExpressLoad segment were not present) minus 1, giving "new" segment number (the number the segment actually has in the file). One entry for each segment (except the ExpressLoad segment).
(varies) (varies) Segment header info array. An array of variable-length elements, one for each segment (except the ExpressLoad segment), each with the following contents:
Offset Type Contents
+0 long Offset from beginning of the file to the data part of this segment's LCONST record
+4 long Length of this segment's LCONST record
+8 long Offset from beginning of the file to this segment's first relocation record
+$C long Total length of this segment's relocation records
+$10 (varies) Copy of this segment's header. The copy is not complete - it omits the first 12 bytes (i.e. the BYTECNT, RESSPC, and LENGTH fields).
Since these entries are variable-length, they must be found using the offsets given in the segment list above. Note that this structure implies that all segments in an ExpressLoad-able file must store their data in a single LCONST record.

The offsets and lengths in the segment header info array are what makes ExpressLoad fast. Using this information, it can go directly to wherever it needs to go in the file, without having to read and parse the entire file.

The segment remapping list is used to make LoadSegNum and UnloadSegNum work as if the ExpressLoad header were not present. This is done so that applications don't need to worry about whether or not they've been made ExpressLoad-able. Paradoxically, this is also why LoadSegNum and UnloadSegNum should not be used on an ExpressLoad-able application if there's any possibility of it running on a system where ExpressLoad is not available - they would access different segments depending on whether or not the application was loaded by ExpressLoad.

The segment remapping list allows for the possibility that segments could be reordered when the ExpressLoad segment is added, but I've never seen a program that does this - in all the examples I've examined, segment_remapping_list[i] = i + 2.

ExpressLoad Data Structures in Memory

ExpressLoad ignores the Loader data structures described above, and instead hangs its information off of a single master list of ExpressLoaded files. Finding the start of this list can be tricky.

So how do you find the direct page? If the Loader major version is 4, it's easy - the direct page's address is in the word at offset +6 in the Loader's global area. The hard case is major version 3 - I haven't found any easier method than scanning the the Memory Manager's handle list for a 256-byte block in bank 0 with User ID $7050. If no such handle exists, then ExpressLoad is not available.

ExpressLoad's master file list is a doubly-linked list of 2048-byte blocks, each with the following layout:

Offset Type Contents
+0 long Handle to next block of list (0 = end of list)
+4 long Handle to previous block of list (0 = beginning of list) (? maybe - I've never seen seen the list get full enough to need a second block)
+8 8 bytes reserved (0)
+$10 16*N bytes Array of up to 127 16-byte entries, one for each ExpressLoaded file, each with the following layout:
Offset Type Contents
+0 word User ID for file (0 if this entry is unused)
+2 long Handle to ExpressLoad segment from disk
+6 word Direct page/stack address (if the file contains a direct page/stack segment, 0 if not)
+8 word Direct page/stack length (if the file contains a direct page/stack segment, 0 if not)
+$A long Pointer to entry point
+$E word reserved (0)

The ExpressLoad segment (whose handle is at offset +2 in the file entry) is augmented with the file modification date/time and pathname:

Offset Type Contents
+0 (varies) ExpressLoad data from disk
+N-8 8 bytes File modification date/time, in GS/OS/ReadTimeHex format
+N GS/OS string Full pathname of file (GS/OS-style string, with length word)

The offset from the beginning of the ExpressLoad segment to the pathname can be found at offset +2 of the ExpressLoad segment. To find the offset of of the modification date/time, subtract 8 from the offset of the pathname.

LLX > Neil Parker > Apple II > Loader Secrets

Original: July 30, 2017