GPT分区表的备份与恢复
对于现在的系统来说,分区的类型千百种,但对于磁盘分区的layout来说,最常接触的只是三种而已: MBR(Master Boot Record), GPT(Globe Partition Table)和Apple Partition(Mixed分区)。
MBR分区表: 磁盘上最重要的数据结构,其中包含小段引导代码,磁盘信息,分区表等。在MBR的尾部有一个2-byte字段标记签名或分区结构的结束,总标记为0x55AA。
GUID分区表:对于GPT磁盘采用带有主备分区表结构的GUID分区表。这个结构分别保存在磁盘开头和结尾的部分。相比MBR采用扇区Sector来识别的方法,GPT采用logical Block Address(LBA)来识别。Protective MBR: 在GPT磁盘中,LBA 0的位置存放着第一个结构Protective MBR,紧跟着,在LBA 1的位置存放这主GPT头。随后是GUID的分区Entry信息。
MBR和GPT分区格式对比图
|
Apple分区表: Apple Partition Map (APM)是苹果计算机上用来定义磁盘分区的数据结构,它引用了Logical Block的概念,通常512 bytes定义为一个Block,第一个Block中存放了苹果特有的Block0结构。这种分区结构主要是在DOS分区的复杂性和BSD分区的数量限制中的一种折衷方案。苹果分区表能描述任意数量的分区,以及后续扇区的数据结构。
APPLE分区表结构
|
Protective MBR
在GPT分区表的最开头(LBA0),处于兼容性考虑仍然存储了一份传统的MBR,用来防止不支持GPT的硬盘管理工具错误识别并破坏硬盘中的数据,这个MBR也叫做叫做保护MBR。在支持从GPT启动的操作系统中,这里也用于存储第一阶段的启动代码。在这个MBR中,只有一个标识为0xEE的分区,以此来表示这块硬盘使用GPT分区表。不能识别GPT硬盘的操作系统通常会识别出一个未知类型的分区,并且拒绝对硬盘进行操作,除非用户特别要求删除这个分区,这就避免了意外删除分区的危险。另外,能够识别GPT分区表的操作系统会检查保护MBR中的分区表,如果分区类型不是0xEE或者MBR分区表中有多个项,也会拒绝对硬盘进行操作。
Protective MBR
|
在使用MBR/GPT混合分区表的硬盘中,这部分存储了GPT分区表的一部分分区(通常是前四个分区),可以使不支持从GPT启动的操作系统从这个MBR启动,启动后只能操作MBR分区表中的分区。
MBR的分区表结构
MBR分区表是一个64-byte的数据结构,用来识别磁盘分区类型和位置。每个分区表项都是16bytes,最多4个分区表。每个分区表项在开始扇区的位置是预定的。
Partition 1 0x01BE (446)
Partition 2 0x01CE (462)
Partition 3 0x01DE (478)
Partition 4 0x01EE (494)
MBR分区表
|
GPT的分区表结构:
GPT分区表头定义了硬盘的可用空间以及组成分区表的项的大小和数量。分区表头还记录了这块硬盘的GUID,记录了分区表头本身的位置和大小(位置总是在LBA 1)以及备份分区表头和分区表的位置和大小(在硬盘的最后)。它还储存着它本身和分区表的CRC32校验。固件、引导程序和操作系统在启动时可以根据这个校验值来判断分区表是否出错,如果出错了,可以使用软件从硬盘最后的备份GPT中恢复整个分区表,如果备份GPT也校验错误,硬盘将不可使用。所以GPT硬盘的分区表不可以直接使用16进制编辑器修改。
主分区表和备份分区表的头分别位于硬盘的第二个扇区(LBA 1)以及硬盘的最后一个扇区。备份分区表头中的信息是关于备份分区表的。
GPT Header Structure-Data
|
GPT Header Structure
|
GPT分区头信息依然占用一个完整的Block,其中在90-512未定义的字节都为预留。
对于分区表项,在64位系统的机器上,最多可以创建128个分区,即分区表中保留了128个项,其中每个都是128字节。(EFI标准要求分区表最小要有16,384字节,即128个分区项的大小)
GPT Partition Entry Example
|
GPT Partition Entry
|
对于Apple系统需要注意:不要总是假定块大小总是512bytes。很多固态硬盘可能包含1024字节的LBAs,而MO驱动器采用2048-bytes的Sectore(MO通常都不分区)。
因此,对于分区表的操作,都是基于Block来处理。
LBA0 Protective MBR
LBA1 GPT Header
LBA2-33 GPT Partition Entry (一个LBA里包含4个分区表信息,512/128=4)。
注意:在对磁盘分区的规范中,不是从字节或者Sector。而是LBA角度进行了定义,也就是说即使有磁盘格式并不是512的block,而是1024或者4096的,但是存放分区表的位置依然是34个Block。
备份方法:
根据以上对磁盘分区的理解,对于GPT格式的磁盘,备份及恢复可以采用以下方式(未完全验证)
1、备份分区表信息
sudo fdisk -l >hda.txt #分区表信息重定向输出到文件中
parted p
2、备份分区表
a, 备份Protective MBR
linux#dd if=/dev/sda of=gpt-mbr bs=512 count=1 #输入文件/dev/sda, 输出文件mbr(自己定义),输入(出)块大小512字节,复制一次,由于mbr是512个字节,所以读取写到mbr文件中了
1+0 records in
1+0 records out
512 bytes (512 B) copied,4.0728e-05 秒,12.6 MB/秒
b,备份完整的GPT分区表(含Protective MBR, GPT头,以及分区表)
linux#dd if=/dev/sda of=gpt-partition bs=512 count=34
c, 仅备份GPT头和GPT分区
linux#dd if=/dev/sda of=gpt-partition bs=512 skip=1 count=33
3、恢复分区表
a, 恢复Protective MBR的分区信息
#dd if=gpt-mbr of=/dev/sda bs=1 skip=446 count=66 #输入文件mbr,输出 /dev/sda ,块大小1个字节,输入跳过446字节,恢复66个字节,看来恢复的只有66个字节
如果逻辑分区都没有了,则用fdisk 照着hda.txt的信息重分一下就行了。
b, 恢复完整的Protective MBR
(在Mac OSX中对磁盘进行抹盘操作后(通常会创建一个128M的无数据区,或者一个Recovery HD区),非常容易将磁盘修改为混合磁盘模式,后续的Windows系统将无法正确识别磁盘,导致系统无法启动)
#dd if=gpt-mbr of=/dev/sda bs=512 count=1
c, 恢复完整的GPT分区表信息
#dd if=gpt--partition of=/dev/sda bs=512 count=34
d, 恢复单独的GPT分区信息(感觉意义不大)
#dd if=gpt-partition of=/dev/sda bs=512 skip=1 seek=1 count=33 (跳过备份表的一个bs, 再跳过sda的第一个bs然后再恢复数据)
另外有采用更为精致的脚本对分区进行恢复的方式(豆瓣不支持附件,就不对脚本进行上传了)。
1、首先下载附件,将gpt.surgeon.py文件放在任意目录下。
2、打开“终端”
3、输入:cd xxxx (这里的xxxx是刚才文件的存放目录,如果你放在桌面那么就直接:cd desktop)
4、输入:chmod +x gpt_surgeon.py
5、输入:sudo ./gpt_surgeon.py list /dev/disk1 (disk1是需要修复的磁盘,可以在磁盘工具中看到这个标识)
6、输入管理员密码后看到:
Read MBR and GPT from /dev/disk1.
partition 0:
type: EFI System
name: u'EFI System Partition'
flags: 0x00000000
partition 1:
type: Microsoft Basic Data
name: u'\u672a\u547d\u540d 1'
flags: 0x00000000
7、可以看到磁盘所有可以识别的分区信息,找到你要恢复的分区表的编号。
6、输入:sudo ./gpt_surgeon.py repair /dev/disk1 1 (disk1后面的1就是需要修复的分区表的编号)
7、完成。
Reference:
关于GPT磁盘的分区表备份
http://www.linuxdiyf.com/bbs/thread-310996-1-1.html
http://www.informit.com/articles/article.aspx?p=376123&seqNum=3
How Basic Disks and Volumes Work
https://technet.microsoft.com/en-us/library/cc739412%28v=ws.10%29.aspx
gpt_surgeon.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys, os, struct, binascii, uuid
def readStruct(stream, fmt):
length = struct.calcsize(fmt)
data = stream.read(length)
if len(data) == length:
return struct.unpack(fmt, data)
else:
return None
def readStruct1(stream, fmt):
fields = readStruct(stream, fmt)
if fields and len(fields) == 1:
return fields[0]
else:
return None
def writeStruct(stream, fmt, *fields):
stream.write(struct.pack(fmt, *fields))
blockSize = 512 # bytes
efiHeaderExpectedSize = 92 # bytes
efiHeaderFmt = '<8s4sII4xQQQQ16sQIII'
efiSignature = "EFI PART"
efiExpectedVersion = "\x00\x00\x01\x00"
efiEntryExpectedSize = 128 # bytes
efiEntryFmt = '<16s16sQQQ72s'
def crc32(bytes):
return binascii.crc32(bytes) & 0xffffffff
class EFIPartitionTable(object):
def __init__(self, disk):
rawHeader = disk.read(efiHeaderExpectedSize)
sig, version, \
headerSize, headerCRC, \
self.currentLBA, self.backupLBA, \
self.firstUsableLBA, self.lastUsableLBA, \
diskUUID, \
self.partitionTableStartLBA, \
self.partitionTableEntryCount, self.partitionTableEntrySize, \
self.partitionTableCRC = struct.unpack(efiHeaderFmt, rawHeader)
self.diskUUID = uuid.UUID(bytes_le=diskUUID)
# sanity checks
assert sig == efiSignature
assert version == efiExpectedVersion
assert len(rawHeader) == efiHeaderExpectedSize
assert headerSize == efiHeaderExpectedSize
assert self.lastUsableLBA >= self.firstUsableLBA
assert self.currentLBA != self.backupLBA
assert self.partitionTableStartLBA >= 2
# corruption check
headerForCRC = rawHeader[:16] + struct.pack('<I', 0) + rawHeader[20:]
assert crc32(headerForCRC) == headerCRC
disk.seek(blockSize * self.partitionTableStartLBA)
rawTable = disk.read(self.partitionTableEntryCount * self.partitionTableEntrySize)
# corruption check
assert crc32(rawTable) == self.partitionTableCRC
self.partitionTable = []
for idx in xrange(self.partitionTableEntryCount):
self.partitionTable.append(EFIPartitionEntry(
rawTable[idx * self.partitionTableEntrySize:(idx + 1) * self.partitionTableEntrySize]))
def pack(self, tableCRC):
rawHeader = struct.pack(efiHeaderFmt,
efiSignature, efiExpectedVersion, \
efiHeaderExpectedSize, 0, \
self.currentLBA, self.backupLBA, \
self.firstUsableLBA, self.lastUsableLBA, \
self.diskUUID.bytes_le, \
self.partitionTableStartLBA, \
self.partitionTableEntryCount, self.partitionTableEntrySize, \
tableCRC)
headerCRC = crc32(rawHeader)
rawHeader = rawHeader[:16] + struct.pack('<I', headerCRC) + rawHeader[20:]
return rawHeader
unusedUUID = uuid.UUID("00000000-0000-0000-0000-000000000000")
hfsPlusUUID = uuid.UUID("48465300-0000-11AA-AA11-00306543ECAC")
knownUUIDs = {
uuid.UUID("00000000-0000-0000-0000-000000000000"):"Unused",
uuid.UUID("024DEE41-33E7-11D3-9D69-0008C781F39F"):"MBR Scheme",
uuid.UUID("C12A7328-F81F-11D2-BA4B-00A0C93EC93B"):"EFI System",
uuid.UUID("21686148-6449-6E6F-744E-656564454649"):"BIOS Boot",
uuid.UUID("E3C9E316-0B5C-4DB8-817D-F92DF00215AE"):"Microsoft Reserved",
uuid.UUID("EBD0A0A2-B9E5-4433-87C0-68B6B72699C7"):"Microsoft Basic Data",
uuid.UUID("5808C8AA-7E8F-42E0-85D2-E1E90434CFB3"):"Microsoft Logical Disk Manager metadata",
uuid.UUID("AF9B60A0-1431-4F62-BC68-3311714A69AD"):"Microsoft Logical Disk Manager data",
uuid.UUID("48465300-0000-11AA-AA11-00306543ECAC"):"Apple HFS+",
uuid.UUID("55465300-0000-11AA-AA11-00306543ECAC"):"Apple UFS",
uuid.UUID("52414944-0000-11AA-AA11-00306543ECAC"):"Apple RAID",
uuid.UUID("52414944-5F4F-11AA-AA11-00306543ECAC"):"Apple RAID (offline)",
uuid.UUID("426F6F74-0000-11AA-AA11-00306543ECAC"):"Apple Boot",
uuid.UUID("4C616265-6C00-11AA-AA11-00306543ECAC"):"Apple Label",
uuid.UUID("5265636F-7665-11AA-AA11-00306543ECAC"):"Apple TV Recovery",
uuid.UUID("6A898CC3-1DD2-11B2-99A6-080020736631"):"Apple ZFS",
}
class EFIPartitionEntry(object):
def __init__(self, bytes):
partitionType, partitionUUID, \
self.firstLBA, self.lastLBA, \
self.flags, \
name = struct.unpack(efiEntryFmt, bytes)
self.partitionType = uuid.UUID(bytes_le=partitionType)
self.partitionUUID = uuid.UUID(bytes_le=partitionUUID)
name = name.decode('utf-16le')
term = name.find('\x00')
if term >= 0:
name = name[:term]
self.name = name
def pack(self):
rawEntry = struct.pack(efiEntryFmt, \
self.partitionType.bytes_le, self.partitionUUID.bytes_le, \
self.firstLBA, self.lastLBA, \
self.flags, \
self.name.encode('utf-16le'))
return rawEntry
def readMBRAndGPT(diskDevice):
disk = open(diskDevice, 'rb')
mbr = disk.read(blockSize)
gpt = EFIPartitionTable(disk)
disk.close()
print "Read MBR and GPT from %s." % diskDevice
return (mbr, gpt)
def listGPT(diskDevice):
_, gpt = readMBRAndGPT(diskDevice)
for i in xrange(gpt.partitionTableEntryCount):
entry = gpt.partitionTable[i]
if entry.partitionType == unusedUUID: continue
print "partition %d:" % i
print " type: %s" % knownUUIDs.get(entry.partitionType, "<unknown partition type>")
print " name: %r" % entry.name
print " flags: 0x%08x" % entry.flags
def fixGPTPartitionType(diskDevice, selectedPartition):
mbr, gpt = readMBRAndGPT(diskDevice)
gpt.partitionTable[selectedPartition].partitionType = hfsPlusUUID
print "Changing type of partition #%d on %s to HFS+..." % (selectedPartition, diskDevice)
disk = open(diskDevice, 'wb')
print " Opened %s for writing." % diskDevice
disk.write(mbr)
print " Wrote MBR."
table = ""
for i in xrange(gpt.partitionTableEntryCount):
entry = gpt.partitionTable[i]
table = table + entry.pack()
header = gpt.pack(crc32(table))
disk.write(header)
print " Wrote GPT header."
disk.write("\x00" * (blockSize - len(header)))
disk.write(table)
print " Wrote GPT entries."
disk.close()
print " Closed %s." % diskDevice
print "Done."
usage = """
usage:
%(progName)s list </dev/diskN>
Show GPT entries for a disk.
%(progName)s repair </dev/diskN> <partition number>
Change the type of the selected partition on the selected disk to HFS+.
The partition numbers start at 0, as in the list command.
"""[1:]
def exitToUsage(msg):
progName = sys.argv[0]
print
print msg
print
print usage % {'progName':progName}
exit(1)
def main():
argc = len(sys.argv)
if argc < 2:
exitToUsage("No command given!")
cmd = sys.argv[1]
if cmd == 'list':
if argc < 3:
exitToUsage("Too few arguments for list command!")
if argc > 3:
exitToUsage("Too many arguments for list command!")
diskDevice = sys.argv[2]
print
listGPT(diskDevice)
elif cmd == 'repair':
if argc < 4:
exitToUsage("Too few arguments for repair command!")
if argc > 4:
exitToUsage("Too many arguments for repair command!")
diskDevice = sys.argv[2]
selectedPartition = int(sys.argv[3])
print
fixGPTPartitionType(diskDevice, selectedPartition)
else:
exitToUsage("Unsupported command!")
if __name__ == '__main__':
main()