Введение
Пару месяцев назад я решил начать серию статей про написание своей ОС с нуля. Описал написание 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), он даже разбирается с помощью 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
; 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_SIGNATUREequ49h,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 { .Resetdptr .ReadKeyStrokedptr .WaitForKeydptr } structure SIMPLE_INPUT_INTERFACE struc EFI_BOOT_SERVICES_TABLE { .Hdr EFI_TABLE_HEADER .RaisePrioritydptr .RestorePrioritydptr .AllocatePagesdptr .FreePagesdptr .GetMemoryMapdptr .AllocatePooldptr .FreePooldptr .CreateEventdptr .SetTimerdptr .WaitForEventdptr .SignalEventdptr .CloseEventdptr .CheckEventdptr .InstallProtocolInterface dptr .ReInstallProtocolInterface dptr .UnInstallProtocolInterface dptr .HandleProtocoldptr .Voiddptr .RegisterProtocolNotify dptr .LocateHandledptr .LocateDevicePathdptr .InstallConfigurationTable dptr .ImageLoaddptr .ImageStartdptr .Exitdptr .ImageUnLoaddptr .ExitBootServicesdptr .GetNextMonotonicCountdptr .Stalldptr .SetWatchdogTimerdptr .ConnectControllerdptr .DisConnectControllerdptr .OpenProtocoldptr .CloseProtocoldptr .OpenProtocolInformation dptr .ProtocolsPerHandledptr .LocateHandleBufferdptr .LocateProtocoldptr .InstallMultipleProtocolInterfaces dptr .UnInstallMultipleProtocolInterfaces dptr .CalculateCrc32dptr .CopyMemdptr .SetMemdptr } structure EFI_BOOT_SERVICES_TABLE struc EFI_RUNTIME_SERVICES_TABLE { .Hdr EFI_TABLE_HEADER .GetTimedptr .SetTimedptr .GetWakeUpTimedptr .SetWakeUpTimedptr .SetVirtualAddressMapdptr .ConvertPointerdptr .GetVariabledptr .GetNextVariableNamedptr .SetVariabledptr .GetNextHighMonoCountdptr .ResetSystemdptr } structure EFI_RUNTIME_SERVICES_TABLE struc EFI_TIME { .Yearint16 .Monthint8 .Dayint8 .Hourint8 .Minuteint8 .Secondint8 .Pad1int8 .Nanosecondint32 .TimeZoneint16 .Daylightint8 .Pad2int8 .sizeofrb 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 { .Revisionint32 .ParentHandleint64 .SystemTabledptr .DeviceHandleint64 .FilePathdptr .Reservedint64 .LoadOptionsSizeint32 .ImageBasedptr .ImageSizeint64 .ImageCodeTypeint32 .ImageDataTypeint32 .UnLoaddptr } 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 { .Revisionint64 .Mediadptr .Resetdptr .ReadBlocksdptr .WriteBlocksdptr .FlushBlocksdptr } structure EFI_BLOCK_IO_PROTOCOL struc EFI_BLOCK_IO_MEDIA { .MediaIdint32 .RemovableMediaint8 .MediaPresentint8 .LogicalPartitionint8 .ReadOnlyint8 .WriteCachingint8 .BlockSizeint32 .IoAlignint32 .LastBlockint64 } 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 { .QueryModedptr .SetModedptr .Bltdptr .Modedptr } structure EFI_GRAPHICS_OUTPUT_PROTOCOL struc EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE { .MaxModeint32 .CurrentModeint32 .ModeInfodptr .SizeOfModeInfointn .FrameBufferBasedptr .FrameBufferSizeintn } structure EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE struc EFI_GRAPHICS_OUTPUT_MODE_INFORMATION { .Versionint32 .HorizontalResolutionint32 .VerticalResolutionint32 .PixelFormatint32 .RedMaskint32 .GreenMaskint32 .BlueMaskint32 .Reservedint32 .PixelsPerScanlineint32 } structure EFI_GRAPHICS_OUTPUT_MODE_INFORMATION macro InitializeLib { clc orrdx, rdx jz.badout cmpdword [rdx], 20494249h je@f .badout: xorrcx, rcx xorrdx, rdx stc @@:mov[efi_handler], rcx mov[efi_ptr], rdx } macro uefi_call_wrapperinterface,function,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10,arg11 { numarg = 0 if ~ arg11 eq numarg = numarg + 1 if ~ arg11 eq rdi movrdi, arg11 end if end if if ~ arg10 eq numarg = numarg + 1 if ~ arg10 eq rsi movrsi, arg10 end if end if if ~ arg9 eq numarg = numarg + 1 if ~ arg9 eq r14 movr14, arg9 end if end if if ~ arg8 eq numarg = numarg + 1 if ~ arg8 eq r13 movr13, arg8 end if end if if ~ arg7 eq numarg = numarg + 1 if ~ arg7 eq r12 movr12, arg7 end if end if if ~ arg6 eq numarg = numarg + 1 if ~ arg6 eq r11 movr11, arg6 end if end if if ~ arg5 eq numarg = numarg + 1 if ~ arg5 eq r10 movr10, arg5 end if end if if ~ arg4 eq numarg = numarg + 1 if ~ arg4 eq r9 movr9, arg4 end if end if if ~ arg3 eq numarg = numarg + 1 if ~ arg3 eq r8 movr8, arg3 end if end if if ~ arg2 eq numarg = numarg + 1 if ~ arg2 eq rdx movrdx, 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> movrcx, arg1 end if end if end if xorrax, rax moval, numarg if interface in <ConsoleInHandle,ConIn,ConsoleOutHandle,ConOut,StandardErrorHandle,StdErr,RuntimeServices,BootServices> movrbx, [efi_ptr] movrbx, [rbx + EFI_SYSTEM_TABLE.#interface] else if ~ interface eq rbx movrbx, interface end if end if if arg1 in <ConsoleInHandle,ConIn,ConsoleOutHandle,ConOut,StandardErrorHandle,StdErr,RuntimeServices,BootServices> movrcx, rbx end if if defined SIMPLE_INPUT_INTERFACE.#function movrbx, [rbx + SIMPLE_INPUT_INTERFACE.#function] else if defined SIMPLE_TEXT_OUTPUT_INTERFACE.#function movrbx, [rbx + SIMPLE_TEXT_OUTPUT_INTERFACE.#function] else if defined EFI_BOOT_SERVICES_TABLE.#function movrbx, [rbx + EFI_BOOT_SERVICES_TABLE.#function] else if defined EFI_RUNTIME_SERVICES_TABLE.#function movrbx, [rbx + EFI_RUNTIME_SERVICES_TABLE.#function] else if defined EFI_GRAPHICS_OUTPUT_PROTOCOL.#function movrbx, [rbx + EFI_GRAPHICS_OUTPUT_PROTOCOL.#function] else if defined EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE.#function movrbx, [rbx + EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE.#function] else movrbx, [rbx + function] end if end if end if end if end if end if calluefifunc } section '.text' code executable readable uefifunc: movqword [uefi_rsptmp], rsp andesp, 0FFFFFFF0h bteax, 0 jnc@f pushrax @@:cmpal, 11 jb@f pushrdi @@:cmpal, 10 jb@f pushrsi @@:cmpal, 9 jb@f pushr14 @@:cmpal, 8 jb@f pushr13 @@:cmpal, 7 jb@f pushr12 @@:cmpal, 6 jb@f pushr11 @@:cmpal, 5 jb@f pushr10 @@: subrsp, 4*8 callrbx movrsp, qword [uefi_rsptmp] ret section '.data' data readable writeable efi_handler:dq0 efi_ptr:dq0 uefi_rsptmp:dq0
листинг 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-загрузчик.
Поддержка
Всегда непротив конструктивной критики. Принимаю идеи по улучшению статьи и проекта в целом. Всегда буду рад полезным ссылкам. Спасибо за прочтение!
ссылка на оригинал статьи https://habr.com/ru/post/680270/
Добавить комментарий