An LE 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 LE 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; Windows and the DOS extenders read 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 LE header. |
00000000 4D 5A 90 00 03 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 80 00 00 00 ................
0xB8, CS:IP 0000:0000, the relocation table offset 0x40, and e_lfanew 0x80e_lfanew
The 32-bit word at 0x3C is the file offset of the LE header. The explorer
follows it and checks the header it finds; see
Telling it apart. Table offsets in the
LE 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 LE header if that comes first. The
explorer disassembles it as 16-bit code.
A Windows VxD has a small stub that prints a message and exits. The usual one is
fourteen bytes of code and then the message, ending with $:
00000040 0E 1F BA 0E 00 B4 09 CD 21 B8 01 4C CD 21 54 68 ........!..L.!Th
00000050 69 73 20 70 72 6F 67 72 61 6D 20 63 61 6E 6E 6F is program canno
00000060 74 20 62 65 20 72 75 6E 20 69 6E 20 44 4F 53 20 t be run in DOS
00000070 6D 6F 64 65 2E 0D 0D 0A 24 00 00 00 00 00 00 00 mode....$.......
0E 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
In a DOS extender program the stub is much larger: it is the extender itself, such as DOS/4GW, or a small loader that finds and starts one. The explorer notes a stub over 32 KB as a DOS extender or loader bound to the module.
Bytes between the end of the stub, as its header gives its size, and the LE header are shown as “After the DOS stub”: padding, or 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.
LE header
The LE header is 196 bytes (0xC4). Offsets below are from its start. The
layout follows IBM’s description of LX up to 0xA8; the rest is from Open
Watcom’s exeflat.h and Wine’s IMAGE_VXD_HEADER.
| Offset | Size | Field | Meaning |
|---|---|---|---|
0x00 | 2 | Signature | LE. |
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 | Bytes in the last page | The size of the last page in the file. |
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 data map, or 0. |
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 first page. |
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 | Reserved | Open Watcom calls it the stack size, for OS/2 only. |
0xB0 | 8 | Reserved | Zero. |
0xB8 | 4 | Version resource | Windows VxDs: the file offset of a version resource, or 0. |
0xBC | 4 | Version resource size | Windows VxDs: its size. |
0xC0 | 2 | Device ID | Windows VxDs: the device ID, or 0. |
0xC2 | 2 | DDK version | Windows VxDs: the DDK the driver was built for. |
Signature
The two bytes 4C 45, LE 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
A 32-bit 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 virtual device drivers of Windows in 386 enhanced mode and of Windows 95, 98 and Me |
DOS extender programs linked with Watcom’s linker say 1, OS/2, although they run under DOS.
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
The bits are IBM’s, from the LX description; LE uses the same.
| 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 |
0x38000 | Seen in every Windows VxD examined for this site, which the explorer therefore calls a Windows virtual device driver (VxD). It is not in IBM’s list. |
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:
libraries and VxDs often have no entry point here, since the system finds their entry
points through the entry table. The
automatic data object (0x94) is for 16-bit compatibility.
Page size
Every page of an object is this many bytes in memory, and in an LE file every stored page but the last is this many bytes in the file too. It is usually 4,096, the 386’s page size, but other sizes occur: REGVXD.VXD below has 512-byte pages. The explorer wants a power of two, not 0 and at most 1 MB.
Bytes in the last page
The number of bytes stored for the last page in the file, the one with the highest page number; every other page is a whole page. This is the field LX changed: there the same word is a page offset shift, and each page entry gives its own size.
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 REGVXD.VXD the loader section is 0xA0 bytes, from the object table at
0xC4 to the fixup page table at 0x164, and the fixup section
is 0x6BA bytes, from 0x164 to the import tables at
0x81E.
Table offsets
Most tables are located by an offset from the start of the LE 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 data
map (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 of the first page. Page n of the file is at
data pages + (n − 1) × page size; see
Object pages.
Heap and stack sizes
The heap size at 0xA8 is for 16-bit compatibility. LE has no stack size
field of its own: the word at 0xAC, which Open Watcom’s header calls the
stack size “for OS/2 only”, is reserved in LE and shown with the
VxD fields.
VxD fields
The last 24 bytes of the header, from 0xAC to 0xC3, are
reserved in general, and zero in DOS extender programs. Windows VxDs keep four fields
there:
| Offset | Size | Field | Meaning |
|---|---|---|---|
0xAC | 4 | Reserved | Zero. |
0xB0 | 8 | Reserved | Zero. |
0xB8 | 4 | Version resource offset | The file offset of a version resource, or 0. |
0xBC | 4 | Version resource size | Its size in bytes. |
0xC0 | 2 | Device ID | The VxD’s device ID, or 0 for a device without one. |
0xC2 | 2 | DDK version | The version of the Windows DDK the driver was built for, major version in the high byte: 0x030A (3.10) and 0x0400 (4.0) occur. |
The offsets are from Open Watcom’s exeflat.h, and Wine’s
IMAGE_VXD_HEADER agrees.
Debug information
The file offset and size of debug information, in whatever format the linker chose. In VxDs built with Microsoft’s linker it is a 28-byte debug directory entry, as in a PE file, which points at the debug data:
| Offset | Size | Field | Meaning |
|---|---|---|---|
| 0 | 4 | Characteristics | Reserved, zero. |
| 4 | 4 | Time stamp | When the debug information was made, in seconds since 1970. |
| 8 | 2 | Major version | Of the debug format. |
| 10 | 2 | Minor version | |
| 12 | 4 | Type | 2 is CodeView. |
| 16 | 4 | Size | The size of the debug data. |
| 20 | 4 | Address | Its address in memory, or 0 when it is not loaded. |
| 24 | 4 | Pointer | Its file offset. |
00003C7F 00 00 00 00 58 A0 F6 31 00 00 00 00 02 00 00 00
00003C8B 2D 00 00 00 00 00 00 00 9B 3C 00 00
00003C9B 4E 42 31 30 … NB10 … C:\ddj\regmon\VXD\REGVXD.pdb
0x2D bytes at 0x3C9B, which start with NB10 and name the PDB fileThe explorer reads the bytes as debug directory entries when the size is a whole number
of 28-byte entries that make sense, and shows them as bytes otherwise, marking CodeView
data by its NB signature.
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 LE 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 LE header of REGVXD.VXD, at 0x80:
00000080 4C 45 00 00 00 00 00 00 02 00 04 00 00 00 00 00 LE..............
00000090 00 80 03 00 17 00 00 00 00 00 00 00 00 00 00 00
000000A0 00 00 00 00 00 00 00 00 00 02 00 00 5B 00 00 00
000000B0 BA 06 00 00 00 00 00 00 A0 00 00 00 00 00 00 00
000000C0 C4 00 00 00 02 00 00 00 F4 00 00 00 00 00 00 00
000000D0 00 00 00 00 00 00 00 00 50 01 00 00 5A 01 00 00
000000E0 00 00 00 00 00 00 00 00 64 01 00 00 C4 01 00 00
000000F0 1E 08 00 00 00 00 00 00 1E 08 00 00 00 00 00 00
00000100 00 10 00 00 16 00 00 00 5C 3C 00 00 23 00 00 00
00000110 00 00 00 00 00 00 00 00 7F 3C 00 00 1C 00 00 00
00000120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00000130 00 00 00 00 00 00 00 00 C8 3C 00 00 C4 01 00 00
00000140 00 00 00 04
| Field | Value |
|---|---|
| Byte order, word order, level | 0, 0, 0 |
| CPU, OS | 2, the 80386; 4, Windows 386 |
| Module flags | 0x00038000: the type seen in Windows VxDs |
| Pages | 23 |
| EIP and ESP objects | 0: none |
| Page size | 0x200, 512 bytes |
| Bytes in the last page | 0x5B, 91 |
| Fixup section, loader section | 0x6BA and 0xA0 bytes, no checksums |
| Object table, objects | +0xC4 (file 0x144), 2 |
| Object page table | +0xF4 (file 0x174) |
| Resources | none |
| Resident names, entry table | +0x150, +0x15A |
| Fixup page table, fixup records | +0x164, +0x1C4 |
| Import modules, procedures | both at +0x81E; no imported modules |
| Data pages | file offset 0x1000 |
| Preload pages | 22 |
| Non-resident names | file offset 0x3C5C, 35 bytes |
| Debug information | file offset 0x3C7F, 28 bytes |
| Version resource | file offset 0x3CC8, 0x1C4 bytes |
| Device ID, DDK version | 0; 0x0400, 4.0 |
The pages bear out the 512-byte page size: object 1 is 0x2A68 bytes in 22
pages, which only fits pages of 512 bytes, and the 22 whole pages and the 91-byte last
page, from 0x1000, end just before the non-resident names.