An LX file starts as an MS-DOS program: a DOS header, then a DOS program, the stub. The DOS header’s last field gives the file offset of the LX header, 196 bytes that describe the module and say where every other table is. This page describes both headers, and the module directives, debug information and overlay that only the header points to. All numbers are little-endian.
DOS header
The first 64 bytes are an MZ header with the
extended fields of Windows and OS/2 programs. DOS reads the first 28 bytes and runs the
stub; OS/2 reads only the signature and e_lfanew.
| Offset | Size | Field | Meaning |
|---|---|---|---|
0x00 | 2 | e_magic | MZ. |
0x02 | 2 | e_cblp | Bytes in the last 512-byte page of the DOS program; 0 means the whole page. |
0x04 | 2 | e_cp | 512-byte pages the DOS program fills, counting the last, partial one. |
0x06 | 2 | e_crlc | Entries in the DOS relocation table; usually 0. |
0x08 | 2 | e_cparhdr | The size of the DOS header in 16-byte paragraphs; the stub starts after it. |
0x0A | 2 | e_minalloc | Extra memory the stub needs, in paragraphs. |
0x0C | 2 | e_maxalloc | Extra memory the stub would like, in paragraphs. |
0x0E | 2 | e_ss | The stub’s initial SS, relative to its load segment. |
0x10 | 2 | e_sp | The stub’s initial SP. |
0x12 | 2 | e_csum | A checksum; usually 0. |
0x14 | 2 | e_ip | The stub’s initial IP. |
0x16 | 2 | e_cs | The stub’s initial CS, relative to its load segment. |
0x18 | 2 | e_lfarlc | The file offset of the DOS relocation table: 0x40 or more. |
0x1A | 2 | e_ovno | The overlay number; 0. |
0x1C | 8 | e_res | Reserved, zero. |
0x24 | 2 | e_oemid | An OEM identifier; usually 0. |
0x26 | 2 | e_oeminfo | OEM information; usually 0. |
0x28 | 20 | e_res2 | Reserved, zero. |
0x3C | 4 | e_lfanew | The file offset of the LX header. |
00000000 4D 5A 80 00 01 00 00 00 04 00 00 00 FF FF 00 00 MZ..............
00000010 B8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 ........@.......
00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000030 00 00 00 00 00 00 00 00 00 00 00 00 90 00 00 00 ................
0x80 bytes in it), a 4-paragraph header, no relocations, and e_lfanew 0x90e_lfanew
The 32-bit word at 0x3C is the file offset of the LX header. The explorer
follows it and checks the header it finds; see
Telling it apart. Table offsets in the
LX header are mostly counted from this offset, not from the start of the file.
DOS stub
The stub is the DOS program that runs when the file is started under DOS. It starts
after the DOS header, at e_cparhdr × 16, and the explorer takes it to end
where the DOS header says its image ends, or at the LX header if that comes first. The
explorer disassembles it as 16-bit code. It usually prints a message and exits:
00000040 0E 1F BA 0E 00 B4 09 CD 21 B8 01 4C CD 21 74 68 ........!..L.!th
00000050 69 73 20 69 73 20 61 6E 20 4F 53 2F 32 20 6C 69 is is an OS/2 li
00000060 6E 65 61 72 20 65 78 74 65 6E 64 65 64 20 64 79 near extended dy
00000070 6E 61 6D 69 63 20 6C 69 6E 6B 20 6C 69 62 72 61 namic link libra
00000080 72 79 0D 0A 24 00 00 00 00 00 00 00 00 00 00 00 ry..$...........
$ at 0x840E push cs
1F pop ds ; DS = CS, so DX can point into the stub
BA 0E 00 mov dx, 0x000E ; the message, 14 bytes in
B4 09 mov ah, 9
CD 21 int 21h ; DOS: print the $-terminated string at DS:DX
B8 01 4C mov ax, 0x4C01
CD 21 int 21h ; DOS: exit with return code 1
Bytes between the end of the stub, as its header gives its size, and the LX header are shown as “After the DOS stub”: padding, or, as here, the end of a stub that runs on past its stated size. Bytes between the DOS header’s 64 bytes and the end of the header it states are “Rest of the DOS header”. A DOS relocation table, if there is one, belongs to the stub too.
LX header
The LX header is 196 bytes (0xC4). Offsets below are from its start. The
layout is IBM’s up to 0xA8; the stack size and the reserved bytes after it
are from Open Watcom’s exeflat.h, which pads the header to 196 bytes.
| Offset | Size | Field | Meaning |
|---|---|---|---|
0x00 | 2 | Signature | LX. |
0x02 | 1 | Byte order | 0, little-endian. |
0x03 | 1 | Word order | 0, little-endian. |
0x04 | 4 | Format level | 0. |
0x08 | 2 | CPU type | The processor the module is for. |
0x0A | 2 | OS type | The system the module is for. |
0x0C | 4 | Module version | The module’s own version. |
0x10 | 4 | Module flags | The module type and other flags. |
0x14 | 4 | Pages | Entries in the object page table. |
0x18 | 4 | EIP object | The object (from 1) of the entry point; 0 for none. |
0x1C | 4 | EIP | The entry point’s offset in that object. |
0x20 | 4 | ESP object | The object of the stack. |
0x24 | 4 | ESP | The initial stack pointer in that object. |
0x28 | 4 | Page size | Usually 4,096. |
0x2C | 4 | Page offset shift | Page offsets are shifted left by this many bits. |
0x30 | 4 | Fixup section size | The fixup page table, fixup records and import tables. |
0x34 | 4 | Fixup section checksum | Or 0. |
0x38 | 4 | Loader section size | From the object table on. |
0x3C | 4 | Loader section checksum | Or 0. |
0x40 | 4 | Object table | Offset of the object table. |
0x44 | 4 | Objects | Entries in the object table. |
0x48 | 4 | Object page table | Offset of the object page table. |
0x4C | 4 | Iterated pages | File offset of the iterated pages: 0, or the same as the data pages offset. |
0x50 | 4 | Resource table | Offset of the resource table. |
0x54 | 4 | Resources | Entries in the resource table. |
0x58 | 4 | Resident name table | Offset of the resident names. |
0x5C | 4 | Entry table | Offset of the entry table. |
0x60 | 4 | Module directives | Offset of the module directives table, or 0. |
0x64 | 4 | Directives | Entries in the module directives table. |
0x68 | 4 | Fixup page table | Offset of the fixup page table. |
0x6C | 4 | Fixup record table | Offset of the fixup records. |
0x70 | 4 | Import module table | Offset of the imported module names. |
0x74 | 4 | Import modules | Entries in the import module table. |
0x78 | 4 | Import procedure table | Offset of the imported procedure names. |
0x7C | 4 | Per-page checksums | Offset of the per-page checksum table, or 0. |
0x80 | 4 | Data pages | File offset of the data pages. |
0x84 | 4 | Preload pages | The number of pages loaded at once. |
0x88 | 4 | Non-resident name table | File offset of the non-resident names. |
0x8C | 4 | Non-resident name table size | In bytes. |
0x90 | 4 | Non-resident name table checksum | Or 0. |
0x94 | 4 | Automatic data object | For 16-bit compatibility. |
0x98 | 4 | Debug information | File offset, or 0. |
0x9C | 4 | Debug information size | In bytes. |
0xA0 | 4 | Instance pages, preload | Instance pages in the preload section. |
0xA4 | 4 | Instance pages, demand | Instance pages in the demand section. |
0xA8 | 4 | Heap size | For 16-bit compatibility. |
0xAC | 4 | Stack size | The size of the stack. |
0xB0 | 20 | Reserved | Zero, to 196 bytes. |
Signature
The two bytes 4C 58, LX in ASCII.
Byte and word order
The byte order at 0x02 and the word order at 0x03 are 0 for
little-endian, and 1 would mean big-endian. Only
little-endian files are known, and the explorer turns down any other value.
Format level
The version of the format, 0. A loader refuses a module with any other level, and so does the explorer.
CPU type
| Value | Processor |
|---|---|
| 1 | Intel 80286 |
| 2 | Intel 80386 |
| 3 | Intel 80486 |
These are IBM’s values. Other tools also list values for later Intel processors and for the Intel i860 and MIPS processors, which the explorer accepts too; their exact numbers were not confirmed, and no file examined for this site uses them. The explorer disassembles the code of the x86 types as x86.
OS type
| Value | System |
|---|---|
| 0 | Unknown |
| 1 | OS/2 |
| 2 | Windows |
| 3 | DOS 4.x |
| 4 | Windows 386 |
The System Shock sound drivers below say 1, OS/2, although they are loaded by a DOS program.
Module version
A 32-bit version number of the module itself, set by the linker from the program; 0 in many files. It is not the version of the format.
Module flags
| Bits | Meaning |
|---|---|
0x00000004 | Per-process library initialisation. |
0x00000010 | Internal fixups have been applied. |
0x00000020 | External fixups have been applied. |
0x00000300 | Windowing: 0x100 incompatible with Presentation Manager windowing, 0x200 compatible, 0x300 uses the PM windowing API. |
0x00002000 | The module is not loadable (it had errors when it was linked). |
0x00038000 | The module type, below. |
0x40000000 | Per-process library termination. |
Type (flags & 0x38000) | Module |
|---|---|
0x00000 | Program |
0x08000 | Library (DLL) |
0x18000 | Protected memory library |
0x20000 | Physical device driver |
0x28000 | Virtual device driver |
Counts
The header gives the size of each table as a count beside its offset: pages
(0x14), objects (0x44), resources (0x54), module
directives (0x64) and imported modules (0x74), and the size of
the non-resident name table in bytes (0x8C). The number of preload pages
(0x84) and of instance pages in the preload and demand sections
(0xA0, 0xA4) are for the loader; the explorer shows them
without using them.
Entry point and stack
The entry point is given as an object number, from 1, and an offset in that object
(0x18, 0x1C); the initial stack the same way
(0x20, 0x24). An object number of 0 means there is none, as in
A32SBFM.DLL below. The entry points that other modules use are in the
entry table. The
automatic data object (0x94) is for 16-bit compatibility.
Page size
The size of a page in memory, usually 4,096 bytes. In LX a page stored in the file can be shorter: each page entry gives how many bytes are stored, and the rest of the page is zeros. The explorer wants a power of two, not 0 and at most 1 MB.
Page offset shift
Page offsets in the object page table are shifted left by this many bits. A shift of 12 means offsets count in 4,096-byte units, so pages start at multiples of 4,096 bytes from the data pages; a shift of 0 lets them start at any byte. LE has the size of the last page in the same place, which is one of the changes that make LX a separate format. The explorer turns down a shift above 24.
Sections and checksums
The tables fall into two sections. The loader section starts at the object table and
holds the tables the loader keeps in memory; the fixup section is the fixup page table,
the fixup records and the import tables. The header gives each one’s size and a
checksum, and a checksum of the non-resident name table (0x90). The
checksums are 0 in the example below, and the explorer shows them without checking
them.
In A32SBFM.DLL the fixup section is 0xADB bytes, from the fixup page table
at +0x108 to the end of the import procedure table at +0xBE3.
Its loader section, 0xB1F bytes from the object table at
+0xC4, ends at the same place.
Table offsets
Most tables are located by an offset from the start of the LX header: the object table,
the object page table, the resource table, the resident names, the entry table, the
module directives, the fixup page table, the fixup records, the import module and
procedure tables and the per-page checksums. Three are file offsets: the iterated pages
(0x4C), the non-resident names (0x88) and, below, the data
pages and the debug information. An empty table has an offset of 0, or the offset of
the next table, with a count of 0.
The explorer lays every table out where its offset says, fills the gaps between them, and reports a table that runs past the end of the file or into another.
Data pages
The file offset from which the page offsets in the object page table count; see Object pages.
Heap and stack sizes
The heap size at 0xA8 is for 16-bit compatibility. The stack size at
0xAC and the 20 reserved bytes after it are not in IBM’s 1992 description,
which stops at 0xA8; they are from Open Watcom’s header.
Debug information
The file offset and size of debug information, in whatever format the linker chose,
such as CodeView, Watcom’s own or IBM’s HLL. The explorer shows it as bytes, and marks
CodeView data by its NB signature. When the size is a whole number of
28-byte entries that make sense as debug directory entries, as Microsoft’s linker writes
in LE files, it shows those entries instead.
Module directives
The module directives table holds instructions for the loader, 8 bytes each:
| Offset | Size | Field | Meaning |
|---|---|---|---|
| 0 | 2 | Directive number | Bit 15 (0x8000) set means the directive’s data stays resident. |
| 2 | 2 | Data length | In bytes. |
| 4 | 4 | Data offset | From the LX header for resident directives, from the start of the file for the others. |
IBM’s numbers are 0x8001 verify record, 0x0002 language
information, 0x0003 co-processor required support table and
0x0004 thread state initialisation. None of the files examined for this site
has any directives.
Overlay
Bytes after the last page, table or block the header points to are an overlay, which the loader does not load. The explorer shows them as one block and says what they hold when it can tell, such as an archive or installer data, or zero bytes.
Worked example
The LX header of A32SBFM.DLL, at 0x90:
00000090 4C 58 00 00 00 00 00 00 02 00 01 00 00 00 00 00 LX..............
000000A0 10 82 00 00 04 00 00 00 00 00 00 00 00 00 00 00
000000B0 00 00 00 00 00 00 00 00 00 10 00 00 0C 00 00 00
000000C0 DB 0A 00 00 00 00 00 00 1F 0B 00 00 00 00 00 00
000000D0 C4 00 00 00 01 00 00 00 DC 00 00 00 00 00 00 00
000000E0 FC 00 00 00 00 00 00 00 FC 00 00 00 07 01 00 00
000000F0 00 00 00 00 00 00 00 00 08 01 00 00 1C 01 00 00
00000100 E2 0B 00 00 00 00 00 00 E2 0B 00 00 00 00 00 00
00000110 00 0E 00 00 00 00 00 00 00 00 00 00 00 00 00 00
… (zero to 0x153)
0x118 on is zero| Field | Value |
|---|---|
| Byte order, word order, level | 0, 0, 0 |
| CPU, OS | 2, the 80386; 1, OS/2 |
| Module flags | 0x00008210: a library, internal fixups applied, compatible with PM windowing |
| Pages | 4 |
| EIP and ESP objects | 0: none |
| Page size, page offset shift | 4,096; 12 |
| Fixup section, loader section | 0xADB and 0xB1F bytes, no checksums |
| Object table, objects | +0xC4 (file 0x154), 1 |
| Object page table | +0xDC (file 0x16C) |
| Iterated pages | 0 |
| Resources | none (the offset is the resident name table’s) |
| Resident names, entry table | +0xFC, +0x107 |
| Fixup page table, fixup records | +0x108, +0x11C |
| Import modules, procedures | both at +0xBE2; no imported modules |
| Data pages | file offset 0xE00 |
| Non-resident names, debug information | none |
| Heap, stack | 0, 0 |