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信息。

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

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

GPT分区表的备份与恢复的更多相关文章

  1. 装机、做系统必备:秒懂MBR和GPT分区表

    从Intel 6系列主板之后,就开始提供UEFI BIOS支持,正式支持GPT硬盘分区表,一举取代了此前的MBR分区表格式,不过为了保持对老平台的兼容,微软即使最新的Windows 10系统也继续提供 ...

  2. Linux GPT分区表16进制实例分析

    Linux GPT分区表16进制实例分析 GPT分区表随着win10的普及,已经在越来越多的新电脑上开始使用了.前段时间的新闻有看到说Intel会在后面的新平台中完全取消CSM支持,这也大概相当于后面 ...

  3. 装机、做系统必备:秒懂MBR和GPT分区表____转载网络

    装机.做系统必备:秒懂MBR和GPT分区表 科技美学 2016-10-17 16:36:23 阅读(3835) 评论(4) 很多网友询问MBR和GPT的问题,涉及到硬盘操作系统的安装,其实除了MBR和 ...

  4. 【Windows Server存储】MBR和GPT分区表

    MBR和GPT分区表 分区表用于引导操作系统 master boot record(MBR)于1983年首次在PC上推出 最大4个主分区 2太空间 GUID Partition Table(GPT), ...

  5. 技术文档分享_linux中生成考核用的GPT分区表结构修复

    注:历史版本,后期改用python实现了 实验一: 目的:用于生成大量模拟破坏GPT分区结构案例,并生成唯一方式修复后的评判方法.故障:在一个完整的GPT分区磁盘上,丢失了GPT主分区表,或备份分区表 ...

  6. E​F​I​主​板​和​G​P​T​分​区​表​安​装​系​统以及转换GPT分区表的方法

    现在硬盘越来越大,而原来的MBR分区方式,超过2T的硬盘就会识别不全,只有使用GPT的方式才可以,但是GPT如果用原来的BIOS是无法引导装系统了,不过如果你的主板支持EFI,那么可以用GPT+EFI ...

  7. 【分区】使用 GPT 分区表分区并格式化 (FreeBSD 系统)

    1. 查看磁盘列表 使用命令 diskinfo -v /dev/vtbd1 查看磁盘设备列表. 2. 创建 GPT 分区 1). 执行命令 gpart create -s gpt vtbd1.2). ...

  8. 【分区】使用 GPT 分区表分区并格式化 (非 FreeBSD 系统)

    新购买的 Linux 云服务器,由于数据盘未做分区和格式化,无法使用. 注意: 数据盘中的数据在格式化后将全部被清空.请在格式化之前,确保数据盘中没有数据或已对重要数据进行备份.为避免服务发生异常,格 ...

  9. 统一使用GPT分区表,安装MAC 10.10 和 Win8.1 pro双系统

    步骤一: 为Mac OS 分区,为其它分区留白1,使用OSX Mavericks制作的Mac安装U盘按住Option键启动:2,选择安装Mavericks盘符:3,进入OSX安装启动界面,选择磁盘工具 ...

随机推荐

  1. centos7安装golang环境

    1.下载golang安装包 wget https://dl.google.com/go/go1.12.5.linux-amd64.tar.gz 2.解压至/usr/local文件夹 tar -C /u ...

  2. [题解](线段树最大连续子段和)POJ_3667_Hotel

    题意:1.求一个最靠左的长x的区间全部为0,并修改为1,输出这个区间的左端点 2.修改一个区间为0 实际上是维护最大连续子段和,原来也写过 大概需要维护一个左/右最大子段和,当前这段最大子段长,再维护 ...

  3. EcmaScript学习

    1.eval: ts: declare function eval(x: string): any; js: /** @param {*} x @return {Object} */ eval = f ...

  4. Vue 4 -- 获取原生的DOM的方式、DIY脚手架、vue-cli的使用

    一.获取原生的DOM的方式 在js中,我们可以通过id.class或者标签获取DOM元素,vue中也为我们提供了获取原生DOM的方法,就是给标签或者组件添加ref属性,通过this.$refs获取,如 ...

  5. vbox和宿主机共享文件夹

    首先,查看vbox安装的ubuntu是否支持vboxsf模块 sudo modprobe vboxsf dmesg | grep vboxsf 如果没有安装,需要安装vboxsf模块:(如果安装了请跳 ...

  6. C - Watchmen

    题目链接:https://vjudge.net/contest/237394#problem/C Watchmen are in a danger and Doctor Manhattan toget ...

  7. htaccess转换httpd.ini方法及案例参考

    案例1:httpd.ini适合IIS使用,.htaccess适合Apache使用,nginx.conf适合Nginx使用 转换前:httpd.ini [ISAPI_Rewrite] # 3600 =  ...

  8. [luogu 3369]普通平衡树(fhq_treap)

    题目描述 您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作: 插入x数 删除x数(若有多个相同的数,因只删除一个) 查询x数的排名(排名定义为比当前数小的数的个数+1.若有多 ...

  9. ActionListener 监听事件源产生的事件

    用户在窗体上对组件进行一定动作,比如鼠标点击,会产生一些相应的事件,如ActionEvents,ChangeEvents,ItemEvents等,来响应用户的鼠标点击行为.通过实现ActionList ...

  10. JQuery初识(二)

    一丶链式编程 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UT ...