Ссылки:

Вступление

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

Библиотека

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

рис. 1: старая структура проекта и библиотеки
рис. 1: старая структура проекта и библиотеки
рис. 2: новая структура проекта и библиотеки. Немного страшно, но когда я посмотрел на структуру проекта EDK II, я почувствовал куда более сильные эмоции :)
рис. 2: новая структура проекта и библиотеки. Немного страшно, но когда я посмотрел на структуру проекта EDK II, я почувствовал куда более сильные эмоции :)

Что там нужно уметь.. Ага, нашел..

Для самого базового терминала нам нужно уметь следующее:

  1. Печатать в консоль

  2. Принимать ввод

  3. определять комманды

  4. выполнять комманды

Печатать мы научились ещё в предыдущей статье. Дальше по списку: "принимать ввод". В предыдущей статье был макрос для ожидания нажатия любой клавиши, но этого точно мало для того, чтобы получать строки с коммандами. Дак сделаем новый макрос! Макросы для взаимодействия с вводом / выводом я определяю в файле ioefi.inc. Для приема строки нужно:

  1. принять клавишу.

  2. если нажат Backspace, то убрать последний символ.

  3. если нажат Enter, то поставить null в буффере и закончить работу макроса.

  4. если это обычная клавиша, то напечатать её на экран, сохранить в буффер, сместить курсор буффера и перейти к п.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

Итоги

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

Поддержка

Я буду рад полезным ссылкам на документацию, рекоммендациям как улучшить статью и всяким советам. Так же приминаются полезные пулл реквесты :). Спасибо за внимание!

Комментарии (8)


  1. TalismanChet Автор
    08.08.2022 15:33
    +1

    Забыл добавить скриншот

    2.4 Мб


  1. AlexSky
    08.08.2022 22:42
    +1

    Это очень интересно - играть с железом на самом низком уровне, но совершенно не продуктивно в таком разрезе. Я как в зеркале вижу себя в 2004-ом. Загрузчик, переход в защищённый режим, менеджмент памяти, ввод-вывод.

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

    Я потерял лет пятнадцать, чтобы найти работу своей мечты. А мог бы пройти этот путь за пару лет.


    1. AlexanderAverin
      09.08.2022 09:19
      +1

      Тут палка о двух концах. Чтобы написать своё ядро, уже необходимо иметь серьёзный багаж знаний, имея который, можно уже претендовать на серьёзную работу. Конечно, перед этим всё таки придётся изучить ядро Linux, но, я думаю, что, если имеется большой опыт, то можно с этим справиться в разумных пределах времени...


      1. TalismanChet Автор
        09.08.2022 21:12

        даа, помню пытался собрать ядро Linux на винде, тот ещё ад. ну, сейчас у меня Linux mint и Wine, так что проблем таких нет..


    1. TalismanChet Автор
      09.08.2022 21:10
      +1

      ну, может быть. мне то сейчас 14 y/o, так что, может поэтому?


      1. AlexanderAverin
        10.08.2022 00:50
        +1

        Как до такой жизни то докатились (: ?


  1. nagayev
    08.08.2022 23:58

    Не очень понятно зачем писать терминал на ассемблере.

    100% можно на Си с ассемблерными вставками, если нужно.


    1. TalismanChet Автор
      09.08.2022 21:07
      +3

      Я же писал - просто я обожаю ассемблер