Блог Олега Сердюкова

Схемы разделов в Mac OS X. GUID partition table. Часть 2 (Partition Table Header)

В предыдущей статье ”Схемы разделов в Mac OS X. GUID partition table. Часть 1 (MBR)” мы рассмотрели “Protective MBR”. Сегодня займёмся следующей структурой - Partition Table Header.

Напомню, что я использую такие спецификации:

В нулевом блоке на диске находится Protective MBR. В следующем же - GUID Partition Table Header. Это структура, описывающая различные данные по диску, включая GUID для уникальной идентификации диска, адрес стартового блока массива записей о разделах, размер записи в этом массиве и т.д.

Формат описан в таблице:

MnemonicByteLengthDescription
Signature08Identifies EFI-compatible partition table header. This value must contain the string “EFI PART,” 0x5452415020494645.
Revision84The 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 0x00010000.
HeaderSize124Size 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.
HeaderCRC32164CRC32 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.
Reserved204Must be zero.
MyLBA248The LBA that contains this data structure.
AlternateLBA328LBA address of the alternate GUID Partition Table Header.
FirstUsableLBA408The first usable logical block that may be used by a partition described by a GUID Partition Entry.
LastUsableLBA488The last usable logical block that may be used by a partition described by a GUID Partition Entry.
DiskGUID5616GUID that can be used to uniquely identify the disk.
PartitionEntryLBA728The starting LBA of the GUID Partition Entry array.
NumberOfPartitionEntries804The number of Partition Entries in the GUID Partition Entry array.
SizeOfPartitionEntry844The size, in bytes, of each the GUID Partition Entry structures in the GUID Partition Entry array. Must be a multiple of 8.
PartitionEntryArrayCRC32884The CRC32 of the GUID Partition Entry array. Starts at PartitionEntryLBA and is computed over a byte length of NumberOfPartitionEntries * SizeOfPartitionEntry.
Reserved92BlockSize – 92The 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.

Comments