转自 http://blog.linjian.org/articles/linux-mount-part-image/
最近在做虚拟机相关的事,需要处理一些磁盘和分区的映像文件。如何从一个磁盘映像中挂载指定的分区到本地 Linux 文件系统呢?理论上说,可以用 dd 把该分区从磁盘映像中提取出来再挂载,不过 mount 提供了针对loop 设备的偏移量参数,方便直接从磁盘映像中挂载指定分区。笔记如下:
演示用的磁盘映像使用 qemu-img 制作。我们使用原生的 raw 格式,等价于磁盘上的原始数据流,保证它在任何 Linux 系统上都可以直接挂载。使用 Windows PE 工具盘启动该 qemu 虚拟机,创建一系列不同格式的分区并在其中建立几个文件。
- root@lj-laptop:/opt/vm# qemu-img create -f raw vmtest.img 5G
- root@lj-laptop:/opt/vm# qemu -hda ./vmtest.img -cdrom /dev/cdrom -boot d -m 256M -localtime
磁盘分区结构如截图:
回到宿主系统,使用 fdisk 的 -l、-u 参数查看磁盘映像。其中 -u 表示以扇区为单位显示分区起止位置,方便后续计算。
- root@lj-laptop:/opt/vm# fdisk -l -u vmtest.img
- You must set cylinders.
- You can do this from the extra functions menu.
- Disk vmtest.img: 0 MB, 0 bytes
- 255 heads, 63 sectors/track, 0 cylinders, total 0 sectors
- Units = sectors of 1 * 512 = 512 bytes
- Disk identifier: 0xbd86bd86
- Device Boot Start End Blocks Id System
- vmtest.img1 63 8193149 4096543+ 83 Linux
- vmtest.img2 8193150 10249469 1028160 5 Extended
- vmtest.img3 10249470 10474379 112455 6 FAT16
- vmtest.img5 8193213 8610839 208813+ 83 Linux
- vmtest.img6 8610903 9028529 208813+ 82 Linux swap / Solaris
- vmtest.img7 9028593 10249469 610438+ b W95 FAT32
这时我们只需要为 mount 添加 offset 参数,指定分区在磁盘映像中的逻辑地址,即可挂载这一分区。注意 offset 的单位是字节,通常一个扇区是 512 字节,因此需要用 fdisk 输出的 Start 乘以 512。以 vmtest.img3 分区为例。
- root@lj-laptop:/opt/vm# mount -o loop,offset=$((10249470*512)) vmtest.img ./part/
- root@lj-laptop:/opt/vm# ls ./part/
- InFAT16.txt
- root@lj-laptop:/opt/vm# umount ./part/
如果有必要,我们可以将特定的分区从硬盘映像中提取出来,方便单独使用。通过 dd 可以完成提取,简便起见使用扇区大小 512 字节作为 bs 参数,这样 skip、count 参数很容易从 fdisk 的输出中计算出来。下面提取 vmtest.img5。
- root@lj-laptop:/opt/vm# dd if=vmtest.img of=linux2.img bs=512 skip=8193213 count=$((8610839-8193213+1))
- 417627+0 records in
- 417627+0 records out
- 213825024 bytes (214 MB) copied, 4.67434 s, 45.7 MB/s
- root@lj-laptop:/opt/vm# mount -o loop linux2.img ./part/
- root@lj-laptop:/opt/vm# ls ./part/
- InLinux2.txt lost+found
- root@lj-laptop:/opt/vm# umount ./part/
到这一步,我们已经实现了从磁盘映像中挂载或提取指定分区。其中 fdisk 帮我们完成了磁盘映像的分析,直接将分区的逻辑地址显示给了我们。现在我们再借机复习一下磁盘分区表的格式,试试手工计算相关地址。这里推荐大家阅读网上这篇《解读 Windows 操作系统分区表的秘密》,其中详解过的概念这里不再赘述,下面的内容权当对这篇文章在 Linux 平台下的补充。
首先查看磁盘映像的主引导扇区,其中分区表位于 0x1be 开始的 64 字节。
- root@lj-laptop:/opt/vm# cat vmtest.img | xxd | head -32
- ...
- 00001b0: 0000 0000 0000 0000 86bd 86bd 0000 0001 ................
- 00001c0: 0100 83fe 7ffd 3f00 0000 3f04 7d00 0000 ......?...?.}...
- 00001d0: 41fe 05fe bf7d 7e04 7d00 8060 1f00 0000 A....}~.}..`....
- 00001e0: 817e 06fe bf8b fe64 9c00 8e6e 0300 0000 .~.....d...n....
- 00001f0: 0000 0000 0000 0000 0000 0000 0000 55aa ..............U.
对于 vmtest.img1 和 vmtest.img3 这两个主分区,我们读出它们的起始逻辑地址分别为 0x3f 和 0x9c64fe(注意在分区表中以 Little-endian 存储),换算成十进制与 fdisk 输出的一致。bash 的算术表达式支持十六进制字面值,挂载方式同上。
- root@lj-laptop:/opt/vm# mount -o loop,offset=$((0x3f*512)) vmtest.img ./part/
- root@lj-laptop:/opt/vm# ls ./part/
- InLinux.txt lost+found
- root@lj-laptop:/opt/vm# umount ./part/
- root@lj-laptop:/opt/vm# mount -o loop,offset=$((0x9c64fe*512)) vmtest.img ./part/
- root@lj-laptop:/opt/vm# ls ./part/
- InFAT16.txt
- root@lj-laptop:/opt/vm# umount ./part/
对于扩展分区 vmtest.img2,我们通过 dd 定位到它所指向的第一个逻辑分区 vmtest.img5 前面的卷引导记录,查看逻辑分区的链式分区表。
- root@lj-laptop:/opt/vm# dd if=vmtest.img bs=512 skip=$((0x7d047e)) | xxd | head -32
- ...
- 00001b0: 0000 0000 0000 0000 0000 0000 0000 0001 ................
- 00001c0: 41fe 83fe bf17 3f00 0000 5b5f 0600 0000 A.....?...[_....
- 00001d0: 8118 05fe bf31 9a5f 0600 9a5f 0600 0000 .....1._..._....
- 00001e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
- 00001f0: 0000 0000 0000 0000 0000 0000 0000 55aa ..............U.
vmtest.img5 相对于 vmtest.img2 的偏移量为 0x3f,按此地址挂载。
- root@lj-laptop:/opt/vm# mount -o loop,offset=$(((0x7d047e+0x3f)*512)) vmtest.img ./part/
- root@lj-laptop:/opt/vm# ls ./part/
- InLinux2.txt lost+found
- root@lj-laptop:/opt/vm# umount ./part/
沿着链式分区表的第二条记录(即链表指针)指示的偏移量,我们可以找到第二、第三个逻辑分区(vmtest.img6、vmtest.img7)前面的卷引导记录以及这两个分区的逻辑地址。
- root@lj-laptop:/opt/vm# dd if=vmtest.img bs=512 skip=$((0x7d047e+0x65f9a)) | xxd | head -32
- ...
- 00001b0: 0000 0000 0000 0000 0000 0000 0000 0001 ................
- 00001c0: 8118 82fe bf31 3f00 0000 5b5f 0600 0000 .....1?...[_....
- 00001d0: 8132 05fe bf7d 34bf 0c00 4ca1 1200 0000 .2...}4...L.....
- 00001e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
- 00001f0: 0000 0000 0000 0000 0000 0000 0000 55aa ..............U.
- root@lj-laptop:/opt/vm# dd if=vmtest.img bs=512 skip=$((0x7d047e+0xcbf34)) | xxd | head -32
- ...
- 00001b0: 0000 0000 0000 0000 0000 0000 0000 0001 ................
- 00001c0: 8132 0bfe bf7d 3f00 0000 0da1 1200 0000 .2...}?.........
- 00001d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
- 00001e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
- 00001f0: 0000 0000 0000 0000 0000 0000 0000 55aa ..............U.
取得了相应的地址,挂载或提取分区当然是轻而易举的。下面挂载了 vmtest.img7、提取了 vmtest.img5。
- root@lj-laptop:/opt/vm# mount -o loop,offset=$(((0x7d047e+0xcbf34+0x3f)*512)) vmtest.img ./part/
- root@lj-laptop:/opt/vm# ls ./part/
- InFAT32.txt
- root@lj-laptop:/opt/vm# umount ./part/
- root@lj-laptop:/opt/vm# dd if=vmtest.img of=linux2.img bs=512 skip=$((0x7d047e+0x3f)) count=$((0x65f5b))
- 417627+0 records in
- 417627+0 records out
- 213825024 bytes (214 MB) copied, 4.65149 s, 46.0 MB/s
- root@lj-laptop:/opt/vm# mount -o loop ./linux2.img ./part/
- root@lj-laptop:/opt/vm# ls ./part/
- InLinux2.txt lost+found
- root@lj-laptop:/opt/vm# umount ./part/