直接读取VHD/VMDK文件会失败,因其是含元数据的容器格式,非普通二进制镜像;需解析头部签名、footer校验、磁盘类型、块映射表及大小端序等,否则出现全0扇区、乱码或越界。

直接读取VHD/VMDK文件会遇到什么问题
VHD和VMDK不是普通二进制镜像,它们是带元数据结构的容器格式,头部有固定签名和动态布局。直接用fread读取扇区会失败——因为前512字节可能不是客户机MBR,而是VHD头(如conectix签名)或VMDK描述段(如# Disk DescriptorFile)。跳过头部也不行:VHD有动态/差异盘类型,VMDK有sparse、streamOptimized、monolithicSparse等子格式,块映射表位置和解析方式完全不同。
常见错误现象包括:
- 读到全0扇区,实际客户机磁盘非空
-
fseek(fd, 512, SEEK_SET)后读出乱码,因未解析块偏移表 - 在VMDK中硬编码跳过4KB,结果在streamOptimized格式下直接越界
解析VHD必须处理三种磁盘类型
VHD规范定义了Fixed、Dynamic、Differencing三类,仅Dynamic和Differencing需要解析Block Allocation Table (BAT)。Fixed格式虽可直接按偏移读取,但必须先校验footer末尾的CRC32并确认CurrentType == 2(Fixed),否则可能误判为Dynamic。
实操要点:
- 从文件末尾读取512字节
footer,验证Cookie == "conectix"且Checksum有效 - 根据
CurrentType决定路径:type 2走Fixed逻辑,type 3/4必须解析header(位于footer前512字节)获取TableOffset和MaxTableEntries - Dynamic盘的BAT是uint32数组,每个值表示该块在文件中的扇区偏移(单位:512字节),值为0xFFFFFFFF表示未分配——此时应返回全0缓冲区,而非读文件
VMDK解析要先区分descriptor与flat两种形态
VMDK文件常成对出现:disk.vmdk(文本descriptor)+ disk-flat.vmdk(二进制数据)。但也有单文件streamOptimized格式,其descriptor嵌入文件开头。不先识别形态就开读,必然失败。
判断逻辑必须做三步:
- 读前1024字节,检查是否含
"# Disk DescriptorFile"——是则为descriptor文件,需解析RDWR/SPARSE标志及extent字段定位flat文件 - 若无此标记,检查前4字节是否为
0x56 0x4D 0x44 0x4B("VMDK" ASCII)——是则为单文件格式,需解析grain table,其偏移由header中gtOffset给出 - streamOptimized格式的
gtOffset可能为0,此时需用numGTEsPerGT和rgdOffset二级查找,不能假设table紧邻header
C++里别自己手写CRC32和大端转换
VHD footer的Checksum、VMDK header里的capacity等字段全是大端序,而x86_64是小端。手动htonl易错,且CRC32算法有多种变体(ISO 3309 vs Castagnoli),VHD用的是标准CRC32(多项式0xEDB88320),但VMDK文档未明说,实测用同一算法能通过校验。
立即学习“C++免费学习笔记(深入)”;
建议直接用:
-
ntohl()或be32toh()处理所有32位整数字段 - Boost.CRC或
zlib的crc32()函数校验footer,传参时注意VHD要求校验范围是footer全部512字节,但checksum字段本身要置0再计算 - 用
std::ifstream配合std::ios::binary | std::ios::in打开,避免Windows下换行符干扰
解析VHD/VMDK最易被忽略的是“未分配块”的语义处理——它不等于文件空洞,而是客户机视角的逻辑空白,必须由宿主程序主动填充0,不能指望底层read()返回0。


















