System Files:QFILEMCD
QFILEMCD is the larger of the two container files found on AS/400 SAVSYS and Licensed Internal Code installation media. It is a backup of SLIC itself — the System Licensed Internal Code that sits below OS/400 — stored as a small number of very large memory segments. Its internal format is called COPYDIR after the eyecatcher at offset zero.
Practically, this is the file to reach for if you want to read the machine's own code without touching a running system. Its segments are a byte-for-byte image of what the link loader maps into memory, so an address recovered from a crash, a trace or a system reference code can be looked up directly in a file on your workstation. The companion file System Files:QFILEIML carries the loadable IOP, adapter and service processor microcode instead.
Both files are containers of LIDs, but at different granularities, and the relationship between them is described under Two directories, two granularities below.
The figures quoted here come from one V4R4 image of 472,846,336 bytes (451 MiB). Treat structure as general and numbers as release-specific; the format is known to have survived at least to V7R2 with the same shape.
Header
| Offset | Contents |
|---|---|
0x00
|
Eyecatcher COPYDIR in EBCDIC (C3 D6 D7 E8 C4 C9 D9 40), a zero byte, then the two EBCDIC characters 00
|
0x1C
|
32-bit total size of the payload area — see below |
0x26
|
Release and build string, EBCDIC |
0x50
|
Licensed Internal Code banner, EBCDIC, in 48-character chunks |
0x1C0
|
Start of the directory |
The release string in this image is:
V4R4M044-C6295 2606071417
The first part identifies the release and build. The ten digits that follow parse naturally as a timestamp of the form YYMMDDHHMM, which would make this backup 2026-06-07 14:17 — plausible, since the image was produced by a SAVSYS on a running machine rather than shipped by IBM. That reading is an inference from the shape of the field.
Note! The header is not a fixed 192 (
0xC0) bytes, despite older format notes saying so. The IBM legal banner runs on well past that point, and in this V4R4 image the directory does not begin until0x1C0. A parser should locate the directory by scanning for the first 64-byte-aligned block that has the shape of an entry, rather than seeking to a constant.
The 32-bit field at 0x1C holds 0x1C2F0000, which is exactly the end of the last segment's data. It is a total-payload-size field and makes a good consistency check after parsing the directory: accumulate the entries and you should arrive at the same number.
The directory
The directory is a run of 64-byte entries beginning at 0x1C0.
| Offset | Size | Field |
|---|---|---|
+0x00
|
8 | Always zero in observed data |
+0x08
|
8 | Flags. See the bit-numbering warning below |
+0x10
|
8 | SLS address of the saved segment |
+0x18
|
8 | Segment attributes; the top 16 bits carry the useful value |
+0x20
|
8 | Always zero in observed data |
+0x28
|
8 | Offset to the segment data, absolute from the start of the file |
+0x30
|
4 | Segment length, in raw bytes — not blocks |
+0x34
|
4 | Always zero in observed data |
+0x38
|
8 | Always zero in observed data |
Two details cause most of the trouble people have with this structure, and both were originally got wrong here:
- Flags use IBM's MSB-first bit numbering. Older notes describe the field as
bbt 0:3, and a reader used to LSB-first numbering will look in the bottom nibble and find nothing. Bit 0 is the most significant bit, so the value lives in the top nibble of the 8-byte field. A normal entry reads0x1000000000000000; the end-of-directory marker reads0xF000000000000000. - The data offset is absolute. It is measured from the start of the file, not from the end of the directory. Reading it as relative shifts every extracted segment by the size of the directory, which produces code that disassembles almost plausibly and is wrong everywhere.
In this image the directory holds 32 entries, with the terminator at 0x9C0 and the directory ending at 0xA00. The first entry has a data offset of zero, which means the header and directory physically occupy the first 0xA00 bytes of the first segment's stored image. This is expected rather than a bug, but a tool that extracts that segment will see the container's own header at the front of it.
Segment lengths are consistently a whole number of 4 KiB pages less than a power-of-two boundary: 0xFFF000 is 16 MiB minus one page, 0x7FF000 is 8 MiB minus one page, and so on.
Contents of a typical backup
The 32 segments of this image, with names from a community-maintained SLS address reference. Where the name column is blank, the segment's purpose has not been established.
| SLS address | Length | Name |
|---|---|---|
FFFFFFFFB1 000000
|
16 MiB | |
FFFFFFFFB2 000000
|
16 MiB | |
FFFFFFFFB3 000000
|
16 MiB | |
FFFFFFFFB5 000000
|
16 MiB | |
FFFFFFFFC1 000000
|
16 MiB | Link Loader — LIC Code |
FFFFFFFFC2 000000
|
16 MiB | Link Loader — LIC Data |
FFFFFFFFC3 000000
|
16 MiB | Link Loader — LIC Code (2) |
FFFFFFFFC4 000000
|
16 MiB | Link Loader — LIC Code (3) |
FFFFFFFFC5 000000
|
16 MiB | Link Loader — LIC Code (4) |
FFFFFFFFC6 000000
|
8 MiB | Link Loader — LIC Code (5) |
FFFFFFFFC7 000000
|
16 MiB | Mixed — component code and NLS text pools |
FFFFFFFFC8 000000
|
8 MiB | Mixed — bignum/JVM code and NLS text pools |
FFFFFFFFF1 000000
|
4 MiB | Link Loader TOC |
FFFFFFFFF2 000000
|
16 MiB | Service LID |
FFFFFFFFF4 000000
|
16 MiB | Service LID (2) |
FFFFFFFFFE 000000
|
15 MiB | Pageable BLA |
FFFFFFFFD1 000000
|
16 MiB | Link Loader Information |
FFFFFFFFD2 000000
|
16 MiB | Link Loader Information, Code, Data |
FFFFFFFFD3 000000
|
16 MiB | Link Loader Information (2) |
FFFFFFFFD4 000000
|
16 MiB | Mixed — JVM strings and DST debug symbol table |
FFFFFFFF90 010000
|
60 KiB | LID Directory |
FFFFFFFF91 000000
|
16 MiB | LID NUC2 A-Side |
FFFFFFFF92 000000
|
16 MiB | LID NUC2 B-Side |
FFFFFFFF93 000000
|
16 MiB | LID NUC1 A-Side |
FFFFFFFF94 000000
|
16 MiB | LID NUC1 B-Side |
FFFFFFFF95 000000
|
16 MiB | |
FFFFFFFF96 000000
|
16 MiB | |
FFFFFFFF9A 000000
|
8 MiB | |
FFFFFFFF9B 000000
|
8 MiB | |
FFFFFFFFA1 000000
|
16 MiB | LID Manager |
FFFFFFFFA2 000000
|
16 MiB | |
FFFFFFFF69 000000
|
16 MiB | IDE |
So a backup of this shape is the SLIC boot and loader nucleus: link loader code and data, the loader's table of contents, the LID subsystem's own bookkeeping, and both IPL sides of the two nucleus LIDs. It is not the whole of the machine's code.
A and B sides
Several segments appear twice, distinguished as A-side and B-side. An AS/400 keeps two copies of its Licensed Internal Code so that an update can be applied to one while the other remains a fallback. A normal, unattended IPL runs the B side, so B is what a running machine is executing and B is what static analysis should be done against. Because the two sides are normally near-identical, a byte comparison against the A side is not evidence that A is the live one — the test only means something on a segment that actually differs between them.
Two directories, two granularities
A common source of confusion is that QFILEMCD appears to have two completely different directory formats. It does, and they nest:
- The coarse level is
COPYDIR's own directory, documented above: 64-byte entries, one per 16 MiB-class memory segment, 32 of them. - The fine level is a catalogue of individual LIDs, thousands of entries, using the 32-byte record described at Data Structures:LID.
The fine level is not a second header in the file. It is simply the contents of one particular segment — the one named LID Directory, at FFFFFFFF90 010000. Extract that segment and you find, after a preamble of about 1536 bytes, an EBCDIC Directory. eyecatcher, a version label, and then the records. In this image the label reads v4r4m01204.0.03, which is the same label carried by System Files:QFILEIML — a quick way to confirm that two files came off the same media.
That is also why tools which dump this file emit two dissimilar-looking listings: a short table of large memory extents, and a long list of LIDs. They are two levels of one structure, not two formats.
Extracting a segment
The practical recipe, and the reason this file is worth knowing about:
- Read the
COPYDIReyecatcher at offset 0 to confirm the format. - Find the directory by scanning 64-byte-aligned offsets for the first entry-shaped block, rather than assuming
0xC0or0x1C0. - Walk entries until the flags field has
0xFin its top nibble. - For the segment you want, seek to the data offset as an absolute file offset and read length raw bytes.
- Load the result at its SLS address in a disassembler. The bytes are what the link loader maps, so addresses match.
Step 5 is the payoff. A 64-bit address such as FFFFFFFFC6 1F1690 can be typed straight into the DST Display/Alter storage panel on a live machine of the same build, and the sixteen rows it displays will match the extracted file byte for byte. Static and live analysis share one address space, which means you can do the slow work offline and use the machine only for confirmation.
Two caveats on addresses. They are per-build, not per-release, so comparisons are only meaningful between identical builds; the same class sits at a quite different address on a V5R4 machine. And the SLS address recorded in the directory is the address the segment was saved from, which is not always where it runs — the resident nucleus segment, for instance, is stored in the container under the SID of the B-side nucleus LID rather than its runtime SID.
Reading the code
Once a segment is loaded, two structures make it navigable, and both are worth knowing because they turn addresses into names:
- Traceback trailers. Every compilation unit ends its code with a
TBTBtrailer, and many carry the module's name. A trailer names a whole compilation unit rather than a single function, so a hit means "this address is inside module X", not "this address is function X". - Link-loader descriptor blocks. Considerably richer: one block per procedure, carrying the entry address at
+0x08and the name in EBCDIC at+0x38. Names beginning with#are SLIC-internal modules; the rest are C++ mangled symbols.
Merging both across every segment of this V4R4 image yields roughly 184,000 named records, which is enough to name a substantial fraction of any address you encounter. See SRC for how to use that to get from a reference code back to a module.
Calls between modules do not go through a conventional dispatch table, which surprises people disassembling SLIC for the first time. An external call is a two-instruction sequence — ori r11,r13,ordinal followed by bla into a transfer vector at the very top of the address space — and the 16-byte stub it lands on computes the target arithmetically. There is no table to read off; the ordinal is the callee's word offset within a window, and the stub supplies the window. The stub constants cannot be derived and must be read from a dump of the transfer vector.
The trailing page
The payload area ends at 0x1C2F0000, but the file is one 4 KiB page longer than that. The final page is not padding: it holds 468 non-zero 8-byte records, which appear to be address-and-value pairs.
They fall into two clear populations. The first 110 are in ascending address order with a value of 8, addressing the Link Loader — LIC Data segment among others. The remaining 256 are identical to each other: the same address 0xFE911220, in the Pageable BLA segment, paired with the value 0x48000002 — which as PowerPC is a branch-absolute to zero, the shape of a deliberately poisoned entry.
This has the flavour of a fixup or relocation list applied after the segments are restored, but that is a guess from shape alone. It is recorded here because it is real, reproducible and unexplained, and because a parser that assumes the file ends with the payload will silently ignore it.
Open questions
- The purpose of the trailing page.
- The header bytes other than the eyecatcher, size field, release string and banner.
- The meaning of the segment attribute field's top 16 bits, which take values
0x0001,0x0040,0x0080and0x0100and correlate loosely with segment size. - Names for the nine segments left blank in the table above.