File Formats:PGM

From Try-AS/400
Revision as of 13:54, 9 August 2026 by Friedkiwi (talk | contribs) (Document the MI *PGM object: segments, SLS pointers, header chain, and where the instruction stream lives (via create-page on MediaWiki MCP Server))
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

A *PGM is a Machine Interface program object. It is not a file with a text section and a symbol table. It is a set of single-level store segments, chained together by 8-byte SLS pointers, with the executable instructions embedded in the first segment among the metadata that describes them.

This page describes enough of that structure to walk a program object offline — that is, without a console session and without printing a storage dump for every object of interest. The bytes can come from a save file, which makes any distribution medium, PTF or backup a source of real program objects to examine.

Everything below is big endian, and text fields are EBCDIC.

Addressing

Pointers are 8 bytes: a 40-bit segment identifier in the high bits and a 24-bit offset in the low bits, so a segment is 16 MiB. This is the same split used throughout the machine, including by the DST Display/Alter storage panel and by the SLS address field of a LID directory record.

segment = pointer >> 24
offset  = pointer & 0xFFFFFF

An all-zero pointer means the object has no data for that structure. That is precisely what the ###INFO: OBJECT DOES NOT CONTAIN DATA FOR THIS SECTION line in a Display/Alter/Dump printout is reporting.

Some fields are not pointers but SEGOFF pairs: a 4-byte segment index, counting from one into the object's own segment table, followed by a 4-byte offset. These appear where a genuine pointer would have to be relocated when the object moves.

The segments of one program have unrelated segment identifiers. A decoder therefore needs the whole set: a single segment cannot resolve its own pointers.

Segments

A single-module bound program typically has four segments, distinguished by the type field in each segment's own header:

Type Role
0x0001 The program object proper: all headers, tables, and the instruction stream.
0x0022 Module constants and the module string directory.
0x0018 Static storage.
0x0028 Trace-back and mapping data.

Note that a program's segment table records a segment use code, while each segment's own header records a type. They are different numberings and should not be conflated.

Segment header

Every MI segment opens with a header, called YYSGHDR in IBM's own dump formatting.

Offset Size Field
+0x00 2 Segment type
+0x02 2 Size, in 512-byte units
+0x04 1 New flags
+0x05 1 Flags
+0x08 8 Address of the owning object
+0x18 8 Space address

Object header

Immediately after it, at +0x20 of the first segment, sits the object header, YYEPAHDR. Offsets below are relative to it.

Offset Size Field Notes
+0x02 1 MI object type Together with the next byte, 0x0201 for a program.
+0x03 1 MI object subtype
+0x04 30 Object name EBCDIC.
+0x24 4 Space size
+0x30 8 Creation timestamp
+0x38 8 Owning user profile A pointer to the profile object.
+0x48 8 Context That is, the library.
+0x50 8 Object space The pointer to the program header. Everything else hangs off this.
+0x5A 2 ASP
+0x60 8 Last-modified timestamp

The type and subtype pair uses the same code space as the item descriptors in a save file, so one table of MI type codes serves both.

The program header

Found through the object header's object-space pointer; in practice it sits at offset 0x1000 of the first segment.

Offset Size Field
+0x00 8 Program attributes
+0x08 8 Pointer to the version table
+0x10 8 Pointer to the segment table
+0x18 8 Pointer to the activation header
+0x20 8 Pointer to the signature table
+0x28 8 Pointer to the program string directory
+0x30 8 Pointer to activation group information
+0x40 8 Activation start
+0x48 8 Activation end
+0x50 8 Activation and PEP end — the last byte of the code
+0x58 2 Flags
+0x5C 2 Program state
+0x90 1 Program type
+0x94 4 Program entry procedure module number
+0x9C 4 Program entry procedure string ID
+0xA0 2 Minimum parameters
+0xA2 2 Maximum parameters
+0xD0 8 Pointer to the program header extension
+0xD8 8 Pointer to the trace-back locator
+0xE0 8 Pointer to the module table
+0xE8 8 Pointer to observability information
+0xF0 8 Pointer to the maintenance header

The header extension adds a pointer to the GLU code list, SEGOFF pairs for the program history and the segment table extension, and a 16-byte hardware feature set at +0x50.

Version table

Forty bytes of packed version-release-modification halfwords, where 0x0740 reads as V7R4M0. Among them are the binder's internal and MI levels at +0x02 and +0x04, the target release at +0x06, the created-on release at +0x08, the language release at +0x10, the CCSID at +0x1E and the low and high optimisation levels at +0x20 and +0x22.

The target and created-on values here are what decide whether an object will restore onto a given release, so they are worth reading before attempting a restore that might fail.

