В конце 90-х компания Number Nine Visual Technology, тогдашний светоч дизайна видеокарт, предлагала на сайте VGA BIOS для своих PCI-устройств. Ничего примечательного в этом событии нет. Разве что, видеокарты Number Nine могли работать как на IBM PC-совместимых платформах, так и в MAC-системах, использующих Power PC. Поэтому одно и то же устройство комплектовалось различными файлами BIOS.
Скорее всего, тогда и не могло быть иначе. Как сейчас дело обстоит с поддержкой устройств, рассчитанных на работу в разных аппаратных средах? Ответ на этот вопрос дает спецификация UEFI, в рамках которой предлагается изящное решение – EFI Byte Code или EBC. С его помощью можно создавать кроссплатформенные приложения для firmware.
В рамках UEFI-стандарта определяется архитектура виртуальной машины регистрового типа EFI Byte Code Virtual Machine. Интерпретатор команд входит в состав firmware системной платы. Встроенное программное обеспечение плат расширения пишется в системе команд виртуальной машины, в идеале, без использования инструкций центрального процессора. Таким образом, плата расширения будет работоспособна на любой системной плате, поддерживающей EBC, независимо от типа центрального процессора. На сегодня их список не блещет разнообразием: как обычно, здесь есть AMD и Intel в 32-битном и 64-битном вариантах, Itanium, ARM.
64-разрядный виртуальный процессор EBC содержит 8 регистров общего назначения (R0-R7), поддерживает прямую, косвенную и непосредственную адресацию операндов. Система команд включает арифметические и логические операции, сдвиги, пересылки операндов с поддержкой знакового расширения, условную и безусловную передачу управления, вызовы подпрограмм и возвраты, а также ряд вспомогательных операций. Поддерживается стек, при этом указатель стека (регистр R0) согласно традициям архитектуры x86, классифицируется как регистр общего назначения. Примечательно, что специальная форма инструкции CALL, позволяет из EBC-подпрограмм вызывать подпрограммы, написанные на «родном» языке платформы, в силу того, что иногда такая необходимость все же возникает. Таким же образом из EBC-программ можно вызывать процедуры поддержки UEFI-протоколов, используя при этом модель передачи входных и выходных параметров, не зависящую от типа центрального процессора.
Предлагаемый пример «Hello, EBC!» является UEFI-приложением, написанным в системе команд виртуальной машины EBC (EFI Byte Code). Как уже сказано выше, интерпретатор команд EBC, позволяющий запускать модули данного типа, резидентно входит в состав UEFI firmware системной платы. Использование EBC вместо машинного кода позволяет создавать кроссплатформенные приложения и драйверы, включая firmware различных плат расширения, что делает данные устройства совместимыми с платформами, использующими центральные процессоры архитектуры, отличной от x86.
Программа выводит текстовое сообщение, используя процедуру вывода строки из набора функций EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL. Рассмотрим детальнее ее исходный код.
Для вызова данной EFI-функции в стеке необходимо передать два параметра: указатель на интерфейсный блок используемого протокола и указатель на строку, представленную в формате UNICODE. Эти параметры подготавливаются в регистрах R1 и R2, затем заносятся в стек инструкциями PUSHn, начиная с последнего параметра. Затем, в регистре R3 размещается адрес для вызова процедуры, прочитанный из интерфейсного блока используемого протокола и выполняется вызов целевой процедуры вывода строки. После возврата, освобождаем стек инструкциями POPn.
Для вызова сервисных процедур UEFI-протоколов, приложения используют указатели, находящиеся в системных таблицах UEFI и различных интерфейсных блоках. В 32-битных реализациях UEFI используются указатели размером 4 байта, в 64-битных – размером 8 байт. Следовательно, адрес указателя внутри таблицы будет зависеть от разрядности центрального процессора. Как же обеспечивается кроссплатформенность?
Рассмотрим пример инструкции, передающей в регистр R1 содержимое ячейки памяти, адрес которой равен исходному значению регистра R1 плюс смещение.
Смещение задано в виде двух слагаемых: +5 и +24.
Первое слагаемое +5 является номером адресуемого указателя. Интерпретатор команд EBC умножает это значение на размер указателя, который равен 4 для 32-битных реализаций UEFI и 8 для 64-битных.
Второе слагаемое +24 является константой, не зависящей от типа платформы. Оно используется для задания размера заголовка таблицы EFI_SYSTEM_TABLE.
Подобным образом работают инструкции PUSHn (Push Natural), используемые при подготовке стекового фрейма для вызываемых процедур. Разрядность параметров, записываемых в стек (32 или 64 бита) зависит от разрядности центрального процессора. Так обеспечивается шлюзование между EBC-кодом приложения и процедурами, входящими в состав UEFI-firmware написанными в системе команд центрального процессора.
Для трансляции программы и генерации EBC-приложения используется FASM 1.69.50. Инструкции виртуальной машины EFI Byte Code заданы в виде шестнадцатеричных констант. Руководствуясь исследовательским интересом, мы намеренно отказались от использования языков высокого уровня и написали наш пример на ассемблере EBC. При этом нам пришлось решить несколько задач, связанных с тем, что транслятор FASM не поддерживает EBC.
После трансляции, в заголовке файла helloebc.efi, по адресам 84h, 85h байты 64h, 86h необходимо заменить на BCh, 0Eh. Таким образом, поле Machine Type, исходно содержащее 8664h (x86-64 machine) заменяем на 0EBCh (EBC machine). Для запуска редактора, встроенного в UEFI Shell, в командной строке требуется набрать: hexedit helloebc.efi.
Рис 1. Коррекция поля Machine Type в заголовке приложения
После этого можно запускать приложение.
Рис 2. Результат работы приложения
Приложение также можно запустить под отладчиком Intel EBC Debugger.
Рис 3. Загрузка отладчика командой load и запуск EBC-приложения под отладчиком Intel EBC Debugger
Работу тестового примера мы проверили в средах IA32 EFI и x64 UEFI. Теоретически, он должен работать на платформах с процессорами Itanium и ARM, но из-за недоступности указанных систем мы не смогли в этом убедиться.
Приложение транслируется в режиме PE64 (Portable Executable 64-bit). Некоторые устаревшие EFI-реализации (например, эмулятор Intel EFI Version 1.10.14.59 Sample Implementation, запускаемый с загрузочной дискеты) не совместимы с данным форматом приложения. Это выражается в некорректной интерпретации таблицы перемещаемых элементов, используемой при настройке модуля на адреса загрузки. Один из путей решения – выполнять настройку программно.
Так как транслятор FASM не поддерживает EFI Byte Code, для обеспечения эффективного программирования на уровне ассемблера EBC в среде FASM, нам предстоит сделать следующее:
Скорее всего, тогда и не могло быть иначе. Как сейчас дело обстоит с поддержкой устройств, рассчитанных на работу в разных аппаратных средах? Ответ на этот вопрос дает спецификация UEFI, в рамках которой предлагается изящное решение – EFI Byte Code или EBC. С его помощью можно создавать кроссплатформенные приложения для firmware.
Как работает EBC
В рамках UEFI-стандарта определяется архитектура виртуальной машины регистрового типа EFI Byte Code Virtual Machine. Интерпретатор команд входит в состав firmware системной платы. Встроенное программное обеспечение плат расширения пишется в системе команд виртуальной машины, в идеале, без использования инструкций центрального процессора. Таким образом, плата расширения будет работоспособна на любой системной плате, поддерживающей EBC, независимо от типа центрального процессора. На сегодня их список не блещет разнообразием: как обычно, здесь есть AMD и Intel в 32-битном и 64-битном вариантах, Itanium, ARM.
Архитектура виртуального процессора EBC
64-разрядный виртуальный процессор EBC содержит 8 регистров общего назначения (R0-R7), поддерживает прямую, косвенную и непосредственную адресацию операндов. Система команд включает арифметические и логические операции, сдвиги, пересылки операндов с поддержкой знакового расширения, условную и безусловную передачу управления, вызовы подпрограмм и возвраты, а также ряд вспомогательных операций. Поддерживается стек, при этом указатель стека (регистр R0) согласно традициям архитектуры x86, классифицируется как регистр общего назначения. Примечательно, что специальная форма инструкции CALL, позволяет из EBC-подпрограмм вызывать подпрограммы, написанные на «родном» языке платформы, в силу того, что иногда такая необходимость все же возникает. Таким же образом из EBC-программ можно вызывать процедуры поддержки UEFI-протоколов, используя при этом модель передачи входных и выходных параметров, не зависящую от типа центрального процессора.
Приступаем к экспериментам
Предлагаемый пример «Hello, EBC!» является UEFI-приложением, написанным в системе команд виртуальной машины EBC (EFI Byte Code). Как уже сказано выше, интерпретатор команд EBC, позволяющий запускать модули данного типа, резидентно входит в состав UEFI firmware системной платы. Использование EBC вместо машинного кода позволяет создавать кроссплатформенные приложения и драйверы, включая firmware различных плат расширения, что делает данные устройства совместимыми с платформами, использующими центральные процессоры архитектуры, отличной от x86.
Пояснения к примеру
Программа выводит текстовое сообщение, используя процедуру вывода строки из набора функций EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL. Рассмотрим детальнее ее исходный код.
Для вызова данной EFI-функции в стеке необходимо передать два параметра: указатель на интерфейсный блок используемого протокола и указатель на строку, представленную в формате UNICODE. Эти параметры подготавливаются в регистрах R1 и R2, затем заносятся в стек инструкциями PUSHn, начиная с последнего параметра. Затем, в регистре R3 размещается адрес для вызова процедуры, прочитанный из интерфейсного блока используемого протокола и выполняется вызов целевой процедуры вывода строки. После возврата, освобождаем стек инструкциями POPn.
Системные таблицы и кроссплатформенность
Для вызова сервисных процедур UEFI-протоколов, приложения используют указатели, находящиеся в системных таблицах UEFI и различных интерфейсных блоках. В 32-битных реализациях UEFI используются указатели размером 4 байта, в 64-битных – размером 8 байт. Следовательно, адрес указателя внутри таблицы будет зависеть от разрядности центрального процессора. Как же обеспечивается кроссплатформенность?
Рассмотрим пример инструкции, передающей в регистр R1 содержимое ячейки памяти, адрес которой равен исходному значению регистра R1 плюс смещение.
MOVnw R1,@R1(+5,+24)
Смещение задано в виде двух слагаемых: +5 и +24.
Первое слагаемое +5 является номером адресуемого указателя. Интерпретатор команд EBC умножает это значение на размер указателя, который равен 4 для 32-битных реализаций UEFI и 8 для 64-битных.
Второе слагаемое +24 является константой, не зависящей от типа платформы. Оно используется для задания размера заголовка таблицы EFI_SYSTEM_TABLE.
Подобным образом работают инструкции PUSHn (Push Natural), используемые при подготовке стекового фрейма для вызываемых процедур. Разрядность параметров, записываемых в стек (32 или 64 бита) зависит от разрядности центрального процессора. Так обеспечивается шлюзование между EBC-кодом приложения и процедурами, входящими в состав UEFI-firmware написанными в системе команд центрального процессора.
Трансляция и запуск
Для трансляции программы и генерации EBC-приложения используется FASM 1.69.50. Инструкции виртуальной машины EFI Byte Code заданы в виде шестнадцатеричных констант. Руководствуясь исследовательским интересом, мы намеренно отказались от использования языков высокого уровня и написали наш пример на ассемблере EBC. При этом нам пришлось решить несколько задач, связанных с тем, что транслятор FASM не поддерживает EBC.
После трансляции, в заголовке файла helloebc.efi, по адресам 84h, 85h байты 64h, 86h необходимо заменить на BCh, 0Eh. Таким образом, поле Machine Type, исходно содержащее 8664h (x86-64 machine) заменяем на 0EBCh (EBC machine). Для запуска редактора, встроенного в UEFI Shell, в командной строке требуется набрать: hexedit helloebc.efi.
Рис 1. Коррекция поля Machine Type в заголовке приложения
После этого можно запускать приложение.
Рис 2. Результат работы приложения
Приложение также можно запустить под отладчиком Intel EBC Debugger.
Рис 3. Загрузка отладчика командой load и запуск EBC-приложения под отладчиком Intel EBC Debugger
Резюме
Работу тестового примера мы проверили в средах IA32 EFI и x64 UEFI. Теоретически, он должен работать на платформах с процессорами Itanium и ARM, но из-за недоступности указанных систем мы не смогли в этом убедиться.
Приложение транслируется в режиме PE64 (Portable Executable 64-bit). Некоторые устаревшие EFI-реализации (например, эмулятор Intel EFI Version 1.10.14.59 Sample Implementation, запускаемый с загрузочной дискеты) не совместимы с данным форматом приложения. Это выражается в некорректной интерпретации таблицы перемещаемых элементов, используемой при настройке модуля на адреса загрузки. Один из путей решения – выполнять настройку программно.
Так как транслятор FASM не поддерживает EFI Byte Code, для обеспечения эффективного программирования на уровне ассемблера EBC в среде FASM, нам предстоит сделать следующее:
- Написать небольшую сервисную утилиту для автоматизации перезаписи поля Machine Type в заголовке UEFI-приложения с пересчетом контрольной суммы модуля.
- Подготовить набор макросов для использования ассемблерных мнемоник EBC в среде FASM.