В предыдущей статье “Схемы разделов в 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.