Segment table

A header of allocated size, entry count and version, then the entries beginning at +0x10. Entries are a fixed 48 bytes; the size field is the allocation, not header plus count times entry, so deriving the stride from it gives the wrong answer.

Each entry holds the segment address at +0x00, a limbo address at +0x08, a declared page count at +0x10, and a one-byte segment use at +0x12: 0x01 static storage, 0x02 the program object, 0x04 constants, 0x06 trace-back.

A parallel segment table extension, reached by a SEGOFF in the program header extension, records how much of each segment is actually used as opposed to allocated. Its entries begin at +0x18 and are 24 bytes, of which the first four are the byte count.

Activation header

Offset Size Field
+0x00 8 Program entry procedure entry point — the first instruction
+0x08 4 PBV size
+0x10 8 Pointer to the PBV relocation array
+0x18 8 Pointer to the static PBV array
+0x20 4 PBV relocation count
+0x24 4 Dependent service program count
+0x28 8 Pointer to the dependent service program array
+0x4C 4 Constant frame count
+0x50 8 Pointer to the constant frame definition array
+0x58 8 Pointer to the program export array
+0x60 4 Export count

For a bound program with no exports most of these are zero. For a service program they are the published interface, and the dependent service program array is the object's dependency list — which makes it possible to work out what a program binds to without a machine to run DSPPGM on.

Maintenance header

Holds the copyright list size and count and a SEGOFF to the copyright text — a 4-byte length followed by EBCDIC — along with SEGOFF pairs for the external object list, static activation and export information, the last non-observable segment and size, and the lowest and highest PBV identifiers.

Modules

The module table has the same shape as the segment table: header, then 48-byte entries from +0x10, each giving a pointer to a module header, a module domain and a module subtype.

A module header is a long run of pointers: to the program header it belongs to, the copyright table, the module version table, binding information, static information, the end-of-line table, observability information, the module string directory, the entry point table, the VLIC table, the procedure table, module constants, and the module header extension. It also carries the module type at +0x010x01 for OPM and 0x03 for ILE — the module attributes at +0xA8, and the program entry procedure's dictionary ID, string ID, procedure number and parameter counts from +0xAC to +0xBC.

Two of its pointers characteristically leave the program segment: the module string directory and the module constants both live in the 0x0022 segment. When hunting for literal data referenced by the code — message identifiers, format names, table constants — that is the segment to search, and these two pointers give its exact extent.

Module version table

This is the most immediately informative structure in the whole object.

Offset Size Field
+0x02 2 Language release
+0x0A 2 Instruction release
+0x0C 2 Target release
+0x0E 2 Created-on release
+0x10 2 Optimisation level
+0x14 30 Source module name
+0x32 30 Source module qualifier
+0x50 20 Compiler name
+0x64 2 Earliest compiler release

Read the compiler name before spending any time on a disassembly. A module reporting MX CONVERTED was translated from an older object rather than compiled from source at this release, and the generated code will not resemble what a current compiler emits.

The instruction stream

The code is a single contiguous run inside the first segment, bounded by two fields already listed above:

start = activation header, program entry procedure entry point
end   = program header, activation and PEP end        (inclusive)

It is 64-bit big-endian PowerPC AS. A stock disassembler invoked as objdump -D -b binary -m powerpc:common64 -EB, with the load address adjusted to the start offset, will produce a listing whose addresses line up with anything printed by Display/Alter/Dump.

Two caveats apply, and both matter.

PowerPC AS is not plain PowerPC. The tagged-pointer instructions and other AS-only operations have no encoding in a stock PowerPC disassembler and will appear as invalid, or worse, as an unrelated mnemonic that looks plausible. Treat such a listing as a starting point rather than as authority.

The run is also not pure code. Trace-back tables and constants are interleaved with instructions, so a linear sweep will decode data as code somewhere. For an object that still has its observability information, the trace-back locator and the module tables provide the boundaries needed to separate the two; for an object whose observability has been removed, they do not, and the separation has to be inferred from the code itself.

Scope of this description

Field offsets were derived by lining a printed Display/Alter/Dump of a program object up against the same object's bytes extracted from a save file, and then checked by decoding the object independently and comparing every field against the printout. Structures that the dump prints but that were empty in the object used — the signature table, the export and import arrays, the service program tables and the secondary entry point table — are located by pointer but their contents are not described here; establishing those needs a service program to work from.

Only *PGM is covered. *SRVPGM and *MODULE share much of this layout but have additional tables of their own.

See also

  • File Formats:SAVF — how to get a program object's bytes off a machine or out of a distribution medium
  • Data Structures:LID — the same single-level store address split, in a different container