Под капотом..
1. Разметка CHS и LBA;
2. Формат таблицы разделов MBR;
3. Формат таблицы разделов GPT;
4. Практика – сбор и вывод информации;
5. Заключение.
-----------------------------------------------------
1. CHS и LBA – общие сведения
Ещё в 90-х годах прошлого столетия стало очевидно, что исправно служившая столько лет трёхмерная геометрия жёстких дисков CHS (Cylinder, Head, Sector) не удовлетворяет уже современным требованиям и её срочно нужно менять. Так, следующим шагом к совершенству был ввод в прошивку HDD модуля под названием "транслятор адреса", который налету превращал физический номер сектора CHS, в логический LBA (Logical Block Address). Но и это лишь на время отложило неизбежную смерть механизма CHS-to-LBA, поскольку основная проблема была зарыта ещё глубже. Она заключалась в том, что в системе адресации логических блоков критически не хватало двоичных разрядов, и их нужно было увеличить как-минимум в 1,5 раза, с 32 до 48-бит. Это требовало-бы полной модернизации аппаратной части подсистемы хранения данных, на что инженеры шли без энтузиазма.
• Головки-чтения "Head"
В любом накопителе HDD изначально присутствуют от двух до шести магнитных дисков (пластины, блины). Информация наносится на них с обеих сторон, а считывают её головки чтения/записи. Соответственно получаем головок вдвое больше, чем кол-во двусторонних дисков. Все головки перемещаются вместе, хотя в любой момент активной может быть только одна. Изначально, для их адресации BIOS выделял 8-бит, что позволяло выбрать одну из 256 штук. Однако позже общее кол-во урезали до 16-ти (4-бит, макс.8 блинов), а оставшуюся тетраду отдали в распоряжение номера цилиндров.
• Цилиндры "Cylinder"
Поскольку все диски накопителя крепятся на общем (передающим крутящий момент) шпинделе, то дорожка(0) диска(0) оказывается прямо над дорожками(0) всех остальных дисков. Дорожки с одинаковыми номерами на всех дисках собираются в обобщённое понятие "цилиндр", а биос резервирует под общее их количество 10-бит, которыми можно адресовать максимум 1024 цилиндра. Если учесть отобранную у головок дополнительную тетраду, то получается уже 14-бит = 16.383 всего возможных цилиндров, на уровне IDE.
• Секторы "Sector"
На поверхности каждого из дисков имеются концентрические (не спиралевидные) дорожки, или как их ещё называют "треки". Эти треки поделены на сектора – основную единицу хранения данных. C диска нельзя считать 1-байт, а только минимум 512-байтный сектор целиком. Как-правило, в одном треке имеется фиксированное кол-во секторов = 63, под которые BIOS отводит всего 6-бит.
Таким образом, адрес конкретного сектора формируется из трёх составляющих – головка (выбирает диск), цилиндр на этом диске, и сектор на поверхности выбранного цилиндра. Рисунок ниже представляет трехмерный адрес CHS в визуальной форме:
Теперь проведём арифметические расчёты, чтобы определить макс.возможную ёмкоcть дисков с разметкой CHS. Для этого достаточно найти произведение всех значений BIOS и результат умножить на размер сектора. Для накопителей HDD, сектор в 512-байт является скорее правилом, чем исключением, хотя на твёрдотелых SSD он может достигать информационного веса в 4 Кб (его подогнали под размер виртуальной страницы ОЗУ). Но сейчас разговор об HDD, поэтому условимся считать сектор равным именно 512-байт. Тогда имеем..
цилиндров..(С): 14-бит = 16.383
головок....(H): 04-бит = 16
секторов...(S): 06-бит = 63
==========================================
16383*16*63 = 16.514.064 (всего секторов)
16514064*512 = 8.455.200.768 (всего байт)
Как видим, используя трёхмерную геометрию CHS, дисковый сервис биоса INT-13h способен оперировать накопителем с макс.размером ~8 Gb. Ещё в эпоху неолита таких объёмов не хватало даже для домашнего пользования. Поэтому инженеры сняли ограничения
Хоть биос и поддерживал теперь 64-битный дисковый сервис, на практике использовался лишь LBA-32. В результате, ёмкость диска не превышала
Таким образом, инженерам пришлось положить на операционный стол и MBR, чтобы перекроить его под таблицу "GUID Partition Table", в простонародье GPT. По сути требовал этого и сам прогресс, который решил "рубануть с плеча" и полностью искоренить устаревшую систему ввода-вывода BIOS, сделав бартер на более современный EFI. Рассмотрим бегло отличительные особенности этих таблиц, после чего завернём их в ассемблерный код.
2. Формат таблицы-разделов в секторе MBR
Термин MBR берёт своё начало от "Master Boot Record", что в дословно переводится как "Основная загрузочная запись". MBR занимает самый первый сектор с координатами: головка(0), цилиндр(0), сектор(1). Здесь нужно отметить, что в геометрии CHS отсчёт цилиндров и головок начинается с нуля, а секторов с единицы. Однако если мы имеем дело с выстроенными в ряд логическими блоками LBA, то нумеровать эти блоки (аля секторы) принято уже с нуля – такая вот муть..
Посмотрим на рисунок ниже, где представлена таблица-разделов отживающего свой век диска Seagate, объёмом 80 Gb. Здесь я открыл его в HEX-редакторе HxD как физический диск, и скопировал в новое окно только интересную на данный момент таблицу-разделов MBR. Как упоминалось выше, MBR – это равно один сектор, начиная с нуля и до адреса
Первые 446-байт до смещения
В окне "Partition Table" выше я собрал в блоки одноимённые поля всех четырёх разделов. Одна строка – это запись об одном разделе. Судя по этим данным, мой диск имеет всего 2-раздела, поскольку две последние строки забиты нулями. Коричневый первый байт со-значением
Байт с типом раздела по смещению(4) информирует о том, основной это раздел, или расширенный. Для файловой системы NTFS сейчас встречаются только три типа:
Наибольший интерес в таблицах MBR представляют последние два 32-битных значения – это номер сектора LBA с которого начинается раздел (зелёный блок), и всего секторов LBA в разделе (красный блок). Тома и разделы не могут начинаться с середины дорожки диска, а только с нового цилиндра. Поэтому в зелёном блоке первого раздела мы видим значение
Последний dword в красном блоке хранит общее число секторов в разделе:
Чтобы на программном уровне было проще обращаться к MBR, имеет смысл оформить этот сектор в структуру соответствующего вида. Вот что у меня из этого вышло:
Рассмотрев анатомические особенности таблицы-разделов MBR можно сделать вывод, что она действительно отжила свой век и ей давно уже пора на покой. Во-первых, чтобы при малейшем чихе не потерять навсегда терабайты своих данных, обязательно нужно иметь резервную копию этой таблицы, с её контрольной суммой. Ведь превратить в труху диски MBR проще-простого – достаточно элементарно сбросить "Boot-Flag", сменить тип по-смещению(4) на какой-нибудь от фонаря, или поменять местами два последних поля LBA. Всё.. сушим вёсла.. После этих действий, система в лучшем случае откажется грузиться, а в худшем – вообще не распознает дисковые тома, что приведёт к полной потере информации. Все эти просчёты были учтены в более современной таблице-разделов GPT – разберём её на атомы..
3. Формат таблицы-разделов GPT
GPT это "GUID Partition Table". Права на неё принадлежат Intel, коллектив которой разработал её в 90-х прошлого столетия. Однако распространение таблица получила только с приходом интерфейса EFI "Extensible Firmware Interface" в конце 2000-х. По задумке инженеров, весь хлам из MBR планировалось выкинуть за борт, однако пережившие себя древние устройства не разделяли такую позицию, и грязно высказываясь разработчикам пришлось тащить за собой воз обратной совместимости.
В результате, в секторе(0) GPT по прежнему лежит MBR, только теперь он ущербный без загрузчика (первые 440-байт, забиты нулями), а осталась лишь описывающая всего один раздел запись. Причём последний дворд в ней под кличкой "всего LBA" выставлен на максимум
Если в MBR под описатель каждого из 4-х разделов выделялось по 16-байт, то в GPT раздел "Partition" описывает уже структура размером аж в 128-байт (четверть 512-байтного сектора). Здесь нет расширенных томов как в MBR – все разделы имеют одинаковые права и являются основными, а общее их кол-во увеличено с четырёх до 128-ми. Поскольку места на харде теперь предостаточно, под GPT выделяется целых 33-сектора =16 Кб, не в пример 64-байтам в MBR. При этом основная таблица GPT продублирована резервной копией, которую в зеркальном виде забросили в самый конец дискового пространства. Общая схема занимаемых секторов представлена рисунком ниже:
Здесь видно, что первые 33-сектора заняты служебкой, а непосредственно под данные выделяются секторы начиная с LBA(34). Точная ксерокопия основной таблицы притаилась в хвосте и ждёт своего часа. При каждом включении машины, код системного EFI пересчитывает контрольную сумму GPT, которая хранится по смещению(10h) в её заголовке. При несовпадении CRC, основной заголовок вместе со-всеми записями восстанавливается из резервной копии, на автомате корректируя таким образом служебную инфу.
Согласно документации, вне зависимости от реального числа разделов 128 или всего пару-штук, они начинаются исключительно с сектора LBA(34). Однако на моём буке, первый раздел сдвинут ещё дальше и занимает позицию LBA(2048), хотя в заголовке указано правильно
В общем случае, вся таблица GUID состоит из двух частей – заголовок "GPT Header" с определением глобальных характеристик накопителя, и 128 элементного массива записей "Partition Entry". Каждая такая запись имеет размер 128-байт и описывает свой раздел, которых так-же может быть 128 штук. Детальное рассмотрение всех составляющих
3.1. Заголовок таблицы "GPT-Header"
Программно распознать диск с разметкой GPT можно по сигнатуре "EFI PART" в начале сектора LBA(1), или-же по идентификатору типа-раздела
Обратите внимание, что в заголовке имеются две контрольные суммы CRC32.
Первая по смещению(10h) – сумма исключительно самого заголовка, а вторая с офсетом(58h) – всех имеющихся записей "Partition Entry". Так разрабы закрыли GPT аж на два амбарных замка, а специальная процедура дотошного EFI постоянно их проверяет. В случае малейшего несоответствия, на территории сразу включается ревун и данные тут-же восстанавливаются из резервной копии. Достопочтенный BIOS со-своим MBR о таких мелочах мог только мечтать. Остальные поля заголовка пояснений вроде не требуют.
3.2. Записи о разделах "Partition Entry"
В заключении рассмотрим формат паспорта каждого из разделов. Как упоминалось выше, таблица GPT поддерживает макс.128 разделов, и в 33-х секторах для каждого из них зарезервировано место под описатель. Лично мне не встречались диски с таким кол-вом томов, но как говорят: "лучше еврей без бороды, чем борода без еврея" – пусть лежат на чёрный день, авось когда-нибудь понадобятся.
Обратите внимание на 64-битную маску атрибутов по смещению(30h). Большая часть битов в ней отправлена в резерв, а список активных представлен ниже. Если в двух словах, то у обычных разделов маска имеет значение нуль, т.е. все биты сброшены. А если какой-то из них взведён (как-правило нулевой или под номером 63), то он означает следующее:
В записях разделов огромную роль играет GUID – "Globally Unique Identifier", или глобально-уникальный идентификатор. В каждой записи по два таких GUID'a (см.предыдущий скрин с форматом). Первый – системный и определяет тип данного раздела, по аналогии с байтом "типа" по смещению(4) в таблице MBR. Второй GUID – это просто рандомный номер раздела, по которому виндовый диспетчер-дисков монтирует том в систему. Кстати если в ком.строке запросить утилиту mountvol.exe без параметров, то она сбросит на консоль список именно этих идентификаторов, с назначенными им буквами разделов.
Ниже перечислены некоторые GUID, предопределяющие тип раздела в операционной системе Win. Например разделы с пользовательскими данными будут иметь тип "Basic Data Partition", а раздел типа "EFI System Partition" зарезервирован для кода загрузчика EFI. В таблице GPT любого накопителя их GUID'ы будут одинаковыми, поскольку они являются глобальными внутри системы:
4. Практика – сбор и вывод информации о разделах
В практической части напишем небольшую утилиту, которая позволит динамически определить способ разметки диска MBR или GPT. Дальше, код в цикле обойдёт все записи в "Partition Table" и отрапортует о собранной информации на консоль. Это будет просто демонстрацией того, как можно подобраться на программном уровне к данным таблицы-разделов. А что дальше делать с этими данными – это уже вопрос к нашей совести. Поскольку в атрибутах разделов GPT имеется бит(60), то можно взвести его в записи любого раздела, в результате чего раздел станет доступным только для чтения, без возможности записи на него. Или-же скрыть его к чертям, установив в единицу бит(62).
Небольшую проблему может создать вывод значений GUID на экран в приглядном виде типа:
Эта функция сбрасывает в буфер GUID в виде Unicode-строки, значит для вывода на консоль её нужно будет преобразовать в ASCII, просто читая по 2-байта, и сохраняя в тот-же буфер по одному (т.е. отсекать парные нули). Весь алгоритм программы можно представить так:
1. Запросить номер диска на случай, если их несколько в системе.
2. Открыть указанный диск при помощи CreateFile() обязательно с шарой Write, чтобы диск был доступен остальным для записи.
3. Функцией VirtualAlloc() выделить память под 10-секторов диска (в идеале под 33-сектора, но хватит и 10-ти).
4. Через ReadFile() считать секторы из диска в память (чтение разрешено всем, а запись только админу).
5. Проверкой байта "тип-раздела" в MBR, распознать способ разметки GPT или MBR (байт должен иметь значение EEh).
6. В зависимости от результата, спроецировать соответствующую структуру на считанные сектора, и пропарсить их.
Мы не можем заранее знать, на машину какой разрядности попадёт наш код, 32 или 64-бит. Поэтому в таких случаях лучше писать 32-битное приложение. Если оно попадёт на х64, то отработает через её WOW64 (Windows-on-Windows). Зато 64-битное приложение вообще не запуститься на х32, и мы обломаемся по полной. Так-как большинство полей в GPT 64-битные, то придётся оперировать ими через сопр FPU. Исходник этой задумки на лексиконе ассемблера FASM представлен ниже. Инклуд с описанием структур MBR/GPT я спрятал в скрепку:
Посмотрим, что в итоге получили..
Значит перед нами таблица GPT, в которой сначала идёт заголовок с информацией о самом диске, а дальше логи из записей "Partition Entry". В заголовке видно, что резервная копия заголовка лежит в конце пространства, в секторе(976773167). Сама таблица лежит в секторе(2) – это текущий сектор, ..а разделы с данными (как и утверждал производитель) начинаются с сектора(34). Чтобы вычислить ёмкость раздела в секторах, нужно от Last отнять First-LBA.
Однако в моём случае, внутри записи первого раздела видим начальный сектор(2048), а не 34. Раздел размером 500 Мб и в его атрибутах взведены биты 0 и 63, что означает "Защищённый ОЕМ-раздел, без буквы", т.е. не отображается в проводнике Win. Это бокс для различных драйверов и прочей системной утвари, которую любезно сбросил туда производитель моего бука. Нужно сказать, что и последующие два раздела тоже из этой-же кухни с установленным битом(63), только они не принадлежат вендору ОЕМ (разработчику). В разделе EFI лежит загрузчик ОС размером 100 Мб (старушка MBR его таскала с собой), а в третьем разделе – барахло мелкомягких.
5. Заключение
Доступ к стартовым секторам диска с правами на запись открывает большие возможности. В своё время это было излюбленное место червей и всякой нечисти, поэтому начиная с Висты, MS отобрала у нас эти права (привет Жанне Рутковской, с её "голубой пилюлей"). Читать – пожалуйста, а вот записывать в начало диска (до файловой системы), юзеру нельзя. Чтобы сидя на нарах не ждать с воли сухарей, мы всегда должны помнить об уголовной ответственности за порчу чужой информации. Поэтому всё сказанное здесь носит чисто оборонительный характер, чтобы мы были осведомлены, как при программных сбоях можно восстановить работоспособность своего накопителя. Особенно актуально это для размеченных в формате MBR дисков, где самостоятельная правка пару байт может сэкономить Вам честно заработанные шекели.
В скрепке лежит готовый исполняемый файл для тестов, исходник загрузчика из MBR, а так-же инклуд с описанием структур "Partition Table". Всем удачи, пока!
1. Разметка CHS и LBA;
2. Формат таблицы разделов MBR;
3. Формат таблицы разделов GPT;
4. Практика – сбор и вывод информации;
5. Заключение.
-----------------------------------------------------
1. CHS и LBA – общие сведения
Ещё в 90-х годах прошлого столетия стало очевидно, что исправно служившая столько лет трёхмерная геометрия жёстких дисков CHS (Cylinder, Head, Sector) не удовлетворяет уже современным требованиям и её срочно нужно менять. Так, следующим шагом к совершенству был ввод в прошивку HDD модуля под названием "транслятор адреса", который налету превращал физический номер сектора CHS, в логический LBA (Logical Block Address). Но и это лишь на время отложило неизбежную смерть механизма CHS-to-LBA, поскольку основная проблема была зарыта ещё глубже. Она заключалась в том, что в системе адресации логических блоков критически не хватало двоичных разрядов, и их нужно было увеличить как-минимум в 1,5 раза, с 32 до 48-бит. Это требовало-бы полной модернизации аппаратной части подсистемы хранения данных, на что инженеры шли без энтузиазма.
• Головки-чтения "Head"
В любом накопителе HDD изначально присутствуют от двух до шести магнитных дисков (пластины, блины). Информация наносится на них с обеих сторон, а считывают её головки чтения/записи. Соответственно получаем головок вдвое больше, чем кол-во двусторонних дисков. Все головки перемещаются вместе, хотя в любой момент активной может быть только одна. Изначально, для их адресации BIOS выделял 8-бит, что позволяло выбрать одну из 256 штук. Однако позже общее кол-во урезали до 16-ти (4-бит, макс.8 блинов), а оставшуюся тетраду отдали в распоряжение номера цилиндров.
• Цилиндры "Cylinder"
Поскольку все диски накопителя крепятся на общем (передающим крутящий момент) шпинделе, то дорожка(0) диска(0) оказывается прямо над дорожками(0) всех остальных дисков. Дорожки с одинаковыми номерами на всех дисках собираются в обобщённое понятие "цилиндр", а биос резервирует под общее их количество 10-бит, которыми можно адресовать максимум 1024 цилиндра. Если учесть отобранную у головок дополнительную тетраду, то получается уже 14-бит = 16.383 всего возможных цилиндров, на уровне IDE.
• Секторы "Sector"
На поверхности каждого из дисков имеются концентрические (не спиралевидные) дорожки, или как их ещё называют "треки". Эти треки поделены на сектора – основную единицу хранения данных. C диска нельзя считать 1-байт, а только минимум 512-байтный сектор целиком. Как-правило, в одном треке имеется фиксированное кол-во секторов = 63, под которые BIOS отводит всего 6-бит.
Таким образом, адрес конкретного сектора формируется из трёх составляющих – головка (выбирает диск), цилиндр на этом диске, и сектор на поверхности выбранного цилиндра. Рисунок ниже представляет трехмерный адрес CHS в визуальной форме:
Теперь проведём арифметические расчёты, чтобы определить макс.возможную ёмкоcть дисков с разметкой CHS. Для этого достаточно найти произведение всех значений BIOS и результат умножить на размер сектора. Для накопителей HDD, сектор в 512-байт является скорее правилом, чем исключением, хотя на твёрдотелых SSD он может достигать информационного веса в 4 Кб (его подогнали под размер виртуальной страницы ОЗУ). Но сейчас разговор об HDD, поэтому условимся считать сектор равным именно 512-байт. Тогда имеем..
цилиндров..(С): 14-бит = 16.383
головок....(H): 04-бит = 16
секторов...(S): 06-бит = 63
==========================================
16383*16*63 = 16.514.064 (всего секторов)
16514064*512 = 8.455.200.768 (всего байт)
Как видим, используя трёхмерную геометрию CHS, дисковый сервис биоса INT-13h способен оперировать накопителем с макс.размером ~8 Gb. Ещё в эпоху неолита таких объёмов не хватало даже для домашнего пользования. Поэтому инженеры сняли ограничения
CHS=16383/16/63
и добавили в биос расширенный сервис INT-13h, более известный как "Enhanced Disk Drive", EDD. Если традиционный сервис адресовал секторы через регистры процессора CX
и DX
, то в расширенном ввели линейную адресацию LBA и т.н. "адресный пакет", который находится уже в памяти ОЗУ. Теперь регистры не ограничивают макс.номер сектора, и трюк позволил растянуть адресацию до LBA-64:
C-подобный:
Традиционный INT-13h, AH=02h (читать секторы CHS):
---------------------------------------------------
Вход: DL = номер диска (0=A; 80h=C; 81h=D)
DH = номер головки
CH = номер цилиндра
CL = номер сектора
AL = число секторов (макс.63 = цилиндр)
BX = адрес приёмного буфера
Выход: CF = 1 ошибка, иначе буфер содержит прочитанные данные.
;//***************************************************************
Расширенный INT-13h, AH=42h (читать секторы LBA):
---------------------------------------------------
Вход: DL = номер диска
SI = указатель на "пакет-адреса"
Выход: CF = 1 ошибка, иначе буфер содержит прочитанные данные.
;//------------------------------
;// Структура "пакета-адресации"
;//------------------------------
Offs Size Смысл
0 1 Размер данного пакета (не меньше 16)
1 1 Резерв = 0
2 1 Число секторов для чтения/записи (1..127)
3 1 Резерв = 0
4 4 Сегментный адрес буфера (FFFF:FFFF означает, что используется поле 16)
8 8 Номер сектора LBA-64
16 8 Линейный адрес буфера (используется, если в поле(4) установлено FFFF:FFFF).
Хоть биос и поддерживал теперь 64-битный дисковый сервис, на практике использовался лишь LBA-32. В результате, ёмкость диска не превышала
4.294.967.295
всего секторов (32-бит) – при размере сектора 512-байт это получалось 2 тераБайт. На этот раз засаду устроила "таблица-разделов" накопителя, что в оригинале звучит как "MBR Partition Table". Эта таблица описывает разделы уже самого жёсткого диска, и в ней под номер сектора LBA зарезервировано именно 32-бита.Таким образом, инженерам пришлось положить на операционный стол и MBR, чтобы перекроить его под таблицу "GUID Partition Table", в простонародье GPT. По сути требовал этого и сам прогресс, который решил "рубануть с плеча" и полностью искоренить устаревшую систему ввода-вывода BIOS, сделав бартер на более современный EFI. Рассмотрим бегло отличительные особенности этих таблиц, после чего завернём их в ассемблерный код.
2. Формат таблицы-разделов в секторе MBR
Термин MBR берёт своё начало от "Master Boot Record", что в дословно переводится как "Основная загрузочная запись". MBR занимает самый первый сектор с координатами: головка(0), цилиндр(0), сектор(1). Здесь нужно отметить, что в геометрии CHS отсчёт цилиндров и головок начинается с нуля, а секторов с единицы. Однако если мы имеем дело с выстроенными в ряд логическими блоками LBA, то нумеровать эти блоки (аля секторы) принято уже с нуля – такая вот муть..
Посмотрим на рисунок ниже, где представлена таблица-разделов отживающего свой век диска Seagate, объёмом 80 Gb. Здесь я открыл его в HEX-редакторе HxD как физический диск, и скопировал в новое окно только интересную на данный момент таблицу-разделов MBR. Как упоминалось выше, MBR – это равно один сектор, начиная с нуля и до адреса
0200h
.Первые 446-байт до смещения
01BEh
отданы в распоряжение загрузчика ОС, а следующий за ним (выделенный) 64-байтный блок и есть таблица 4-х возможных разделов диска HDD. Правда я захватил в хвосте ещё и сигнатуру 55AAh
по которой BIOS делает вывод, что сектор фактически является загрузочным. Каждый раздел Partition описывает своя 16-байтная запись (одна строка в нижнем окне), итого 16*4=64 байта. Ограниченный размер данной таблицы не позволяет создавать больше 4-х основных разделов, хотя раздел может быть и расширенным, тогда в нём присутствует своя таблица, ещё для 4-х его логических томов:В окне "Partition Table" выше я собрал в блоки одноимённые поля всех четырёх разделов. Одна строка – это запись об одном разделе. Судя по этим данным, мой диск имеет всего 2-раздела, поскольку две последние строки забиты нулями. Коричневый первый байт со-значением
80h
характеризует флаг загрузочного раздела – обычно это тот, с которого загружается Win. Два серых блока хранят адрес начала и конца раздела в формате CHS. Если там лежит значение FEFFFFh
, значит поле не действительно и нужно использовать геометрию LBA. Байт с типом раздела по смещению(4) информирует о том, основной это раздел, или расширенный. Для файловой системы NTFS сейчас встречаются только три типа:
07h
= основной, 0Fh
= расширенный раздел, а так-же идентификатор EEh
= таблица разделов GPT (о ней ниже). Со списком остальных типов можно ознакомиться
Ссылка скрыта от гостей
.Наибольший интерес в таблицах MBR представляют последние два 32-битных значения – это номер сектора LBA с которого начинается раздел (зелёный блок), и всего секторов LBA в разделе (красный блок). Тома и разделы не могут начинаться с середины дорожки диска, а только с нового цилиндра. Поэтому в зелёном блоке первого раздела мы видим значение
0000003Fh=63
. Если вспомнить, что в одной дорожке 63-сектора, то всё совпадает. Тогда выходит, что между MBR и началом первого раздела всегда имеются бесхозные 62-сектора (~32 Кб), которые мы можем подмять под себя.Последний dword в красном блоке хранит общее число секторов в разделе:
02711637h = 40.965.687
. Теперь можно вычислить и его размер: 40965687*512=20Gb
. Аналогично и для второго партишена: 06DF8F8Ah = 115.314.570
всего секторов, а размер: 115314570*512=59Gb
.Чтобы на программном уровне было проще обращаться к MBR, имеет смысл оформить этот сектор в структуру соответствующего вида. Вот что у меня из этого вышло:
Рассмотрев анатомические особенности таблицы-разделов MBR можно сделать вывод, что она действительно отжила свой век и ей давно уже пора на покой. Во-первых, чтобы при малейшем чихе не потерять навсегда терабайты своих данных, обязательно нужно иметь резервную копию этой таблицы, с её контрольной суммой. Ведь превратить в труху диски MBR проще-простого – достаточно элементарно сбросить "Boot-Flag", сменить тип по-смещению(4) на какой-нибудь от фонаря, или поменять местами два последних поля LBA. Всё.. сушим вёсла.. После этих действий, система в лучшем случае откажется грузиться, а в худшем – вообще не распознает дисковые тома, что приведёт к полной потере информации. Все эти просчёты были учтены в более современной таблице-разделов GPT – разберём её на атомы..
3. Формат таблицы-разделов GPT
GPT это "GUID Partition Table". Права на неё принадлежат Intel, коллектив которой разработал её в 90-х прошлого столетия. Однако распространение таблица получила только с приходом интерфейса EFI "Extensible Firmware Interface" в конце 2000-х. По задумке инженеров, весь хлам из MBR планировалось выкинуть за борт, однако пережившие себя древние устройства не разделяли такую позицию, и грязно высказываясь разработчикам пришлось тащить за собой воз обратной совместимости.
В результате, в секторе(0) GPT по прежнему лежит MBR, только теперь он ущербный без загрузчика (первые 440-байт, забиты нулями), а осталась лишь описывающая всего один раздел запись. Причём последний дворд в ней под кличкой "всего LBA" выставлен на максимум
FFFFFFFFh
(нет места на диске), а флаг с типом раздела по смещению(4) имеет значение EEh
. Это сделано для того, чтобы не знакомый с GPT софт времён динозавров, не повредил случайно таблицу GUID.Если в MBR под описатель каждого из 4-х разделов выделялось по 16-байт, то в GPT раздел "Partition" описывает уже структура размером аж в 128-байт (четверть 512-байтного сектора). Здесь нет расширенных томов как в MBR – все разделы имеют одинаковые права и являются основными, а общее их кол-во увеличено с четырёх до 128-ми. Поскольку места на харде теперь предостаточно, под GPT выделяется целых 33-сектора =16 Кб, не в пример 64-байтам в MBR. При этом основная таблица GPT продублирована резервной копией, которую в зеркальном виде забросили в самый конец дискового пространства. Общая схема занимаемых секторов представлена рисунком ниже:
Здесь видно, что первые 33-сектора заняты служебкой, а непосредственно под данные выделяются секторы начиная с LBA(34). Точная ксерокопия основной таблицы притаилась в хвосте и ждёт своего часа. При каждом включении машины, код системного EFI пересчитывает контрольную сумму GPT, которая хранится по смещению(10h) в её заголовке. При несовпадении CRC, основной заголовок вместе со-всеми записями восстанавливается из резервной копии, на автомате корректируя таким образом служебную инфу.
Согласно документации, вне зависимости от реального числа разделов 128 или всего пару-штук, они начинаются исключительно с сектора LBA(34). Однако на моём буке, первый раздел сдвинут ещё дальше и занимает позицию LBA(2048), хотя в заголовке указано правильно
22h=34
. Видимо в доках имелось в виду минимальный сектор(34), а дальше по настроению. Так-что их высказывания можно классифицировать по разному, а лучше не доверяя проверять всё на практике.В общем случае, вся таблица GUID состоит из двух частей – заголовок "GPT Header" с определением глобальных характеристик накопителя, и 128 элементного массива записей "Partition Entry". Каждая такая запись имеет размер 128-байт и описывает свой раздел, которых так-же может быть 128 штук. Детальное рассмотрение всех составляющих
Ссылка скрыта от гостей
, а в данной статье мы рассмотрим их лишь поверхностно.3.1. Заголовок таблицы "GPT-Header"
Программно распознать диск с разметкой GPT можно по сигнатуре "EFI PART" в начале сектора LBA(1), или-же по идентификатору типа-раздела
EEh
в секторе LBA(0) таблицы MBR. Ниже приводится описание полей данного заголовка, всего 1 сектор = 512-байт:Обратите внимание, что в заголовке имеются две контрольные суммы CRC32.
Первая по смещению(10h) – сумма исключительно самого заголовка, а вторая с офсетом(58h) – всех имеющихся записей "Partition Entry". Так разрабы закрыли GPT аж на два амбарных замка, а специальная процедура дотошного EFI постоянно их проверяет. В случае малейшего несоответствия, на территории сразу включается ревун и данные тут-же восстанавливаются из резервной копии. Достопочтенный BIOS со-своим MBR о таких мелочах мог только мечтать. Остальные поля заголовка пояснений вроде не требуют.
3.2. Записи о разделах "Partition Entry"
В заключении рассмотрим формат паспорта каждого из разделов. Как упоминалось выше, таблица GPT поддерживает макс.128 разделов, и в 33-х секторах для каждого из них зарезервировано место под описатель. Лично мне не встречались диски с таким кол-вом томов, но как говорят: "лучше еврей без бороды, чем борода без еврея" – пусть лежат на чёрный день, авось когда-нибудь понадобятся.
Обратите внимание на 64-битную маску атрибутов по смещению(30h). Большая часть битов в ней отправлена в резерв, а список активных представлен ниже. Если в двух словах, то у обычных разделов маска имеет значение нуль, т.е. все биты сброшены. А если какой-то из них взведён (как-правило нулевой или под номером 63), то он означает следующее:
В записях разделов огромную роль играет GUID – "Globally Unique Identifier", или глобально-уникальный идентификатор. В каждой записи по два таких GUID'a (см.предыдущий скрин с форматом). Первый – системный и определяет тип данного раздела, по аналогии с байтом "типа" по смещению(4) в таблице MBR. Второй GUID – это просто рандомный номер раздела, по которому виндовый диспетчер-дисков монтирует том в систему. Кстати если в ком.строке запросить утилиту mountvol.exe без параметров, то она сбросит на консоль список именно этих идентификаторов, с назначенными им буквами разделов.
Ниже перечислены некоторые GUID, предопределяющие тип раздела в операционной системе Win. Например разделы с пользовательскими данными будут иметь тип "Basic Data Partition", а раздел типа "EFI System Partition" зарезервирован для кода загрузчика EFI. В таблице GPT любого накопителя их GUID'ы будут одинаковыми, поскольку они являются глобальными внутри системы:
4. Практика – сбор и вывод информации о разделах
В практической части напишем небольшую утилиту, которая позволит динамически определить способ разметки диска MBR или GPT. Дальше, код в цикле обойдёт все записи в "Partition Table" и отрапортует о собранной информации на консоль. Это будет просто демонстрацией того, как можно подобраться на программном уровне к данным таблицы-разделов. А что дальше делать с этими данными – это уже вопрос к нашей совести. Поскольку в атрибутах разделов GPT имеется бит(60), то можно взвести его в записи любого раздела, в результате чего раздел станет доступным только для чтения, без возможности записи на него. Или-же скрыть его к чертям, установив в единицу бит(62).
Небольшую проблему может создать вывод значений GUID на экран в приглядном виде типа:
{EBD0A0A2-B9E5-4433-87C0-68B6B72699C7}
. Для этого воспользуемся функцией из библиотеки ole32.dll StringFromGUID2(). Всё-что ей нужно, это указатель на GUID для преобразования, и указатель на приёмный буфер для результирующей строки. Если на выходе получим нуль, значит приёмный буф слишком мал и в аргумент "cchMax" вернётся требуемая длинна. Вот её прототип:
C-подобный:
StringFromGUID2
rguid dd 0 ;// указатель на GUID
lpsz dd 0 ;// указатель на приёмный буфер для строки
cchMax dd 0 ;// вернётся длинна строки
Эта функция сбрасывает в буфер GUID в виде Unicode-строки, значит для вывода на консоль её нужно будет преобразовать в ASCII, просто читая по 2-байта, и сохраняя в тот-же буфер по одному (т.е. отсекать парные нули). Весь алгоритм программы можно представить так:
1. Запросить номер диска на случай, если их несколько в системе.
2. Открыть указанный диск при помощи CreateFile() обязательно с шарой Write, чтобы диск был доступен остальным для записи.
3. Функцией VirtualAlloc() выделить память под 10-секторов диска (в идеале под 33-сектора, но хватит и 10-ти).
4. Через ReadFile() считать секторы из диска в память (чтение разрешено всем, а запись только админу).
5. Проверкой байта "тип-раздела" в MBR, распознать способ разметки GPT или MBR (байт должен иметь значение EEh).
6. В зависимости от результата, спроецировать соответствующую структуру на считанные сектора, и пропарсить их.
Мы не можем заранее знать, на машину какой разрядности попадёт наш код, 32 или 64-бит. Поэтому в таких случаях лучше писать 32-битное приложение. Если оно попадёт на х64, то отработает через её WOW64 (Windows-on-Windows). Зато 64-битное приложение вообще не запуститься на х32, и мы обломаемся по полной. Так-как большинство полей в GPT 64-битные, то придётся оперировать ими через сопр FPU. Исходник этой задумки на лексиконе ассемблера FASM представлен ниже. Инклуд с описанием структур MBR/GPT я спрятал в скрепку:
C-подобный:
format pe console
include 'win32ax.inc'
include 'equates\mbr_gpt.inc' ;// инклуд с описанием структур
entry start
;//----------
.data
dName db '\\.\PhysicalDrive' ;// драйв для открытия
drive db 0,0,0,0,0,0,0,0,0,0 ;// ..^^^под номер (с защитой от дурака)
fpuRes0 dq 0 ;// под результаты,
fpuRes1 dq 0 ;// сопроцессора FPU,
fpuRes2 dq 0 ;// ^^^^
secSize dd 512 ;// размер лог.сектора LBA
kByte dd 1024*1024 ;//
mByte dd 1024*1024*1024 ;//
ccMax dd 0 ;// для StringFromGUID2()
dataOffs dd 0 ;// для VirtualAlloc()
tableOffs dd 0 ;// смещение к сл.таблице разделов MBR/GPT
hndl dd 0 ;//
buff db 0
;//----------
.code
start:
;//----- Обзываем консоль и запрашиваем номер диска
invoke SetConsoleTitle,<'*** GPT/MBR info v0.1 ***',0>
cinvoke printf,<10,'Select Disk (0..7): ',0>
cinvoke gets,drive,<'%s',0>
;//----- Пытаемся открыть указанный диск
invoke CreateFile, dName,GENERIC_READ,FILE_SHARE_WRITE,0,\
OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0
mov [hndl],eax
cmp eax,-1
jne @f
cinvoke printf,<' ----> Disk opening error!!!',0>
jmp @exit
;//----- ОК! Выделить память под 10-секторов GPT.
;//----- в 10-ти секторах лежат записи о 32-х разделах диска - хватит с избытком.
@@: invoke VirtualAlloc,0,512*10,MEM_COMMIT,PAGE_READWRITE
mov [dataOffs],eax
or eax,eax
jnz @f
cinvoke printf,<' ----> VirtualAlloc() error!!!',0>
jmp @exit
;//----- ОК! Читаем секторы с диска в выделенную память,
;//----- после чего закрываем дескриптор харда.
@@: invoke ReadFile,[hndl],[dataOffs],512*10,buff,0
invoke CloseHandle,[hndl]
;//----- Проверим тип раздела на GPT (ESI = адрес буфера с данными)
mov esi,[dataOffs]
cmp byte[esi+MBR.Volume0.Type],0xEE
jz @GPT_Partition_Table
jmp @MBR_Partition_Table
;//************* ИНФОРМАЦИЯ О ЗАГОЛОВКЕ GPT **************//
@GPT_Partition_Table:
mov esi,[dataOffs]
add esi,[secSize]
push esi esi esi
cinvoke printf,<10,'GPT-Header info',\
10,'=====================',\
10,' Header signature..........: %s',0>,esi
pop esi
mov eax,[esi+GPT_HEADER.BackupOffset+4]
mov ebx,[esi+GPT_HEADER.BackupOffset]
push eax ebx
fild qword[esp]
fstp [fpuRes0]
add esp,4*2
cinvoke printf,<10,' Header backup offset LBA..: %.0f',\
10,' GUID Partition Table LBA..: %04d',\
10,' Disk first partition LBA..: %04d',0>,\
dword[fpuRes0],dword[fpuRes0+4],\
[esi+GPT_HEADER.PartTableOffset],\
[esi+GPT_HEADER.FirstPartOffset]
pop esi
add esi,GPT_HEADER.DiskGUID
call guid2str
cinvoke printf,<10,' Physical disk GUID........: %s',0>,buff
pop esi
mov eax,[esi+GPT_HEADER.TotalDiskSector+4]
mov ebx,[esi+GPT_HEADER.TotalDiskSector]
push eax ebx
fild qword[esp]
fimul [secSize]
fidiv [mByte]
fstp [fpuRes0]
add esp,4*2
cinvoke printf,<10,' Physical disk size........: %.1f Gb',10,0>,\
dword[fpuRes0],dword[fpuRes0+4]
cinvoke printf,<10,'GUID Partition Table',\
10,'=====================',0>
mov esi,[dataOffs]
add esi,[secSize]
add esi,GPT_HEADER.PART0
mov [tableOffs],esi
;//************* ЦИКЛ ОБХОДА ЗАПИСЕЙ "GPT PARTITION ENTRY" **************//
@start_GPT_scan:
mov esi,[tableOffs]
cmp dword[esi],0 ;// проверить на пустую запись
je @stop_GPT_scan ;// выйти, если в записи болото нулей
add esi,GPT.PartitionTypeGUID
call guid2str
mov esi,[tableOffs]
add esi,GPT.PartitionUnicodeName
push esi
call Unicode2Asc
pop esi
cinvoke printf,<10,' Type........: %s',\
10,' GUID........: %s',0>,esi,buff
mov esi,[tableOffs]
mov eax,[esi+GPT.FirstLBA+4]
mov ebx,[esi+GPT.FirstLBA]
push eax ebx
fild qword[esp]
fstp [fpuRes0] ;// запомнить LBA начала раздела
mov eax,[esi+GPT.LastLBA+4]
mov ebx,[esi+GPT.LastLBA]
push eax ebx
fild qword[esp]
fstp [fpuRes1] ;// запомнить последний LBA раздела
fild qword[esp]
fisub dword[esp+8]
fimul [secSize]
fidiv [kByte]
fstp [fpuRes2] ;// запомнить разницу = ёмкость раздела
add esp,4*4 ;// очистить стек от параметров..
mov eax,[esi+GPT.Attributes+4]
mov ebx,[esi+GPT.Attributes]
cinvoke printf,<10,' Firts LBA...: %.0f',\
10,' Last LBA...: %.0f',\
10,' Attributes..: 0x%08X%08X',\
10,' Part size...: %.0f Mb',10,0>,\
dword[fpuRes0],dword[fpuRes0+4],\
dword[fpuRes1],dword[fpuRes1+4],\
eax,ebx,\
dword[fpuRes2],dword[fpuRes2+4]
add [tableOffs],128 ;// пряжок на сл.запись!
jmp @start_GPT_scan
@stop_GPT_scan:
jmp @exit
;//************* ЦИКЛ ОБХОДА "PARTITION TABLE" В MBR **************//
@MBR_Partition_Table:
mov esi,[dataOffs]
mov eax,[esi+MBR.DevID]
cinvoke printf,<10,'*** Drive-ID: %08X',10,\
10,'MBR Partition Table',\
10,'======================',0>,eax
mov esi,[dataOffs]
mov [tableOffs],esi
add [tableOffs],MBR.Volume0
@start_MBR_scan:
mov esi,[tableOffs]
movzx eax,byte[esi+VOLUME_ENTRY.BootFlag]
movzx ebx,byte[esi+VOLUME_ENTRY.Type]
mov ecx,[esi+VOLUME_ENTRY.FirstLBASector]
mov edx,[esi+VOLUME_ENTRY.LastLBASector]
or ecx,ecx
jz @exit
call PrintVolumeInfo
add [tableOffs],16
jmp @start_MBR_scan
@exit: ;invoke VirtualFree,[dataOffs],0,MEM_RELEASE
cinvoke gets,buff
cinvoke exit,0
;//************* ВСПОМОГАТЕЛЬНЫЕ ПРОЦЕДУРЫ **************//
proc PrintVolumeInfo
push edx
cinvoke printf,<10,' Boot-flag....: %02X',\
10,' Volume type..: %02X',\
10,' First LBA....: %u',\
10,' Total LBA....: %u',0>,\
eax,ebx,ecx,edx
fild dword[esp]
fimul [secSize]
fidiv [mByte]
fstp [fpuRes0]
add esp,4*1
cinvoke printf,<10,' Volume size..: %.1f Gb',10,0>,\
dword[fpuRes0],dword[fpuRes0+4]
ret
endp
;//---------------
proc guid2str
invoke StringFromGUID,esi,buff,ccMax
mov esi,buff
mov edi,esi
mov ecx,38
@@: lodsw
stosb
loop @b
xor eax,eax
stosd
ret
endp
;//---------------
proc Unicode2Asc
mov edi,esi
@@: lodsw
or ax,ax
je @f
stosb
jmp @b
@@: stosw
ret
endp
;//---------------
section '.idata' import data readable
library msvcrt,'msvcrt.dll',kernel32,'kernel32.dll',ole32,'ole32.dll'
import msvcrt, printf,'printf',gets,'gets',exit,'exit'
import ole32, StringFromGUID,'StringFromGUID2'
include 'api\kernel32.inc'
Посмотрим, что в итоге получили..
Значит перед нами таблица GPT, в которой сначала идёт заголовок с информацией о самом диске, а дальше логи из записей "Partition Entry". В заголовке видно, что резервная копия заголовка лежит в конце пространства, в секторе(976773167). Сама таблица лежит в секторе(2) – это текущий сектор, ..а разделы с данными (как и утверждал производитель) начинаются с сектора(34). Чтобы вычислить ёмкость раздела в секторах, нужно от Last отнять First-LBA.
Однако в моём случае, внутри записи первого раздела видим начальный сектор(2048), а не 34. Раздел размером 500 Мб и в его атрибутах взведены биты 0 и 63, что означает "Защищённый ОЕМ-раздел, без буквы", т.е. не отображается в проводнике Win. Это бокс для различных драйверов и прочей системной утвари, которую любезно сбросил туда производитель моего бука. Нужно сказать, что и последующие два раздела тоже из этой-же кухни с установленным битом(63), только они не принадлежат вендору ОЕМ (разработчику). В разделе EFI лежит загрузчик ОС размером 100 Мб (старушка MBR его таскала с собой), а в третьем разделе – барахло мелкомягких.
5. Заключение
Доступ к стартовым секторам диска с правами на запись открывает большие возможности. В своё время это было излюбленное место червей и всякой нечисти, поэтому начиная с Висты, MS отобрала у нас эти права (привет Жанне Рутковской, с её "голубой пилюлей"). Читать – пожалуйста, а вот записывать в начало диска (до файловой системы), юзеру нельзя. Чтобы сидя на нарах не ждать с воли сухарей, мы всегда должны помнить об уголовной ответственности за порчу чужой информации. Поэтому всё сказанное здесь носит чисто оборонительный характер, чтобы мы были осведомлены, как при программных сбоях можно восстановить работоспособность своего накопителя. Особенно актуально это для размеченных в формате MBR дисков, где самостоятельная правка пару байт может сэкономить Вам честно заработанные шекели.
В скрепке лежит готовый исполняемый файл для тестов, исходник загрузчика из MBR, а так-же инклуд с описанием структур "Partition Table". Всем удачи, пока!