Ссылки:
Вступление
В предыдущей статье мы написали простейший загрузчик, печатающий на экран "Hello, World!" и завершающийся по нажанию клавиши. Сегодня напишем терминал, у которого будет несколько комманд, обновим библиотеку для UEFI и сделаем ещё пару вещей.
Библиотека
По сравнению с библиотекой из предыдущей статьи, до сегодня она стала массивнее, и поддерживать её в таком виде, как на рис. 1 становится почти невозможно. Поэтому было принято решение поменять структуру библиотеки. Итоговая структура изображена на рис. 2:


Что там нужно уметь.. Ага, нашел..
Для самого базового терминала нам нужно уметь следующее:
Печатать в консоль
Принимать ввод
определять комманды
выполнять комманды
Печатать мы научились ещё в предыдущей статье. Дальше по списку: "принимать ввод". В предыдущей статье был макрос для ожидания нажатия любой клавиши, но этого точно мало для того, чтобы получать строки с коммандами. Дак сделаем новый макрос! Макросы для взаимодействия с вводом / выводом я определяю в файле ioefi.inc. Для приема строки нужно:
принять клавишу.
если нажат
Backspace, то убрать последний символ.если нажат
Enter, то поставитьnullв буффере и закончить работу макроса.если это обычная клавиша, то напечатать её на экран, сохранить в буффер, сместить курсор буффера и перейти к
п.1.
В коде примерно так:
; libuefi.inc __scanln: pushaq mScankey __key_buf popaq mov bp, word [__key_buf+2] cmp bp, word kEnter je .exit cmp bp, word kBackspace je .bs mPrint __key_buf+2 mov [rbx], word bp add rbx, 2 jmp __scanln .exit: ;mPrintln mov [rbx], word cNull ret .bs: cmp rbx, rcx je __scanln mPrint __bs_string sub rbx, 2 mov [rbx], word cNull jmp __scanln
; ioefi.inc macro mScanln _buffer { if ~ buffer eq mov rbx, _buffer mov rcx, rbx call __scanln end if }
Ура! Но это не всё. если последний пункт выполняется условным прыжком, то сравнение строк ещё не реализовано! Что же, не проблема - я набросал 2 макроса: для сравнения строк целиком и для сравнения, начинается одна строка с другой или нет. Вышло как-то так:
; libuefi.inc __compare_unicode_strings: rep cmpsw mov rbx, rsi mCall __length_unicode_string mov rcx, rdx mov rbx, rdi mCall __length_unicode_string cmp rcx, rdx ret __compare_unicode_strings_prefixes: repnz cmpsw ret __length_unicode_string: mov ax, word [rbx] test ax, ax jz .exit inc rcx add rbx, 2 jmp __length_unicode_string .exit: ret
; macroefi.inc macro mCompareStrings str1, str2 { lea rsi, [str1] lea rdi, [str2] mCall __compare_unicode_strings } macro mCompareStringsPrefixes str1, str2 { lea rsi, [str1] lea rdi, [str2] mCall __compare_unicode_strings_prefixes } macro mLenString _str, _dest { mov rbx, _str xor rcx, rcx mCall __length_unicode_string mov _dest, rcx } ; так же набросал эти два макроса, но они не так важны и используется ; только в одной функции из файла libuefi.inc macro pushaq { push rax rbx rcx rdx push rbp rsi rdi;rsp } macro popaq { pop rdi rsi rbp;rsp pop rdx rcx rbx rax }
Соединяем всё вместе
Итак, напишем программу, которая будет имитировать терминал:
; bootx64.asm ; импортируем библиотеку include "include/uefi+.inc" ; определяем макросы mDefMacros ; простите, не могу без этого :_) nl EQU cNl ; определяем точку входа mEntry main ; определяем системные функции mDefSystem ; секция кода section ".text" code executable readable ; главная функция main: ; готовим библиотеку mInit fatal_error ; пишем, что все ОК mPrintln str_start_OK ; внимание: мы - это админ mPrintln str_admin_WARN ; переход на новую строку mNewline 1 .command_loop: mNewline 1 ; читаем строку mScanln command ; сравниваем и исполняем mCompareStrings command, str_cmd_help je execute.help mCompareStrings command, str_cmd_exit je execute.exit mCompareStrings command, str_cmd_boot je execute.boot mCompareStringsPrefixes command, str_cmd_echo je execute.echo jmp .command_loop ; выход со статусом УСПЕШНО .exit: ; mExit EFI_SUCCESS ; фатальная ошибка fatal_error: mPrint str_fatal_FAIL mWaitkey kEnter mExit EFI_ERROR jmp fatal_error ; функции - комманды execute: .help: mPrintln str_help_NORM jmp main.command_loop .echo: mPrintln command+10 jmp main.command_loop .exit: mPrint str_exit_NORM @@: ; в цикле ждем [Enter] или [Escape] mScankey key cmp word [key.unicode], kEnter je main.exit cmp word [key.unicode], kEscape je main.command_loop jmp @b .boot: mPrintln str_boot_FAIL jmp main.command_loop ; определяем системные данные mDefLibrary section ".data" data readable writeable ; тут всякие строки str_start_OK sString '[ OK ] Started: /efi/boot/boox64.efi' str_admin_WARN sString "[ WARN ] You are logged in as admin" str_fatal_FAIL sString '[ FAIL ] Fatal error occurred. Press [Enter] for exit...' str_help_NORM sString 'VUS - Very Useless Shell', nl, nl, "help - shows this message", nl, 'echo [message] - echoes typed message', nl, "exit - exit VUS", nl, "boot - not implemented" str_boot_FAIL sString "[ FAIL ] Not implemented" str_exit_NORM sString "Press [Enter] for exit or [Esc] for cancel" str_cmd_help sString "help" str_cmd_echo sString "echo " str_cmd_exit sString "exit" str_cmd_boot sString "boot" ; буффер для комманды command sStrbuf 512 ; буффер клавиши key sKey ; объявляем системные поля mEfidata ; зачем-то нужна `\_(._.)_/` section ".reloc" fixups data discardable

Итоги
В течении статьи мы обновили библиотеку и создали простой терминал. В целом, из этого уже можно что-нибудь да сделать (там, псевдогравические игры или программы без сохрания изменений и пр.). Надеюсь, эта статья будет кому-нибудь полезна. Не забудьте посетить мой гитхаб! Там есть всякие интересности :).
Поддержка
Я буду рад полезным ссылкам, рекоммендациям как улучшить статью и всяким советам, репортам по опечаткам и фактическим ошибкам. Так же приминаются полезные пулл реквесты :). Спасибо за внимание!
