From 55d4cc10bbfa7bc445948b222b6a72d2d4b127a2 Mon Sep 17 00:00:00 2001 From: Jonas Hvid Date: Mon, 9 Mar 2020 15:13:34 +0100 Subject: [PATCH] Make it run on UEFI! The program is now usable, albeit barely, running inside QEMU, with the only dependency being UEFI! Very cool! Right now, no output is printed to the screen when you type characters, newlines aren't printed correctly, and the program doesn't accept Enter as valid whitespace when reading input. --- .gitignore | 4 ++- Makefile | 25 +++++++++++---- impl.asm | 4 +-- main.asm | 48 ++++++++++++++++++----------- uefi/main.asm => uefi.asm | 65 +++++++++++++++------------------------ uefi/.gitignore | 3 -- uefi/Makefile | 27 ---------------- 7 files changed, 79 insertions(+), 97 deletions(-) rename uefi/main.asm => uefi.asm (67%) delete mode 100644 uefi/.gitignore delete mode 100644 uefi/Makefile diff --git a/.gitignore b/.gitignore index 95811e0..5557c76 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -/main +/out +/OVMF_CODE.fd +/OVMF_VARS.fd diff --git a/Makefile b/Makefile index cb7bff3..ff474b0 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,23 @@ -.PHONY: run -run: main - cat sys.f - | ./main +.PHONY: qemu +qemu: out/main OVMF_CODE.fd OVMF_VARS.fd + # Based on https://wiki.osdev.org/UEFI#Emulation_with_QEMU_and_OVMF + qemu-system-x86_64 -cpu qemu64 \ + -drive if=pflash,format=raw,unit=0,file=OVMF_CODE.fd,readonly=on \ + -drive if=pflash,format=raw,unit=1,file=OVMF_VARS.fd \ + -net none \ + -drive format=raw,file=fat:rw:out \ + -display type=gtk,zoom-to-fit=on -main: main.asm impl.asm bootstrap.asm sys.f - fasm $< $@ +# Assuming 'ovmf' package on Arch Linux is installed. +OVMF_CODE.fd: /usr/share/ovmf/x64/OVMF_CODE.fd + cp $< $@ +OVMF_VARS.fd: /usr/share/ovmf/x64/OVMF_VARS.fd + cp $< $@ + +out/main: main.asm impl.asm bootstrap.asm sys.f uefi.asm + mkdir -p out + fasm $< out/main .PHONY: clean clean: - rm -f main + rm -rf out OVMF_CODE.fd OVMF_VARS.fd diff --git a/impl.asm b/impl.asm index 659d98e..ea6c04e 100644 --- a/impl.asm +++ b/impl.asm @@ -1,6 +1,6 @@ ;; vim: syntax=fasm -segment readable executable +section '.text' code readable executable macro printlen msg, len { push rsi @@ -231,7 +231,7 @@ parse_number: newline sys_terminate 100 -segment readable writable +section '.data' readable writable find.search_length dq ? find.search_buffer dq ? diff --git a/main.asm b/main.asm index 691e9f3..4ed478d 100644 --- a/main.asm +++ b/main.asm @@ -1,6 +1,6 @@ ;; vim: syntax=fasm -format ELF64 executable +include "uefi.asm" ;; "Syscalls" {{{ @@ -22,10 +22,15 @@ format ELF64 executable ;; ;; Clobbers: RAX, RCX, R11, RDI, RSI macro sys_print_string { - mov rax, 1 - mov rdi, 1 - mov rsi, rcx - syscall + push r8 + push r9 + push r10 + + call uefi_print_string + + pop r10 + pop r9 + pop r8 } ;; Read a character from the user into the given buffer. @@ -38,16 +43,25 @@ macro sys_print_string { ;; ;; Clobbers: RAX, RCX, R11, RDI, RSI, RDX macro sys_read_char { - mov rax, 0 - mov rdi, 0 - mov rdx, 1 - syscall + push rbx + push r8 + push r9 + push r10 + push r15 + + mov rcx, rsi + call uefi_read_char + + pop r15 + pop r10 + pop r9 + pop r8 + pop rbx } macro sys_terminate code { - mov rax, $3C - mov rdi, code - syscall + mov rax, code + call uefi_terminate } ;; }}} @@ -109,9 +123,7 @@ macro forth_asm label, name, immediate { .start: } -segment readable executable - -entry main +section '.text' code readable executable include "impl.asm" ; Misc. subroutines include "bootstrap.asm" ; Forth words encoded in Assembly @@ -120,6 +132,8 @@ main: cld ; Clear direction flag so LODSQ does the right thing. mov rbp, return_stack_top ; Initialize return stack + call uefi_initialize + mov rax, MAIN jmp qword [rax] @@ -597,7 +611,7 @@ forth INPUT_LENGTH, 'INPUT-LENGTH' dq LIT, input_buffer_length dq EXIT -segment readable writable +section '.data' readable writable ;; The LATEST variable holds a pointer to the word that was last added to the ;; dictionary. This pointer is updated as new words are added, and its value is @@ -637,8 +651,6 @@ here_top rq $4000 rq $2000 return_stack_top: -segment readable - ;; We store some Forth code in sys.f that defined common words that the user ;; would expect to have available at startup. To execute these words, we just ;; include the file directly in the binary, and then interpret it at startup. diff --git a/uefi/main.asm b/uefi.asm similarity index 67% rename from uefi/main.asm rename to uefi.asm index 431a64c..6584315 100644 --- a/uefi/main.asm +++ b/uefi.asm @@ -3,20 +3,6 @@ format pe64 dll efi entry main -;; [TODO] We need to provide the following: -;; - [X] Print a string of a given length -;; - [ ] Print a single character -;; - [ ] Terminate the program (? - What should this do?) -;; - [X] Read a single character -;; - This should allow the user to type in a string, and then feed the -;; buffer to us one character at a time. -;; - [ ] We want to show the user's input on the screen while reading -;; - [ ] Read a file that was bundled with the program -;; - It looks like we can use EFI_LOAD_FILE_PROTOCOL.LoadFile() to load -;; a file into a buffer. In order to be able to use this, we need to -;; have some way of interpreting a static buffer instead of reading as -;; we go. - ;; EFI struct definitions {{{ EFI_NOT_READY = 0x8000_0000_0000_0000 or 6 @@ -74,25 +60,9 @@ struct EFI_INPUT_KEY section '.text' code executable readable -main: +uefi_initialize: ; At program startup, RDX contains an EFI_SYSTEM_TABLE*. mov [system_table], rdx - - mov rcx, hello_string - mov rdx, hello_string.len - call print_string - - mov rcx, char_buffer - call read_char - - mov rcx, char_buffer - mov rdx, 1 - call print_string - - mov rcx, hello_string - mov rdx, hello_string.len - call print_string - ret ;; Print a string of the given length. @@ -100,7 +70,9 @@ main: ;; Inputs: ;; - RCX = String buffer ;; - RDX = String length -print_string: +;; +;; [TODO] Handle newlines correctly. (I.e. translate '\n' to '\r\n'.) +uefi_print_string: mov r8, rcx mov r9, rdx @@ -146,13 +118,16 @@ print_string: ;; ;; Inputs: ;; - RCX = Character buffer (1 byte) -read_char: +;; +;; [TODO] Show the user's input on screen while they are typing. +;; [TODO] Handle enter key correctly (should return '\n'). +uefi_read_char: mov r15, rcx .read_key: - mov rcx, [system_table] ; EFI_SYSTEM_TABLE* rcx - mov rcx, [rcx + EFI_SYSTEM_TABLE.ConIn] ; EFI_SIMPLE_TEXT_INPUT_PROTOCOL* rcx + mov rcx, [system_table] ; EFI_SYSTEM_TABLE* rcx + mov rcx, [rcx + EFI_SYSTEM_TABLE.ConIn] ; EFI_SIMPLE_TEXT_INPUT_PROTOCOL* rcx mov rbx, [rcx + EFI_SIMPLE_TEXT_INPUT_PROTOCOL.ReadKeyStroke] ; EFI_INPUT_READ_KEY rbx - mov rdx, input_key ; EFI_INPUT_KEY* rdx + mov rdx, input_key ; EFI_INPUT_KEY* rdx sub rsp, 32 call rbx add rsp, 32 @@ -166,14 +141,24 @@ read_char: ret +;; Terminate with the given error code. +;; +;; Inputs: +;; - RCX = Error code +uefi_terminate: + mov rcx, terminated_msg + mov rdx, terminated_msg.len + call uefi_print_string + jmp $ + section '.data' readable writable -system_table dq ? ; EFI_SYSTEM_TABLE* +system_table dq ? ; EFI_SYSTEM_TABLE* -hello_string db 'Hello, world!', 0xD, 0xA, 'Here is some more text.', 0xD, 0xA -.len = $ - hello_string +terminated_msg db 0xD, 0xA, '(The program has terminated.)', 0xD, 0xA +.len = $ - terminated_msg -print_string.output_buffer rq 0x400 +uefi_print_string.output_buffer rq 0x400 char_buffer db ? diff --git a/uefi/.gitignore b/uefi/.gitignore deleted file mode 100644 index 5557c76..0000000 --- a/uefi/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/out -/OVMF_CODE.fd -/OVMF_VARS.fd diff --git a/uefi/Makefile b/uefi/Makefile deleted file mode 100644 index aeff045..0000000 --- a/uefi/Makefile +++ /dev/null @@ -1,27 +0,0 @@ -.PHONY: qemu -qemu: out/main out/hello OVMF_CODE.fd OVMF_VARS.fd - # Based on https://wiki.osdev.org/UEFI#Emulation_with_QEMU_and_OVMF - qemu-system-x86_64 -cpu qemu64 \ - -drive if=pflash,format=raw,unit=0,file=OVMF_CODE.fd,readonly=on \ - -drive if=pflash,format=raw,unit=1,file=OVMF_VARS.fd \ - -net none \ - -drive format=raw,file=fat:rw:out \ - -display type=gtk,zoom-to-fit=on - -# Assuming 'ovmf' package on Arch Linux is installed. -OVMF_CODE.fd: /usr/share/ovmf/x64/OVMF_CODE.fd - cp $< $@ -OVMF_VARS.fd: /usr/share/ovmf/x64/OVMF_VARS.fd - cp $< $@ - -out/main: main.asm - mkdir -p out - fasm $< out/main - -out/hello: - mkdir -p out - echo -e "Hello!\nThis is a test." > out/hello - -.PHONY: clean -clean: - rm -f main -- 2.39.2