Подробный гайд: DXE в контексте UEFI и загрузки компьютера - Часть 1
Коротко: что такое DXE
DXE расшифровывается как Driver Execution Environment — среда выполнения драйверов. Это одна из ключевых фаз инициализации платформы в архитектуре UEFI PI (Platform Initialization), которая наступает после фазы PEI и предшествует фазе BDS.
Если говорить упрощённо, DXE — это этап, на котором компьютер уже выполнил самую раннюю инициализацию, обнаружил оперативную память и базовые ресурсы, а теперь firmware должна:
- загрузить и выполнить множество драйверов;
- инициализировать устройства;
- создать программные интерфейсы для загрузчика ОС;
- подготовить консоль, диски, сеть, ACPI, переменные, безопасность и другие сервисы;
- передать управление фазе выбора загрузки — BDS.
DXE можно представить как «прошивочную операционную среду до операционной системы». Это ещё не ОС, но уже достаточно сложное окружение с драйверами, сервисами, событиями, памятью, протоколами и менеджером устройств.
UEFI, PI и место DXE
Часто термины UEFI и DXE смешивают, но важно понимать разницу. - UEFI Specification описывает интерфейс между firmware и операционной системой: системную таблицу, Boot Services, Runtime Services, протоколы, загрузку образов, переменные, работу с дисками, сетью и т.д. - UEFI PI Specification описывает внутреннюю архитектуру инициализации платформы: фазы SEC, PEI, DXE, BDS, TSL, Runtime и связанные механизмы — HOB, PPI, Firmware Volumes, DXE Dispatcher и т.д.
DXE — это прежде всего понятие из PI-модели, но именно в DXE формируется большая часть тех UEFI-интерфейсов, которые затем использует операционная система или загрузчик.
Упрощённо:
- PEI готовит фундамент: память, ранние ресурсы, HOB.
- DXE строит на этом фундаменте полноценную среду драйверов и сервисов.
- BDS решает, откуда и как загружать ОС.
- OS Loader загружает и запускает ядро ОС.
- Runtime Services остаются доступны ОС после загрузки.
Общая схема загрузки
Типичный поток загрузки UEFI-системы выглядит так:
Reset / Power On
↓
SEC
↓
PEI
↓
DXE Core
↓
DXE Dispatcher
↓
DXE-драйверы / UEFI-драйверы
↓
BDS
↓
OS Loader
↓
ExitBootServices
↓
OS Runtime
Каждая фаза решает свой класс задач.
| Фаза | Основная задача | Память | Ключевые механизмы |
|---|---|---|---|
| SEC | Самый ранний старт после reset | Обычно CAR | ассемблер, кэш как RAM |
| PEI | Инициализация памяти и базовой платформы | RAM уже доступна | PPI, HOB |
| DXE | Загрузка драйверов, инициализация устройств | Полноценная RAM | протоколы, DXE Dispatcher |
| BDS | Выбор загрузочного пути | Полноценная RAM | BootOrder, Boot#### |
| TSL | Выполнение загрузчика ОС | Полноценная RAM | LoadImage/StartImage |
| Runtime | Сервисы firmware для ОС | ОС управляет памятью | Runtime Services |
DXE — самая насыщенная фаза с точки зрения кода, драйверов, сервисов и потенциальных точек отладки.
Что происходит до DXE
Чтобы понять DXE, нужно коротко представлять, что происходит раньше.
SEC
Фаза SEC начинается сразу после сброса процессора. Это очень ранний код, часто написанный на ассемблере. На x86 процессор стартует в известном состоянии, без полноценной оперативной памяти. Поэтому часто используется механизм Cache-as-RAM — кэш процессора временно применяется как оперативная память.
SEC решает задачи вроде:
- обработка reset-вектора;
- минимальная инициализация процессора;
- подготовка временной памяти;
- поиск PEI Core;
- передача управления в PEI.
PEI
PEI означает Pre-EFI Initialization.
Здесь firmware выполняет раннюю инициализацию платформы:
- инициализирует оперативную память;
- обнаруживает базовые ресурсы;
- инициализирует часть чипсета;
- готовит HOB — Hand-Off Blocks;
- определяет режим загрузки;
- может инициализировать микрокод, TPM, ранние средства безопасности;
- передаёт управление DXE Core.
PEI использует механизм PPI — PEIM-to-PEIM Interfaces. Это аналог протоколов, но для ранней среды с ограниченными ресурсами.
Важнейший результат PEI — набор HOB. Это структуры данных, которые передаются в DXE и описывают:
- доступную память;
- ресурсы платформы;
- Firmware Volumes;
- CPU-информацию;
- режим загрузки;
- дополнительные данные от PEI-модулей.
Именно HOB являются основным способом передачи информации из PEI в DXE.
Что такое DXE Core
DXE Core — это ядро фазы DXE. Это не обычный драйвер, а центральный модуль, который создаёт саму среду выполнения драйверов. Обычно DXE Core находится в Firmware Volume как отдельный файл типа DXE_CORE. Его задача — принять управление от PEI, разобрать HOB и создать инфраструктуру DXE.
DXE Core делает примерно следующее:
- Получает управление от PEI/DXE IPL.
- Получает указатель на список HOB.
- Инициализирует память и сервисы распределения памяти.
- Строит внутреннюю базу ресурсов.
- Создаёт системную таблицу UEFI.
- Инициализирует Boot Services.
- Инициализирует DXE Services.
- Создаёт базу дескрипторов и протоколов.
- Запускает DXE Dispatcher.
- Обеспечивает загрузку и выполнение драйверов.
Если PEI — это фундамент, то DXE Core — каркас здания, на котором затем размещаются драйверы.
Передача управления из PEI в DXE
Переход из PEI в DXE обычно происходит через специальный PEIM — DXE IPL (Initial Program Load для DXE).
Упрощённо последовательность такая:
PEI Core завершает раннюю инициализацию
↓
DXE IPL находит DXE Core в Firmware Volume
↓
При необходимости DXE Core распаковывается
↓
DXE IPL передаёт управление DXE Core
↓
DXE Core получает HOB-список
В HOB-списке DXE Core находит данные, оставленные PEI:
- где находится доступная память;
- какие Firmware Volumes известны;
- какой режим загрузки выбран;
- какие ресурсы уже описаны;
- какие специальные параметры нужно учитывать.
После этого DXE Core начинает самостоятельную работу.
Что такое Firmware Volume
Firmware Volume, или FV, — это контейнер внутри firmware-образа, в котором хранятся модули, драйверы, приложения, данные и вложенные образы.
В терминах EDK II firmware-образ обычно описывается FDF-файлом и может содержать несколько FV:
- PEI FV с PEIM-модулями;
- DXE FV с DXE-драйверами;
- FV с UEFI-приложениями;
- FV с переменными или служебными данными;
- вложенные FV внутри сжатых секций.
Внутри Firmware Volume находятся файлы firmware-файловой системы — FFS, Firmware File System. Каждый файл имеет GUID, тип и секции.
Типичные типы файлов:
| Тип | Назначение |
|---|---|
| PEI_CORE | ядро PEI |
| PEIM | PEI-модуль |
| DXE_CORE | ядро DXE |
| DRIVER | DXE/UEFI-драйвер |
| APPLICATION | UEFI-приложение |
| FIRMWARE_VOLUME_IMAGE | вложенный Firmware Volume |
| RAW | сырые данные |
| FREEFORM | произвольные данные |
DXE Dispatcher ищет драйверы именно внутри Firmware Volumes.
DXE Dispatcher
DXE Dispatcher — это механизм внутри DXE Core, который занимается поиском, загрузкой и запуском DXE-драйверов. Если DXE Core — это ядро среды, то DXE Dispatcher — это её планировщик инициализации.
Он выполняет примерно следующий цикл:
Найти доступные Firmware Volumes
↓
Найти в них драйверы
↓
Прочитать зависимости каждого драйвера
↓
Если зависимости удовлетворены — загрузить и запустить драйвер
↓
Драйвер может установить новые протоколы
↓
Из-за новых протоколов могут стать доступны другие драйверы
↓
Повторять цикл, пока возможна дальнейшая инициализация
Важно понимать, что DXE Dispatcher не просто исполняет драйверы подряд. Он анализирует зависимости и определяет порядок запуска.
Зависимости DXE-драйверов: Depex
Каждый DXE-драйвер может иметь выражение зависимости — Dependency Expression, или Depex. Depex описывает, какие протоколы должны присутствовать в системе, чтобы драйвер можно было запустить.
Примеры:
[Depex]
TRUE
означает, что драйвер можно запускать сразу, если нет других ограничений.
[Depex]
gEfiPciRootBridgeIoProtocolGuid
означает, что драйвер может быть запущен только после появления протокола gEfiPciRootBridgeIoProtocolGuid.
Более сложный пример:
[Depex]
gEfiPciRootBridgeIoProtocolGuid AND gEfiVariableArchProtocolGuid
означает, что нужны оба протокола.
Также существуют специальные конструкции порядка:
[Depex]
BEFORE gSomeProtocolGuid
или:
[Depex]
AFTER gSomeProtocolGuid
Они используются для влияния на порядок диспетчеризации. Depex — это не просто формальность. На практике именно ошибки в Depex часто приводят к тому, что драйвер не запускается, потому что нужный протокол ещё не установлен или отсутствует вовсе.
Что такое протокол в UEFI
Один из главных механизмов DXE — протоколы.
Протокол UEFI — это именованный интерфейс, идентифицируемый GUID. Он может представлять собой:
- структуру с функциями;
- доступ к устройству;
- сервис firmware;
- абстракцию шины;
- файловую систему;
- консоль;
- переменные;
- ACPI;
- сетевой стек;
- криптографический сервис;
- интерфейс безопасности.
Протокол устанавливается на handle — дескриптор устройства или программного объекта.
Упрощённо:
Handle = объект
Protocol = интерфейс, прикреплённый к объекту
Например, один handle может представлять PCI-устройство и иметь установленные протоколы:
- EFI_DEVICE_PATH_PROTOCOL;
- EFI_PCI_IO_PROTOCOL;
- EFI_LOAD_FILE_PROTOCOL;
- EFI_BLOCK_IO_PROTOCOL;
- EFI_SIMPLE_FILE_SYSTEM_PROTOCOL;
- EFI_GRAPHICS_OUTPUT_PROTOCOL;
- и другие, в зависимости от устройства.
DXE-драйверы общаются между собой именно через протоколы.
Handle Database
DXE Core ведёт базу дескрипторов — Handle Database.
Каждый handle может содержать набор протоколов. Драйвер может:
- создать новый handle;
- установить протокол на handle;
- удалить протокол;
- найти handle по протоколу;
- открыть протокол с определёнными правами;
- закрыть протокол;
- подключить драйвер к контроллеру.
Для работы с handle и протоколами используются Boot Services, например:
InstallProtocolInterface;UninstallProtocolInterface;HandleProtocol;LocateProtocol;LocateHandle;LocateHandleBuffer;OpenProtocol;CloseProtocol;ConnectController;DisconnectController.
Через эти механизмы DXE строит граф устройств и драйверов.
Boot Services
Во время DXE активны Boot Services — сервисы загрузки UEFI. Они доступны до вызова ExitBootServices, который обычно делает загрузчик ОС.
Boot Services включают:
- управление памятью;
- управление событиями;
- таймеры;
- загрузку образов;
- запуск образов;
- работу с протоколами;
- работу с handle;
- подключение драйверов;
- watchdog-таймер;
- получение памяти и системной информации.
Примеры функций:
AllocatePages
FreePages
AllocatePool
FreePool
CreateEvent
SetTimer
WaitForEvent
LoadImage
StartImage
Exit
InstallProtocolInterface
LocateProtocol
ConnectController
GetMemoryMap
ExitBootServices
После ExitBootServices Boot Services больше недоступны. Остаются только Runtime Services. Это очень важно при разработке DXE-модулей: если код должен работать после загрузки ОС, он должен быть оформлен как runtime-компонент и не полагаться на Boot Services.
Runtime Services
Runtime Services — это сервисы firmware, которые остаются доступны операционной системе после загрузки.
К ним относятся, например:
GetVariable;SetVariable;GetNextVariableName;QueryVariableInfo;GetTime;SetTime;GetWakeupTime;SetWakeupTime;ResetSystem;UpdateCapsule;QueryCapsuleCapabilities;SetVirtualAddressMap.
Именно поэтому часть DXE-драйверов может быть связана с runtime-функциями. Например, драйвер переменных обычно инициализируется в DXE, но его функциональность остаётся доступной ОС.
DXE Services
Помимо UEFI Boot Services и Runtime Services, в EDK II часто используется понятие DXE Services.
Это сервисы уровня платформы, которые помогают управлять:
- памятью;
- адресным пространством;
- Firmware Volumes;
- диспетчеризацией;
- GCD — Global Coherency Domain;
- ресурсами ввода-вывода;
- атрибутами памяти.
В коде EDK II для доступа к ним часто используется глобальная таблица gDS.
Примеры задач DXE Services:
- добавление memory space;
- добавление I/O space;
- назначение атрибутов памяти;
- управление ресурсами платформы;
- поддержка диспетчера.
Обычному прикладному DXE-драйверу они нужны реже, чем Boot Services, но для платформенных драйверов и драйверов чипсета они очень важны.
Архитектурные протоколы
В DXE существует особый класс протоколов — архитектурные протоколы. Они описывают базовые сервисы платформы, без которых нормальная работа DXE/BDS невозможна.
Примеры архитектурных протоколов:
- EFI_CPU_ARCH_PROTOCOL;
- EFI_TIMER_ARCH_PROTOCOL;
- EFI_VARIABLE_ARCH_PROTOCOL;
- EFI_VARIABLE_WRITE_ARCH_PROTOCOL;
- EFI_RUNTIME_ARCH_PROTOCOL;
- EFI_SECURITY_ARCH_PROTOCOL;
- EFI_SECURITY2_ARCH_PROTOCOL;
- EFI_RESET_ARCH_PROTOCOL;
- EFI_REAL_TIME_CLOCK_ARCH_PROTOCOL;
- EFI_WATCHDOG_TIMER_ARCH_PROTOCOL;
- EFI_FIRMWARE_VOLUME2_PROTOCOL;
- EFI_DEVICE_PATH_PROTOCOL;
- EFI_PCI_HOST_BRIDGE_RESOURCE_ALLOCATION_PROTOCOL.
На практике не все они абсолютно одинаково трактуются в разных спецификациях и платформах, но смысл один: это базовые сервисы, на которые опираются другие модули.
Например:
- без Timer Arch Protocol сложно работать событиями и таймерами;
- без Variable Arch Protocol недоступны переменные;
- без Security Arch Protocol невозможна нормальная проверка Secure Boot;
- без CPU Arch Protocol затруднено управление прерываниями и кэшем;
- без Reset Arch Protocol невозможно корректно выполнить сброс системы.
DXE Dispatcher учитывает появление таких протоколов при запуске зависимых драйверов.
Что делает DXE на практике
Если отвлечься от спецификаций, DXE выполняет огромный пласт работы:
- инициализирует чипсет и платформы;
- настраивает PCIe;
- обнаруживает устройства;
- загружает UEFI-драйверы;
- инициализирует накопители;
- поднимает файловые системы;
- инициализирует графический вывод;
- подготавливает консоль;
- инициализирует сеть;
- создаёт ACPI-таблицы;
- заполняет SMBIOS;
- работает с переменными;
- инициализирует TPM и measured boot;
- проверяет Secure Boot;
- готовит HII и setup-меню;
- передаёт управление BDS.
По сути, когда пользователь видит логотип производителя, меню BIOS/UEFI или загрузочное меню, большая часть необходимой инфраструктуры уже была подготовлена в DXE или готовится в конце DXE и в BDS.
DXE и инициализация памяти
Одна из главных причин, почему DXE настолько мощнее PEI, — наличие полноценной оперативной памяти.
В PEI память только появляется, и код часто работает в очень ограниченных условиях. В DXE уже можно:
- выделять память динамически;
- использовать сложные структуры данных;
- загружать PE/COFF-образы;
- строить таблицы;
- создавать события;
- использовать протоколы;
- хранить большие буферы;
- работать с файловой системой firmware;
- выполнять распаковку;
- держать множество handle.
DXE Core использует HOB от PEI, чтобы понять, какие диапазоны памяти доступны, а затем строит собственные сервисы управления памятью.
В UEFI память описывается через memory map. Типы памяти включают, например:
EfiReservedMemoryType;EfiLoaderCode;EfiLoaderData;EfiBootServicesCode;EfiBootServicesData;EfiRuntimeServicesCode;EfiRuntimeServicesData;EfiConventionalMemory;EfiUnusableMemory;EfiACPIReclaimMemory;EfiACPIMemoryNVS;EfiMemoryMappedIO;EfiMemoryMappedIOPortSpace;EfiPalCode;EfiPersistentMemory.
Это важно, потому что ОС после загрузки получит memory map и будет знать, какие области заняты firmware, какие являются runtime-сервисами, какие можно использовать, а какие трогать нельзя.
PE/COFF-образы в DXE
Большинство DXE-драйверов представляют собой PE/COFF-образы, похожие по формату на исполняемые файлы Windows. DXE Dispatcher находит такой образ внутри Firmware Volume, загружает его в память, выполняет релокации и вызывает точку входа.
Типичная точка входа DXE-драйвера выглядит так:
#include <Uefi.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Library/DebugLib.h>
EFI_STATUS
EFIAPI
SampleDxeEntry (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
DEBUG ((DEBUG_INFO, "SampleDxe: started\n"));
return EFI_SUCCESS;
}
Если драйвер успешно инициализировался, он обычно возвращает EFI_SUCCESS. Если он не может работать в текущих условиях, он может вернуть ошибку.
Важно:
DXE-драйвер не является обычным пользовательским приложением. Он работает в привилегированной среде firmware и имеет прямой доступ к ресурсам платформы.
Firmware Volume и поиск драйверов
После запуска DXE Core должен понять, где искать драйверы.
Источники информации:
- HOB от PEI, которые могут описывать известные Firmware Volumes.
- Протоколы Firmware Volume, установленные платформой.
- Вложенные Firmware Volume, которые могут быть распакованы или обнаружены позже.
DXE Dispatcher просматривает Firmware Volumes и ищет файлы подходящих типов, например:
EFI_FV_FILETYPE_DRIVER;EFI_FV_FILETYPE_COMBINED_PEIM_DRIVER;EFI_FV_FILETYPE_APPLICATION;EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE.
Для каждого найденного драйвера проверяется:
- тип файла;
- наличие PE32-секции или другого исполняемого представления;
- dependency expression;
- статус предыдущей загрузки;
- порядок приоритета, если задан a priori файл.
Если зависимости удовлетворены, образ загружается и запускается.
A priori file
В Firmware Volume может присутствовать специальный файл — a priori file. Он задаёт список драйверов, которые должны быть диспетчеризованы раньше остальных. Это нужно для платформ, где критично запустить некоторые модули в строгом порядке, не полагаясь только на Depex.
Например, платформа может хотеть, чтобы сначала были запущены:
- драйвер переменных;
- драйвер безопасности;
- драйвер chipset;
- драйвер консоли;
- драйвер ACPI.
Depex описывает логические зависимости, а a priori file может дополнительно влиять на порядок.
Цикл диспетчеризации
DXE Dispatcher работает не один раз, а циклически. Причина проста: запуск одного драйвера может сделать доступными протоколы, которые нужны другим драйверам.
Пример:
Драйвер A устанавливает протокол X
↓
Драйвер B зависит от X
↓
DXE Dispatcher видит, что X появился
↓
Драйвер B может быть запущен
↓
Драйвер B устанавливает протокол Y
↓
Драйвер C зависит от Y
↓
Теперь можно запустить драйвер C
Поэтому DXE-инициализация похожа на распространение зависимостей по графу. Этот механизм делает систему гибкой: модули не обязаны знать друг о друге заранее. Они объявляют зависимости и обмениваются интерфейсами через протоколы.
DXE-драйвер против UEFI-драйвера
В контексте DXE часто возникает путаница между терминами DXE driver и UEFI driver. Оба выполняются в DXE-фазе, но их роль может отличаться.
DXE-драйвер
Обычно это платформенный или сервисный модуль, который выполняет инициализацию сразу после запуска.
Примеры:
- драйвер переменных;
- драйвер ACPI;
- драйвер SMBIOS;
- драйвер чипсета;
- драйвер консоли;
- драйвер безопасности;
- драйвер TPM;
- драйвер настройки платформы.
Такой модуль часто делает работу один раз во время boot.
UEFI-драйвер
UEFI-драйвер обычно реализует модель драйверов UEFI и предназначен для управления устройствами.
Он может:
- не подключаться сразу ко всем устройствам;
- ждать, пока его подключат через
ConnectController; - поддерживать горячее подключение;
- управлять дочерними устройствами;
- корректно отключаться через
Stop.
Примеры:
- USB-драйверы;
- драйверы mass storage;
- сетевые драйверы;
- драйверы файловых систем;
- драйверы видео;
- драйверы шины PCI;
- option ROM UEFI-драйверы.
То есть любой UEFI-драйвер выполняется в DXE-среде, но не любой DXE-драйвер реализует полную модель UEFI Driver Model.
Driver Binding Protocol
UEFI-драйверы часто используют Driver Binding Protocol.
Он содержит три ключевые функции:
Supported
Start
Stop
Supported
Проверяет, может ли драйвер управлять данным контроллером. Например, драйвер файловой системы FAT может проверить, присутствует ли на устройстве нужная сигнатура или нужный протокол блочного устройства.
Start
Подключает драйвер к контроллеру.
Обычно в Start драйвер:
- открывает нужные протоколы;
- создаёт handle для устройства или дочернего устройства;
- устанавливает собственные протоколы;
- подключает дочерние контроллеры, если нужно.
Stop
Отключает драйвер.
В Stop драйвер должен:
- остановить дочерние устройства;
- удалить созданные протоколы;
- закрыть открытые протоколы;
- освободить ресурсы.
Эта модель позволяет UEFI гибко управлять устройствами без жёсткой привязки драйверов к конкретным адресам или IRQ, как это часто было в старых BIOS-моделях.
ConnectController и DisconnectController
Для подключения драйверов к устройствам используются функции:
gBS->ConnectController
gBS->DisconnectController
ConnectController пытается найти подходящие драйверы для контроллера и подключить их. Например, когда появляется PCI-устройство, PCI Bus Driver может вызвать подключение драйверов, которые умеют работать с этим устройством.
DisconnectController выполняет обратную операцию: отключает драйверы от контроллера.
Это важно для:
- горячей замены;
- корректного завершения работы;
- переподключения устройств;
- отладки;
- смены загрузочного пути;
- безопасного извлечения устройств.
Device Path Protocol
Один из важнейших протоколов UEFI — EFI_DEVICE_PATH_PROTOCOL. Он описывает путь к устройству в виде последовательности узлов.
Device Path может описывать, например:
- PCI-путь;
- SATA/NVMe-устройство;
- USB-устройство;
- раздел диска;
- файл на файловой системе;
- сетевой адрес;
- ACPI-устройство;
- vendor-specific устройство.
Пример логического пути:
PCI → NVMe → Partition → FAT → \EFI\BOOT\BOOTX64.EFI
Device Path используется:
- загрузочными записями;
- BDS;
- драйверами;
- shell;
- операционной системой;
- переменными
Boot####; - утилитами настройки.
Когда вы видите в UEFI Boot Menu запись вроде UEFI: NVMe SSD или Windows Boot Manager, за ней обычно стоит Device Path.
Переменные UEFI и DXE
Переменные UEFI — это механизм хранения данных firmware между перезагрузками.
Они могут хранить:
- порядок загрузки;
- загрузочные записи;
- параметры Secure Boot;
- ключи;
- настройки setup;
- состояние платформы;
- диагностические данные;
- capsule-обновления.
Основной доступ к переменным осуществляется через Runtime Services:
GetVariable
SetVariable
GetNextVariableName
QueryVariableInfo
Инициализация сервиса переменных обычно происходит в DXE. Для этого устанавливаются архитектурные протоколы, связанные с переменными.
Примеры переменных:
BootOrder;Boot0001,Boot0002и т.д.;BootNext;BootCurrent;SecureBoot;SetupMode;PK;KEK;db;dbx.
Переменные могут быть volatile или non-volatile. Non-volatile переменные обычно хранятся в энергонезависимой памяти, например в SPI-flash.
Переменные загрузки
Особое значение имеют переменные загрузки.
BootOrder
Содержит список загрузочных записей в порядке приоритета.
Например:
BootOrder = 0002, 0001, 0003
означает, что сначала firmware попробует Boot0002, затем Boot0001, затем Boot0003.
Boot
Каждая загрузочная запись хранится в переменной вида Boot0001, Boot0002, Boot0003 и т.д.
Она содержит:
- описание;
- Device Path;
- атрибуты;
- при необходимости параметры командной строки.
Пример логического содержимого:
Boot0001 = Windows Boot Manager
Boot0002 = UEFI: NVMe SSD
Boot0003 = USB Drive
BootNext
Позволяет однократно загрузиться с конкретной записи при следующей перезагрузке.
BootCurrent
Показывает, какая загрузочная запись используется сейчас. Эти переменные создаются и изменяются firmware, setup-приложением, ОС или пользователем через утилиты вроде efibootmgr.
DXE и консоль
В DXE подготавливается консоль UEFI.
Она включает:
- ввод:
ConIn; - вывод:
ConOut; - поток ошибок:
StdErr.
Для текстового вывода часто используется EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL. Для графики используется EFI_GRAPHICS_OUTPUT_PROTOCOL, сокращённо GOP. GOP предоставляет framebuffer и заменяет старые VGA/VBE-механизмы в UEFI-среде.
Примерно:
Graphics Output Driver
↓
Framebuffer
↓
Console Driver
↓
ConOut
↓
BDS / Setup / OS Loader
Если вы видите логотип, анимацию загрузки, setup-меню или текстовую UEFI Shell — всё это использует консольные и графические протоколы, подготовленные в DXE/BDS.
DXE и PCI
Одна из важнейших задач DXE — инициализация PCIe/PCI.
Обычно это делается с помощью:
- PCI Root Bridge Driver;
- PCI Bus Driver;
- PCI Host Bridge Resource Allocation Protocol;
- EFI_PCI_IO_PROTOCOL;
- EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL.
PCI Bus Driver выполняет:
- сканирование шин;
- обнаружение устройств;
- назначение bus number;
- распределение BAR-ресурсов;
- включение декодеров;
- создание handle для устройств;
- установку PCI I/O Protocol;
- загрузку UEFI Option ROM, если это разрешено политикой платформы;
- подключение драйверов.
Пример упрощённого дерева:
PCI Root Bridge
↓
PCIe Root Port
↓
NVMe Controller
↓
NVMe Namespace
↓
Partition
↓
File System
Для загрузчика ОС важно, чтобы к моменту BDS уже были видны нужные контроллеры и файловые системы.
DXE и накопители
DXE инициализирует дисковую подсистему.
Это может включать:
- SATA/AHCI;
- NVMe;
- USB Mass Storage;
- SD/eMMC;
- SCSI/SAS на серверных платформах;
- RAID-контроллеры;
- virtio-blk в виртуальных машинах.
Обычно стек выглядит так:
Host Controller Driver
↓
Device Controller
↓
Block I/O Protocol
↓
Partition Driver
↓
File System Driver
↓
Simple File System Protocol
Ключевые протоколы:
EFI_BLOCK_IO_PROTOCOL;EFI_DISK_IO_PROTOCOL;EFI_SIMPLE_FILE_SYSTEM_PROTOCOL;EFI_DEVICE_PATH_PROTOCOL.
Для загрузки UEFI обычно требуется FAT-файловая система на EFI System Partition, хотя спецификация допускает и другие механизмы через Load File.
EFI System Partition
EFI System Partition, или ESP, — специальный раздел, с которого UEFI может загружать приложения и загрузчики.
Обычно он содержит файлы вроде:
\EFI\BOOT\BOOTX64.EFI
\EFI\Microsoft\Boot\bootmgfw.efi
\EFI\Linux\grubx64.efi
\EFI\systemd\systemd-bootx64.efi
BOOTX64.EFI — это fallback-путь для x64-систем. Он полезен, если конкретная загрузочная запись повреждена или ещё не создана.
В DXE/BDS firmware должна уметь:
- найти ESP;
- открыть файловую систему;
- найти файл;
- загрузить PE-образ;
- запустить его.
DXE и сеть
DXE может инициализировать сетевой стек UEFI.
Он включает:
- SNP — Simple Network Protocol;
- MNP — Managed Network Protocol;
- ARP;
- IP4/IP6;
- UDP;
- TCP;
- DHCP4/DHCP6;
- HTTP;
- PXE/Boot File Download;
- Load File Protocol.
Сетевая загрузка может выглядеть так:
Network Controller
↓
SNP
↓
MNP
↓
IP4
↓
UDP
↓
DHCP
↓
TFTP/HTTP Boot
↓
Load File Protocol
↓
Boot Manager
Если в BootOrder есть сетевая запись, BDS может попытаться загрузиться по сети.
DXE и ACPI
DXE подготавливает ACPI-таблицы, которые затем использует операционная система.
ACPI описывает:
- процессоры;
- прерывания;
- устройства;
- питание;
- термальные зоны;
- батареи;
- кнопки;
- PCI-конфигурацию;
- NUMA;
- спящие состояния;
- методы управления питанием.
Типичные таблицы:
- DSDT;
- SSDT;
- MADT;
- MCFG;
- FADT;
- HPET;
- SRAT;
- SLIT;
- BGRT;
- TPM2;
- IORT для ARM.
ACPI-таблицы устанавливаются через EFI_ACPI_TABLE_PROTOCOL или публикуются через конфигурационную таблицу UEFI. Если ACPI некорректен, ОС может не увидеть устройства, неправильно управлять питанием или не выйти из сна.
DXE и SMBIOS
SMBIOS — это структура данных с информацией о системе:
- производитель BIOS;
- версия firmware;
- производитель материнской платы;
- модель системы;
- серийные номера;
- информация о процессорах;
- информация о памяти;
- слоты;
- chassis;
- системные события.
В DXE часто устанавливается EFI_SMBIOS_PROTOCOL, через который драйверы добавляют SMBIOS-записи. Операционная система и утилиты вроде dmidecode читают SMBIOS уже после загрузки.
DXE и HII
HII — Human Interface Infrastructure. Это механизм для создания setup-меню, форм, строк, изображений и пользовательских настроек.
HII используется для:
- BIOS Setup;
- меню загрузки;
- настройки Secure Boot;
- настройки паролей;
- диагностических экранов;
- OEM-утилит.
DXE-драйверы могут регистрировать:
- формы;
- строки;
- шрифты;
- изображения;
- переменные настроек;
- callback-функции.
Когда пользователь заходит в UEFI Setup, он часто взаимодействует с интерфейсом, подготовленным DXE-драйверами и отображаемым через BDS/браузер форм.
DXE и безопасность
DXE — критичная фаза с точки зрения безопасности.
Именно здесь обычно работают или подготавливаются:
- Secure Boot;
- measured boot;
- TPM;
- проверка образов;
- переменные ключей;
- политика загрузки;
- защита SMRAM;
- защита переменных;
- capsule authentication;
- защита SPI-flash.
Secure Boot
Secure Boot проверяет цифровые подписи загружаемых образов.
Основные ключи и базы:
- PK — Platform Key;
- KEK — Key Exchange Key;
- db — database разрешённых подписей/хэшей;
- dbx — database запрещённых подписей/хэшей.
Когда firmware пытается загрузить образ, политика Secure Boot может проверить:
- подпись Authenticode;
- хэш образа;
- наличие в db;
- отсутствие в dbx;
- допустимость загрузки в текущем режиме.
Если проверка не проходит, загрузка образа может быть запрещена.
Measured Boot
Measured boot не запрещает загрузку напрямую, а измеряет её: записывает хэши и события в TPM.
Например, могут измеряться:
- firmware-образы;
- DXE-драйверы;
- option ROM;
- загрузчик;
- параметры конфигурации.
Результатом является цепочка измерений, которую можно позже проверить с помощью attestation. Secure Boot и measured boot могут работать вместе, но это разные механизмы: первый — политика допуска, второй — журнал измерений.
DXE и SMM
SMM — System Management Mode. Это особый привилегированный режим процессора, используемый firmware для критичных задач. DXE может взаимодействовать с SMM через специальные протоколы и драйверы.
В SMM часто выносят:
- защищённую запись переменных;
- обработку Secure Boot-политик;
- защиту SMRAM;
- управление питанием;
- обработку SMI;
- некоторые runtime-операции.
Например, когда ОС вызывает SetVariable, firmware может перейти в SMM, чтобы проверить права и безопасно изменить защищённую область.
Важно понимать:
DXE-драйвер и SMM-драйвер — не одно и то же. SMM-драйвер выполняется в более изолированной и привилегированной среде, а DXE-драйвер — в обычной DXE-среде.
Boot Mode
DXE учитывает режим загрузки, переданный из PEI.
Примеры режимов:
- полная конфигурация;
- минимальная конфигурация;
- S3 resume;
- recovery;
- firmware update;
- diagnostics.
От boot mode зависит, какие драйверы нужно запускать и какие пути инициализации использовать. Например, при S3 resume не обязательно заново выполнять полную инициализацию всех устройств. Вместо этого платформа может восстановить состояние и передать управление ОС. В recovery-режиме DXE может сосредоточиться на поиске восстановительного образа, а не на обычной загрузке.
BDS: следующий шаг после DXE
Когда DXE подготовил достаточную инфраструктуру, управление передаётся BDS — Boot Device Selection.
BDS отвечает за:
- чтение
BootOrder; - обработку
BootNext; - отображение загрузочного меню;
- проверку доступности загрузочных устройств;
- подключение необходимых контроллеров;
- выбор загрузочного образа;
- запуск OS Loader;
- fallback-загрузку;
- запуск UEFI Shell, если настроено;
- обработку ошибок загрузки.
Можно сказать так:
DXE готовит возможности
BDS выбирает, как ими воспользоваться
DXE может создать протоколы дисков, сети, консоли и переменных, а BDS решает, какой из вариантов загрузки использовать.
TSL: загрузка операционной системы
После BDS начинается фаза TSL — Time After Load / Time Before Load, в зависимости от терминологии, но суть в том, что выполняется загрузчик ОС.
Примеры загрузчиков:
bootmgfw.efiдля Windows;grubx64.efiдля GRUB;systemd-bootx64.efiдля systemd-boot;BOOTX64.EFIкак fallback.
Загрузчик использует Boot Services:
- читает файлы;
- выводит графику;
- получает memory map;
- загружает ядро и initrd;
- вызывает
ExitBootServices.
После ExitBootServices Boot Services прекращают работу, и ОС берёт управление платформой на себя.
ExitBootServices
ExitBootServices — один из самых важных моментов в загрузке. Загрузчик ОС вызывает его, когда больше не нуждается в Boot Services. Перед вызовом загрузчик должен получить актуальную memory map и её ключ — MapKey. Если memory map изменилась, вызов может завершиться ошибкой, и загрузчик должен повторить процесс.
После успешного ExitBootServices:
- Boot Services недоступны;
- память типа Boot Services может быть освобождена ОС;
- Runtime Services остаются;
- firmware больше не управляет обычной загрузкой;
- ОС сама управляет прерываниями, памятью и устройствами.
Для DXE-разработчика это критическая граница:
До ExitBootServices: можно использовать Boot Services
После ExitBootServices: Boot Services использовать нельзя
Что из DXE остаётся после загрузки ОС
Хотя DXE как фаза завершается до загрузки ОС, часть её результатов остаётся навсегда.
ОС использует:
- ACPI-таблицы;
- SMBIOS;
- UEFI Runtime Services;
- переменные;
- memory map;
- некоторые firmware-регионы;
- конфигурационную таблицу UEFI;
- при необходимости TPM-события;
- графическую информацию, например GOP, на ранних этапах.
Windows, Linux, FreeBSD и другие ОС умеют загружаться через UEFI и использовать эти интерфейсы.
Например, в Linux можно увидеть:
/sys/firmware/efi
/sys/firmware/efi/efivars
/sys/firmware/efi/vars
dmesg с сообщениями UEFI
А утилита efibootmgr показывает загрузочные записи, созданные firmware и ОС.
Практический пример минимального DXE-драйвера
Чтобы понять DXE практически, полезно представить простой модуль.
Файл INF может выглядеть так:
[Defines]
INF_VERSION = 0x00010005
BASE_NAME = SampleDxe
FILE_GUID = 5F0F2DB0-7A5B-4F6A-9F2E-5B8F2C1F0001
MODULE_TYPE = DXE_DRIVER
VERSION_STRING = 1.0
ENTRY_POINT = SampleDxeEntry
[Sources]
SampleDxe.c
[Packages]
MdePkg/MdePkg.dec
[LibraryClasses]
UefiDriverEntryPoint
DebugLib
[Depex]
TRUE
Исходный файл:
#include <Uefi.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Library/DebugLib.h>
EFI_STATUS
EFIAPI
SampleDxeEntry (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
DEBUG ((DEBUG_INFO, "SampleDxe: entry executed\n"));
return EFI_SUCCESS;
}
Чтобы модуль был встроен в firmware, его нужно добавить в DSC и FDF.
Пример в DSC:
[Components]
MyPkg/SampleDxe/SampleDxe.inf
Пример в FDF:
[FV.DXEFV]
INF MyPkg/SampleDxe/SampleDxe.inf
Если модуль есть в DSC, но отсутствует в FDF, он может собраться, но не попасть в итоговый firmware-образ.
Типы модулей EDK II
В EDK II важно правильно указывать MODULE_TYPE.
Основные типы:
| Тип | Назначение |
|---|---|
| SEC | модуль SEC-фазы |
| PEI_CORE | ядро PEI |
| PEIM | PEI-модуль |
| DXE_CORE | ядро DXE |
| DXE_DRIVER | обычный DXE-драйвер |
| DXE_RUNTIME_DRIVER | runtime-драйвер |
| DXE_SMM_DRIVER | SMM-драйвер |
| UEFI_DRIVER | UEFI-драйвер |
| UEFI_APPLICATION | UEFI-приложение |
| MM_STANDALONE | standalone MM-модуль, если поддерживается |
Для простого платформенного драйвера часто используют DXE_DRIVER:
- Для драйвера, который должен остаться после
ExitBootServices, используютDXE_RUNTIME_DRIVER. - Для драйвера, который должен управлять устройствами по модели UEFI Driver Model, часто используют
UEFI_DRIVER.
Где физически находится DXE
В обычной системе DXE-код находится в SPI-flash в составе firmware-образа.
Образ может включать несколько регионов:
- BIOS region;
- ME region;
- GbE region;
- descriptor region;
- platform-specific regions.
Внутри BIOS region находятся Firmware Volumes, а внутри них — DXE Core, DXE-драйверы, UEFI-драйверы, приложения и данные. В EDK II layout описывается FDF-файлом.
Пример упрощённой логики:
Flash Image
↓
BIOS Region
↓
FVMAIN
↓
DXEFV
↓
DXE Core + DXE Drivers
На виртуальных машинах, например OVMF, firmware-образ является файлом, который QEMU использует как BIOS/UEFI firmware.
DXE в виртуальной машине
DXE можно удобно изучать на виртуальной машине.
Например, используют:
- OVMF;
- QEMU;
- EDK II;
- UEFI Shell.
Типичная сборка OVMF может выглядеть примерно так:
build -a X64 -p OvmfPkg/OvmfPkgX64.dsc -b DEBUG
Запуск QEMU может выглядеть так:
qemu-system-x86_64 \
-bios Build/OvmfX64/DEBUG_GCC5/FV/OVMF.fd \
-serial stdio
В зависимости от платформы и сборки могут использоваться дополнительные параметры, например debugcon для порта 0x402. OVMF удобен тем, что можно видеть debug-сообщения, экспериментировать с драйверами, переменными, shell и загрузкой без риска для физического железа.
Отладка DXE
Отладка DXE — отдельная большая тема, но базовые инструменты такие:
Debug-печать
В EDK II обычно используют:
DEBUG ((DEBUG_INFO, "Message\n"));
DEBUG ((DEBUG_ERROR, "Error: %r\n", Status));
Уровни печати:
DEBUG_ERROR;DEBUG_WARN;DEBUG_INFO;DEBUG_VERBOSE;- и другие.
Чтобы печать работала, нужно включить соответствующие PCD и библиотеки.
ASSERT
ASSERT_EFI_ERROR (Status);
Полезно на этапе разработки, но в release-сборках поведение может отличаться.
Serial console
Если платформа поддерживает серийный порт, debug-сообщения можно выводить туда. На физических материнских платах это часто требует подключения UART-переходника и наличия debug-поддержки в firmware.
Debug port
В QEMU/OVMF часто используют debug port 0x402. В логе можно видеть сообщения firmware до загрузки ОС.
UEFI Shell
Если система загружается в UEFI Shell, можно использовать команды для диагностики.
Примеры команд:
dh
drivers
devices
map
memmap
dmpstore
bcfg boot dump
pci
Набор команд зависит от сборки Shell.
UEFITool
Для анализа firmware-образа удобно использовать UEFITool. Он позволяет:
- смотреть регионы;
- раскрывать Firmware Volumes;
- искать файлы по GUID;
- извлекать секции;
- находить DXE-драйверы;
- анализировать структуру образа.
Что делать, если DXE-драйвер не запускается
Типичные причины:
- Модуль не добавлен в FDF.
- Модуль не попал в нужный Firmware Volume.
- Depex не удовлетворяется.
- Требуемый протокол отсутствует.
- Драйвер вернул ошибку.
- Неправильный
MODULE_TYPE. - Не хватает библиотечных классов.
- Образ повреждён или некорректно упакован.
- Secure Boot блокирует внешний образ.
- Драйвер запускается слишком рано и использует недоступные ресурсы.
Полезный порядок диагностики:
Есть ли драйвер в firmware-образе?
↓
Появляется ли debug-сообщение о загрузке?
↓
Вызывается ли точка входа?
↓
Какой статус возвращает точка входа?
↓
Выполняются ли зависимости?
↓
Появляется ли ожидаемый протокол/handle?
Частая ошибка: путать DXE-драйвер и приложение
UEFI-приложение и DXE-драйвер внешне могут быть похожи, но их жизненный цикл разный.
Приложение:
- запускается пользователем или BDS;
- возвращает управление вызывающему;
- обычно не устанавливает долгосрочные сервисы;
- может быть загружено из shell или boot-меню.
DXE-драйвер:
- запускается DXE Dispatcher или другим механизмом firmware;
- может устанавливать протоколы;
- может оставаться активным на всю загрузку;
- влияет на поведение платформы;
- не предназначен для ручного запуска как обычная программа.
Для тестов иногда проще создать UEFI-приложение, а затем уже превращать его в DXE-драйвер.
Частая ошибка: использование Boot Services после ExitBootServices
Некоторые runtime-модули ошибочно пытаются использовать Boot Services после того, как ОС вызвала ExitBootServices. Это недопустимо.
После ExitBootServices:
- нельзя вызывать
AllocatePoolкак boot-сервис; - нельзя использовать
LocateProtocol; - нельзя создавать boot-события;
- нельзя использовать boot-таймеры;
- нельзя подключать драйверы через Boot Services.
Если модуль должен работать после загрузки ОС, он должен быть runtime-модулем и использовать только разрешённые механизмы.
Частая ошибка: неправильные типы памяти
В DXE важно правильно выбирать тип выделяемой памяти.
Например:
- для данных, нужных только до загрузки ОС, можно использовать
EfiBootServicesData; - для runtime-данных нужно использовать
EfiRuntimeServicesData; - для ACPI-таблиц, которые ОС может прочитать и затем освободить, может использоваться
EfiACPIReclaimMemory; - для данных, которые должны сохраняться при S3, может использоваться
EfiACPIMemoryNVSили другой подходящий тип.
Неправильный тип памяти может привести к тому, что ОС освободит или перезапишет нужные firmware данные.
Частая ошибка: драйвер ожидает протокол, но не объявляет зависимость
Если драйверу нужен протокол, лучше явно указать его в Depex или использовать механизм уведомления.
Плохо:
Status = gBS->LocateProtocol (&gSomeProtocolGuid, NULL, (VOID **)&Proto);
if (EFI_ERROR (Status)) {
return Status;
}
Если протокол ещё не установлен, драйвер завершится ошибкой.
Лучше:
[Depex]
gSomeProtocolGuid
Или использовать protocol notify, если нужен более гибкий сценарий.
Практика: установка протокола
Пример упрощённой установки протокола:
EFI_HANDLE Handle = NULL;
Status = gBS->InstallProtocolInterface (
&Handle,
&gMyProtocolGuid,
EFI_NATIVE_INTERFACE,
MyProtocolInterface
);
После этого другие драйверы смогут найти протокол через LocateProtocol или через handle database. Если протокол устанавливается на существующее устройство, handle может быть не пустым, а полученным от родительского контроллера.
Практика: поиск протокола
Пример поиска протокола:
EFI_ACPI_TABLE_PROTOCOL *AcpiTable = NULL;
Status = gBS->LocateProtocol (
&gEfiAcpiTableProtocolGuid,
NULL,
(VOID **)&AcpiTable
);
Если статус успешный, можно использовать интерфейс.
На практике всегда нужно проверять статус:
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "LocateProtocol failed: %r\n", Status));
return Status;
}
Практика: открытие протокола
Для драйверов, управляющих устройствами, важно использовать OpenProtocol.
Пример:
Status = gBS->OpenProtocol (
ControllerHandle,
&gEfiBlockIoProtocolGuid,
(VOID **)&BlockIo,
ImageHandle,
NULL,
EFI_OPEN_PROTOCOL_BY_DRIVER
);
Атрибут EFI_OPEN_PROTOCOL_BY_DRIVER показывает, что драйвер берёт эксклюзивное управление протоколом. Это позволяет избежать ситуации, когда два драйвера одновременно пытаются управлять одним устройством.
DXE и подключение устройств
В UEFI не обязательно подключать все устройства сразу.
Платформа может использовать стратегии:
- полное подключение всех контроллеров;
- минимальное подключение для ускорения boot;
- подключение только нужного загрузочного устройства;
- отложенная инициализация USB;
- отложенная инициализация сети;
- подключение по запросу пользователя.
Этим управляют BDS и платформенная политика, но возможности для этого создаются в DXE.
Fast Boot и DXE
Режимы быстрой загрузки могут влиять на DXE:
- меньше устройств подключается;
- часть инициализации пропускается;
- консоль может инициализироваться упрощённо;
- сетевой стек может быть отключён;
- USB-устройства могут быть подключены только при необходимости;
- setup-меню может быть недоступно до специальной комбинации клавиш.
Это ускоряет загрузку, но усложняет диагностику и может скрывать устройства до загрузки ОС.
DXE и CSM
CSM — Compatibility Support Module. Это механизм совместимости с legacy BIOS.
Если CSM включён, firmware может:
- выполнять legacy Option ROM;
- эмулировать BIOS-прерывания;
- поддерживать загрузку с MBR;
- поддерживать старые видео-режимы;
- обеспечивать совместимость со старыми ОС и устройствами.
В современных системах CSM часто отключают, оставляя чистый UEFI-режим. DXE и CSM могут сосуществовать, но чистый UEFI-путь проще, безопаснее и лучше соответствует современным требованиям.
DXE и загрузка Linux
При UEFI-загрузке Linux обычно используется один из загрузчиков:
- GRUB;
- systemd-boot;
- UKI-образ;
- direct kernel boot через EFI stub.
Упрощённо:
UEFI Firmware
↓
BDS
↓
GRUB/systemd-boot
↓
Linux Kernel EFI stub
↓
ExitBootServices
↓
Linux Kernel
Linux получает от firmware:
- memory map;
- ACPI;
- SMBIOS;
- EFI System Table;
- переменные;
- при необходимости initrd через firmware-протоколы.
После ExitBootServices ядро продолжает работу уже сам.
DXE и загрузка Windows
Windows при UEFI-загрузке использует:
\EFI\Microsoft\Boot\bootmgfw.efi
Упрощённо:
UEFI Firmware
↓
BDS
↓
bootmgfw.efi
↓
winload.efi
↓
Windows Kernel
Windows также использует UEFI Runtime Services для переменных, secure boot-состояния, BitLocker-related параметров и других задач.
DXE и загрузочные ошибки
Если проблема возникает на уровне DXE, симптомы могут быть такими:
- система не проходит POST;
- зависание на логотипе;
- перезагрузка до появления boot-меню;
- не видны диски;
- не видны USB-устройства;
- нет графического вывода;
- setup-меню зависает;
- ошибка загрузки ОС;
- некорректные ACPI-ошибки в ОС;
- Secure Boot ругается на загрузчик;
- сетевая загрузка не стартует.
Для пользователя базовые шаги обычно такие:
- обновить firmware;
- сбросить настройки BIOS/UEFI;
- проверить порядок загрузки;
- проверить режим UEFI/CSM;
- отключить лишние устройства;
- проверить состояние Secure Boot;
- проверить аккумулятор CMOS и питание;
- при подозрении на повреждение переменных очистить NVRAM.
Для разработчика нужны уже debug-логи, POST-коды, serial, JTAG или анализ firmware-образа.
Безопасность DXE
DXE — крупная поверхность атаки.
Причины:
- DXE содержит много драйверов;
- DXE работает с устройствами до ОС;
- DXE может выполнять option ROM;
- DXE работает с переменными и ключами;
- DXE имеет высокий уровень привилегий;
- ошибки в DXE могут приводить к bootkit-сценариям.
Поэтому современные платформы используют:
- Secure Boot;
- measured boot;
- TPM;
- Intel Boot Guard или аналоги;
- защиту SPI-flash;
- защиту SMRAM;
- IOMMU/DMA-защиту;
- UEFI memory protection;
- signed firmware updates;
- authenticated variables.
При разработке DXE-кода важно:
- проверять статусы;
- не доверять входным данным;
- не использовать небезопасные строковые функции;
- проверять размеры буферов;
- избегать TOCTOU;
- корректно работать с памятью;
- не оставлять debug-функции в release;
- осторожно обращаться с переменными и ключами;
- учитывать защиту от DMA;
- не запускать недоверенные образы без проверки.
Производительность DXE
DXE может существенно влиять на время загрузки.
Основные потребители времени:
- PCIe enumeration;
- инициализация накопителей;
- USB enumeration;
- сетевой стек;
- графика;
- option ROM;
- ACPI generation;
- setup/HII;
- проверка безопасности;
- декомпрессия firmware-образов.
Для ускорения используют:
- отложенное подключение устройств;
- отключение неиспользуемых драйверов;
- минимальный boot mode;
- a priori file для критичных модулей;
- оптимизацию Depex;
- уменьшение числа Firmware Volumes;
- сжатие, но с умом;
- профилирование через Timer и performance hooks.
Связь DXE с EDK II
EDK II — основная открытая реализация UEFI/PI firmware, используемая во многих проектах.
В EDK II ключевые места DXE связаны с:
MdeModulePkg/Core/Dxe— DXE Core;MdeModulePkg/Core/Dxe/Dispatcher— DXE Dispatcher;MdePkg— базовые библиотеки и include-файлы;MdeModulePkg— универсальные модули;OvmfPkg— виртуальная UEFI-платформа;UefiCpuPkg— CPU-related модули;SecurityPkg— безопасность;NetworkPkg— сеть;FatPkg— FAT;ShellPkg— UEFI Shell;SignedCapsulePkgи другие пакеты обновлений.
Если вы изучаете DXE, EDK II — лучшая практивная среда.
Полезные команды UEFI Shell
Ниже — часто используемые команды, если shell доступен.
Посмотреть handle database:
dh
Посмотреть драйверы:
drivers
Посмотреть устройства:
devices
Посмотреть memory map:
memmap
Посмотреть переменные:
dmpstore
Посмотреть boot-конфигурацию:
bcfg boot dump
Посмотреть PCI:
pci
Посмотреть файловые системы:
map -r
Конкретный набор команд зависит от сборки shell.
Диагностика из операционной системы
Если система уже загрузилась, часть UEFI-информации можно посмотреть из ОС.
Linux
Посмотреть, что система загрузилась через UEFI:
ls /sys/firmware/efi
Посмотреть переменные:
sudo efivar -l
Посмотреть загрузочные записи:
sudo efibootmgr -v
Посмотреть сообщения ядра про UEFI:
dmesg | grep -i efi
Windows
Можно посмотреть EFI System Partition:
mountvol Z: /s
dir Z:\EFI
Также используются инструменты вроде bcdedit, Get-SecureBootPolicy, системные журналы и фирменные утилиты вендоров.
Почему DXE важен для разных ролей
Для разработчика firmware
DXE — основная среда, где создаются драйверы, сервисы и платформенная логика.
Для BIOS/UEFI-инженера
DXE — место, где платформа оживает: устройства, ACPI, setup, boot policy.
Для специалиста по безопасности
DXE — критичная поверхность атаки и защиты: Secure Boot, переменные, option ROM, bootkits.
Для системного администратора
Понимание DXE помогает диагностировать проблемы с загрузкой, дисками, сетью, Secure Boot и NVRAM.
Для разработчика ОС
DXE определяет, какие таблицы, переменные и memory map получит ОС.
Итог первой части
DXE — это центральная фаза UEFI PI, в которой firmware превращает раннюю инициализацию платформы в полноценную среду для загрузки операционной системы.
В DXE происходят:
- запуск DXE Core;
- разбор HOB от PEI;
- поиск Firmware Volumes;
- диспетчеризация драйверов;
- установка протоколов;
- инициализация устройств;
- подготовка консоли, дисков, сети, ACPI, SMBIOS;
- работа с переменными;
- безопасность Secure Boot и measured boot;
- передача управления BDS.
Понимание DXE — это ключ к пониманию того, как современный компьютер проходит путь от включения питания до загрузки Windows, Linux или другой UEFI-совместимой операционной системы.
Мы делимся этой технической информацией, чтобы помочь вам в решении задач — используйте её с пониманием. Статья носит рекомендательный характер, поэтому, пожалуйста, применяйте описанные методы осмотрительно.