GPT分区表的备份与恢复

时间:2024-01-03 09:02:44

GPT分区表的备份与恢复

GPT分区表的备份与恢复 keenshoes 2016-01-13 21:02:25
关键词: GPT, Partition, MBR,APPLE, GUID, Protective MBR

对于现在的系统来说,分区的类型千百种,但对于磁盘分区的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信息。

GPT分区表的备份与恢复
MBR和GPT分区格式对比图

Apple分区表: Apple Partition Map (APM)是苹果计算机上用来定义磁盘分区的数据结构,它引用了Logical Block的概念,通常512 bytes定义为一个Block,第一个Block中存放了苹果特有的Block0结构。这种分区结构主要是在DOS分区的复杂性和BSD分区的数量限制中的一种折衷方案。苹果分区表能描述任意数量的分区,以及后续扇区的数据结构。

GPT分区表的备份与恢复
APPLE分区表结构

Protective MBR
在GPT分区表的最开头(LBA0),处于兼容性考虑仍然存储了一份传统的MBR,用来防止不支持GPT的硬盘管理工具错误识别并破坏硬盘中的数据,这个MBR也叫做叫做保护MBR。在支持从GPT启动的操作系统中,这里也用于存储第一阶段的启动代码。在这个MBR中,只有一个标识为0xEE的分区,以此来表示这块硬盘使用GPT分区表。不能识别GPT硬盘的操作系统通常会识别出一个未知类型的分区,并且拒绝对硬盘进行操作,除非用户特别要求删除这个分区,这就避免了意外删除分区的危险。另外,能够识别GPT分区表的操作系统会检查保护MBR中的分区表,如果分区类型不是0xEE或者MBR分区表中有多个项,也会拒绝对硬盘进行操作。

GPT分区表的备份与恢复
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)

GPT分区表的备份与恢复
MBR分区表

GPT的分区表结构:
GPT分区表头定义了硬盘的可用空间以及组成分区表的项的大小和数量。分区表头还记录了这块硬盘的GUID,记录了分区表头本身的位置和大小(位置总是在LBA 1)以及备份分区表头和分区表的位置和大小(在硬盘的最后)。它还储存着它本身和分区表的CRC32校验。固件、引导程序和操作系统在启动时可以根据这个校验值来判断分区表是否出错,如果出错了,可以使用软件从硬盘最后的备份GPT中恢复整个分区表,如果备份GPT也校验错误,硬盘将不可使用。所以GPT硬盘的分区表不可以直接使用16进制编辑器修改。
主分区表和备份分区表的头分别位于硬盘的第二个扇区(LBA 1)以及硬盘的最后一个扇区。备份分区表头中的信息是关于备份分区表的。

GPT分区表的备份与恢复
GPT Header Structure-Data
GPT分区表的备份与恢复
GPT Header Structure

GPT分区头信息依然占用一个完整的Block,其中在90-512未定义的字节都为预留。

对于分区表项,在64位系统的机器上,最多可以创建128个分区,即分区表中保留了128个项,其中每个都是128字节。(EFI标准要求分区表最小要有16,384字节,即128个分区项的大小)

GPT分区表的备份与恢复
GPT Partition Entry Example
GPT分区表的备份与恢复
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分区表的备份与恢复
2016-01-15 23:50:28 keenshoes

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()