MZ 格式可执行文件详细结构
MZ 格式(俗称 DOS EXE 文件)是微软为 MS-DOS 设计的可执行文件格式,因文件头前两个字节为 MZ(十六进制 4D 5A,即 ASCII 字符 "MZ")而得名。
一、整体结构概览
+————————+ <- 文件开头 (偏移 0)
| MZ 头部 | (0x00 – 0x3F, 共 64 字节)
+————————+
| 重定位表 | (位置和大小由头部指定)
+————————+
| 程序代码和数据 | (可执行映像,通常以 0x200 字节对齐)
+————————+
二、MZ 头部详细结构(64 字节)
| 00h | 2 | e_magic | 签名标志,固定为 5A4Dh (即 "MZ") |
| 02h | 2 | e_cblp | 文件最后一页的字节数(0-511) |
| 04h | 2 | e_cp | 文件总页数(每页 512 字节) |
| 06h | 2 | e_crlc | 重定位表项数 |
| 08h | 2 | e_cparhdr | 头部占用的段落数(1 段落 = 16 字节) |
| 0Ah | 2 | e_minalloc | 程序所需最小额外内存(段落数) |
| 0Ch | 2 | e_maxalloc | 程序所需最大额外内存(段落数) |
| 0Eh | 2 | e_ss | 初始 SS 的相对值(相对于映像基址) |
| 10h | 2 | e_sp | 初始 SP 值 |
| 12h | 2 | e_csum | 校验和(通常为 0,可忽略) |
| 14h | 2 | e_ip | 初始 IP 值 |
| 16h | 2 | e_cs | 初始 CS 的相对值(相对于映像基址) |
| 18h | 2 | e_lfarlc | 重定位表在文件中的偏移量 |
| 1Ah | 2 | e_ovno | 覆盖号(0 表示主程序) |
| 1Ch | 4 | e_res[4] | 保留字(4 个字节,通常为 0) |
| 24h | 2 | e_oemid | OEM 标识符(用于特定系统) |
| 26h | 2 | e_oeminfo | OEM 信息 |
| 28h | 10 | e_res2[10] | 保留字(10 个字节) |
| 32h | 4 | e_lfanew | 扩展头偏移(仅限 NE/PE 格式,DOS 程序可为 0) |
三、关键字段计算示例
假设某 EXE 文件的 MZ 头信息如下:
e_magic = 0x5A4D ("MZ")
e_cblp = 0x0120 (最后一页 288 字节)
e_cp = 0x0008 (总 8 页 = 4096 字节)
e_crlc = 0x0004 (4 个重定位项)
e_cparhdr = 0x0002 (头部 2 段落 = 32 字节)
e_minalloc = 0x0010 (最少需 16 段额外内存)
e_ss = 0x0005 (SS 相对值 = 5)
e_sp = 0x0100 (SP = 256)
e_ip = 0x0000 (IP = 0)
e_cs = 0x0008 (CS 相对值 = 8)
e_lfarlc = 0x0040 (重定位表在偏移 0x40)
文件大小计算:
-
总字节数 = e_cp * 512 – (512 – e_cblp)
-
= 8 × 512 – (512 – 288) = 4096 – 224 = 3872 字节
四、重定位表格式
重定位表紧跟在 MZ 头部之后(由 e_lfarlc 指示),每项结构如下:
| 0 | 2 | 需要修正的段地址(偏移量,在映像中的位置) |
| 2 | 2 | 需要修正的段值(相对于映像基址的逻辑段) |
修正公式:
实际物理段地址 = 映像加载段基址 + 重定位项中的段值
五、程序加载流程(DOS 加载器)
读取 MZ 头部,校验 e_magic == "MZ"
根据 e_cparhdr 计算头部字节数(e_cparhdr * 16)
读取重定位表(位置 e_lfarlc,项数 e_crlc)
分配内存(至少 (头部段落数 + 映像段落数 + e_minalloc) * 16 字节)
在分配内存起始处建立 PSP(程序段前缀,256 字节)
将程序映像加载到 PSP 之后的地址(偏移 e_cparhdr * 16)
根据重定位表修正所有需要重定位的地址
设置 DS、ES = PSP 段地址
设置 SS = PSP 段地址 + e_ss,SP = e_sp
跳转到入口:CS = PSP 段地址 + e_cs,IP = e_ip
六、与 COM 文件的区别
| 文件头 | 有 64 字节 MZ 头 | 无头 |
| 最大大小 | 理论无上限(可分段) | < 64KB |
| 入口点 | CS:IP 可任意指定 | 固定 CS:IP = PSP:0x100 |
| 重定位 | 需要重定位表 | 无需(所有段地址固定) |
| 内存模型 | 任意模型(TINY/SMALL/MEDIUM/LARGE/HUGE) | 仅 TINY 模型 |
| 签名 | 前两字节 "MZ" | 无 |
七、内存中的最终布局
低地址
+——————+ <- PSP 段地址 (例如 0x1000)
| PSP | (256 字节,包含命令行环境等)
| (0x000 – 0x0FF) |
+——————+ <- 映像加载起始地址 = PSP + e_cparhdr
| 程序映像 |
| (代码 + 数据) |
+——————+ <- 堆栈段 (SS = PSP + e_ss)
| 堆栈 |
+——————+
高地址
八、常用工具
-
EXE2BIN:将 MZ EXE 转换为 COM 文件(要求 TINY 模型)
-
DEBUG.EXE:可用 L 命令加载 EXE,R 查看寄存器
-
DUMPBIN /HEADERS:Windows SDK 工具,可显示 MZ 头信息
-
Hiew / WinHex:十六进制编辑器,可手动分析
这个结构是 DOS 时代可执行文件的基础,现代的 Windows PE 格式(.exe)依然在文件开头保留了 MZ 头(用于向后兼容,其 e_lfanew 指向真正的 PE 头)。





