Подробный гайд: DXE в контексте UEFI и загрузки компьютера - Часть 1

Подробный разбор фазы DXE в UEFI: загрузка драйверов, протоколы, память, ACPI, Secure Boot и передача управления BDS перед стартом ОС.

2026.08.21                  


Подробный гайд: DXE в контексте UEFI и загрузки компьютера - Часть 1Подробный гайд: 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 делает примерно следующее:

  1. Получает управление от PEI/DXE IPL.
  2. Получает указатель на список HOB.
  3. Инициализирует память и сервисы распределения памяти.
  4. Строит внутреннюю базу ресурсов.
  5. Создаёт системную таблицу UEFI.
  6. Инициализирует Boot Services.
  7. Инициализирует DXE Services.
  8. Создаёт базу дескрипторов и протоколов.
  9. Запускает DXE Dispatcher.
  10. Обеспечивает загрузку и выполнение драйверов.

Если 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 должен понять, где искать драйверы.

Источники информации:

  1. HOB от PEI, которые могут описывать известные Firmware Volumes.
  2. Протоколы Firmware Volume, установленные платформой.
  3. Вложенные 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-драйвер не запускается

Типичные причины:

  1. Модуль не добавлен в FDF.
  2. Модуль не попал в нужный Firmware Volume.
  3. Depex не удовлетворяется.
  4. Требуемый протокол отсутствует.
  5. Драйвер вернул ошибку.
  6. Неправильный MODULE_TYPE.
  7. Не хватает библиотечных классов.
  8. Образ повреждён или некорректно упакован.
  9. Secure Boot блокирует внешний образ.
  10. Драйвер запускается слишком рано и использует недоступные ресурсы.

Полезный порядок диагностики:

Есть ли драйвер в 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-совместимой операционной системы.


Перейти к Части 2


Мы делимся этой технической информацией, чтобы помочь вам в решении задач — используйте её с пониманием. Статья носит рекомендательный характер, поэтому, пожалуйста, применяйте описанные методы осмотрительно.


Статью подготовил: Аверко Денис Сергеевич @Nymexis г. Омск (специалист по ЗИ)

Комментарии

Загрузка...
Если комментарии не загружаются, можете попробовать отключить блокировщик рекламы для этого сайта