Что такое система efi windows

29 апреля 2025

Что такое EFI / UEFI ?

UFI BIOS что это за система и в каком разделе ее найти. подробное описание от специалистов компании Serty service.

   Как только мы включаем компьютер, в нем немедленно начинает работать миниатюрная операционная система, которую мы знаем как BIOS. Она занимается тестированием устройств, памяти, загрузкой операционных систем, распределением ресурсов аппаратуры. Многие функции этого набора программ (их объем обычно около 256-512 Кб) позволяют поддерживать старые операционные системы вроде MS-DOS, предоставляя им множество возможностей. Со времен PC/AT-8086 BIOS менялся очень мало, а ко времени запуска первых Пентиумов его развитие почти остановилось. Собственно, менять в нем стало нечего, кроме двойного BIOS, поддержки сетевых средств и возможности перепрошивки. А вот минусов стало много: стартовый вход в реальный режим процессора, 16-разрядная адресация и 1 Мб доступной памяти, невозможность иметь «ремонтную» консоль. И, конечно, вечная проблема поддержки жестких дисков. Даже сейчас гарантированно поддерживаются диски до 2,2 Тб, не более.

   Компания Intel еще в 2005 года решила поменять BIOS на EFI/UEFI (Unified Extensible Firmware Interface). Система EFI — более продвинутая базовая операционная система. На некоторых платформах Unix и Windows UEFI уже давно работает, но массового перехода пока не свершилось, несмотря на благие намерения. А они таковы:

  • Наличие пресловутой консоли для ремонта системных параметров и установки ОС;
  • Раздел EFI даёт возможность совершать некоторые действия, не загружая ОС (просмотр фильмов, запуск музыки);
  • Вход в Интернет и, следовательно, наличие установленных драйверов сети, стека TCP/IP и т.д.);
  • Присутствие графического режима и пользовательских скриптов;
  • Поддержка гигантских по объему дисков;
  • Хранение UEFI на разделах нового формата (GPT);
  • Полная поддержка всей аппаратуры с момента старта.


   UEFI может использовать универсальную исполняющую машину вроде JVM для использования аппаратно-независимого кода, а это открывает огромные горизонты для создания «загрузочного» ПО.

Отличие в процессе загрузки BIOS и UEFI. как это происходит и что за что отвечает.

   Существует и критика этой технологии. В частности, внедрение ее может привести к отсечению от рынка операционных систем новых игроков: для этого всегда найдется в коде какая-нибудь технологическая лазейка. Как, например, невозможность загружать ОС Windows 98 из современных BIOS. Но, что хуже, придется забыть о миллионах программ MS-DOS и других систем, которые опирались в своей работе на функции BIOS. Возможно, они еще будут эмулироваться, но в этом есть сомнения. А среди них наверняка есть и важные программы, которые некому будет переписывать. Впрочем, все это решаемые вопросы – хотя бы за счет виртуальных операционных систем. Но вот то, что появятся новые виды вирусов – это точно, и мы сможем увидеть это довольно скоро.

Напомним, что специалисты «Serty-Service» готовы помочь в настройке BIOS.

Введение

Пару месяцев назад я решил начать серию статей про написание своей ОС с нуля. Описал написание Legacy MBR загрузчика и переход в защищенный режим (без прерываний) и ещё пару мелочей. Сегодня я решил, что попытаюсь «перезапустить» эту серию (сохранив нумерацию частей). Суть в том, что теперь будут использоваться актуальные на август 2022 года материалы, и разработанное ПО можно будет легко протестировать на своей (U)EFI-машине.

Ассемблер — всему голова!

Не знаю как с этим у других людей, но лично я влюблен в ассемблер. При этом, переходил я на него, предварительно изучив Python, а не С или Фортран. В общем, программировать я буду всё ещё на ассемблере, конкретно — fasm, так как у него очень мощный препроцессор и компоновщик встроен в компилятор.

Загрузчики бывают разные…

Я назову 2 основных: это Legacy, или же MBR, и Secure, или же EFI-загрузчики (надеюсь, с терминами не напутал). В первой части я писал двух-ступенчатый загрузчик: MBR и фиксированно-расположенный SSB (Second-Stage Bootloader, загрузчик 2 стадии). В этот раз будем писать EFI-загрузчик.

Как загружаются UEFI-загрузчики?

Ну, примерно так: запускается биос (вроде с F000:FFFF или где-то там), производит POST (Power On Self Test), если все ОК, то запускает (U)EFI. UEFI в свою очередь ищет диски с известными файловыми системами (ntfs, brtfs, extFAT, FAT32, isofs, cdfs, ext2/3/4, есть ещё, но это уже опционально), ищет на них по адрессу /EFI/BOOT/ файл с расширениет *.efi , который называется bootX.efi где Х это платформа, для которой написан загрузчик. В целом, это всё. (ах, да, чуть не забыл: UEFI32 запускают bootia32.efi в защищенном режиме, а UEFI64 запускают bootx64.efi в долгом режиме).

Что вообще такое efi-приложение?

Технически, это тот же самый PE (Portable Excutable, формат исполняемых файлов для Microsoft Windows, *.exe, *.msi, *.dll, *.sys, *.mui), он даже разбирается с помощью IDA!

Приступим!

Для начала, просто exe-шник не подойдет. в строку с format нужно писать нечто подобное:

format PE64 DLL EFI

Потом, будет ну прям ОЧЕНЬ (на самом деле не ПРЯМ очень, но будет) сложно писать efi-app без библиотек. поэтому я набросал свою (листинг 1, 2, 3).

Листинг 1 (sysuefi.inc) — некоторые структуры, макросы и функции, без которых жизнь медом не покажется.

Листинг 2 (libuefi.inc) — некоторые полезные функции, начинаются с __, потому, что будут обернуты в Листинге 3

Листинг 3 (macroefi.inc) — обертка над Листингами 1 и 2, полностью из макросов и структур.

Также рекомендую использование такой вещи как struct.inc, гуляющей по всем интернетам. В библиотечках я её не использовал, но в будущем будет удобно свои структурки определять.

В итоге, получается вот-такая файловая система у нашего «проекта» (рис. 1)

рис. 1: Файловая система проекта
листинг 1

; sysuefi.inc for fasm assembly ;

struc int8 {
  align 1
  . db ?
}
struc int16 {
  align 2
  . dw ?
  align 1
}
struc int32 {
  align 4
  . dd ?
  align 1
}
struc int64 {
  align 8
  . dq ?
  align 1
}
struc intn {
  align 8
  . dq ?
  align 1
}
struc dptr {
  align 8
  . dq ?
  align 1
}
 
;symbols
 
EFIERR = 0x8000000000000000
EFI_SUCCESS			= 0
EFI_LOAD_ERROR			= EFIERR or 1
EFI_INVALID_PARAMETER		= EFIERR or 2
EFI_UNSUPPORTED 		= EFIERR or 3
EFI_BAD_BUFFER_SIZE		= EFIERR or 4
EFI_BUFFER_TOO_SMALL		= EFIERR or 5
EFI_NOT_READY			= EFIERR or 6
EFI_DEVICE_ERROR		= EFIERR or 7
EFI_WRITE_PROTECTED		= EFIERR or 8
EFI_OUT_OF_RESOURCES		= EFIERR or 9
EFI_VOLUME_CORRUPTED		= EFIERR or 10
EFI_VOLUME_FULL 		= EFIERR or 11
EFI_NO_MEDIA			= EFIERR or 12
EFI_MEDIA_CHANGED		= EFIERR or 13
EFI_NOT_FOUND			= EFIERR or 14
EFI_ACCESS_DENIED		= EFIERR or 15
EFI_NO_RESPONSE 		= EFIERR or 16
EFI_NO_MAPPING			= EFIERR or 17
EFI_TIMEOUT			= EFIERR or 18
EFI_NOT_STARTED 		= EFIERR or 19
EFI_ALREADY_STARTED		= EFIERR or 20
EFI_ABORTED			= EFIERR or 21
EFI_ICMP_ERROR			= EFIERR or 22
EFI_TFTP_ERROR			= EFIERR or 23
EFI_PROTOCOL_ERROR		= EFIERR or 24
 
macro structure name
{
  virtual at 0
    name name
  end virtual
}
 
;structureures
EFI_SYSTEM_TABLE_SIGNATURE	equ	49h,42h,49h,20h,53h,59h,53h,54h
struc EFI_TABLE_HEADER {
 .Signature    		int64
 .Revision     		int32
 .HeaderSize   		int32
 .CRC32        		int32
 .Reserved     		int32
}
structure EFI_TABLE_HEADER
 
struc EFI_SYSTEM_TABLE {
 .Hdr		        EFI_TABLE_HEADER
 .FirmwareVendor        dptr
 .FirmwareRevision      int32
 .ConsoleInHandle       dptr
 .ConIn 	        dptr
 .ConsoleOutHandle      dptr
 .ConOut	        dptr
 .StandardErrorHandle   dptr
 .StdErr	        dptr
 .RuntimeServices       dptr
 .BootServices	        dptr
 .NumberOfTableEntries  intn
 .ConfigurationTable    dptr
}
structure EFI_SYSTEM_TABLE
 
