diff --git a/README.md b/README.md index 31c2d3c..2458bd1 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ is given, then LLVM CLang + lld used, and native PE is generated, no conversion | `LDFLAGS` | linker flags you want to use (I don't think you'll ever need this, just in case) | | `LIBS` | additional libraries you want to link with (like "-lm", only static .a libraries allowed) | | `USE_LLVM` | set this if you want LLVM Clang + Lld instead of GNU gcc + ld | -| `ARCH` | the target architecture (only x86_64 supported for now, but the toolchain can handle multiple archs) | +| `ARCH` | the target architecture | Here's a more advanced **Makefile** example: ``` @@ -82,6 +82,9 @@ LIBS = -lm USE_LLVM = 1 include uefi/Makefile ``` +The build environment configurator was created in a way that it can handle any number of architectures, however +there's only `x86_64` crt0 implemented for now. There's an experimental `aarch64` crt0, which only compiles with +the LLVM toolchain, GNU ld has some issues with it, saying "unsupported relocation" for ImageBase. Notable Differences to POSIX libc --------------------------------- @@ -94,12 +97,13 @@ All strings in the UEFI environment are stored with 16 bits wide characters. The so for example your main() is NOT like `main(int argc, char **argv)`, but `main(int argc, wchar_t **argv)` instead. All the other string related libc functions (like strlen() for example) use this wide character type too. For this reason, you must specify your string literals with `L""` and characters with `L''`. Functions that supposed to handle characters in int -type (like `getchar`, `putchar`), do not truncate to unsigned char, rather to wchar_t. There's an additional `getchar_ifany` -function, which does not block, but returns 0 when there's no key pressed. +type (like `getchar`, `putchar`), do not truncate to unsigned char, rather to wchar_t. File types in dirent are limited to directories and files only (DT_DIR, DT_REG), but for stat in addition to S_IFDIR and S_IFREG, S_IFIFO also returned (for console streams: stdin, stdout, stderr). +Note that `getenv` and `setenv` aren't POSIX standard, because UEFI environment variables are binary blobs. + That's about it, everything else is the same. List of Provided POSIX Functions @@ -114,7 +118,7 @@ List of Provided POSIX Functions | rewinddir | as usual | | closedir | as usual | -Because UEFI has no concept of devices files nor of symlinks, only DT_DIR and DT_REG supported. +Because UEFI has no concept of device files nor of symlinks, dirent fields are limited and only DT_DIR and DT_REG supported. ### stdlib.h @@ -135,13 +139,30 @@ Because UEFI has no concept of devices files nor of symlinks, only DT_DIR and DT | mbstowcs | as usual (UTF-8 string to wchar_t string) | | wcstombs | as usual (wchar_t string to UTF-8 string) | | srand | as usual | -| rand | as usual, uses EFI_RNG_PROTOCOL if possible | +| rand | as usual, but uses EFI_RNG_PROTOCOL if possible | +| getenv | pretty UEFI specific | +| setenv | pretty UEFI specific | + +```c +int exit_bs() +``` +Exit Boot Services. Returns 0 on success. + +```c +uint8_t *getenv(wchar_t *name, uintn_t *len); +``` +Query the value of environment variable `name`. On success, `len` is set, and a malloc'd buffer returned. It is +the caller's responsibility to free the buffer later. On error returns NULL. +```c +int setenv(wchar_t *name, uintn_t len, uint8_t *data); +``` +Sets an environment variable by `name` with `data` of length `len`. On success returns 1, otherwise 0 on error. ### stdio.h | Function | Description | |---------------|----------------------------------------------------------------------------| -| fopen | as usual, but accepts wide char strings, for mode L"r", L"w" and L"a" only | +| fopen | as usual, but accepts wide char strings, also for mode | | fclose | as usual | | fflush | as usual | | fread | as usual, only real files accepted (no stdin) | @@ -161,7 +182,7 @@ Because UEFI has no concept of devices files nor of symlinks, only DT_DIR and DT | getchar_ifany | non-blocking, returns 0 if there was no key press, UNICODE otherwise | | putchar | as usual, stdout only (no redirects) | -File open modes: `r` read, `w` write, `a` append. Because of UEFI peculiarities, `wd` creates directory. +File open modes: `L"r"` read, `L"w"` write, `L"a"` append. Because of UEFI peculiarities, `L"wd"` creates directory. String formating is limited; only supports padding via number prefixes, `%d`, `%x`, `%X`, `%c`, `%s`, `%q` and `%p`. Because it operates on wchar_t, it also supports the non-standard `%C` (printing an UTF-8 character, needs @@ -247,7 +268,7 @@ Calling UEFI functions is as simple as with EDK II, just do the call, no need fo ``` There are two additional, non-POSIX calls in the library. One is `exit_bs()` to exit Boot Services, and the other is -a non-blocking `getchar_ifany()`. +a non-blocking version `getchar_ifany()`. Unlike gnu-efi, POSIX-UEFI does not pollute your application's namespace with unused GUID variables. It only provides header definitions, so you must create each GUID instance if and when you need them. @@ -267,3 +288,14 @@ naming conflicts. POSIX-UEFI itself ships the very minimum set of typedefs and s #include #include /* this will work as expected! Both POSIX-UEFI and EDK II / gnu-efi typedefs available */ ``` +The advantage of this is that you can use the simplicity of the POSIX-UEFI library and build environment, while getting +access to the most up-to-date protocol and interface definitions at the same time. + +License +------- + +POSIX_UEFI is licensed under the terms of the MIT license. + +Cheers, + +bzt diff --git a/uefi/Makefile b/uefi/Makefile index 78c462d..9b2809b 100644 --- a/uefi/Makefile +++ b/uefi/Makefile @@ -8,12 +8,14 @@ endif SRCS ?= $(wildcard *.c) $(wildcard *.S) TMP = $(SRCS:.c=.o) OBJS = $(TMP:.S=.o) -CFLAGS += -mno-red-zone -fshort-wchar -fno-strict-aliasing -ffreestanding -fno-stack-protector -fno-stack-check \ - -D__$(ARCH)__ -DHAVE_USE_MS_ABI \ - -I/usr/include -I. -I./uefi -I/usr/include/efi -I/usr/include/efi/$(ARCH) -I/usr/include/efi/protocol +CFLAGS += -fshort-wchar -fno-strict-aliasing -ffreestanding -fno-stack-protector -fno-stack-check -I. -I./uefi \ + -I/usr/include -I/usr/include/efi -I/usr/include/efi/protocol -I/usr/include/efi/$(ARCH) -D__$(ARCH)__ +ifeq ($(ARCH),x86_64) +CFLAGS += -DHAVE_USE_MS_ABI -mno-red-zone +endif # for libuefi.a -LIBSRCS = $(filter-out crt_$(ARCH).c,$(wildcard *.c)) $(wildcard *.S) +LIBSRCS = $(filter-out $(wildcard crt_*.c),$(wildcard *.c)) $(wildcard *.S) TMP = $(LIBSRCS:.c=.o) LIBOBJS = $(TMP:.S=.o) @@ -22,7 +24,10 @@ ifeq ($(wildcard /usr/bin/gcc),) USE_LLVM = 1 endif ifeq ($(USE_LLVM),) -CFLAGS += -Wno-builtin-declaration-mismatch -fpic -maccumulate-outgoing-args +ifeq ($(ARCH),x86_64) +CFLAGS += -maccumulate-outgoing-args +endif +CFLAGS += -Wno-builtin-declaration-mismatch -fpic -fPIC LDFLAGS += -nostdlib -shared -Bsymbolic -Luefi uefi/crt_$(ARCH).o LIBS += -o $(TARGET).so -luefi -T uefi/elf_$(ARCH)_efi.lds # see if we're cross-compiling @@ -54,7 +59,7 @@ endif all: $(ALLTARGETS) uefi/libuefi.a: - make --no-print-directory -C uefi libuefi.a USE_LLVM=$(USE_LLVM) + @make --no-print-directory -C uefi libuefi.a USE_LLVM=$(USE_LLVM) ARCH=$(ARCH) libuefi.lib: $(LIBOBJS) diff --git a/uefi/crt_aarch64.c b/uefi/crt_aarch64.c new file mode 100644 index 0000000..337427b --- /dev/null +++ b/uefi/crt_aarch64.c @@ -0,0 +1,195 @@ +/* + * crt_aarch64.c + * + * Copyright (C) 2021 bzt (bztsrc@gitlab) + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * This file is part of the POSIX-UEFI package. + * @brief C runtime, bootstraps an EFI application to call standard main() + * + */ + +#include + +/* this is implemented by the application */ +extern int main(int argc, wchar_t **argv); + +/* definitions for elf relocations */ +typedef uint64_t Elf64_Xword; +typedef int64_t Elf64_Sxword; +typedef uint64_t Elf64_Addr; +typedef struct +{ + Elf64_Sxword d_tag; /* Dynamic entry type */ + union + { + Elf64_Xword d_val; /* Integer value */ + Elf64_Addr d_ptr; /* Address value */ + } d_un; +} Elf64_Dyn; +#define DT_NULL 0 /* Marks end of dynamic section */ +#define DT_RELA 7 /* Address of Rela relocs */ +#define DT_RELASZ 8 /* Total size of Rela relocs */ +#define DT_RELAENT 9 /* Size of one Rela reloc */ +typedef struct +{ + Elf64_Addr r_offset; /* Address */ + Elf64_Xword r_info; /* Relocation type and symbol index */ +} Elf64_Rel; +#define ELF64_R_TYPE(i) ((i) & 0xffffffff) +#define R_AARCH64_RELATIVE 1027 /* Adjust by program base */ + +/* globals to store system table pointers */ +efi_handle_t IM = NULL; +efi_system_table_t *ST = NULL; +efi_boot_services_t *BS = NULL; +efi_runtime_services_t *RT = NULL; +efi_loaded_image_protocol_t *LIP = NULL; + +/* we only need one .o file, so use inline Assembly here */ +void bootstrap() +{ + __asm__ __volatile__ ( + /* call init in C */ + " .align 4\n" +#ifndef __clang__ + " .globl _start\n" + "_start:\n" + " adr x2, ImageBase\n" +/* " mov x2, xzr\n" */ + " adrp x3, _DYNAMIC\n" + " add x3, x3, #:lo12:_DYNAMIC\n" + " bl uefi_init\n" + " ret\n" + + /* fake a relocation record, so that EFI won't complain */ + " .data\n" + "dummy: .long 0\n" + " .section .reloc, \"a\"\n" + "label1:\n" + " .long dummy-label1\n" + " .long 10\n" + " .word 0\n" + ".text\n" +#else + " .globl __chkstk\n" + "__chkstk:\n" + " ret\n" +#endif + + /* setjmp and longjmp */ + " .p2align 3\n" + " .globl setjmp\n" + "setjmp:\n" + " mov x16, sp\n" + " stp x19, x20, [x0, #0]\n" + " stp x21, x22, [x0, #16]\n" + " stp x23, x24, [x0, #32]\n" + " stp x25, x26, [x0, #48]\n" + " stp x27, x28, [x0, #64]\n" + " stp x29, x30, [x0, #80]\n" + " str x16, [x0, #96]\n" + " stp d8, d9, [x0, #112]\n" + " stp d10, d11, [x0, #128]\n" + " stp d12, d13, [x0, #144]\n" + " stp d14, d15, [x0, #160]\n" + " mov w0, #0\n" + " ret\n" + + " .globl longjmp\n" + "longjmp:\n" + " ldp x19, x20, [x0, #0]\n" + " ldp x21, x22, [x0, #16]\n" + " ldp x23, x24, [x0, #32]\n" + " ldp x25, x26, [x0, #48]\n" + " ldp x27, x28, [x0, #64]\n" + " ldp x29, x30, [x0, #80]\n" + " ldr x16, [x0, #96]\n" + " ldp d8, d9, [x0, #112]\n" + " ldp d10, d11, [x0, #128]\n" + " ldp d12, d13, [x0, #144]\n" + " ldp d14, d15, [x0, #160]\n" + " mov sp, x16\n" + " cmp w1, #0\n" + " mov w0, #1\n" + " csel w0, w1, w0, ne\n" + " br x30\n" + ); +} + +/** + * Initialize POSIX-UEFI and call the application's main() function + */ +int uefi_init ( + efi_handle_t image, efi_system_table_t *systab +#ifndef __clang__ + , uintptr_t ldbase, Elf64_Dyn *dyn +#endif +) { + efi_guid_t shpGuid = EFI_SHELL_PARAMETERS_PROTOCOL_GUID; + efi_shell_parameters_protocol_t *shp = NULL; + efi_guid_t shiGuid = SHELL_INTERFACE_PROTOCOL_GUID; + efi_shell_interface_protocol_t *shi = NULL; + efi_guid_t lipGuid = EFI_LOADED_IMAGE_PROTOCOL_GUID; + efi_status_t status; + int argc = 0; + wchar_t **argv = NULL; +#ifndef __clang__ + int i; + long relsz = 0, relent = 0; + Elf64_Rel *rel = 0; + uintptr_t *addr; + /* handle relocations */ + for (i = 0; dyn[i].d_tag != DT_NULL; ++i) { + switch (dyn[i].d_tag) { + case DT_RELA: rel = (Elf64_Rel*)((unsigned long)dyn[i].d_un.d_ptr + ldbase); break; + case DT_RELASZ: relsz = dyn[i].d_un.d_val; break; + case DT_RELAENT: relent = dyn[i].d_un.d_val; break; + default: break; + } + } + if (rel && relent) { + while (relsz > 0) { + if(ELF64_R_TYPE (rel->r_info) == R_AARCH64_RELATIVE) + { addr = (unsigned long *)(ldbase + rel->r_offset); *addr += ldbase; break; } + rel = (Elf64_Rel*) ((char *) rel + relent); + relsz -= relent; + } + } +#endif + /* save EFI pointers and loaded image into globals */ + IM = image; + ST = systab; + BS = systab->BootServices; + RT = systab->RuntimeServices; + BS->HandleProtocol(image, &lipGuid, (void **)&LIP); + /* get command line arguments */ + status = BS->OpenProtocol(image, &shpGuid, (void **)&shp, image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if(!EFI_ERROR(status) && shp) { argc = shp->Argc; argv = shp->Argv; } + else { + /* if shell 2.0 failed, fallback to shell 1.0 interface */ + status = BS->OpenProtocol(image, &shiGuid, (void **)&shi, image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if(!EFI_ERROR(status) && shi) { argc = shi->Argc; argv = shi->Argv; } + } + /* call main */ + return main(argc, argv); +} diff --git a/uefi/crt_x86_64.c b/uefi/crt_x86_64.c index 972b811..d3fb72c 100644 --- a/uefi/crt_x86_64.c +++ b/uefi/crt_x86_64.c @@ -70,59 +70,61 @@ void bootstrap() { __asm__ __volatile__ ( /* call init in C */ - " .align 4;" + " .align 4\n" #ifndef __clang__ - " .globl _start;" - "_start:" - " lea ImageBase(%rip), %rdi;" - " lea _DYNAMIC(%rip), %rsi;" - " call uefi_init;" - " ret;" + " .globl _start\n" + "_start:\n" + " lea ImageBase(%rip), %rdi\n" + " lea _DYNAMIC(%rip), %rsi\n" + " call uefi_init\n" + " ret\n" /* fake a relocation record, so that EFI won't complain */ - " .data;" - "dummy: .long 0;" - " .section .reloc, \"a\";" - "label1:;" - " .long dummy-label1;" - " .long 10;" - " .word 0;" - ".text;" + " .data\n" + "dummy: .long 0\n" + " .section .reloc, \"a\"\n" + "label1:\n" + " .long dummy-label1\n" + " .long 10\n" + " .word 0\n" + ".text\n" #else - " .globl __chkstk;" - "__chkstk:" - " ret;" + " .globl __chkstk\n" + "__chkstk:\n" + " ret\n" #endif + /* setjmp and longjmp */ - " .globl setjmp;" - "setjmp:" - " pop %rsi;" - " movq %rbx,0x00(%rdi);" - " movq %rsp,0x08(%rdi);" - " push %rsi;" - " movq %rbp,0x10(%rdi);" - " movq %r12,0x18(%rdi);" - " movq %r13,0x20(%rdi);" - " movq %r14,0x28(%rdi);" - " movq %r15,0x30(%rdi);" - " movq %rsi,0x38(%rdi);" - " xor %rax,%rax;" - " ret;" - " .globl longjmp;" - "longjmp:" - " movl %esi, %eax;" - " movq 0x00(%rdi), %rbx;" - " movq 0x08(%rdi), %rsp;" - " movq 0x10(%rdi), %rbp;" - " movq 0x18(%rdi), %r12;" - " movq 0x20(%rdi), %r13;" - " movq 0x28(%rdi), %r14;" - " movq 0x30(%rdi), %r15;" - " xor %rdx,%rdx;" - " mov $1,%rcx;" - " cmp %rax,%rdx;" - " cmove %rcx,%rax;" - " jmp *0x38(%rdi);" + " .globl setjmp\n" + "setjmp:\n" + " pop %rsi\n" + " movq %rbx,0x00(%rdi)\n" + " movq %rsp,0x08(%rdi)\n" + " push %rsi\n" + " movq %rbp,0x10(%rdi)\n" + " movq %r12,0x18(%rdi)\n" + " movq %r13,0x20(%rdi)\n" + " movq %r14,0x28(%rdi)\n" + " movq %r15,0x30(%rdi)\n" + " movq %rsi,0x38(%rdi)\n" + " xor %rax,%rax\n" + " ret\n" + + " .globl longjmp\n" + "longjmp:\n" + " movl %esi, %eax\n" + " movq 0x00(%rdi), %rbx\n" + " movq 0x08(%rdi), %rsp\n" + " movq 0x10(%rdi), %rbp\n" + " movq 0x18(%rdi), %r12\n" + " movq 0x20(%rdi), %r13\n" + " movq 0x28(%rdi), %r14\n" + " movq 0x30(%rdi), %r15\n" + " xor %rdx,%rdx\n" + " mov $1,%rcx\n" + " cmp %rax,%rdx\n" + " cmove %rcx,%rax\n" + " jmp *0x38(%rdi)\n" ); } diff --git a/uefi/elf_aarch64_efi.lds b/uefi/elf_aarch64_efi.lds new file mode 100644 index 0000000..836d982 --- /dev/null +++ b/uefi/elf_aarch64_efi.lds @@ -0,0 +1,63 @@ +OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64") +OUTPUT_ARCH(aarch64) +ENTRY(_start) +SECTIONS +{ + .text 0x0 : { + _text = .; + *(.text.head) + *(.text) + *(.text.*) + *(.gnu.linkonce.t.*) + *(.srodata) + *(.rodata*) + . = ALIGN(16); + } + _etext = .; + _text_size = . - _text; + .dynamic : { *(.dynamic) } + .data : ALIGN(4096) + { + _data = .; + *(.sdata) + *(.data) + *(.data1) + *(.data.*) + *(.got.plt) + *(.got) + + /* the EFI loader doesn't seem to like a .bss section, so we stick + it all into .data: */ + . = ALIGN(16); + _bss = .; + *(.sbss) + *(.scommon) + *(.dynbss) + *(.bss) + *(COMMON) + . = ALIGN(16); + _bss_end = .; + } + + .rela.dyn : { *(.rela.dyn) } + .rela.plt : { *(.rela.plt) } + .rela.got : { *(.rela.got) } + .rela.data : { *(.rela.data) *(.rela.data*) } + . = ALIGN(512); + _edata = .; + _data_size = . - _data; + + . = ALIGN(4096); + .dynsym : { *(.dynsym) } + . = ALIGN(4096); + .dynstr : { *(.dynstr) } + . = ALIGN(4096); + .note.gnu.build-id : { *(.note.gnu.build-id) } + /DISCARD/ : + { + *(.rel.reloc) + *(.eh_frame) + *(.note.GNU-stack) + } + .comment 0 : { *(.comment) } +} diff --git a/uefi/stdlib.c b/uefi/stdlib.c index 812a65d..798396c 100644 --- a/uefi/stdlib.c +++ b/uefi/stdlib.c @@ -249,3 +249,25 @@ int rand() ret ^= (int)(__srand_seed>>33); return ret; } + +uint8_t *getenv(wchar_t *name, uintn_t *len) +{ + efi_guid_t globGuid = EFI_GLOBAL_VARIABLE; + uint8_t tmp[EFI_MAXIMUM_VARIABLE_SIZE], *ret; + uint32_t attr; + efi_status_t status = RT->GetVariable(name, &globGuid, &attr, len, &tmp); + if(EFI_ERROR(status) || *len < 1 || !(ret = malloc((*len) + 1))) { + *len = 0; + return NULL; + } + memcpy(ret, tmp, *len); + ret[*len] = 0; + return ret; +} + +int setenv(wchar_t *name, uintn_t len, uint8_t *data) +{ + efi_guid_t globGuid = EFI_GLOBAL_VARIABLE; + efi_status_t status = RT->SetVariable(name, &globGuid, 0, len, data); + return !EFI_ERROR(status); +} diff --git a/uefi/uefi.h b/uefi/uefi.h index f7229c3..0c0ec4f 100644 --- a/uefi/uefi.h +++ b/uefi/uefi.h @@ -213,6 +213,7 @@ typedef struct { uint64_t FP; uint64_t LR; uint64_t IP0; + uint64_t reserved; uint64_t D8; uint64_t D9; uint64_t D10; @@ -608,7 +609,7 @@ typedef efi_status_t (EFIAPI *efi_set_wakeup_time_t)(boolean_t Enable, efi_time_ typedef efi_status_t (EFIAPI *efi_set_virtual_address_map_t)(uintn_t MemoryMapSize, uintn_t DescriptorSize, uint32_t DescriptorVersion, efi_memory_descriptor_t *VirtualMap); typedef efi_status_t (EFIAPI *efi_convert_pointer_t)(uintn_t DebugDisposition, void **Address); -typedef efi_status_t (EFIAPI *efi_get_variable_t)(wchar_t *VariableName, efi_guid_t *VendorGuid, uintn_t *Attributes, +typedef efi_status_t (EFIAPI *efi_get_variable_t)(wchar_t *VariableName, efi_guid_t *VendorGuid, uint32_t *Attributes, uintn_t *DataSize, void *Data); typedef efi_status_t (EFIAPI *efi_get_next_variable_name_t)(uintn_t *VariableNameSize, wchar_t *VariableName, efi_guid_t *VendorGuid); @@ -1246,6 +1247,8 @@ extern int getchar (void); /* non-blocking, only returns UNICODE if there's any key pressed, 0 otherwise */ extern int getchar_ifany (void); extern int putchar (int __c); +extern uint8_t *getenv(wchar_t *name, uintn_t *len); +extern int setenv(wchar_t *name, uintn_t len, uint8_t *data); /* string.h */ #ifndef __clang__