Every MS-DOS program starts with this header: fourteen 16-bit words, 28 bytes, that say how large the program is, where its relocations are, how much memory it needs and where it starts. The header is then padded to a whole number of 16-byte paragraphs, and the relocation table usually sits inside it. All numbers are little-endian.
Fields
| Offset | Size | Field | Meaning |
|---|---|---|---|
0x00 | 2 | e_magic | MZ (4D 5A). A few very early files have ZM. |
0x02 | 2 | e_cblp | Bytes used in the last 512-byte page of the image; 0 means the whole page. |
0x04 | 2 | e_cp | 512-byte pages in the image, counting the last, partial one. |
0x06 | 2 | e_crlc | The number of relocation entries; may be 0. |
0x08 | 2 | e_cparhdr | The size of the header in paragraphs. The load module starts straight after it. |
0x0A | 2 | e_minalloc | Extra paragraphs the program needs after the image. |
0x0C | 2 | e_maxalloc | Extra paragraphs it would like; 0xFFFF asks for all there is. |
0x0E | 2 | e_ss | The initial SS, relative to the start of the load module. |
0x10 | 2 | e_sp | The initial SP. |
0x12 | 2 | e_csum | A checksum; usually 0. |
0x14 | 2 | e_ip | The initial IP. |
0x16 | 2 | e_cs | The initial CS, relative to the start of the load module. |
0x18 | 2 | e_lfarlc | The file offset of the relocation table. |
0x1A | 2 | e_ovno | The overlay number; 0 for the main program. |
0x1C | … | extended fields or a mark | Whatever lies between the fields and the relocation table. |
Signature
e_magic is the two bytes 4D 5A, MZ in ASCII. DOS
also accepts ZM, which a few very early files have. Without one of these,
DOS runs the file as a .com program,
whatever its name.
Image size
e_cp and e_cblp give the size of the image: the part of the
file that DOS reads, header included. e_cp counts 512-byte pages, the last
one perhaps only partly used, and e_cblp says how many bytes of that last
page are used:
image size = e_cp × 512 when e_cblp is 0
image size = (e_cp − 1) × 512 + e_cblp otherwise
The image is often the whole file. Anything after it is an
overlay, which DOS does not load. An image
longer than the file means the file has been cut short; the explorer says so and shows
what there is. e_cblp above 512 or an e_cp of 0 cannot be
right, and the explorer does not take such a file as an MZ program.
Relocation count
e_crlc is the number of 4-byte entries in the
relocation table. A program that never
uses a segment address of its own, such as one built in a single segment, has none.
Header size
e_cparhdr is the size of the header in 16-byte paragraphs, relocation
table included. The load module starts at e_cparhdr × 16. It must be at
least 2, so that the header holds its own 28 bytes.
Linkers pad the header to a whole number of paragraphs, and many pad it to 512 bytes or more. The explorer shows the bytes between the end of the relocation table and the end of the header as “Rest of the header”.
Memory
The image holds only the program’s initialised code and data. Its uninitialised data
and its stack are not stored: e_minalloc says how many paragraphs the
program needs after the load module for them, and DOS refuses to start the program if
that much memory is not free. e_maxalloc is how many it would like;
0xFFFF asks for all the free memory.
Stack
e_ss is the stack segment, in paragraphs from the start of the load
module, and e_sp the stack pointer. DOS adds the load segment to
e_ss and copies e_sp as it is. The stack usually lies in the
memory e_minalloc asks for, past the end of the image.
Checksum
The checksum is correct when the 16-bit sum of all the words in the file, this one included, is 0. DOS does not check it, and most linkers write 0. The explorer shows it without checking it.
Entry point
e_cs and e_ip give the first instruction. CS is in
paragraphs from the start of the load module, so the entry point is at file offset
e_cparhdr × 16 + e_cs × 16 + e_ip.
CS can be “negative”. Programs packed with PKLITE use CS:IP FFF0:0100:
FFF0 is −0x10 paragraphs, the segment of the PSP, and 0x100
bytes into the PSP is the load module’s first byte. So the program starts at the start
of the load module. The explorer reads CS as a signed number for this reason.
Relocation table offset
e_lfarlc is the file offset of the
relocation table. Most DOS linkers put
the table straight after the fields, at 0x1C, or a little later after a
mark of their own. Windows and OS/2 linkers put it at 0x40 or later, so
that the extended fields fit before it. A table that
starts inside the first 28 bytes, or runs past the end of the file, cannot be
right.
Overlay number
e_ovno is 0 for the main program. Overlay managers that keep the parts of a
program as further MZ images number those parts here; see
Overlays and appended data.
Extended fields
Windows and OS/2 programs extend the header to 64 bytes and start the relocation table
at 0x40 or later. The words from 0x1C become these fields:
| Offset | Size | Field | Meaning |
|---|---|---|---|
0x1C | 8 | e_res | Reserved, zero. |
0x24 | 2 | e_oemid | An OEM identifier for e_oeminfo. |
0x26 | 2 | e_oeminfo | OEM information. |
0x28 | 20 | e_res2 | Reserved, zero. |
0x3C | 4 | e_lfanew | The file offset of the new header. |
The explorer shows 0x1C to 0x3F as these fields only when
e_lfarlc is at least 0x40, the header is at least 64 bytes,
and the bytes from 0x1C to 0x3B are all zero. Programs packed
with PKLITE also start their relocations at 0x52, but keep text of their
own there; see Marks.
e_lfanew
In a Windows or OS/2 program, the 32-bit word at 0x3C is the file offset
of a second header, which says what the file really is:
PE\0\0 (PE32 or
PE32+), NE
(NE), LE
(LE) or LX
(LX). DOS ignores it and runs the stub.
A file reaches this format only when e_lfanew does not lead to a valid
header of those kinds. When the extended fields are there, the explorer shows the
value and why it was not followed: it points past the end of the file, at no known
signature, or at a header whose tables do not fit. In a program without the extended
fields, the bytes at 0x3C belong to the relocation table, the padding or
the code, and mean nothing here.
Marks
Many DOS programs have bytes between the 28-byte fields and the relocation table. Linkers and packers put their own marks there. The explorer names the ones below, which were seen in files examined for this site:
Bytes at 0x1C | Made by | Notes |
|---|---|---|
01 00 FB vv, often then 6A 72 (“jr”) | Borland TLINK | vv is a version byte; 0x30 and 0x50 were seen. e_lfarlc is then 0x3E. The meaning of the other bytes is not documented. |
LZ09 or LZ91 | LZEXE 0.90 or 0.91 | A program compressed with LZEXE; its own decompressor is the entry point. |
Two bytes (14 11 seen), then PKLITE Copr. 1990-92 PKWARE Inc. | PKLITE | A compressed program; e_lfarlc is 0x52. |
WDOSX 0.95 DOS extender Copyright (c) 1996-1998 Michael Tippach | WDOSX | A program for the WDOSX DOS extender. |
Many other files have e_lfarlc 0x1E and the word
01 00 at 0x1C. Which linker writes it, and what it means, has
not been confirmed. The explorer shows any such bytes as “After the fixed header”.
Worked example
The start of ORGART.EXE, an 18,256-byte DOS program built with Borland’s tools (it carries “Portions Copyright (c) 1983,92 Borland”):
00000000 4D 5A 50 01 24 00 5F 00 1A 00 97 0C 97 AC F2 0C MZP.$._.........
00000010 00 40 00 00 69 08 00 00 1C 00 00 00 08 00 00 00 .@..i...........
0x1C| Field | Bytes | Value |
|---|---|---|
| e_magic | 4D 5A | MZ |
| e_cblp | 50 01 | 336 bytes in the last page |
| e_cp | 24 00 | 36 pages |
| e_crlc | 5F 00 | 95 relocations |
| e_cparhdr | 1A 00 | 26 paragraphs: a 416-byte header |
| e_minalloc | 97 0C | 0x0C97 paragraphs (51,568 bytes) |
| e_maxalloc | 97 AC | 0xAC97 paragraphs |
| e_ss | F2 0C | 0x0CF2 |
| e_sp | 00 40 | 0x4000 |
| e_csum | 00 00 | 0, not filled in |
| e_ip | 69 08 | 0x0869 |
| e_cs | 00 00 | 0 |
| e_lfarlc | 1C 00 | 0x1C: straight after the fields, so there is no mark |
| e_ovno | 00 00 | 0, the main program |
The image is (36 − 1) × 512 + 336 = 18,256 bytes, the whole file, so there is no
overlay. The load module starts at 26 × 16 = 0x1A0, and the entry point is
at 0x1A0 + 0x869 = 0xA09. The load module is 17,840 bytes,
0x45B paragraphs; with the 0xC97 paragraphs of
e_minalloc after it, the program needs 0x10F2 paragraphs. The
stack segment 0x0CF2 plus 0x4000 bytes of stack
(0x400 paragraphs) comes to 0x10F2 too: the top of the stack
is the end of the memory the program asks for.