Путешествие сквозь секреты прошивок: исследование основ
Процесс запуска компьютера всегда был интересен пользователям. Именно здесь начинается магия, которое продолжается, пока устройство включено. В этой статье рассматривается общая картина процесса загрузки, включая различные этапы, ключевые компоненты, задачи, с которыми сталкивается система во время загрузки. Несмотря на то, что основное наше внимание будет сосредоточено на x86 архитектуре, остальные архитектуры будут иметь много общего в их процессе запуска. Надеюсь, что данная статья станет ценным ресурсом для тех, кто хочет углубить свои знания в этой области. Приступим!
Содержание:
BOOT ROM
Интегральная микросхема (чип), которая расположена на материнской плате и содержит код прошивки, отвечающий за загрузку компьютера, называется BOOT ROM. Это название не является стандартизованным, поэтому другие разработчики часто называют его FLASH ROM, BIOS FLASH, BOOT FLASH, SPI FLASH и так далее (эти названия присваиваются из-за технологии/интерфейса/назначения). Эти термины взаимозаменяемы, они означают одно и то же в большинстве контекстов. Код прошивки в BOOT ROM выполняется первым при включении компьютера, он выполняет базовые тесты, инициализирует оборудование, а затем загружает ОС-загрузчик с загрузочного устройства, такого как жесткий диск или USB-накопитель, в память. Этот чип изготовлен из non-volatile memory (NVM).
Non-Volatile Memory
Энергонезависимая память (Non-volatile memory) — это тип компьютерной памяти, которая сохраняет свое содержимое даже при отключении питания. Именно поэтому этот тип памяти идеально подходит для хранения важных данных, которые необходимо сохранять даже при выключении компьютера. В данной статье акцентируется внимание только на памяти, использующейся для хранения прошивки. Мы не будем рассматривать хранилища, такие как жесткие диски (HDD), твердотельные накопители (SSD), дискеты и т.д.
В общем, мы можем классифицировать этот тип памяти на следующие группы.
One Time Programmable
Masked ROM: Содержимое определяется во время производства и не может быть изменено после этого.
Programmable ROM (PROM): В отличие от Masked ROM, этот тип памяти может быть запрограммирован после производства. Однако, только один раз.
Field programmable
Erasable Programmable ROM (EPROM): Может быть запрограммирована многократно, но ее содержимое может быть стерто и перепрограммировано с помощью ультрафиолетового света.
Electrically Erasable Programmable (EEPROM): Может быть перепрограммирована многократно с помощью электрических сигналов.
NOR Flash memory: Архитектурно организована в блоках, где данные стираются на блочном уровне, а читаются и записываются на байтовом уровне. NOR память доступна непосредственно через стандартные интерфейсы, такие как I2C или SPI.
В индустрии существует негласное правило о том, термин EEPROM означает память, которая стирается на уровне байтов, в отличие от flash-памяти, которая стирается на уровне блоков.
Вместе с программируемой памятью приходит одно важное правило: стирание перед записью. В такой памяти запись новых данных более сложный процесс, поскольку данные хранятся в виде заряда на floating gate (это связано с тем как устроена память на физическом уровне). Количество заряда на транзисторе определяет, хранит ли ячейка »0» или »1». При стирании flash-памяти все биты данных, хранящиеся на ней, устанавливаются в заранее известное (заданное) состояние, обычно это логическая »1». Такой сброс в начальное состояние позволяет записывать новые данные на чип, не имея никаких остатков старых данных. При записи новых данных на чип состояние отдельных битов изменяется с »1» на »0», чтобы репрезентировать новые данные.
Если вы решите просто записать новые данные на чип без предварительной очистки, новые данные объединятся со старыми, что может привести к непредсказуемым результатам. Предположим, что flash-память хранит 8 бит данных со следующим значением:»0110 0010». Если вы попытаетесь записать новые данные »1100 1001» на чип без предварительного стирания, содержимое памяти изменится на »0100 0000», что может не соответствовать вашим намерениям.
Основное недопонимание связано с термином ROM, который означает Read Only Memory (память только для чтения). Данный термин исторически использовался для обозначения памяти, которую нельзя изменить, только читать. Однако с развитием технологий определение ROM изменилось, и сейчас часто используется для обозначения памяти, которая заранее запрограммирована на фабрике и не может быть легко изменена конечным пользователем. Однако, если пользователь обладает необходимыми навыками и специализированным оборудованием (например, программатором), то он может перепрограммировать чип. Название ROM осталось прежним, хотя определение изменилось, как историческое напоминание об исходной цели памяти.
С помощью технологии write protection (защиты от записи), некоторые типы перепрограммируемой ROM-памяти можно сделать доступными только для чтения (read only).
Здесь приведены НЕ ВСЕ типы существующей энергонезависимой памяти (non-volatile memory), но упомянуты все наиболее популярных, о которых вы могли уже где-то случайно слышать. В настоящее время на большинстве материнских плат эти микросхемы создаются с использованием технологии NOR Flash.
eXecute In Place (XIP)
Execute in Place (XIP) — это метод, позволяющий процессору выполнять код непосредственно из ROM, не копируя его сначала в энергозависимую память (такую как RAM). Это достигается путем маппинг flash-памяти в адресном пространстве процессора, так что выполнение кода может быть произведено напрямую из ROM. Таким образом, система может начать выполнение кода сразу же, не дожидаясь инициализации RAM.
Стоп… процессор может взаимодействовать с BOOT ROM через протоколы SPI/Parallel/etc? Конечно, не может. Он просто получает инструкции из системной памяти, запросы к этой области памяти перенаправляются на Intel Direct Media Interface (DMI) или AMD Infinity Fabric (IF) / Unified Media Interface (UMI) (предшественник). Этот интерфейс связывает между собой процессор и чипсет на материнской плате. На этом этапе декодирование адреса производится через декодеры, расположенные в чипсете, и данные с ROM передаются в процессор.
Когда чип сделан из NOR flash памяти, которая поддерживает случайный доступ к чтению (random access reads), но не поддерживает случайный доступ к записи (random access writes), возникает одна проблема. Поскольку доступной записываемой памяти нет, все вычисления должны выполняться в регистрах процессора. Следовательно, на этом этапе код может быть написан только на языке ассемблера, его задача — как можно быстрее настроить окружения для более высокоуровнего языка (обычно для C). Причина этого заключается в том, что инициализация памяти стала настолько сложной, что ее было бы трудно написать чисто на ассемблере. Поскольку такие языки требуют по крайней мере кучи и стека, нам нужна память, в которую мы сможем делать записи. Некоторые процессоры имеют встроенную SRAM на самом чипе, но более современный подход заключается в использовании Cache memory As RAM (CAR).
Cache-As-Ram (CAR)
Кэш процессора — это быстрая память, которая хранит копии частоо используемых данных/инструкций из основной памяти. Кэш расположен близко к процессорсу и организован в нескольких уровнях (L1, L2, L3, и т.д.), причем каждый следующий уровень больше и медленнее предыдущего. Если данные находятся в кэше, CPU может извлечь запрошенные данные из кэша (это называется попаданием в кэш/cache hit). Если кэш CPU не может найти требуемые данные, это приводит к кэш-промаху/cache miss. Это может произойти из-за того, что данные никогда не были сохранены в кэше или потому, что данные ранее были сохранены, но были вытеснены (evicted) из кэша. В любом случае, процессор должен обратиться к основной памяти, чтобы получить доступ к данным и скопировать их в кэш.
Вытеснение кэша (Cache eviction) — это процесс удаления данных из кэша для освобождения места для новой порции данных. Вытеснение данных может быть инициирована либо системой кэширования (обычно, когда кэш заполнен и требуется хранить новые данные, или когда истекает времени жизни данных), либо явным запросом.
Однако, если мы хотим использовать кэш процессора как оперативную память (Cache-As-Ram), нам нужно настроить кэш на работу в Non-Eviction Mode, также известном как No-Fill Mode. Данная техника предотвращает вытеснение (eviction) из-за промахов кэша (cache miss). Вместо этого кэш обрабатывается как обычная SRAM, и все обращения (чтение/запись) будут попадать в кэш и НЕ будут попадать в основную память. Данный режим активируется с помощью инструкций, специфичных для производителя процессора.
Layout & Memory Mapping
В настоящее время, BOOT ROM содержит в себе несколько различных прошивок. И только одна из них является тем, что мы привыкли называть прошивка / firmware. Их надо как-то различать между собой, для этого требуется их организация в каком-либо формате. Давайте выясним, какие форматы использовались и используются сейчас.
Non-Descriptor Mode
Одно из самых первых известных поведений чипсета это простой маппинг содержимого всего BOOT ROM в адресном пространстве памят (от 4GB до 4GB — 16MB). Если же размер BOOT ROM был меньше 16 MB, то содержимое маппилось несколько раз. Процессор и прошивка могли читать и записывать данных в ROM без каких-либо ограничений.
Non-Descriptor Mode более не поддерживается на новых чипсетах.
Intel Flash Descriptor / Descriptor mode
Позже, с появлением чипсета ICH8, Intel начал использовать специальный layout для BOOT ROM. Загрузочный чип разделен на следующие регионы:
Flash Descriptor (FD) — эта структура данных должна находится в начале ROM со смещением равным
0x10
. Она состоит из одиннадцати секций, как показано на рисунке ниже:Descriptor MAP содержит указатели на другие секции и размер каждого из них.
Component содержит информацию о флэш-памяти в системе (количество чипов, размер каждого из них, недопустимые инструкции и так далее).
В секции Masters определены права доступа (чтение/запись) для регионов. Информация, хранящаяся в этой области, должна быть доступна только в режиме чтения, следовательно может быть записана только в процессе производства.
Для лучшего понимания деталей предлагаю вам прочитать презентацию, подготовленную Open Security Training.
BIOS — единственный регион, который маппится в память.
Intel Converged Security and Management Engine (CSME / ME) — прошивка для поддержки различных технологий от Intel, в том числе Intel ME.
Gigabit Ethernet (GbE) — прямой доступ возможен только через Gigabit Ethernet контроллер.
Platform data
Embedded Controller (EC)
Flash Descriptor и Intel ME — единственные обязательные регионы.
Intel Firmware Interface Table (FIT)
FIT — это структура данных, расположенная внутри BIOS области, и содержит различные записи, описывающие конфигурацию платформы. Каждая запись в таблице имеет размер 16 байт. Первая запись называется FIT заголовок (header), а остальные — FIT запись (entry). Найти данную стуктуру можно с помощью FIT указателя (pointer), расположенного по физическому адресу 0xFFFFFFC0
(4GB — 0×40).
Эти компоненты должны быть обработаны до выполнения первой инструкции процессором из reset vector. Записи включается в себя обновление микрокода CPU, Startup ACM, Platform Boot/TPM/BIOS/TXT policies и другие компоненты. Как минимум, FIT должен содержать следующие записи: FIT заголовок (header), обновление микрокода CPU. Чаще всего FIT используют как раз для обновления микрокода перед выполнением reset vector'a. В памяти данная структура выглядит следующим образом:
Как всегда, для получения больших деталей, используйте FIT specification 1.4.
AMD Embedded Firmware Structure
К сожалению, информации об этой структуре гораздо меньше. Я не смог найти никакой утекших документации на чипсеты AMD с подробными сведениями о этой структуре. Поэтому я не смогу вам рассказать это лучше, чем написано в документации coreboot. Она написана на основе документации AMD, которая доступна только по соглашению о неразглашении (NDA).
Однако будет достаточно знать, что AMD аналогом Flash Descriptor является Embedded Firmware Structure, которая содержит указатели на PSP Directory Table, BIOS Directory Table и другие компоненты.
Silicon Initialization
Если вы очень хотели узнать и взглянуть на код, который инициализирует процессор и современную память, то мне придется вас огорчить. Intel и AMD не торопятся раскрывать этот инициализационный код общественности. Поскольку такая информация не является общедоступной, они предлагают бинарное дистрибьюцию такого кода инициализации важнейших компонентов. Это можно рассматривать как библиотеку для firmware developers, которая содержит бинарный код для инициализации контроллера памяти, чипсета, ЦП и других различных частей системы.
Intel Firmware Support Package (FSP)
Этот бинарный файл можно разделить на 4 компонента:
FSP-T: Настройка среды для раннего выполнения («Temporary RAM»), в которой может выполняться C код. На практике, этот компонент включает CAR mode, но также выполняет некоторую раннюю аппаратную инициализацию, такую как настройка пространства конфигурации с отображением памяти (memory-mapped configuration space) PCIe.
FSP-M: Инициализация постоянной памяти (такой как DRAM).
FSP-S: Завершение инициализации, включая инициализацию процессора и контроллера ввода-вывода.
FSP-O: Дополнительный компонент, обеспечивающий инициализацию OEM-устройств.
Репозиторий бинарников Intel FSP, которые размещаются самим Intel, вы можете найти на их github. Спецификацию FSP версии 2.1 общедоступна и вы можете скачать ее с официального сайта Intel.
AMD Generic Encapsulated Software Architecture (AGESA)
AGESA для компьютеров, выпущенных раньше Family 17h, известна как v5 или Arch2008. В то время, AGESA была выпущена как open-source продукт, а исходный код был доступен в репозитории coreboot (он был удален после выпуска версии 4.18). Спецификацию для Arch2008 можно найти на веб-сайте AMD.
С появлением Family 17h (микроархитектура Zen), AMD не публиковала исходный код AGESA, только предварительно скомпилированные бинарники. Такой преемник, поддерживающий Family 17h и более новые версии, называется AGESA v9.
openSIL
Пока что нет никакой подробной информации, только новости об этом.
Autonomous Subsystems
Неотъемлемая часть современного процесса загрузки x86, без которой ядра x86 не будут активированы. Поэтому их невозможно полностью отключить. Эти технологии отвечают за инициализацию аппаратных средств, проверку целостности системы, управление питанием и запуск CPU. Прошивка для этих подсистем загружается и выполняется до того, как основной процессор начинает выполнять свою собственную прошивку. Код на таких системах работает независимо от ядер CPU платформы.
Поскольку многие компании по аппаратному обеспечению используют принцип безопасности через засекреченность (security through obscurity), ни исходный код, ни документация для этих подсистем не доступны. К счастью, мы знаем, как это влияет на процесс загрузки — смотрите Hardware Power Sequences.
Мы не будем углубляться в подробности, потому что в Интернете уже есть обширные статьи от исследователей со всего мира. Только лишь краткое описание того, что это такое.
Intel Management Engine (ME)
Intel ME — это отдельный i486/80486 микропроцессор, интегрированный в чипсеты Intel (PCH) начиная с 2008 года. Он имеет свою собственную RAM, встроенный ROM, доступ ко всем шинам внутри чипсета (в результате он может получить доступ к сети и даже к основной RAM на процессоре), и многое другое. Работает на специально-предназначенной для этого ОС, основанной на MINIX.
AMD Platform Security Processor (PSP)
AMD PSP работает на отдельном ARM процессоре с Trustzone extension. Встроен в качестве копроцессора на основной кристалл процессора (находятся на одном и том же чипе). Этот процессор был введен на большинсво AMD платформ начиная с 2013 года. Работает на недокументированной и проприетарной ОС.
Hardware Power Sequences
Этот процесс, также известный как Power On Sequence или Power Sequncing, обеспечивает определенный порядок подачи напряжений на материнской плате. Проще говоря, он отвечает за включение компонентов платформы в определенном порядке. Процесс зависит от конкретной системы или дизайна платформы, но обычно стандартный ПК включает в себя следующие шаги:
Вы нажимаете кнопку питания. Но подождите… эта кнопка находится на корпусе компьютера и не является обязательной его часть. Обычно, кнопка питания это одна из сторон кабеля, на другой же стороне располагается переключатель (switch), который устанавливается на два металлических контакта на материнской плате. При нажатии на кнопку, эти контакты соединяются, таким образом электричество проходит через них. Посмотрите видео о том, как включить компьютер без кнопки питания, если вас это интересует.
Материнская плата отправляет сигнал в блок питания (PSU).
Блок питания получает сигнал, обеспечивает необходимое количество электричества и отправляет сигнал обратно на материнскую плату.
Как только материнская плата получает power good signal, она начинает включать компоненты платформы, такие как ядра, часы (clocks), чипсет, память, различные контроллеры и так далее.
Различные подсистемы, включая autonomous subsystems (рассмотренные выше), могут запускаться до запуска основного процессора.
Intel-based systems
Чипсет (ICH/PCH) находит Intel Flash Descriptor в BOOT ROM.
Чипсет копирует CSME прошивку в свою внутреннюю память, где Intel ME может получить к ней доступ и начинает ее выполнение.
Чипсет маппит BIOS region в память.
Обновление микрокода, находящиеся в Firmware Interface Table загружаются в CPU. Они должны применяться при каждом запуске системы.
(опционально) Если Authenticated Code Modules (ACM) был обнаружен, то тоже исполняется.
Все это время CPU reset signal был активен, что предотвращало запуск процессора до того, как остальные части системы будут готовы. Когда платформа готова, подача питания на CPU reset line прекращается. В многопроцессорной или многоядерной системе один процессор динамически выбирается в качестве загрузочного процессора (bootstrap processor (BSP)), который запускает весь код инициализации. Оставшиеся процессоры, называемые application processors (AP), остаются бездействующими до того момента пока они будут явно активированы прошивкой/ядром операционной системы.
При первом включении CPU начинает свою работу в real mode. Большинство регистров имеются определенные стандартные значения, включая Instruction Pointer (IP), Code Segment (CS) и Descriptor Cache, который является копией каждого segment descriptor'а внутри процессора для обеспечения более быстрого доступа к памяти сегментов.
Segment Descriptor является записью в Global Descriptor Table (GDT) и содержит base-address, segment limit и access information (в нашем случае эта часть игнорируется, так как в real mode нет контроля доступа, как в protected mode). Чтобы не обращаться к GDT (которая находится в памяти) при каждом взаимодействии к памяти, информация хранится в descriptor cache.
Однако real mode не использует GDT, поэтому процессор генерирует записи при первом запуске самостоятельно. Регистр CS selector, используемый для доступа к segment descriptor, содержит значение
0xF000
. CS base-address инициализируется значением0xFFFF_0000
. IP инициализируется значением0xFFF0
. Следовательно, процессора начинается извлекать инструкции из памяти, расположенной по физическому адресу0xFFFF_FFF0
(0xFFFF_0000
+0x0000_FFF0
). Первая инструкция, исполняемая по этому адресу, называется reset vector.ПРИМЕЧАНИЕ: Этот трюк позволяет получить доступ к high address space, однако нельзя получить доступ к коду, расположенному ниже адреса
0xFFFF_0000
. CS base-address остается с этим начальным значением до тех пор, пока регистр CS selector не будет изменен прошивкой. Это можно сделать выполнив far jump. На этом этапе наилучшем решением будет является переход в protected mode, который может работать адресовать 4 GB. Если прошивка этого не сделает, то для работы в real mode, чипсет должен будет переместить/скопировать данные из диапазона чуть ниже 4 GB в диапазон памяти ниже 1 MB (иначе не будет возможности исполнять код прошивки). Некоторые чипсеты не делают этого и может потребоваться переход в другой operating mode, прежде чем будет выполнен первый long jump.Адрес исполнения кода находится в non-volatile memory, поэтому CPU использует Execute In Place (XIP) метод. Хотя, если это система на базе AMD, скорее всего этот адрес будет находиться в основной памяти.
Процессор начинает исполнение кода прошивки.
Рекомендую вам посмотреть данное видео о power-on sequence, которое объясняет этот процесс на примере материнской платы ASUS P9×79.
Данная статья сосредоточена на теоретической информации, связанной с тем, как работает процесс загрузки компьютера. Однако, чтобы действительно понять этот процесс, нам нужно рассмотреть исходный код и архитектуру существующих прошивок. В следующей статье планируется подробно рассмотротреть BIOS, UEFI и coreboot.