forth

A WIP implementation of Forth targeting UEFI x86-64.
git clone git://git.christianermann.dev/forth
Log | Files | Refs

commit d3727ac45a0773a291326f3056a42a60c508f479
parent 5cb64f840aa50d32c02ff15576715d75a35fdff5
Author: Christian Ermann <christianermann@gmail.com>
Date:   Wed, 22 Nov 2023 16:29:29 -0500

Add EFI print function

Diffstat:
ADockerfile | 23+++++++++++++++++++++++
AMakefile | 4++++
Aefi.asm | 285+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aforth.asm | 29+++++++++++++++++++++++++++++
Arun.sh | 9+++++++++
5 files changed, 350 insertions(+), 0 deletions(-)

diff --git a/Dockerfile b/Dockerfile @@ -0,0 +1,23 @@ +FROM debian:stable-slim + +RUN apt-get update && apt-get -y upgrade && \ + apt-get --no-install-recommends -y install \ + qemu-system-x86 \ + ovmf \ + fasm \ + mtools + +COPY forth.asm / +COPY efi.asm / +COPY run.sh / + +RUN fasm forth.asm BOOTX64.EFI + +RUN dd if=/dev/zero of=boot.img bs=1M count=512 && \ + mformat -i boot.img :: && \ + mmd -i boot.img ::/EFI && \ + mmd -i boot.img ::/EFI/BOOT && \ + mcopy -i boot.img BOOTX64.EFI ::/EFI/BOOT + +ENTRYPOINT ["/run.sh"] + diff --git a/Makefile b/Makefile @@ -0,0 +1,4 @@ +all: + docker build . --tag forth + docker image prune -f + docker run --interactive --rm forth diff --git a/efi.asm b/efi.asm @@ -0,0 +1,285 @@ +; This file is adapted from `uefi.inc` hosted on osdev.org. It's purpose is +; to provide an easy-to-use interface to the EFI protocol. I've stripped a lot +; of stuff out as I don't need it at the moment. I'll probably have to add some +; of it back at a later date. +; + +; These definitions are for portability across 32- and 64-bit systems and +; provide automatic alignment in structure definitions. +struc int8 { + . db ? +} +struc int16 { + align 2 + . dw ? +} +struc int32 { + align 4 + . dd ? +} +struc int64 { + align 8 + . dq ? +} +struc intn { + align 8 + . dq ? +} +struc dptr { + align 8 + . dq ? +} + +; Definitions of status codes returned by various EFI calls. +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 + + +; Helper macro for definition of relative structure member offsets. +macro struct name +{ + virtual at 0 + name name + end virtual +} + +; Definitions of EFI structures + +struc EFI_TABLE_HEADER { + .Signature int64 + .Revision int32 + .HeaderSize int32 + .CRC32 int32 + .Reserved int32 +} +struct 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 +} +struct 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 +} +struct SIMPLE_TEXT_OUTPUT_INTERFACE + +struc SIMPLE_INPUT_INTERFACE { + .Reset dptr + .ReadKeyStroke dptr + .WaitForKey dptr +} +struct SIMPLE_INPUT_INTERFACE + +struc INPUT_KEY { + .ScanCode dw ? + .UnicodeChar dw ? + align 8 +} +struct INPUT_KEY + +sys_initialize: +; Store the address of the EFI system table in the `system_table` variable. +; +; This function should be called ASAP after program start to ensure that the +; RDX register still contains the address of the system table. +; +; `system_table` is defined as an 8-byte variable in the data section at the +; bottom of this file. +; + mov [system_table], rdx + ret + +sys_clear_screen: +; Clear the screen. +; +; This function will clobber the RCX and RBX registers. +; + mov rcx, [system_table] + mov rcx, [rcx + EFI_SYSTEM_TABLE.ConOut] + mov rbx, [rcx + SIMPLE_TEXT_OUTPUT_INTERFACE.ClearScreen] + sub rsp, 32 + call rbx + add rsp, 32 + ret + +sys_print_string: +; Print an ascii string to stdout. +; +; This function uses the `.output_buffer` local variable to store the UTF-16 +; null-terminated version of the input string. 1024 bytes (512 characters) are +; reserved for this buffer in the data section at the bottom of this file. +; +; This function will clobber the RDI, RAX, RBX, RCX, and RDX registers. +; +; Args: +; RCX: The address of an ASCII string. The string does not need to be +; null-terminated. +; RDX: The length of the string. +; + mov rdi, .output_buffer + +.copy_char: +; Copy a character from the ASCII string into the AL register. +; +; If there are no characters left, print the converted UTF-16 string. +; + cmp rdx, 0 + je .print_string + + mov al, byte [rcx] + cmp al, 0xA + jne .not_newline + +.is_newline: +; Convert "\n" (ASCII) to "\r\n" (UTF-16). +; +; The converted character is stored in `.output_buffer`. +; + mov byte [rdi], 0xD + inc rdi + mov byte [rdi], 0 + inc rdi + mov byte [rdi], 0xA + inc rdi + mov byte [rdi], 0 + inc rdi + jmp .next_char + +.not_newline: +; Convert an ASCII character into a UTF-16 character. +; +; The converted character is stored in `.output_buffer`. +; + mov byte [rdi], al + inc rdi + mov byte [rdi], 0 + inc rdi + +.next_char: +; Process the next character in the ASCII string. +; + inc rcx + dec rdx + jmp .copy_char + +.print_string: +; Print a UTF-16 string to stdout. +; +; The string is null-terminated before being passed to the EFI OutputString +; function in the RDX register. +; + mov word [rdi], 0 + mov rdx, .output_buffer + mov rcx, [system_table] + mov rcx, [rcx + EFI_SYSTEM_TABLE.ConOut] + mov rbx, [rcx + SIMPLE_TEXT_OUTPUT_INTERFACE.OutputString] + sub rsp, 32 + call rbx + add rsp, 32 + ret + +sys_read_char: +; Read an ASCII character from stdin. +; +; The EFI ReadKeyStroke function actually returns a UTF-16 character but I only +; want to deal with ASCII characters for now. +; + mov rcx, [system_table] + mov rcx, [rcx + EFI_SYSTEM_TABLE.ConIn] + mov rbx, [rcx + SIMPLE_INPUT_INTERFACE.ReadKeyStroke] + mov rdx, .input_key + +.read_key: +; Call the EFI ReadKeyStroke function until a key is pressed. +; +; The status code is returned in the RAX register. If a key is successfully +; read, it is stored in the `.input_key` local variable through a pointer in +; the RDX register. +; + sub rsp, 32 + call rbx + add rsp, 32 + + mov r8, EFI_NOT_READY + cmp rax, r8 + je .read_key + +.process_key: +; Move the character read from stdin into the RAX register. +; +; Carriage returns ("\r") are replaced with newlines ("\n"). +; + movzx rax, word [.input_key.UnicodeChar] + cmp ax, 0xD + jne .no_enter + mov rax, 0xA + +.no_enter: +; Echo the character onto stdout and return. +; +; The echo will likely be removed in the future but is here currently for +; debugging purposes. +; + push rax + mov [.char_buffer], al + mov rcx, .char_buffer + mov rdx, 1 + call sys_print_string + pop rax + ret + +section '.data' data readable writeable + +system_table dq ? + +sys_print_string.output_buffer rq 0x400 + +sys_read_char.input_key INPUT_KEY +sys_read_char.char_buffer db ? + diff --git a/forth.asm b/forth.asm @@ -0,0 +1,29 @@ +; An in-progress Forth compiler and tutorial for UEFI x86_64 systems. +; +; Written By Christian Ermann +; + +format pe64 dll efi +entry main + +section '.text' code executable readable + +include 'efi.asm' + +main: +; Initialize the system and print a version string. +; +; Later on, this will start the interpreter as well. +; + call sys_initialize + call sys_clear_screen + mov rcx, version_string + mov rdx, version_string.length + call sys_print_string + call sys_read_char + +section '.data' readable writable + +version_string db 'soup forth v0.1', 0xA +.length = $ - version_string + diff --git a/run.sh b/run.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +exec qemu-system-x86_64 \ + -nographic \ + -serial mon:stdio \ + -net none \ + -bios /usr/share/ovmf/OVMF.fd \ + -drive file=boot.img,format=raw +