struc SIMPLE_TEXT_OUTPUT_INTERFACE {
 .Reset 	    	dptr
 .OutputString	    	dptr
 .TestString	    	dptr
 .QueryMode	    	dptr
 .SetMode	    	dptr
 .SetAttribute	    	dptr
 .ClearScreen	    	dptr
 .SetCursorPosition 	dptr
 .EnableCursor	    	dptr
 .Mode		    	dptr
}
structure SIMPLE_TEXT_OUTPUT_INTERFACE
 
;---include ends
 
struc SIMPLE_INPUT_INTERFACE {
 .Reset			dptr
 .ReadKeyStroke		dptr
 .WaitForKey		dptr
}
structure SIMPLE_INPUT_INTERFACE
 
struc EFI_BOOT_SERVICES_TABLE {
 .Hdr		       	EFI_TABLE_HEADER
 .RaisePriority		dptr
 .RestorePriority	dptr
 .AllocatePages		dptr
 .FreePages		dptr
 .GetMemoryMap		dptr
 .AllocatePool		dptr
 .FreePool		dptr
 .CreateEvent		dptr
 .SetTimer		dptr
 .WaitForEvent		dptr
 .SignalEvent		dptr
 .CloseEvent		dptr
 .CheckEvent		dptr
 .InstallProtocolInterface dptr
 .ReInstallProtocolInterface dptr
 .UnInstallProtocolInterface dptr
 .HandleProtocol	dptr
 .Void			dptr
 .RegisterProtocolNotify dptr
 .LocateHandle		dptr
 .LocateDevicePath	dptr
 .InstallConfigurationTable dptr
 .ImageLoad		dptr
 .ImageStart		dptr
 .Exit			dptr
 .ImageUnLoad		dptr
 .ExitBootServices	dptr
 .GetNextMonotonicCount	dptr
 .Stall			dptr
 .SetWatchdogTimer	dptr
 .ConnectController	dptr
 .DisConnectController	dptr
 .OpenProtocol		dptr
 .CloseProtocol		dptr
 .OpenProtocolInformation dptr
 .ProtocolsPerHandle	dptr
 .LocateHandleBuffer	dptr
 .LocateProtocol	dptr
 .InstallMultipleProtocolInterfaces dptr
 .UnInstallMultipleProtocolInterfaces dptr
 .CalculateCrc32	dptr
 .CopyMem		dptr
 .SetMem		dptr
}
structure EFI_BOOT_SERVICES_TABLE
 
struc EFI_RUNTIME_SERVICES_TABLE {
 .Hdr		       	EFI_TABLE_HEADER
 .GetTime		dptr
 .SetTime		dptr
 .GetWakeUpTime		dptr
 .SetWakeUpTime		dptr
 .SetVirtualAddressMap	dptr
 .ConvertPointer	dptr
 .GetVariable		dptr
 .GetNextVariableName	dptr
 .SetVariable		dptr
 .GetNextHighMonoCount	dptr
 .ResetSystem		dptr
}
structure EFI_RUNTIME_SERVICES_TABLE
 
struc EFI_TIME {
 .Year			int16
 .Month			int8
 .Day			int8
 .Hour			int8
 .Minute		int8
 .Second		int8
 .Pad1			int8
 .Nanosecond		int32
 .TimeZone		int16
 .Daylight		int8
 .Pad2			int8
 .sizeof		rb 1
}
structure EFI_TIME
 
EFI_LOADED_IMAGE_PROTOCOL_UUID equ 0A1h,31h,1bh,5bh,62h,95h,0d2h,11h,8Eh,3Fh,0h,0A0h,0C9h,69h,72h,3Bh
struc EFI_LOADED_IMAGE_PROTOCOL {
 .Revision		int32
 .ParentHandle		int64
 .SystemTable		dptr
 .DeviceHandle		int64
 .FilePath		dptr
 .Reserved		int64
 .LoadOptionsSize	int32
 .ImageBase		dptr
 .ImageSize		int64
 .ImageCodeType		int32
 .ImageDataType		int32
 .UnLoad		dptr
}
structure EFI_LOADED_IMAGE_PROTOCOL
 
EFI_BLOCK_IO_PROTOCOL_UUID equ 21h,5bh,4eh,96h,59h,64h,0d2h,11h,8eh,39h,00h,0a0h,0c9h,69h,72h,3bh
struc EFI_BLOCK_IO_PROTOCOL {
 .Revision		int64
 .Media			dptr
 .Reset			dptr
 .ReadBlocks		dptr
 .WriteBlocks		dptr
 .FlushBlocks		dptr
}
structure EFI_BLOCK_IO_PROTOCOL
 
struc EFI_BLOCK_IO_MEDIA {
 .MediaId		int32
 .RemovableMedia	int8
 .MediaPresent		int8
 .LogicalPartition	int8
 .ReadOnly		int8
 .WriteCaching		int8
 .BlockSize		int32
 .IoAlign		int32
 .LastBlock		int64
}
structure EFI_BLOCK_IO_MEDIA
 
EFI_GRAPHICS_OUTPUT_PROTOCOL_UUID equ 0deh, 0a9h, 42h,90h,0dch,023h,38h,04ah,96h,0fbh,7ah,0deh,0d0h,80h,51h,6ah
struc EFI_GRAPHICS_OUTPUT_PROTOCOL {
 .QueryMode		dptr
 .SetMode		dptr
 .Blt			dptr
 .Mode			dptr
}
structure EFI_GRAPHICS_OUTPUT_PROTOCOL
 
struc EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE {
 .MaxMode		int32
 .CurrentMode		int32
 .ModeInfo		dptr
 .SizeOfModeInfo	intn
 .FrameBufferBase	dptr
 .FrameBufferSize	intn
}
structure EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE
 
struc EFI_GRAPHICS_OUTPUT_MODE_INFORMATION {
 .Version		int32
 .HorizontalResolution	int32
 .VerticalResolution	int32
 .PixelFormat		int32
 .RedMask		int32
 .GreenMask		int32
 .BlueMask		int32
 .Reserved		int32
 .PixelsPerScanline	int32
}
structure EFI_GRAPHICS_OUTPUT_MODE_INFORMATION

macro InitializeLib
{
		clc
		or			rdx, rdx
		jz			.badout
		cmp			dword [rdx], 20494249h
		je			@f
.badout:	  xor			rcx, rcx
		xor			rdx, rdx
		stc
@@:		mov			[efi_handler], rcx
		mov			[efi_ptr], rdx
}

