![]()
В предыдущей статье “Схемы разделов в Mac OS X. GUID partition table. Часть 1 (MBR)” мы рассмотрели “Protective MBR”. Сегодня займёмся следующей структурой – Partition Table Header.
Напомню, что я использую такие спецификации:
- Extensible Firmware Interface Specification 1.10, раздел 11.2.2, страница 367.
- UEFI Specification Version 2.3, раздел 5 “GUID Partition Table (GPT) Format”, страница 91.
В нулевом блоке на диске находится Protective MBR. В следующем же – GUID Partition Table Header. Это структура, описывающая различные данные по диску, включая GUID для уникальной идентификации диска, адрес стартового блока массива записей о разделах, размер записи в этом массиве и т.д.
Формат описан в таблице:
| Mnemonic | Byte | Length | Description |
| Signature | 0 | 8 | Identifies EFI-compatible partition table header. This value must contain the string “EFI PART,” 0×5452415020494645. |
| Revision | 8 | 4 | The revision number for this header. This revision value is not related to the UEFI Specification version. This header is version 1.0, so the correct value is 0×00010000. |
| HeaderSize | 12 | 4 | Size in bytes of the GUID Partition Table Header. The HeaderSize must be greater than 92 and must be less than or equal to the logical block size. |
| HeaderCRC32 | 16 | 4 | CRC32 checksum for the GUID Partition Table Header structure. This value is computed by setting this field to 0, and computing the 32-bit CRC for HeaderSize bytes. |
| Reserved | 20 | 4 | Must be zero. |
| MyLBA | 24 | 8 | The LBA that contains this data structure. |
| AlternateLBA | 32 | 8 | LBA address of the alternate GUID Partition Table Header. |
| FirstUsableLBA | 40 | 8 | The first usable logical block that may be used by a partition described by a GUID Partition Entry. |
| LastUsableLBA | 48 | 8 | The last usable logical block that may be used by a partition described by a GUID Partition Entry. |
| DiskGUID | 56 | 16 | GUID that can be used to uniquely identify the disk. |
| PartitionEntryLBA | 72 | 8 | The starting LBA of the GUID Partition Entry array. |
| NumberOfPartitionEntries | 80 | 4 | The number of Partition Entries in the GUID Partition Entry array. |
| SizeOfPartitionEntry | 84 | 4 | The size, in bytes, of each the GUID Partition Entry structures in the GUID Partition Entry array. Must be a multiple of 8. |
| PartitionEntryArrayCRC32 | 88 | 4 | The CRC32 of the GUID Partition Entry array. Starts at PartitionEntryLBA and is computed over a byte length of NumberOfPartitionEntries * SizeOfPartitionEntry. |
| Reserved | 92 | BlockSize – 92 | The rest of the block is reserved by UEFI and must be zero. |
За время, прошедшее с прошлой статьи о GPT, диск, на котором я ставил эксперименты, успел уйти под другие задачи, поэтому продолжим на флеш-драйве 8GB с GUID, разбитом на два раздела:
$ diskutil list /dev/disk3 /dev/disk3 #: TYPE NAME SIZE IDENTIFIER 0: GUID_partition_scheme *8.0 GB disk3 1: EFI 209.7 MB disk3s1 2: Apple_HFS Flash1 4.0 GB disk3s2 3: Apple_HFS Flash2 3.6 GB disk3s3
Считываем Partition Table Header:
sudo dd if=/dev/disk3 of=/dev/stdout bs=512 count=1 skip=1 2>/dev/null | hexdump -C 00000000 45 46 49 20 50 41 52 54 00 00 01 00 5c 00 00 00 |EFI PART....\...| 00000010 9b 3c cb 8f 00 00 00 00 01 00 00 00 00 00 00 00 |.<..............| 00000020 ff 5f ef 00 00 00 00 00 22 00 00 00 00 00 00 00 |._......".......| 00000030 de 5f ef 00 00 00 00 00 0d 70 a7 c1 50 34 a3 40 |._.......p..P4.@| 00000040 99 45 46 0a 87 cf 8c 9a 02 00 00 00 00 00 00 00 |.EF.............| 00000050 80 00 00 00 80 00 00 00 25 4d a5 b6 00 00 00 00 |........%M......| 00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 00000200
Напомню, что BlockSize не обязательно 512, его можно узнать для каждого конкретного случая через вызов ioctl с селектором DKIOCGETBLOCKSIZE - программу я привёл в статье "Получение информации по диску через ioctl". На нашем диске BlockSize=512.
- 00-07 (0x00-0x07) 8 байт. Signature. 45 46 49 20 50 41 52 54 = "EFI PART".
- 08-11 (0x08-0x0B) 4 байт. Revision. 00 00 01 00 = 0x00010000 = Номер ревизии 1.0.
- 12-15 (0x0C-0x0F) 4 байт. HeaderSize. 5c 00 00 00 = 0x0000005c = 92 байтов.
- 16-19 (0x10-0x13) 4 байт. HeaderCRC32. 9b 3c cb 8f. Для того, чтобы вычислить CRC32, в это поле записываются нули, после чего CRC32 считается для блока размером HeaderSize, т.е. для 92 байт.
Для вычисления CRC32 я воспользовался кодом на C. Пожалуй, отдельно напишу программу для разбора GPT, сейчас же воспользуюсь скомпилированным кодом и Hex Fiend для "обнуления".
Считываем нужный нам блок в файл 1.dump:
$ sudo dd if=/dev/disk3 of=1.dump bs=512 count=1 skip=1
Считываем из него 92 байта и записываем в 2.dump (здесь поле HeaderCRC32 ещё не обнулено):
$ sudo dd if=1.dump of=2.dump ibs=1 obs=1 count=92
Запускаем Hex Fiend, обнуляем байты 0x10-0x13 и записываем в файл 3.dump:
В HeaderCRC32 было значение 9B 3C CB 8F = 0x8FCB2C9B. Запускаем вычисление CRC32:
$ ./crc_32 3.dump FFFFFFFF8FCB3C9B 92 3.dump
Что и требовалось доказать, CRC32 верен.
- 20-23 (0x14-0x17) 4 байт. Reserved. 00 00 00 00. Все нули.
- 24-31 (0x18-0x1F) 8 байт. MyLBA. 01 00 00 00 00 00 00 00. Да, Partition Table Header начинается с 1-го блока (а MBR protective - с 0-го).
- 32-39 (0x20-0x27) 8 байт. AlternateLBA. ff 5f ef 00 00 00 00 00 = 0xEF5FFF = 15687679. Адрес альтернативной Partition Table Header. Детальнее смотрите описание к LastUsableLBA.
Проверим, действительно ли по адресу AlternateLBA находится Partition Table Header:
$ dd if=/dev/disk3 of=/dev/stdout bs=512 count=1 skip=15687679 2>/dev/null | hexdump -C 00000000 45 46 49 20 50 41 52 54 00 00 01 00 5c 00 00 00 |EFI PART....\...| 00000010 fc 0d 5b f0 00 00 00 00 ff 5f ef 00 00 00 00 00 |..[......_......| 00000020 01 00 00 00 00 00 00 00 22 00 00 00 00 00 00 00 |........".......| 00000030 de 5f ef 00 00 00 00 00 0d 70 a7 c1 50 34 a3 40 |._.......p..P4.@| 00000040 99 45 46 0a 87 cf 8c 9a df 5f ef 00 00 00 00 00 |.EF......_......| 00000050 80 00 00 00 80 00 00 00 25 4d a5 b6 00 00 00 00 |........%M......| 00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 00000200
Да, так и есть, это копия PTH.
- 40-47 (0x28-0x2F) 8 байт. FirstUsableLBA. 22 00 00 00 00 00 00 00 = 0x22. Посмотрите, как вычисляется размер массива Partition Entry в описании поля SizeOfPartitionEntry. Первый блок на диске занят под Protective MBR, второй - под Partition Entry Header, затем в 0x20 блоках находится Partition Entry Array, т.е. занято 0x22 блока. Адресация блоков начинается с нуля, значит разделы можно располагать на диске, начиная с блока 0x22.
- 48-55 (0x30-0x37) 8 байт. LastUsableLBA. de 5f ef 00 00 00 00 00 = 0xEF5FDE = 15687646. Последний блок, который можно использовать под разделы.
Давайте посмотрим причину. Размер диска посмотрим в diskutil:
$ diskutil info /dev/disk3 | grep "Total Size:" Total Size: 8.0 GB (8032092160 Bytes) (exactly 15687680 512-Byte-Blocks) TotalDiskSize = 15687680 = 0xEF6000 LastUsableLBA = 15687646 = 0xEF5FDE AlternateLBA = 15687679 = 0xEF5FFF
Схема GPT Partition Table показана на рисунке:
Alternate Partition Table Header находится в последнем блоке на диске, нумерация с нуля, т.е. она находится в блоке (TotalDiskSize - 1) = 0xEF6000 - 1 = 0xEF5FFF. Так и есть, этот адрес мы видели в поле AlternateLBA.
Перед Alternate Partition Table Header располагается копия Partition Entry Array размером 0x20 (смотрите комментарий к SizeOfPartitionEntry). 0xEF5FFF - 0x20 = 0xEF5FDF. Т.е. Partition Entry Array начинается с блока 0xEF5FDF, а последний блок, доступный для разделов, является 0xEF5FDE, и это значение мы видели в поле LastUsableLBA.
- 56-71 (0x38-0x47) 16 байт. DiskGUID. 0d 70 a7 c1 50 34 a3 40 99 45 46 0a 87 cf 8c 9a. К сожалению, пока я не нашёл, как генерируется это поле.
- 72-79 (0x48-0x4F) 8 байт. PartitionEntryLBA. 02 00 00 00 00 00 00 00 = 2. Массив GUID Partition Entry начинается со второго блока диска.
- 80-83 (0x50-0x53) 4 байт. NumberOfPartitionEntries. 80 00 00 00 = 0x80 = 128. Это максимальное количество разделов на диске.
- 84-87 (0x54-0x57) 4 байт. SizeOfPartitionEntry. 80 00 00 00 = 0x80 = 128 байт. Размер каждой записи Partition Entry равен 128 байт. Имея размер массива разделов и размер записи, получаем размер массива. 0x80 x 0x80 = 0x4000 = 16384 байт = 32 блока = 0x20
- 88-91 (0x58-0x5B) 4 байт. PartitionEntryArrayCRC32. 25 4d a5 b6 = 0xB6A54D25.
PartitionEntryArray начинается со второго блока и его размер 0x20 = 32 блоков:
$ sudo dd if=/dev/disk3 of=pea1.dump bs=512 count=32 skip=2
Проверяем CRC32:
$ ./crc_32 pea1.dump FFFFFFFFB6A54D25 16384 pea1.dump
Вычисленное CRC32 совпадает с записанным в PartitionEntryArrayCRC32, всё верно.
- 92-BlockSize (0x5C-0x1FF) 512-92=420 байт. Reserved. 00 ... 00. Все нули.
Partition Table Header мы разобрали, в следующей части (последней) разберём Partition Entry Array. Заодно попробую найти, как генерируется DiskGUID.