macro uefi_call_wrapper			interface,function,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10,arg11
{
numarg = 0
if ~ arg11 eq
 numarg = numarg + 1
 if ~ arg11 eq rdi
		mov			rdi, arg11
 end if
end if
if ~ arg10 eq
 numarg = numarg + 1
 if ~ arg10 eq rsi
		mov			rsi, arg10
 end if
end if
if ~ arg9 eq
 numarg = numarg + 1
 if ~ arg9 eq r14
		mov			r14, arg9
 end if
end if
if ~ arg8 eq
 numarg = numarg + 1
 if ~ arg8 eq r13
		mov			r13, arg8
 end if
end if
if ~ arg7 eq
 numarg = numarg + 1
 if ~ arg7 eq r12
		mov			r12, arg7
 end if
end if
if ~ arg6 eq
 numarg = numarg + 1
 if ~ arg6 eq r11
		mov			r11, arg6
 end if
end if
if ~ arg5 eq
 numarg = numarg + 1
 if ~ arg5 eq r10
		mov			r10, arg5
 end if
end if
if ~ arg4 eq
 numarg = numarg + 1
 if ~ arg4 eq r9
		mov			r9, arg4
 end if
end if
if ~ arg3 eq
 numarg = numarg + 1
 if ~ arg3 eq r8
		mov			r8, arg3
 end if
end if
if ~ arg2 eq
 numarg = numarg + 1
 if ~ arg2 eq rdx
		mov			rdx, arg2
 end if
end if
if ~ arg1 eq
 numarg = numarg + 1
 if ~ arg1 eq rcx
  if ~ arg1 in <ConsoleInHandle,ConIn,ConsoleOutHandle,ConOut,StandardErrorHandle,StdErr,RuntimeServices,BootServices>
		mov			rcx, arg1
  end if
 end if
end if
		xor			rax, rax
		mov			al, numarg
if interface in <ConsoleInHandle,ConIn,ConsoleOutHandle,ConOut,StandardErrorHandle,StdErr,RuntimeServices,BootServices>
		mov			rbx, [efi_ptr]
		mov			rbx, [rbx + EFI_SYSTEM_TABLE.#interface]
else
 if ~ interface eq rbx
		mov			rbx, interface
 end if
end if
if arg1 in <ConsoleInHandle,ConIn,ConsoleOutHandle,ConOut,StandardErrorHandle,StdErr,RuntimeServices,BootServices>
		mov			rcx, rbx
end if
if defined SIMPLE_INPUT_INTERFACE.#function
		mov			rbx, [rbx + SIMPLE_INPUT_INTERFACE.#function]
else
 if defined SIMPLE_TEXT_OUTPUT_INTERFACE.#function
		mov			rbx, [rbx + SIMPLE_TEXT_OUTPUT_INTERFACE.#function]
 else
  if defined EFI_BOOT_SERVICES_TABLE.#function
		mov			rbx, [rbx + EFI_BOOT_SERVICES_TABLE.#function]
  else
   if defined EFI_RUNTIME_SERVICES_TABLE.#function
		mov			rbx, [rbx + EFI_RUNTIME_SERVICES_TABLE.#function]
   else
    if defined EFI_GRAPHICS_OUTPUT_PROTOCOL.#function
		mov			rbx, [rbx + EFI_GRAPHICS_OUTPUT_PROTOCOL.#function]
    else
     if defined EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE.#function
		mov			rbx, [rbx + EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE.#function]
     else
		mov			rbx, [rbx + function]
     end if
    end if
   end if
  end if
 end if
end if
		call			uefifunc
}
 
section '.text' code executable readable
 
uefifunc:
		mov			qword [uefi_rsptmp], rsp
		and			esp, 0FFFFFFF0h
		bt			eax, 0
		jnc			@f
		push			rax
@@:		cmp			al, 11
		jb			@f
		push			rdi
@@:		cmp			al, 10
		jb			@f
		push			rsi
@@:		cmp			al, 9
		jb			@f
		push			r14
@@:		cmp			al, 8
		jb			@f
		push			r13
@@:		cmp			al, 7
		jb			@f
		push			r12
@@:		cmp			al, 6
		jb			@f
		push			r11
@@:		cmp			al, 5
		jb			@f
		push			r10
@@:
		sub			rsp, 4*8
		call			rbx
		mov			rsp, qword [uefi_rsptmp]
		ret
 
section '.data' data readable writeable
efi_handler:	dq			0
efi_ptr:	dq			0
uefi_rsptmp:	dq			0

листинг 2

пока что пуст, но по мере надобности начнет расти!

if ~ defined __EFICODE__
__EFICODE__ EQU 0

end if

листинг 3

if ~ defined __MACROEFI__
__MACROEFI__ EQU 0


;; --- constants --- ;;
true EQU 1
false EQU 0
null EQU 0
nl EQU 13,10
via EQU ,

;; --- structures --- ;;

struc sString [value] {
	common
	if ~ value eq 
	. du value, null
	else
	. du null
	end if
}

struc sStrbuf _len {
	if ~ len eq 
.len dq _len*2
.val rw _len
	else
.len dq 1024*2
.val rw 1024
	end if
}

struc sKey scan, utf {
	if ~ scan eq 
.scancode dw scan
	else
.scancode dw null
	end if
	if ~ utf eq 
.unicode du utf
	else
.unicode:
	end if
	du null
}

;; --- macros --- ;;

macro mEntry _ofs {
	format pe64 dll efi
	entry _ofs
}

macro mInit {
	InitializeLib
	jnc @f
	mExit EFI_SUCCESS
@@:
}

macro mPrint _str {
    if ~_str eq 
	uefi_call_wrapper ConOut, OutputString, ConOut, _str
	end if
}

macro mPrintln _str {
	if ~ _str eq 
	mPrint _str
	end if
	mPrint _crlf
}

macro mReturn [data] {
	if ~ data eq 
	forward
	push data
	end if
	common
	ret
}

macro mInvoke func, [arg] {
	if ~ arg eq 
	reverse
	push arg
	end if
	call func
}; vacuum example: mInvoke fSend via message

macro mEfidata {
	common
	mSect data
	__crlf sString nl
	__key_buf sKey null, null
}

macro mScankey _key {
	if ~ _key eq 
	mov [_key], dword 0
@@:
	uefi_call_wrapper ConIn, ReadKeyStroke, ConIn, _key
	cmp dword [_key], 0
	jz @b
	end if
}

macro mExit status {
	if status eq 
	mov eax, EFI_SUCCESS
	else
	mov eax, status
	end if
	retn
}

macro mSect name, type {
	if type eq data
	section '.#name' data readable writable
	else if type eq code
	section '.#name' code executable readable
	else if type eq text
	section '.#name' code executable readable
	else if type eq fixup
	section '.#name' fixups data discardable
	end if
}

end if

Традиции… Привет, мир!

Напишем helloworld для uefi используя мои листинги. Код получился до смешного коротким и интуитивно понятным. Имхо, под Windows консольную писалку создать сложнее!

; импортируем макросы
include "include/macroefi.inc"

; назначаем точку входа
mEntry main

; импортируем вторую часть библиотеки (обязательно после mEntry!)
include "include/sysuefi.inc"

; секция '.text' code executable readable
mSect text, code

; главная функция
main:
	  ; инициализируем библеотеку
    mInit
    ; печатаем хеловорлд
    mPrint hello
    ; аналог _getch из msvcrt.dll,
    ; ждем клавиши и сохраняем в key
    mScankey key
    ; возвращаемся в UEFI shell
    ; со статусом ОК
    mExit EFI_SUCCESS

; импортируем третью часть библиотеки (желательно посте основного кода)
include "include/libuefi.inc"

; секция '.bsdata' data readable writable
mSect bsdata, data

; utf-8 строка для хеловорлда
hello sString 'Hello UEFI World!'

; ячейка для клавиши
key sKey

; финальный макрос библиотеки. ВСЕГДА в конце всего кода
mEfidata

Как это собрать?

Я создал для этого простенький Makefile. напишите make build и найдете в рабочей папке файл bootx64.efi. Makefile (подсветка от перл потому, что от нее все подсветилось):

build:
	fasm BOOTX64.asm
image: build
	mkdir tmp
	mkdir tmp/efi
	mkdir tmp/efi/boot
	cp BOOTX64.efi tmp/efi/boot/BOOTX64.efi
	genisoimage -o ./image.iso -V BACKUP -R -J ./tmp
	rm tmp/efi/boot/*
	rmdir tmp/efi/boot tmp/efi tmp
	cls
dump: build
	hd BOOTX64.efi

Как это запустить?

Создаете FAT32 флешку или GPT раздел, кидаете в /efi/boot файл под названием bootx64.efi и перезагружаете ПК. При запуске откройте меню выбора загрузочного носителя и там найдите свой файл/флешку/раздел. Грузитесь с него и видите сообщение. Чтобы вернуться, нажмите любую клавишу.

Итоги

В течении статьи я изложил вам все преимущества Secure boot над Legacy, рассказал как все это работает и написал helloworld-загрузчик.

Поддержка

Всегда непротив конструктивной критики. Принимаю идеи по улучшению статьи и проекта в целом. Всегда буду рад полезным ссылкам. Спасибо за прочтение!

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Да или нет?

11.64% Скорее да, чем нет22

8.47% Скорее нет, чем да16

Проголосовали 189 пользователей. Воздержались 40 пользователей.

# Основная информация

(U)EFI или (Унифицированный) Расширяемый Интерфейс микропрограммного обеспечения — это спецификация для платформ x86, x86-64, ARM и Itanium, которая определяет программный интерфейс между операционной системой и микропрограммным обеспечением платформы/BIOS. Оригинальный EFI был разработан в середине 1990-х годов компанией Intel для разработки встроенного ПО/BIOS для платформ Itanium. В 2005 году Intel передала спецификацию новой рабочей группе под названием Unified EFI Forum, состоящей из таких компаний, как AMD, Microsoft, Apple и сама Intel. Все современные ПК поставляются с прошивкой UEFI, UEFI широко поддерживается как коммерческими, так и операционными системами с открытым исходным кодом. Обратная совместимость предусмотрена для устаревших операционных систем.

# Основы UEFI

# Загрузка образов UEFI

Если вы используете VirtualBox для виртуализации, то UEFI уже включен, нет необходимости загружать образ вручную. Вам просто нужно включить его в настройках виртуальной машины, нажав флажок «Настройки» / «Системы» / «Включить EFI (только специальные операционные системы)».

В противном случае для эмуляции и виртуальных машин вам понадобится образ прошивки OVMF.fd. Это может быть сложно найти, поэтому вот несколько альтернативных ссылок для загрузки:

  • TianoCore (opens new window)
  • OVMF-blobs (opens new window)
  • RPM packages (opens new window)
  • Debian packages (opens new window)
  • Gentoo packages (opens new window)
  • Arch packages (opens new window)

В Linux вы также можете установить их с помощью диспетчера пакетов вашего дистрибутива, например:

# Debian / Ubuntu

# RedHat / CentOS

# macOS

Используйте репозиторий OVMF-blobs.

# Windows

Используйте репозиторий OVMF-blobs или загрузите RPM-версию, затем с помощью 7-Zip распакуйте файл OVMF.fd из загруженного архива.

# UEFI против BIOS

Распространенным заблуждением является то, что UEFI является заменой BIOS. На самом деле, как устаревшие материнские платы, так и материнские платы на основе UEFI поставляются с ПЗУ BIOS, которые содержат встроенное ПО, которое выполняет начальную настройку системы при включении питания, прежде чем загружать какой-либо сторонний код в память и переходить к нему. Различия между устаревшей прошивкой BIOS и прошивкой UEFI BIOS заключаются в том, где они находят этот код, как они готовят систему перед переходом к ней и какие удобные функции они предоставляют для вызова кода во время работы.

# Инициализация платформы

BIOS выполняет всю инициализацию платформы (конфигурация контроллера памяти, конфигурация шины PCI и BAR-mapping, инициализация видеокарты и т.д.), но затем переходит в обратно совместимую среду Real Mode. Загрузчик должен включить A20-gate, настроить GDT и IDT, переключиться в Protected Mode, а для процессоров x86-64 настроить подкачку и переключиться в Long Mode.

Прошивка UEFI выполняет те же шаги, но также подготавливает среду Protected Mode с плоской сегментацией, а для процессоров x86-64-среду Long mode с отображением идентификаторов подкачки. A20-gate также включён.

Кроме того, процедура инициализации платформы прошивки UEFI стандартизирована. Это позволяет распространять прошивку UEFI независимо от поставщика платы.

# Механизм загрузки

BIOS загружает 512-байтовый двоичный объект из загрузочного устройства типа MBR(Master Boot Record) в память по физическому адресу 7C00 и переходит к нему. Загрузчик не может вернуться обратно в BIOS. Прошивка UEFI загружает приложение UEFI произвольного размера (исполняемый файл PE) из раздела FAT на загрузочном устройстве с разделом GPT на некоторый адрес, выбранный во время выполнения. Затем он вызывает основную точку входа этого приложения. Приложение может вернуть управление встроенному ПО, которое продолжит поиск другого загрузочного устройства или вызовет меню диагностики.

# Обнаружение системы

BIOS сканирует память на наличие таких структур, как таблицы EBDA, SMBIOS и ACPI. Он использует PIO для связи с корневым контроллером PCI и сканирования шины PCI. Возможно, что в памяти могут присутствовать избыточные таблицы (например, таблица MP в SMBIOS содержит информацию, которая также присутствует в DSDT ACPI), и загрузчик может выбрать, какие из них использовать.

Когда UEFI вызывает функцию точки входа UEFI-приложения, она передает структуру «Системной таблицы», которая содержит указатели на все таблицы ACPI системы, карту памяти и другую информацию, относящуюся к ОС. Устаревшие таблицы (например, SMBIOS) могут отсутствовать в памяти.

# Удобные функции

BIOS подключает различные прерывания, которые загрузчик может запускать для доступа к системным ресурсам, таким как диски и экран. Эти прерывания не стандартизированы, за исключением исторических условностей. Каждое прерывание использует другое для передачи регистра.

UEFI устанавливает в памяти множество вызываемых функций, которые группируются в наборы, называемые «протоколами», и которые можно обнаружить через системную таблицу. Поведение каждой функции в каждом протоколе определяется спецификацией. Приложения UEFI могут определять свои собственные протоколы и сохранять их в памяти для использования другими UEFI-приложениями. Функции вызываются с помощью стандартизированного, современного стандарта системных вызовов, поддерживаемого многими компиляторами языка C.

# Среда разработки

Устаревшие загрузчики могут быть разработаны в любой среде, которая может генерировать двоичные образы: NASM, GCC и т.д. Приложения UEFI могут быть разработаны на любом языке, который может быть скомпилирован и связан в исполняемый файл PE и поддерживает соответствующие вызовы, используемые для доступа к функциям, установленным в памяти прошивкой UEFI. На практике это означает одну из двух сред разработки: Intel TianoCore EDK2, GNU-EFI или POSIX-UEFI.

TianoCore — это большая, сложная среда с собственной системой сборки. Его можно настроить для использования вместе с GCC, MinGW, Microsoft Visual C++ и т.д. Его можно использовать не только для компиляции UEFI-приложений, но и для компиляции прошивки UEFI, которая будет перенесена в ПЗУ BIOS.

GNU-EFI — это набор библиотек и заголовков для компиляции приложений UEFI с собственным GCC системы (не работает с LLVM CLang). Он не может быть использован для компиляции прошивки UEFI. Поскольку это всего лишь пара библиотек, с которыми можно связать UEFI-приложение, его гораздо проще использовать, чем TianoCore.

POSIX-UEFI очень похож на GNU-EFI, но он распространяется в основном как исходный код, а не как двоичная библиотека, имеет имена, подобные ANSI C, и работает с GCC, а также с LLVM CLang. Он поставляется с файлом Makefile.

# Эмуляция

Bochs поставляется с BIOS с открытым исходным кодом по умолчанию. Кроме того, SeaBIOS, популярный BIOS, который был портирован как на эмулированные машины Bochs, так и на машины с эмуляцией QEMU. Оба этих BIOSs реализуют большинство функций BIOS, которые можно было бы ожидать. Тем не менее, они довольно значительно отличаются по эксплуатации от коммерческих BIOS на реальных машинах.

OVMF, популярная прошивка UEFI с открытым исходным кодом, была перенесена на эмулируемую машину QEMU (но не Bochs). Поскольку он реализует спецификацию UEFI, он ведет себя очень похоже на коммерческую прошивку UEFI на реальных машинах. (Сам OVMF построен с помощью TianoCore, но доступны готовые образы.)

# Загрузчик BIOS или приложение для UEFI?

Если вы ориентируетесь на устаревшие системы, для которых UEFI недоступен или ненадежен, вам следует разработать загрузчик для BIOS. Это требует глубокого знания 16-битной адресации и функций обратной совместимости процессора x86 или x86-64. Если вы ориентируетесь на современные системы, вам следует разработать UEFI-приложение. Многие прошивки UEFI могут быть сконфигурированы для эмуляции BIOS, но среди этих эмулируемых сред существует еще больше различий, чем среди реальных BIOS.

# UEFI 0-3 класса и CSM

ПК классифицируются как класс UEFI 0, 1, 2 или 3. Машина класса 0-это устаревшая система с BIOS, т.е. Вообще не система UEFI.

Машина класса 1 — это система с UEFI, которая работает исключительно в режиме модуля поддержки совместимости (CSM). CSM — это спецификация того, как прошивка UEFI может эмулировать устаревший BIOS. Прошивка UEFI в режиме CSM загружает Legacy-загрузчики. Система UEFI класса 1 может вообще не декларировать поддержку UEFI, поскольку она не доступна загрузчику. Это только UEFI «внутри» BIOS.

Машина класса 2 — это система UEFI, которая может запускать UEFI-приложения, но также включает в себя возможность запуска в режиме CSM. Большинство современных ПК — это машины класса UEFI 2. Иногда выбор для запуска UEFI-приложений против CSM — это тот или иной параметр в конфигурации BIOS, и в других случаях BIOS решит, какой из них использовать, после выбора загрузочного устройства и проверки того, у него Legacy-загрузчик или UEFI-приложение.

Машина класса 3 — это система UEFI, которая не поддерживает CSM. Машины класса 3 UEFI запускают только UEFI-приложения и не реализуют CSM для обратной совместимости с Legacy-загрузчиками.

# Безопасная загрузка (Secure Boot)

Безопасная загрузка — это схема цифровой подписи для приложений UEFI, состоящая из четырех компонентов:

  • PK: Ключ платформы
  • KEK: Ключ для обмена ключей
  • db: Белый список
  • dbx: Чёрный список

Прошивка UEFI, поддерживающая безопасную загрузку, всегда находится в одном из трех состояний:

  • Setup mode, Secure Boot off
  • User mode, Secure Boot off
  • User mode, Secure Boot on

В режиме настройки любое приложение UEFI может изменять или удалять PK, добавлять/удалять ключи из KEK, а также добавлять/удалять записи белого списка или черного списка из db или dbx.

В пользовательском режиме, независимо от того, включена или выключена Безопасная загрузка:

  • PK может быть изменен или удален только приложением UEFI, у которого уже есть текущий PK.
  • Ключи могут быть добавлены/удалены из KEK только приложением UEFI, имеющим PK.
  • Записи белого списка и черного списка могут быть добавлены/удалены из db и dbx только приложением UEFI, у которого есть любой из ключей в KEK.

Наконец, в пользовательском режиме с включенной безопасной загрузкой приложения UEFI должны соответствовать одному из следующих четырех требований для запуска:

  • Подписано, с подписью в db, а не в dbx
  • Подписано ключом в db, а не в dbx
  • Подписано ключом в КЕК
  • Не подписано, но хэш приложения находится в db, а не в dbx

Обратите внимание, что приложения UEFI не подписываются PK, если только PK также не находится в KEK.

Не все прошивки UEFI поддерживают безопасную загрузку, хотя это является обязательным требованием для Windows 8. Некоторые прошивки UEFI поддерживают безопасную загрузку, и нет возможности отключить их, что создает проблему для независимых разработчиков, которые не имеют доступа к PK или любому из ключей в KEK и, следовательно, не могут установить свой собственный ключ или подпись приложения или хэш в базу данных белого списка. Независимые разработчики должны разрабатывать используя системы, которые либо не поддерживают безопасную загрузку, либо имеют возможность отключить безопасную загрузку.

# Как использовать UEFI

Традиционные операционные системы, такие как Windows и Linux, имеют существующую программную архитектуру и большую базу кода для выполнения конфигурации системы и обнаружения устройств. С их сложными уровнями абстракции они не получают прямой выгоды от UEFI. В результате их загрузчики UEFI мало что делают, кроме подготовки среды для их запуска.

Независимый разработчик может найти больше пользы в использовании UEFI для написания полнофункциональных приложений UEFI, а не в том, чтобы рассматривать UEFI как временную среду запуска, которую можно выбросить во время процесса загрузки. В отличие от устаревших загрузчиков, которые обычно взаимодействуют с BIOS только для запуска ОС, приложение UEFI может реализовать сложное поведение с помощью UEFI. Другими словами, независимый разработчик не должен спешить покидать «UEFI-land».

Хорошей отправной точкой является написание приложения UEFI, которое использует системную таблицу для извлечения карты памяти и использует протокол «File» для чтения файлов с дисков в формате FAT. Следующим шагом может быть использование системной таблицы для поиска таблиц ACPI.

# Разработка с POSIX-UEFI

Простой способ компиляции приложений EFI в Linux (или любой другой системе, совместимой с POSIX) — это POSIX-UEFI. Он не только предоставляет хорошо известный libc-подобный API для вашего приложения EFI, но и генерирует Makefile, который поможет обнаружить и настроить набор инструментов для вас. Работает как с GNU gcc, так и с LLVM CLang.

Он имеет POSIX-измененные типы (например, uintn_t вместо UINTN), и ему не нужны стандартные заголовки EFI. Но если вы установите их из EDK2 или GNU-EFI, вы также сможете безопасно включить их, конфликтов имен не будет. Тем не менее, эти интерфейсы правильно определены, и все поля имеют точно такое же имя, как и в EDK2, так что это большое преимущество перед GNU-EFI.

Типичный «Hello World» на UEFI выглядит примерно так:

А Makefile выглядит вот так:

Теперь просто запусти сборку командой make и на выходе получишь файл main.efi

# Разработка с GNU-EFI

GNU-EFI можно использовать для разработки как 32-разрядных, так и 64-разрядных приложений UEFI. В этом разделе будут рассмотрены только 64-разрядные приложения UEFI и предполагается, что сама среда разработки работает в системе x86_64, поэтому кросс-компилятор не требуется.

GNU-EFI включает в себя следующие вещи:

  • crt0-efi-x86_64.o: CRT0 (код инициализации среды выполнения C), обеспечивающий точку входа, которую микропрограмма UEFI вызовет при запуске приложения, которое, в свою очередь, вызовет функцию «efi_main», записанную разработчиком.
  • libgnuefi.a: Библиотека, содержащая одну функцию (_relocate), которая используется CRT0.
  • elf_x86_64_efi.lds: Скрипт компоновщика, используемый для связывания двоичных файлов ELF в приложения UEFI.
  • efi.h и другие заголовки: Удобные заголовки, которые предоставляют структуры, типы и константы, улучшают читаемость при доступе к системной таблице и другим ресурсам UEFI.
  • libefi.a: Библиотека, содержащая удобные функции, такие как вычисление CRC, вычисление длины строки и простая печать текста.
  • efilib.h: Заголовок для libefi.a.

Как минимум, 64-разрядное приложение UEFI должно будет связываться с crt0-efi-x86_64.o и libgnuefi.a с помощью скрипта компоновщика elf_x86_64_efi.lds. Скорее всего, вы захотите также использовать предоставленные заголовки и библиотеки удобства, и в этом разделе предполагается, что и в дальнейшем.

Типичный «Hello World» на UEFI выглядит примерно так:

Несколько заметок:

  • efi.h включен, поэтому мы можем использовать такие типы, как EFI_STATUS, EFI_HANDLE и EFI_SYSTEM_TABLE.
  • При создании 32-разрядного приложения UEFI EFIAPI пуст; GCC скомпилирует функцию «efi_main», используя стандартные вызовы C. При создании 64-разрядного приложения UEFI EFIAPI расширяется до «attribute((ms_abi))», и GCC скомпилирует функцию «efi_main», используя стандарт о вызовах Microsoft x64, как указано в UEFI. Только функции, которые будут вызываться непосредственно из UEFI (включая main, но также и обратные вызовы), должны использовать стандарт о вызовах UEFI.
  • «InitializeLib» и «Print» — это удобные функции, предоставляемые libefi.a с прототипами в efilib.h. «InitializeLib» позволяет libefi.a хранить ссылку на ImageHandle и SystemTable, предоставляемые BIOS. «Print» использует эти сохраненные ссылки для вывода строки, обращаясь к функциям, предоставляемым UEFI в памяти. (Позже мы увидим, как найти и вызвать функции, предоставляемые UEFI, вручную.)

Программа скомпилированна и слинкована как показано ниже:

В результате вы получите файл main.efi, которые будет весить 44 КБ.

# Эмуляция с QEMU и OVMF

Любой последней версии QEMU с последней версией OVMF будет достаточно для запуска приложения UEFI. Исполняемые файлы QEMU доступны для многих платформ, а образ OVMF (OVMF.fd) можно найти на веб-сайте TianoCore. QEMU (без загрузочного диска) можно вызвать, как показано ниже. Чтобы предотвратить попытку загрузки по PXE (сети) в последних версиях QEMU при отсутствии загрузочного диска, используйте -net none.

Рекомендуется использовать OVMF (для QEMU 1.6 или новее) с параметром pflash. В приведенных ниже инструкциях предполагается, что у вас есть образ OVMF, разделённый на отдельные разделы CODE и VARS.

Если вы предпочитаете работать через терминал или через SSH/telnet, вы можете запустить QEMU без графической поддержки, используя флаг -nographic.

Если OVMF не найдет загрузочный диск с правильно названным приложением UEFI (подробнее об этом позже), он попадет в оболочку UEFI.

Вы можете просмотреть список доступных команд с помощью команды help.

# Создание образа диска

Чтобы запустить приложение UEFI, вам нужно будет создать образ диска и представить его в QEMU. Прошивка UEFI ожидает, что приложения UEFI будут храниться в файловой системе FAT12, FAT16 или FAT32 (называемой системным разделом EFI) на диске с разделением GPT. Многие прошивки поддерживают только FAT32, так что это то, что вы захотите использовать. В зависимости от вашей платформы существует несколько различных способов создания образа диска, содержащего ваше приложение UEFI, но все они начинаются с создания обнуленного файла образа диска. Минимальный размер раздела FAT32 составляет 33 548 800 байт, плюс вам понадобится место для первичной и вторичной таблиц GPT, а также некоторое свободное пространство, чтобы раздел можно было правильно выровнять. В этих примерах мы создадим образ диска размером 48 000 000 байт (93750 512-байтовых секторов или 48 МБ).

# Приложение uefi-run

Приложение uefi-run полезно для быстрого тестирования. Оно создает временный образ FAT, содержащий ваше приложение EFI, и запускает qemu.

uefi-run в настоящее время не собран для какого-либо дистрибутива. Вы можете установить его с помощью cargo (менеджер пакетов Rust) («cargo install uefi-run»).

# Linux, необходим root-доступ

Этот подход требует root-доступ и использует gdisk, losetup и mkdosfs.

Во-первых, используйте gdisk для создания таблицы разделов GPT с одним системным разделом EFI.

Теперь у вас есть образ диска с таблицей разделов GUID и неформатированный раздел EFI, начиная с сектора 2048. Если вы не отклонились от команд, показанных выше, образ диска будет использовать 512-байтовые секторы, поэтому раздел EFI начинается с байта 1 048 576 и имеет длину 46 934 528 байт.

Используйте losetup для представления раздела в Linux.

(Если /dev/loop0 уже используется, вам нужно будет выбрать другое loopback-устройство.)

Отформатируйте раздел в FAT32 с помощью mkdosfs.

Теперь раздел можно смонтировать, чтобы мы могли копировать в него файлы. В этом примере мы используем каталог «/mnt», но вы также можете создать локальный каталог для временного использования.

Скопируйте все приложения UEFI, которые вы хотите протестировать, в файловую систему.

Наконец, размонтируйте раздел и освободите loopback-устройство.

uefi.img теперь представляет собой образ диска, содержащий первичные и вторичные таблицы GPT, содержащие один раздел типа EFI, содержащий файловую систему FAT32, содержащую одно или несколько приложений UEFI.

# Linux, без root-доступа

Этот подход использует parted, mformat, mcopy и может выполняться с правами пользователя.

Во-первых, используйте parted для создания первичных и вторичных заголовков GPT, а также одного раздела EFI, охватывающего тот же диапазон, что и описанный выше подход.

Теперь создайте новый временный файл образа, который будет содержать данные раздела EFI, и используйте mformat для форматирования его с помощью FAT16.

Используйте mcopy для копирования любых приложений UEFI, которые вы хотите протестировать, в файловую систему.

Наконец, запишите образ раздела в образ основного диска.

uefi.img теперь представляет собой образ диска, содержащий первичные и вторичные таблицы GPT, содержащие один раздел типа EFI, содержащий файловую систему FAT16, содержащую одно или несколько приложений UEFI.

# FreeBSD, требуется root-доступ

Этот подход требует привилегий root и использует mdconfig, gpart, newfs_msdos и mount_msdosfs.

Сначала создайте узел устройства, который представляет обнуленный образ диска в виде блочного устройства. Это позволит нам работать над ним, используя стандартные инструменты разделения и форматирования.

В этом примере новым блочным устройством является md0. Теперь создайте пустые первичные и вторичные таблицы GPT на устройстве.

Теперь мы можем добавить раздел на диск. Мы укажем раздел «EFI», что просто означает, что GPT установит GUID этого раздела для специального типа «EFI». Не все BIOS требуют этого, и раздел по-прежнему можно будет монтировать и просматривать в обычном режиме в Linux, FreeBSD и Windows.

Затем создайте файловую систему FAT16 на новом разделе. Вы можете указать различные параметры для файловой системы, если хотите, но это не обязательно. В идеале вы бы создали раздел FAT32 для лучшей совместимости прошивки, но FreeBSD, похоже, создает разделы FAT32, которые OVMF не может прочитать.

Теперь раздел можно смонтировать, чтобы мы могли копировать в него файлы. В этом примере мы используем каталог /mnt, но вы также можете создать локальный каталог для временного использования.

Скопируйте все приложения UEFI, которые вы хотите протестировать, в файловую систему.

Наконец, размонтируйте раздел и освободите устройство.

uefi.img теперь представляет собой образ диска, содержащий первичные и вторичные таблицы GPT, содержащие один раздел типа EFI, содержащий файловую систему FAT16, содержащую одно или несколько приложений UEFI.

# macOS, не требуется root-доступ

В Mac OS есть один инструмент (hdiutil), который одновременно создает образ диска и копирует файлы.

Допустим, вы создаете UEFI для x86_64. По определению имя файла должно быть BOOTX64.EFI и этот файл должны находиться в папке /EFI/BOOT.

Во-первых, давайте создадим временную папку, которая будет содержать все файлы и папки, необходимые для загрузки UEFI.

Во-вторых, давайте скопируем приложение в нужную директорию:

Наконец, давайте создадим образ диска, разделенный GPT, отформатированный с помощью fat32 (-fs fat32), при необходимости переопределим файл назначения (-ov), определим размер диска (-размер 48m), имя тома (-volname NEWOS), формат файла, в котором будет закодирован диск (-формат UDTO — тот же, что используется для DVD/CD), и исходную папку, содержащую файлы, которые будут скопированы на новый диск:

uefi.cdr готов к использованию в QEMU.

# Запуск UEFI-приложений

Как только ваш образ диска будет готов, вы можете вызвать QEMU, как показано ниже.

Когда OVMF попадет в оболочку UEFI, вы увидите дополнительную запись в «Mapping table» с пометкой «FS0». Это указывает на то, что прошивка обнаружила диск, обнаружила раздел и смогла смонтировать файловую систему. Вы можете изучить файловую систему, переключившись на нее с помощью синтаксиса в стиле DOS «FS0:», как показано ниже.

Вы можете запустить приложение UEFI, введя его имя.

Обратите внимание, что оболочка UEFI возобновилась после завершения работы приложения. Конечно, если бы это был правильный загрузчик, он никогда бы не возобновился, а скорее запустил ОС.

Некоторые коммерческие прошивки UEFI предоставляют оболочки UEFI или возможность запуска выбранных пользователем приложений UEFI, таких как прошивка, поставляемая с линейкой ноутбуков HP EliteBook. Однако большинство из них не предоставляют эту функциональность конечному пользователю.

# Отладка

OVMF может быть построен в режиме отладки, и он будет выводить сообщения журнала на порт ввода-вывода 0x402. Вы можете использовать некоторые флаги, подобные приведенным ниже, для захвата выходных данных.

Обратите внимание, что релизные сборки не будут выводить отладочные сообщения или будут иметь уменьшенный вывод.

# Запуск на реальном железе

# NVRAM переменные

Прошивка UEFI представит большинство своих параметров конфигурации через текстовое или графическое меню конфигурации, как и BIOS. Выбор, сделанный в этих меню, сохраняется в чипе NVRAM между перезагрузками. Однако, в отличие от BIOS, разработчик прошивки имеет возможность предоставить некоторые или все эти «переменные NVRAM» операционной системе и конечному пользователю с помощью удобных функций, размещенных в оперативной памяти прошивкой при загрузке.

Модуль ядра Linux efivarfs будет использовать эти функции для перечисления переменных NVRAM в файле /sys/firmware/efi/efivars. Переменные NVRAM также могут быть сброшены из самой оболочки UEFI с помощью команды dmpstore. Порядок загрузки устройства всегда доступен через переменные NVRAM.

# Загружаемые UEFI-приложения

Переменные NVRAM порядка загрузки определяют, где прошивка будет искать приложения UEFI, которые будут запущены при загрузке. Хотя это можно изменить (например, установщик ОС может настроить загрузочную запись для жесткого диска, на который она была установлена), прошивка обычно ищет приложение UEFI с именем «BOOT.efi» (для 32-разрядных приложений) или «BOOTX64.efi» (для 64-разрядных приложений), хранящееся в пути «/EFI/BOOT» в файловой системе загрузочного устройства. Это путь и имя по умолчанию для OVMF.

В отличие от приложения UEFI, запущенного из оболочки, если загрузочное приложение UEFI возвращает в BIOS, оно продолжит поиск других загрузочных устройств.

# Открытая функциональность

Реальные ПК различаются по объему возможностей UEFI, которые они предоставляют пользователю. Например, даже машина класса 3 может не упоминать UEFI в своей конфигурации BIOS и не предлагать оболочку UEFI. Кроме того, некоторые поставщики BIOS делают свои экраны конфигурации прошивки UEFI идентичными экранам конфигурации BIOS. Машины класса 2 могут представлять несколько запутанные меню загрузки и параметры конфигурации. Например, один производитель ноутбуков включает параметр конфигурации для включения/отключения UEFI (т.е. Переключения между поведением UEFI и CSM) под названием «OS: Windows 8». Другой ноутбук, если ему не удастся найти загрузочное приложение UEFI на выбранном загрузочном устройстве (или если это приложение вернет состояние, отличное от EFI_SUCCESS), вернется к поведению CSM, а затем пожалуется, что на диске поврежден MBR.

Чтобы упростить тестирование на реальном оборудовании, вы можете установить загрузочное приложение UEFI на внутренний жесткий диск системы, которое предоставляет меню загрузки, например rEFInd. Это также может быть удобно для сценариев с несколькими загрузками.

# Разработчики прошивки для ПК

На платформах x86 и x86-64 следующие разработчики BIOS предлагают прошивку UEFI:

  • AMI (Aptio).
  • Phoenix (SecureCore, TrustedCore, AwardCore).
  • Insyde (InsydeH20).

# Системы Apple

Системы Apple реализуют EFI 1.0, в отличие от UEFI, с тем отличием, что приложения UEFI загружаются из файловых систем HFS+ вместо FAT12/16/32. Кроме того, эти приложения UEFI должны быть «подписаны» (либо непосредственно, либо путем нахождения в подписанном каталоге) для загрузки. Blessing устанавливает флаги в файловой системе HFS+, которые проверяет прошивка Apple перед загрузкой приложения. Пакет hfsutils с открытым исходным кодом включает поддержку файлов в файловых системах HFS, но не каталогов и не HFS+.

# UEFI-приложения в деталях

# Бинарный формат

Исполняемые файлы UEFI-это обычные образы PE32 / PE32+ (Windows x32 / x64) с определенной подсистемой. Каждое приложение UEFI в основном представляет собой исполняемый файл Windows (или DLL) без таблиц символов.

Типы UEFI-образов

Тип Описание Подсистема
Приложения Загрузчики ОС и другие утилиты. 10
Драйвер службы загрузки Драйверы, используемые встроенным ПО при загрузке (например, драйверы дисков, сетевые драйверы). 11
Драйвер среды выполнения Драйверы, которые могут оставаться загруженными даже после загрузки ОС и выхода из службы загрузки. 11

Образы UEFI также должны указывать тип машинного кода, который они содержат. Загрузчик UEFI откажется загружать несовместимый образ.

Типы машин

Название Значение
x86 0x014c
x86_64 0x8664
Itanium x64 0x0200
UEFI Byte Code 0x0EBC
ARM 0x01C2
AArch (ARM x64) 0xAA64
RISC-V x32 0x5032
RISC-V x64 0x5064
RISC-V x128 0x5128

ARM означает, что вы можете использовать инструкции Thumb/Thumb 2, но интерфейсы UEFI находятся в режиме ARM.

# Инициализация

Приложения должны либо загрузить ОС и выйти из служб загрузки, либо вернуться из основной функции (в этом случае загрузчик будет искать следующее загружаемое приложение).

Драйверы должны инициализироваться, а затем возвращать 0 при успешном выполнении или код ошибки. Компьютер может не загрузиться, если не загрузится необходимый драйвер.

# Память

Карта памяти, возвращаемая UEFI, будет отмечать области памяти, используемые драйверами.

После завершения загрузки ОС ядру разрешается повторно использовать память, в которую был загружен загрузчик.

Типы памяти — Efi{Loader/BootServices/RuntimeServices}{Code/Data}.

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

Однако память, используемая драйверами среды выполнения, никогда не должна быть затронута — драйверы среды выполнения остаются активными и загруженными до тех пор, пока работает компьютер.

Один из способов увидеть разбивку PE-файла, содержащего приложение UEFI, — это

Его выход довольно длинный. Среди прочего, он показывает подсистему, то есть тип образа UEFI, упомянутый ранее.

# Соглашение о вызовах

UEFI определяет следующие соглашения о вызовах:

  • cdecl для x86 UEFI-функций
  • Microsoft’s 64-bit calling convention для x86-64 UEFI-функций
  • SMC для ARM UEFI-функций

Это оказывает два влияния на разработчиков приложений UEFI:

  • Основная точка входа приложения UEFI должна ожидать вызова с соответствующим соглашением о вызове.
  • Любые функции, предоставляемые UEFI, которые вызывает приложение UEFI, должны вызываться с соответствующим соглашением о вызовах.

Обратите внимание, что функции, строго внутренние для приложения, могут использовать любое соглашение о вызовах, которое выберет разработчик.

# POSIX-UEFI, GNU-EFI and GCC

cdecl — это стандартное соглашение о вызовах, используемое GCC, поэтому для записи основной точки входа или вызова функций UEFI в приложении UEFI x86, разработанном с использованием GNU-EFI, не требуется никаких специальных атрибутов или модификаторов. Однако для x86-64 функция точки входа должна быть объявлена с модификатором «attribute((ms_abi))», и все вызовы функций, предоставляемых UEFI, должны выполняться через функцию «uefi_call_wrapper». Этот преобразователь вызывается с помощью cdecl, но затем преобразуется в соглашение о вызове Microsoft x86-64 перед вызовом запрошенной функции UEFI. Это необходимо, поскольку более старые версии GCC не поддерживают указание соглашений о вызовах для указателей функций.

Для POSIX-UEFI, который также использует GCC, ваша точка входа выглядит как стандартная main(), и никакого специального ABI не требуется. Кроме того, среда сборки заботится о флагах компилятора для вас, поэтому вы можете просто вызывать функции UEFI без «uefi_call_wrapper», независимо от того, используете ли вы gcc или другой кросс-компилятор.

Для удобства разработчиков как POSIX-UEFI, так и GNU-EFI предоставляют макрос «EFIAPI», который расширяется до «cdecl» при таргетинге на x86 и «attribute(ms_abi))» при таргетинге на x86-64. Кроме того, функция «uefi_call_wrapper» просто передаст вызов на x86. Это позволяет использовать один и тот же исходный код для x86 и x86-64. Например, следующая основная функция будет компилироваться с правильным соглашением о вызове как на x86, так и на x86-64, и вызов через функцию «uefi_call_wrapper» выберет правильное соглашение о вызове для использования при вызове функции UEFI (в данном случае вывод строки).

# Биндинги языка

Приложения UEFI обычно пишутся на языке C, хотя биндинги могут быть написаны для любого другого языка, который компилируется в машинный код. Assembler также является опцией; для FASM доступен файл uefi.inc, который позволяет писать приложения UEFI, как показано ниже.

Поскольку приложение UEFI содержит обычный машинный код x86 или x86-64, inline assembly также является опцией в компиляторах, которые ее поддерживают.

# EFI байткод

UEFI также включает спецификацию виртуальной машины, основанную на формате байтового кода, называемом EFI Byte Code (EBC), который может использоваться для написания независимых от платформы драйверов устройств, но не приложений UEFI. По состоянию на 2015 год использование EBC было ограниченным.

# Основные проблемы

# Мое приложение UEFI зависает/сбрасывается примерно через 5 минут

Когда управление передается вашему приложению UEFI с помощью встроенного ПО, оно устанавливает таймер на 5 минут, после чего встроенное ПО повторно активируется, поскольку предполагается, что ваше приложение зависло. Прошивка в этом случае обычно пытается сбросить систему (хотя прошивка OVMF в VirtualBox просто приводит к тому, что экран становится черным и зависает). Чтобы противодействовать этому, вам необходимо обновить таймер до истечения времени ожидания. Кроме того, вы можете полностью отключить его с помощью такого кода, как

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

# Мой загрузчик зависает, если я использую определенные пользователем значения EFI_MEMORY_TYPE

Для функций управления памятью в EFI ОС должна иметься возможность использовать значения «тип памяти» выше 0x80000000 для своих собственных целей. В выпуске прошивки OVFM EFI «r11337» (для Qemu и т.д.) Есть ошибка, при которой прошивка предполагает, что тип памяти находится в диапазоне значений, определенных для собственного использования EFI, и использует тип памяти в качестве индекса массива. Конечным результатом является ошибка «array index out of bounds»»; где более высокие значения типа памяти (например, разрешённые значения выше 0x80000000) приводят к сбою 64-разрядной версии прошивки (page fault) и приводят к тому, что 32-разрядная версия прошивки сообщает о неправильных значениях «attribute». Эта же ошибка также присутствует в любой версии прошивки EFI, используемой VirtualBox (которая выглядит как более старая версия OVFM); и я подозреваю (но не знаю), что ошибка может присутствовать в самых разнообразных прошивках, которые были получены из проекта TianoCore (а не только OVFM).

# Внешние ссылки

  • UEFI Specifications (opens new window)
  • Intel TianoCore EDK2 (opens new window)
  • OVMF firmware images (opens new window)
  • Phoenix UEFI Wiki (opens new window)
  • Sereval articles about UEFI (opens new window)
  • PE specification covering the (U)EFI binary format (opens new window)
  • Blog about UEFI, with bits about UEFI development (opens new window)
  • Presentation guiding through simple UEFI application setup (opens new window)
  • Presentation giving an overview of windows uefi booting (opens new window)
  • POSIX-UEFI (opens new window)
  • Wikipedia Article on EFI (opens new window)

EFI (Extensible Firmware Interface) — интерфейс по централизации оборудования в момент включения системы. Регулирует процессы, происходящие между операционной системой и микропрограммами, осуществляющими управление низкоуровневыми функциями оборудования. EFI загружает компьютер, а впоследствии передает управление загрузчику операционной системы. Является логической заменой интерфейса BIOS, традиционно исользующегося IBM PC-совместимыми компьютерами. 

Компания Intel разработала первую спецификацию EFI. Позднее, интерфейс поменял название: последняя версия стандарта именуется UEFI (Unified Extensible Firmware Interface). На сегодняшний день, стандарт UEFI разрабатывается ассоциацией Unified EFI Forum.

Стандарт EFI имеет поддержку графического меню, а также некоторых дополнительных возможностей (к примеру, Aptio или Great Wall UEFI).

История

Первоначально, стандарт EFI предназначался для использования в первых системах Intel-HP Itanium, появившихся в середине 90-х годов. Те ограниченные возможности, которые демонстрировал PC-BIOS (16-битный код, адресуемая память 1 Мбайт, ограничения аппаратного характера IBM PC/AT и прочее) были неприемлемы для использования в больших серверных платформах, а ведь Itanium планировался именно для таковых. 

Примечательно, что EFI изначально носил название Intel Boot Initiative, это уже позже он был переименован.

Спецификации

История стандарта EFI началась с выхода версии 1.01, но она не увидела широкого применения, посколько была быстро снята с рынка в связи с юридическими проблемами, связанными с использованием торговой марки.

Первая «полноценная» версия EFI — это 1.02, которая была представлена 12 декабря 2000 года.

Поздее, 1 декабря 2002 года была представлена версия EFI 1.10, включавшая в себя модель драйвера EFI, а также несколько «косметических» улучшений, в сравнении с версией 1.02.

В 2005 году компания Intel отнесла спецификацию EFI к организации UEFI Forum, которая впоследствии стала отвечать за дальнейшее развитие интерфейса. Тогда же стандарт EFI был переименован в Unified EFI (UEFI), для того, чтобы подчеркнуть произошедшее изменение. Примечательно, что, несмотря на смену названия, в большинстве документов по сей день свободно применяются оба термина.

7 января 2007 года организация UEFI Forum выпустила версию 2.1 UEFI, в которой была внедрена улучшенная криптография, функция установления подлинности сети, а также обновленная архитектура пользовательского интерфейса.

Содержание

Интерфейс EFI содержит в себе таблицы, в которых включено множество различных данных: информация о платформе, загрузочные и runtime-сервисы, доступные для загрузчика операционной системы и самой операционной системы. Некоторые расширения BIOS (ACPI или SMBIOS) также включены в EFI — в них не обязателен 16-разрядный runtime-интерфейс.

Сервисы

EFI определяет сервисы загрузки, включающие поддержку:

  • текстовой и графической консоли;
  • шин;
  • блоков;
  • файловых сервисов;

также интерфейс определяет runtime-сервисы (дата, время и память).

Драйверы устройств

Стандарт EFI, помимо стандартных, архитектурно-зависимых драйверов, определяет также и независимую от платформы среду драйверов. эта среда носит название EFI Byte Code (EBC). Спецификация UEFI требует от системного программного обеспечения интерпретатор для любых образов EBC, загруженных (фактически или потенциально) в среду. 

Так, EBC вполне можно соотнести с независимым от аппаратных средств Open Firmware, используемым в Apple Macintosh и Sun Microsystems SPARC компьютерах.

Часть архитектурно-зависимых типов драйверов EFI может оснащаться интерфейсами для использования операционной системы, что дает возможность самой операционной системе применять EFI в качестве базовой поддержки графики и сети до загрузки драйверов.

Менеджер загрузки

Менеджер загрузки EFI применяется в целях выбора и последующей загрузки операционной системы. Так, потребность в определенном алгоритме загрузки исключена: загрузчик является приложением EFI.

Поддержка дисков

Вдобавок к стандартному методу разметки дисков (MBR), EFI обладает поддержкой GUID Partition Table (GPT). Эта схема свободна от каких-либо специфических для MBR ограничений. Стандарт EFI не содержит в себе описание для файловых систем, но реализации EFI, как правило, имеют поддержку файловой системы FAT32.

Оболочка

Открытая среда оболочки стандарта позволяет пользователю загружать ее в целях произведения определенных операций. Это гораздо удобнее: пользователь избавлен от загрузки непосредственно самой операционной системы. Оболочка является простым приложением EFI, которое может храниться в ПЗУ платформы (либо на отдельном устройстве, драйверы которого расположены в ПЗУ).

Кроме того, пользователь может применять оболочку и для выполнения иных приложений EFI (например, настройка или установка операционной системы, либо же диагностика, конфигурация или обновление прошивки). Также в функции оболочки входит проигрывание CD/DVD-носителей, без загрузки операционной системы. Кроме того, оболочка EFI позволяет командно произвести операции копирования или перемещения файлов и каталогов, при условии, что работа производится в поддерживаемых файловых системах. Можно также осуществлять загрузку/выгрузку драйверов. Ну, и наконец, оболочка может использовать полный TCP/IP стек.

Оболочка EFI имеет поддержку сценариев в виде файлов с расширением .nsh (аналог пакетного файла в DOS).

Названия команд зачастую заимствуются от интерпретаторов командной строки (COMMAND.COM или Unix shell). Оболочка EFI в полной степени может выступать альтернативой и полноценным аналогом интерпретатора командной строки, либо текстового интерфейса BIOS.

Расширения

Расширения EFI загружаются почти с любого энергонезависимого устройства хранения данных, которое подключено к ПК.

Реализация

Intel Platform Innovation Framework

Intel Platform Innovation Framework («инновационный инструментарий Intel») представляет собой набор спецификаций, выпущенных компанией Intel при сотрудничестве с EFI. В данном случае, EFI определяет интерфейс между операционной системой и аппаратно-программным обеспечением, а на инструментарий возлагается определение применяемой для создания встраиваемого программного обеспечения структуры. Это определение производится на более низком уровне, в сравнении с функциями, заложенными в EFI.

К примеру, в инструментарий входят все этапы, которые необходимо преодолеть для корректной инициализации компьютера с момента включения. Такие внутренние возможности интегрированного программного обеспечения не являются частью спецификации EFI, однако они включены в разработанную ассоциацией UEFI спецификацию инициализации платформы (Platform Initialization Specification). Данный инструментарий опробован на платформах XScale, Itanium и IA-32.

Совместимость с операционной системой, в случае с платформой x86, достигается благодаря применению модуля поддержки совместимости (CSM), содержащего в себе 16-битную программу (CSM16), которая реализуется производителем BIOS. Также в нее входит специальный слой, в функции которого входит связь CSM16 с инструментарием.

Компания Intel является автором уникальной реализации для инструментария, имеющей кодовое название «Tiano». Это полная реализация встраиваемого программного обеспечения с поддержкой EFI. В ней отсутствует традиционная 16-битная часть CSM, однако она обеспечивает интерфейсы, которые необходимы для дополнений, реализуемых изготовителями BIOS. Компания Intel не распространяет полную реализацию Tiano среди конечных пользователей. Часть этой реализации была выпущена в виде исходных текстов TianoCore проекта, подобно EFI Developer Kit (EDK). Данная реализация включает EFI и часть кода инициализации аппаратных средств, но вместе с тем, в ней скрыты характерные особенности самого встраиваемого программного обеспечения. 

Построенные на стандарте EFI продукты можно приобрести через независимых производителей BIOS (к примеру, American Megatrends (AMI) и Insyde Software). Часть реализаций полностью основана на Tiano, другая часть — соответствует спецификациям, однако не строится на реализации Intel.

Платформы, применяющие EFI; сопутствующий инструментарий

В 2000 году, компания Intel разработала системы, построенные на платформе Itanium. Они имели поддержку EFI 1.02.

В 2002 году, компания Hewlett-Packard выпустила системы, построенные на платформе Itanium 2. Они имели поддержку версии EFI 1.10, и имели возможность загружать операционные системы Windows, Linux, FreeBSD и HP-UX.

Системы Itanium или Itanium 2, выпускаемые вместе с интегрированным EFI-совместимым программным обеспечением, обязаны соответствовать спецификации DIG64.

В ноябре 2003 года, компания Gateway обнародовала систему Gateway 610 Media Center, которая являлась первой x86-системой, построенной на базе Windows. В ней использовалось встраиваемое программное обеспечение, которое было основано на инструментарии, InsydeH2O от Insyde Software. Поддержка BIOS реализовывалась благодаря модулю поддержки совместимости (CSM).

Январь 2006 года, компания Apple представляет свои первые ПК Macintosh, построенные на платформе Intel. Системы применяют EFI и сопутствующий инструментарий, взамен Open Firmware, который применяли на предыдущих системах PowerPC-платформы.

5 апреля 2006 года, компания Apple представляет продукт Boot Camp, являющийся стандартным пакетом, позволяющим создавать диск с драйверами Windows XP. Кроме того, новый пакет содержал в себе инструмент разметки дисков, позволяющий установить Windows XP, оставив при этом работоспособным действующий Mac OS X. Кроме того, вышло обновление встраиваемого программного обеспечения. В нем была добавлена поддержка BIOS для реализации EFI. Последующие линейки моделей компьютеров Macintosh выпускались с обновленным и встраиваемым программным обеспечением. Так, на сегдняшний день, все компьютеры Macintosh имеют возможность загружать BIOS-совместимые операционные системы.

Фирменные «интеловские» системные платы производятся, в основном, со встраиваемым программным обеспечением, построенным на основе инструментария (к примеру, DP35DP). Так, в 2005 году было выпущено свыше 1 млн. систем Intel. Производство новых сотовых телефонов, настольных ПК и серверов, работающих на инструментарии, стартовало в 2006 году. Вот, например, все системные платы, построенные на наборе системной логики Intel 945, применяют в своей работе инструментарий. Впрочем, во встраиваемом программном обеспечении, как правило, не включена поддержка EFI, оно ограничивается лишь поддержкой BIOS.

С 2005 года стандарт EFI стали внедрять в не-ПК архитектуры (например, встраиваемые системы, построенные на базе XScale). В EDK включена отдельная цель NT32, допускающая встраиваемое программное оебспечение EFI и его приложения в приложения Windows. В 2007 году компанией Hewlett-Packard был представлен принтер серии 8000. Это был первый принтер, оснащенный встраиваемым программным обеспечением, совместимым с EFI. В 2008 году компанией MSI была представлена линейка системных плат, построенных на чипсете Intel P45, они обладали поддержкой EFI.

Операционные системы

  • С 2000-х годов, операционные системы GNU/Linux нередко применяли EFI для загрузки. 
  • С 2002 года, операционные системы HP-UX стали применять EFI в качестве загрузочного механизма в системах, построенных на платформе IA-64. Операционные системы OpenVMS применяли стандарт с начала 2005 года. 
  • Компания Apple взяла на вооружение стандарт EFI, выпустив линейку компьютеров, построенных на архитектуре Intel. Mac OS X 10.4 (Tiger) для Intel и Mac OS X 10.5 (Leopard) имели поддержку EFI v1.10 не только в 32-разрядном режиме, но и в 64-разрядных центральных процессорах. Так, посредством загрузчика EFI, установка Microsoft Windows 7 на компьютеры Apple осталась невозможной, поскольку этой операционной системе необходимо наличие UEFI или еще более новой версии.
  • Microsoft Windows имеет поддержку EFI для 64-разрядных архитектур. Компания Microsoft отмечает, что отсутствие поддержки EFI на 32-разрядных центральных процессорах возникла ввиду недостаточного участия со стороны производителей ПК. Миграция Microsoft к 64-разрядным операционным системам не позволяет использовать EFI 1.10, поскольку 64-разрядные расширения не поддерживаются окружением процессора. Поддержка x86-64 включена в UEFI 2.0. Itanium версии Windows 2000 (Advanced Server Limited Edition и Datacenter Server Limited Edition) имеют поддержку EFI 1.1.Windows Server 2003 для IA-64, 64-разрядная версия Windows XP и Windows 2000 Advanced Server Limited Edition, заточенные специально под семейство процессоров Intel Itanium, имеют поддержку EFI, определенную для данной платформы спецификацией DIG64. Разработчики компании Microsoft внедрили поддержку UEFI в 64-разрядных операционных системах Windows начиная с Windows Server 2008 и Windows Vista Service Pack 1.

Недостатки

Стандарт EFI попал под оглушительные шквалы критики за усложнение системы. Многие эксперты отмечали, что EFI не дает операционной системе ключевых преимуществ, но при этом существенно ее усложняет. Кроме того, в пользу EFI произошел отказ от альтернативных реализаций BIOS, обладающих полностью открытыми исходными текстами (OpenBIOS и coreboot).

В сентябре 2011 года компанией Microsoft было объявлено, что условия сертификации совместимых с Microsoft Windows 8 компьютеров могут привести к последующему производству устройств, которые не под каким предлогом не будут поддерживать какую‐либо другую операционную систему. Компания Microsoft пояснила, что поставщиками может быть реализована возможность добавления других подписей. Чуть позднее это было сделано обязательным требованием сертификации. Впрочем, что касается устройств на ARM, то в их случае требование следующее: полностью отключить функцию «безопасной загрузки». В таком случае, установка других операционных систем также перестает быть возможной.

Понравилась статья? Поделить с друзьями:
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
  • Ошибка 0x800b0109 windows 7 как исправить
  • Оперативная память забита непонятно чем windows 11
  • Первоначальная настройка windows server 2022
  • Windows 10 display switch
  • Как выбрать главный экран на windows 10