From 53d3be48b7eb81a5ae73a3e9948908c09ef3f295 Mon Sep 17 00:00:00 2001 From: Alon Livne <2005alonlivne@gmail.com> Date: Tue, 23 Sep 2025 15:44:18 +0300 Subject: [PATCH 1/7] feature: fill core header with already found g_rsdp --- uefi/core_header_utils.c | 29 +++++------------------------ 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/uefi/core_header_utils.c b/uefi/core_header_utils.c index 35c7cc5..ee154f5 100644 --- a/uefi/core_header_utils.c +++ b/uefi/core_header_utils.c @@ -3,6 +3,7 @@ #include #include +#include "acpi/tables.h" #include "pci.h" // TODO: This is currently hard coded to a virtio blk device. Make this @@ -13,30 +14,10 @@ static const struct pci_dev_id g_disk_pci_id = {.vendor_id = DISK_PCI_VENDOR_ID, .device_id = DISK_PCI_DEVICE_ID}; /** - * Locates the RSDP in the EFI SystemTable, and fills `core_header.rsdp`. + * Fills `core_header.rsdp` with `g_rsdp`. */ -static err_t fill_rsdp(struct core_header* core_header) { - err_t err = SUCCESS; - static EFI_GUID acpi_20_table_guid = ACPI_20_TABLE_GUID; - void* rsdp = NULL; - - CHECK(ST->ConfigurationTable != NULL); - - for (size_t i = 0; i < ST->NumberOfTableEntries; i++) { - EFI_CONFIGURATION_TABLE* table = &ST->ConfigurationTable[i]; - - if (CompareGuid(&table->VendorGuid, &acpi_20_table_guid) == 0) { - // The RSDP should only appear once. - CHECK(rsdp == NULL); - rsdp = table->VendorTable; - } - } - - CHECK_TRACE(rsdp != NULL, "Unable to find the RSDP\n"); - core_header->rsdp = (uint64_t)(uintptr_t)rsdp; - -cleanup: - return err; +static void fill_rsdp(struct core_header* core_header) { + core_header->rsdp = (uint64_t)(uintptr_t)g_rsdp; } /** @@ -170,7 +151,7 @@ static err_t fill_ram_areas(struct core_header* core_header) { err_t fill_core_header(struct core_header* core_header) { err_t err = SUCCESS; - CHECK_RETHROW(fill_rsdp(core_header)); + fill_rsdp(core_header); CHECK_RETHROW(fill_disk_pci(core_header)); CHECK_RETHROW(fill_ram_areas(core_header)); From 951877eabbef3dd391d4e021223fa79ea625cc6f Mon Sep 17 00:00:00 2001 From: Alon Livne <2005alonlivne@gmail.com> Date: Tue, 23 Sep 2025 16:06:37 +0300 Subject: [PATCH 2/7] feature: add wstrcmp utility --- shared/include/mem.h | 16 +++++++++++++++- uefi/hooks/raw/set_variable/set_variable.c | 16 ++-------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/shared/include/mem.h b/shared/include/mem.h index c604cc6..f92531b 100644 --- a/shared/include/mem.h +++ b/shared/include/mem.h @@ -45,7 +45,21 @@ static inline int strcmp(const char* str1, const char* str2) { while (str1[i] && str2[i]) { if (str1[i] != str2[i]) { - return str1[i] - str2[i]; + break; + } + + i++; + } + + return str1[i] - str2[i]; +} + +static inline int wstrcmp(const wchar_t* str1, const wchar_t* str2) { + size_t i = 0; + + while (str1[i] && str2[i]) { + if (str1[i] != str2[i]) { + break; } i++; diff --git a/uefi/hooks/raw/set_variable/set_variable.c b/uefi/hooks/raw/set_variable/set_variable.c index 1c4d565..bef3205 100644 --- a/uefi/hooks/raw/set_variable/set_variable.c +++ b/uefi/hooks/raw/set_variable/set_variable.c @@ -2,31 +2,19 @@ #include "../headers.h" #include "core/consts.h" +#include "mem.h" #include "trace.h" #define WINDOWS_SLEEP_CHECKPOINT_VARIABLE_NAME (L"SystemSleepCheckpoint") __attribute__((section(".header"))) struct set_variable_hook_header g_hook_header = {}; -static int wchar_cmp(const wchar_t* str1, const wchar_t* str2) { - while (*str1 && *str2) { - if (*str1 != *str2) { - break; - } - - str1++; - str2++; - } - - return *str1 - *str2; -} - EFI_STATUS EFIAPI _start(IN CHAR16* VariableName, IN EFI_GUID* VendorGuid, IN UINT32 Attributes, IN UINTN DataSize, IN VOID* Data) { // The Windows kernel implements a mechanism called "Sleep Checkpoints", which traces the progress of entering sleep // by updating a UEFI variable every few steps in its sleep process. // Some of the writes of this variable occur after updating the waking vector, so we can override it right after. - if (wchar_cmp(VariableName, WINDOWS_SLEEP_CHECKPOINT_VARIABLE_NAME) == 0 && + if (wstrcmp(VariableName, WINDOWS_SLEEP_CHECKPOINT_VARIABLE_NAME) == 0 && *g_hook_header.waking_vector_addr != CORE_MAIN_PHYS_ADDR) { TRACE("Updating the waking vector from the set_variable hook\n"); From 06a69b5afc660a70648b3af88c4d29beb842dc1c Mon Sep 17 00:00:00 2001 From: Alon Livne <2005alonlivne@gmail.com> Date: Tue, 23 Sep 2025 18:17:45 +0300 Subject: [PATCH 3/7] fix: core set waking vector before _PTS --- core/acpi.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/acpi.c b/core/acpi.c index 76b5678..91604d3 100644 --- a/core/acpi.c +++ b/core/acpi.c @@ -41,12 +41,16 @@ void acpi_destroy(void) { } void acpi_return_kernel(void) { + uacpi_prepare_for_sleep_state(UACPI_SLEEP_STATE_S3); + if (g_kernel_waking_vector != 0) { + // This might occur after `uacpi_prepare_for_sleep_state`, since it calls the `_PTS` which is still hooked to + // override the waking vector to core's entry. + // According to the ACPI specs, the `_PTS` should be executed prior to updating the waking vector anyway. uacpi_set_waking_vector(g_kernel_waking_vector, 0); } else { TRACE("Kernel waking vector not set!\n"); } - uacpi_prepare_for_sleep_state(UACPI_SLEEP_STATE_S3); uacpi_enter_sleep_state(UACPI_SLEEP_STATE_S3); } From 6e56b9d05256a72ccd25ff09b71bec150b179ab6 Mon Sep 17 00:00:00 2001 From: Alon Livne <2005alonlivne@gmail.com> Date: Tue, 23 Sep 2025 18:18:09 +0300 Subject: [PATCH 4/7] fix: remove parentheses from const defines --- shared/include/core/consts.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/shared/include/core/consts.h b/shared/include/core/consts.h index d17757e..71b614b 100644 --- a/shared/include/core/consts.h +++ b/shared/include/core/consts.h @@ -1,21 +1,21 @@ #ifndef _INCLUDE_CORE_CONSTS #define _INCLUDE_CORE_CONSTS -#define CORE_HEADER_MAGIC (0x12345678) -#define CORE_DISK_DUMP_MAGIC (0xabcdef12) +#define CORE_HEADER_MAGIC 0x12345678 +#define CORE_DISK_DUMP_MAGIC 0xabcdef12 -#define CORE_PHYS_ADDR (0x41000000) -#define CORE_MAIN_PHYS_ADDR (0x41001000) -#define CORE_MAX_PHYS_MEM_SIZE (0x3000000) +#define CORE_PHYS_ADDR 0x41000000 +#define CORE_MAIN_PHYS_ADDR 0x41001000 +#define CORE_MAX_PHYS_MEM_SIZE 0x3000000 -#define CORE_RM_PHYS_ADDR (0x1000) -#define CORE_MAX_RM_PHYS_MEM_SIZE (0x1000) +#define CORE_RM_PHYS_ADDR 0x1000 +#define CORE_MAX_RM_PHYS_MEM_SIZE 0x1000 -#define CORE_PM_PHYS_ADDR (0x40000000) -#define CORE_MAX_PM_PHYS_MEM_SIZE (0x1000000) +#define CORE_PM_PHYS_ADDR 0x40000000 +#define CORE_MAX_PM_PHYS_MEM_SIZE 0x1000000 #define CORE_MAIN_OFFSET (CORE_MAIN_PHYS_ADDR - CORE_PHYS_ADDR) -#define CORE_WAKEUP_PHYS_ADDR (CORE_RM_PHYS_ADDR) +#define CORE_WAKEUP_PHYS_ADDR CORE_RM_PHYS_ADDR #endif From 0ed80362cbaa620d8780d5214485fc8fb0463950 Mon Sep 17 00:00:00 2001 From: Alon Livne <2005alonlivne@gmail.com> Date: Tue, 23 Sep 2025 18:18:50 +0300 Subject: [PATCH 5/7] feature: add silent checks --- shared/include/error.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/shared/include/error.h b/shared/include/error.h index 5c73228..8d9c6c2 100644 --- a/shared/include/error.h +++ b/shared/include/error.h @@ -20,6 +20,15 @@ typedef enum { } \ } while (0) +#define CHECK_RETHROW_SILENT(expr) \ + do { \ + err_t _err = (expr); \ + if (!IS_SUCCESS(_err)) { \ + err = _err; \ + goto cleanup; \ + } \ + } while (0) + #define CHECK_TRACE(expr, fmt, ...) \ do { \ if (!(expr)) { \ @@ -29,6 +38,14 @@ typedef enum { } \ } while (0) +#define CHECK_SILENT(expr) \ + do { \ + if (!(expr)) { \ + err = ERROR; \ + goto cleanup; \ + } \ + } while (0) + #define IS_SUCCESS(expr) ((expr) == SUCCESS) #define IS_ERROR(expr) ((expr) != SUCCESS) From 21fbdb18b7034f475d375716ca444fcd5146b31e Mon Sep 17 00:00:00 2001 From: Alon Livne <2005alonlivne@gmail.com> Date: Tue, 23 Sep 2025 19:21:40 +0300 Subject: [PATCH 6/7] feature: hook _PTS and overriding waking vector On Linux, the waking vector is set before calling the _PTS (which occurs in the kernel's `acpi_enter_sleep_state_prep`). This commit allows adding a hook on the AML method _PTS, and overriding the waking vector from there to core's entry point. --- .gitignore | 1 + Makefile | 1 + uefi/Makefile | 5 +- uefi/acpi/Makefile | 15 +++++ uefi/acpi/aml.h | 12 ++++ uefi/acpi/asl/pts_hook.asl | 25 ++++++++ uefi/acpi/hook_pts.c | 73 ++++++++++++++++++++++ uefi/acpi/hook_pts.h | 14 +++++ uefi/acpi/patch/dsdt.c | 101 +++++++++++++++++++++++++++++++ uefi/acpi/patch/dsdt.h | 14 +++++ uefi/acpi/patch/method.c | 110 ++++++++++++++++++++++++++++++++++ uefi/acpi/patch/method.h | 35 +++++++++++ uefi/acpi/patch/patch_utils.c | 16 +++++ uefi/acpi/patch/patch_utils.h | 8 +++ uefi/acpi/patch/pkg_length.c | 85 ++++++++++++++++++++++++++ uefi/acpi/patch/pkg_length.h | 39 ++++++++++++ uefi/acpi/tables.h | 1 - uefi/app.c | 4 ++ 18 files changed, 556 insertions(+), 3 deletions(-) create mode 100644 uefi/acpi/Makefile create mode 100644 uefi/acpi/aml.h create mode 100644 uefi/acpi/asl/pts_hook.asl create mode 100644 uefi/acpi/hook_pts.c create mode 100644 uefi/acpi/hook_pts.h create mode 100644 uefi/acpi/patch/dsdt.c create mode 100644 uefi/acpi/patch/dsdt.h create mode 100644 uefi/acpi/patch/method.c create mode 100644 uefi/acpi/patch/method.h create mode 100644 uefi/acpi/patch/patch_utils.c create mode 100644 uefi/acpi/patch/patch_utils.h create mode 100644 uefi/acpi/patch/pkg_length.c create mode 100644 uefi/acpi/patch/pkg_length.h diff --git a/.gitignore b/.gitignore index 09916c3..de4f29a 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ *.mod.c *.bin *.ld.gen +obj/ *~ .*~ diff --git a/Makefile b/Makefile index 4d5111e..1e63f0c 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,7 @@ export VPATH := $(ROOT_DIR) export CC ?= gcc export LD ?= ld export OBJCOPY ?= objcopy +export IASL ?= iasl QEMU ?= qemu-system-x86_64 QEMU_ADDITIONAL_FLAGS ?= diff --git a/uefi/Makefile b/uefi/Makefile index 2fc6143..ae1d779 100644 --- a/uefi/Makefile +++ b/uefi/Makefile @@ -4,6 +4,7 @@ CFLAGS := -I$(GNU_EFI)/inc \ -fpic -ffreestanding -fno-stack-protector \ -fno-stack-check -fshort-wchar -mno-red-zone -maccumulate-outgoing-args \ -I . \ + -I obj/include \ -I $(ROOT_DIR)/shared/include/ \ -D GNU_EFI_USE_MS_ABI \ -D UEFI @@ -31,7 +32,7 @@ OBJS += app.o OBJS += core_header_utils.o OBJS += core_loader.o -OBJS += acpi/tables.o +include acpi/Makefile OBJS += hooks/hooks_loader.o OBJS += hooks/loaders/get_memory_map.o @@ -54,7 +55,7 @@ hooks/raw/build/hooks.o: obj/%.o: %.c mkdir -p $(dir $@) - $(CC) -c $^ -o $@ $(CFLAGS) + $(CC) -c $< -o $@ $(CFLAGS) obj/app.so: $(OBJS) $(GNU_EFI)/x86_64/gnuefi/crt0-efi-x86_64.o hooks/raw/build/hooks.o mkdir -p $(dir $@) diff --git a/uefi/acpi/Makefile b/uefi/acpi/Makefile new file mode 100644 index 0000000..639d484 --- /dev/null +++ b/uefi/acpi/Makefile @@ -0,0 +1,15 @@ +OBJS += acpi/tables.o +OBJS += acpi/hook_pts.o +OBJS += acpi/patch/dsdt.o +OBJS += acpi/patch/method.o +OBJS += acpi/patch/patch_utils.o +OBJS += acpi/patch/pkg_length.o + +IASL_FLAGS := -I $(ROOT_DIR)/shared/include \ + -sc -so + +obj/include/asl/%.c: acpi/asl/%.asl + @mkdir -p $(dir $@) + $(IASL) $(IASL_FLAGS) -p $(dir $@)$* $^ + +obj/acpi/hook_pts.o: obj/include/asl/pts_hook.c diff --git a/uefi/acpi/aml.h b/uefi/acpi/aml.h new file mode 100644 index 0000000..3f8979b --- /dev/null +++ b/uefi/acpi/aml.h @@ -0,0 +1,12 @@ +#ifndef _ACPI_AML_H +#define _ACPI_AML_H + +#define AML_ZERO_OP 0x0 +#define AML_METHOD_OP 0x14 +#define AML_RETURN_OP 0xA4 +#define AML_ARG0_OP 0x68 + +#define AML_METHOD_NAME_LEN 4 +#define AML_METHOD_FLAGS_LEN 1 + +#endif diff --git a/uefi/acpi/asl/pts_hook.asl b/uefi/acpi/asl/pts_hook.asl new file mode 100644 index 0000000..0ec2ccf --- /dev/null +++ b/uefi/acpi/asl/pts_hook.asl @@ -0,0 +1,25 @@ +#include "core/consts.h" + +DefinitionBlock ("", "SSDT", 2, "", "", 0x0) +{ + Method (_PTS, 1, NotSerialized) + { + OperationRegion (FACS, SystemMemory, 0x12345678, 64) + Field (FACS, AnyAcc, NoLock, Preserve) + { + Offset (12), + FWAK, 32, + } + + // Overwrite the waking vector with core's entry. + FWAK = CORE_RM_PHYS_ADDR + + SPTS(arg0) + } + + Method (SPTS, 1, NotSerialized) + { + // This function is a placeholder, and won't be loaded when hooking the `_PTS`. + // In case `_PTS` already exists, it will be renamed to `SPTS` and our hook will call it. + } +} diff --git a/uefi/acpi/hook_pts.c b/uefi/acpi/hook_pts.c new file mode 100644 index 0000000..c7c974f --- /dev/null +++ b/uefi/acpi/hook_pts.c @@ -0,0 +1,73 @@ +#include "hook_pts.h" + +#include + +#include "acpi/aml.h" +#include "acpi/patch/pkg_length.h" +#include "asl/pts_hook.c" +#include "asl/pts_hook.offset.h" +#include "mem.h" +#include "patch/method.h" +#include "tables.h" +#include "utils.h" + +/** + * Relocates the `FACS` OperationRegion in the `_PTS` hook to point to the physical address of the real FACS table. + * + * See the hook `pts_hook.asl` for the OperationRegion declaration, and see the generated `pts_hook.offset.h` for the + * format offsets table. + */ +static err_t pts_relocate_facs_addr(void) { + err_t err = SUCCESS; + + // The offsets table in `pts_hook.offset.h` includes an entry for the `FACS` OperationRegion called `_PTS.FACS`. + // It includes the offset to the address of the `FACS` OperationRegion which we wish to relocate. + size_t facs_addr_reloc_offset = -1; + for (size_t i = 0; i < ARRAY_SIZE(SSDT__OffsetTable); i++) { + if (strcmp(SSDT__OffsetTable[i].Pathname, "_PTS.FACS") == 0) { + // The offset is from the beginning of the AML program, but we need the offset from the `SSDT___PTS_FACS` byte + // array. It comes right after the `SSDT__Header` and `SSDT____PTS`. + facs_addr_reloc_offset = SSDT__OffsetTable[i].Offset - sizeof(SSDT__Header) - sizeof(SSDT___PTS); + } + } + + CHECK(facs_addr_reloc_offset != -1); + // Make sure we don't overflow. + CHECK(facs_addr_reloc_offset + sizeof(uint32_t) <= sizeof(SSDT___PTS_FACS)); + + // Relocate the address to the real FACS table. + uint32_t* facs_addr_reloc = (uint32_t*)(SSDT___PTS_FACS + facs_addr_reloc_offset); + *facs_addr_reloc = (uint32_t)(uintptr_t)g_facs; + +cleanup: + return err; +} + +err_t create_or_hook_pts(void) { + err_t err = SUCCESS; + + CHECK_RETHROW(pts_relocate_facs_addr()); + + if (find_method("_PTS", NULL, NULL) == SUCCESS) { + // Hook the `_PTS` method by renaming it to `SPTS`, and creating a new `_PTS` method which calls the renamed `SPTS`. + // See the code in `pts_hook.asl` for its implementation. + CHECK_RETHROW( + hook_method("_PTS", "SPTS", SSDT___PTS, sizeof(SSDT___PTS), SSDT___PTS_FACS, sizeof(SSDT___PTS_FACS))); + } else { + TRACE("Creating a _PTS method\n"); + + // The hook in `pts_hook.asl` ends with a call to `SPTS` (the original `_PTS`). This is only necessary in the case + // we hook an existing `_PTS`. Otherwise we have to get rid of this call. + // This call is encoded as: + // 0x53, 0x50, 0x54, 0x53, 0x68 /* A call to `SPTS` with `arg0` as an argument */ + // We could truncate the hook's body by 5 bytes, but this would also require fixing its PkgLength. + // Instead, we can replace these bytes with ZeroOps. + size_t call_spts_byte_count = AML_METHOD_NAME_LEN + 1; + memset(SSDT___PTS_FACS + sizeof(SSDT___PTS_FACS) - call_spts_byte_count, AML_ZERO_OP, call_spts_byte_count); + + CHECK_RETHROW(append_method(SSDT___PTS, sizeof(SSDT___PTS), SSDT___PTS_FACS, sizeof(SSDT___PTS_FACS))); + } + +cleanup: + return err; +} diff --git a/uefi/acpi/hook_pts.h b/uefi/acpi/hook_pts.h new file mode 100644 index 0000000..2f5443a --- /dev/null +++ b/uefi/acpi/hook_pts.h @@ -0,0 +1,14 @@ +#pragma once + +#include "error.h" + +/** + * Places a hook on the `_PTS` method if it exists, or creates a new `_PTS` with the hook body if it doesn't exist. + * + * If the `_PTS` method already exists, it is renamed to `SPTS` and our hook calls it when it's done. + * + * The hook content is taken from `pts_hook.asl`. Notice it always tries to call `SPTS`. We explicitly remove this call + * if the `_PTS` doesn't exist (in this case the hook becomes the actual `_PTS` method, and the `SPTS` method does not + * exist). + */ +err_t create_or_hook_pts(void); diff --git a/uefi/acpi/patch/dsdt.c b/uefi/acpi/patch/dsdt.c new file mode 100644 index 0000000..2040e95 --- /dev/null +++ b/uefi/acpi/patch/dsdt.c @@ -0,0 +1,101 @@ +#include "dsdt.h" + +#include +#include +#include +#include + +#include "acpi/tables.h" +#include "mem.h" +#include "patch_utils.h" +#include "utils.h" + +/// Whether the DSDT behind `g_dsdt` is a patched one (which was allocated by `alloc_patched_dsdt`). +static bool g_is_dsdt_patched = false; + +/** + * Allocates a new DSDT table in an EFI reserved memory area. + * + * The DSDT table is allocated in the lower 32 bit physical memory, to ensure `FADT->dsdt` can be used instead of + * `FADT->x-dsdt`. + */ +static err_t alloc_patched_dsdt(size_t size, struct acpi_dsdt** patched_dsdt_out) { + err_t err = SUCCESS; + + struct acpi_dsdt* patched_dsdt = (struct acpi_dsdt*)UINT32_MAX; + CHECK(uefi_call_wrapper(BS->AllocatePages, 4, AllocateMaxAddress, EfiReservedMemoryType, + ALIGN_UP(size, EFI_PAGE_SIZE), (uintptr_t*)&patched_dsdt) == EFI_SUCCESS); + CHECK(patched_dsdt != NULL); + + *patched_dsdt_out = patched_dsdt; + +cleanup: + return err; +} + +/** + * Frees the DSDT table behind `g_dsdt` which was allocated with `alloc_patched_dsdt`. + * The caller must ensure that the DSDT was allocated with `alloc_patched_dsdt`, and isn't the original DSDT. + */ +static err_t free_patched_dsdt(void) { + err_t err = SUCCESS; + + CHECK_TRACE(g_is_dsdt_patched, "The current g_dsdt table is not patched\n"); + + size_t patched_dsdt_size = g_dsdt->hdr.length; + CHECK(uefi_call_wrapper(BS->FreePages, 2, (uintptr_t)g_dsdt, ALIGN_UP(patched_dsdt_size, EFI_PAGE_SIZE)) == + EFI_SUCCESS); + +cleanup: + return err; +} + +/** + * Fixes the FADT table to point to a new allocated DSDT table. + */ +static err_t redirect_dsdt(struct acpi_dsdt* new_dsdt) { + err_t err = SUCCESS; + + CHECK(g_fadt != NULL); + + g_fadt->x_dsdt = (uint64_t)(uintptr_t)new_dsdt; + g_fadt->dsdt = (uint32_t)(uintptr_t)new_dsdt; + + fix_table_checksum((struct acpi_table_header*)g_fadt); + +cleanup: + return err; +} + +err_t append_dsdt(const uint8_t* aml, size_t aml_size) { + err_t err = SUCCESS; + + // Allocate a larger DSDT. + size_t patched_dsdt_size = g_dsdt->hdr.length + aml_size; + struct acpi_dsdt* patched_dsdt = NULL; + CHECK_RETHROW(alloc_patched_dsdt(patched_dsdt_size, &patched_dsdt)); + + // Copy the DSDT's original content, and update its length. + memcpy(patched_dsdt, g_dsdt, g_dsdt->hdr.length); + patched_dsdt->hdr.length = patched_dsdt_size; + + // Append the AML to the end of the new DSDT. + memcpy(patched_dsdt->definition_block + DSDT_AML_SIZE(*g_dsdt), aml, aml_size); + + // Fix the table's checksum after modifying it. + fix_table_checksum((struct acpi_table_header*)patched_dsdt); + + // Override the FADT to point to the newly allocated DSDT. + CHECK_RETHROW(redirect_dsdt(patched_dsdt)); + + if (g_is_dsdt_patched) { + // Free the previously patched DSDT, which was allocated the same way. + CHECK_RETHROW(free_patched_dsdt()); + } + + g_dsdt = patched_dsdt; + g_is_dsdt_patched = true; + +cleanup: + return err; +} diff --git a/uefi/acpi/patch/dsdt.h b/uefi/acpi/patch/dsdt.h new file mode 100644 index 0000000..43495cd --- /dev/null +++ b/uefi/acpi/patch/dsdt.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +#include "error.h" + +/** + * Appends AML code to the end of the DSDT table. + * + * This function reallocates a new larger DSDT to make space for the new AML code. + * It deals with fixing the DSDT's checksum, and fixing the FADT table to point to the new DSDT table. + */ +err_t append_dsdt(const uint8_t* aml, size_t aml_size); diff --git a/uefi/acpi/patch/method.c b/uefi/acpi/patch/method.c new file mode 100644 index 0000000..e9818a8 --- /dev/null +++ b/uefi/acpi/patch/method.c @@ -0,0 +1,110 @@ +#include "method.h" + +#include "acpi/aml.h" +#include "acpi/tables.h" +#include "dsdt.h" +#include "mem.h" +#include "patch_utils.h" + +err_t find_method(const char* name, size_t* start_out, struct acpi_pkg_length* pkg_length_out) { + err_t err = SUCCESS; + + const uint8_t* aml = g_dsdt->definition_block; + size_t aml_size = DSDT_AML_SIZE(*g_dsdt); + + size_t method_start = -1; + struct acpi_pkg_length pkg_length; + + // Scan the DSDT for an AML method declaration for `name`. + // Method declarations are encoded as follows: + // DefMethod := MethodOp PkgLength NameString ... + // So we need to locate the MethodOp, PkgLength and NameString (which is `name`) sequentially. + for (size_t i = 0; i < aml_size; i++) { + if (aml[i] != AML_METHOD_OP) { + continue; + } + + // `parse_pkg_length` deals with the case we overflow beyond `aml_size`. + if (parse_pkg_length(aml + i + 1, aml_size - i - 1, &pkg_length) != SUCCESS) { + continue; + } + + // This looks like a method declaration beginning at `i`. Check whether the declared method has the expected name. + + // Make sure we don't overflow. + CHECK(i + 1 + pkg_length.encoded_size + AML_METHOD_NAME_LEN <= aml_size); + if (memcmp((const char*)&aml[i + 1 + pkg_length.encoded_size], name, AML_METHOD_NAME_LEN) != 0) { + continue; + } + + CHECK_TRACE(method_start == -1, "Method %s found twice\n", name); + method_start = i; + } + + CHECK_TRACE(method_start != -1, "Method %s not found\n", name); + + if (start_out) { + *start_out = method_start; + } + if (pkg_length_out) { + *pkg_length_out = pkg_length; + } + +cleanup: + return err; +} + +err_t append_method(const uint8_t* hook_header_aml, size_t hook_header_size, const uint8_t* hook_body_aml, + size_t hook_body_size) { + err_t err = SUCCESS; + + CHECK_RETHROW(append_dsdt(hook_header_aml, hook_header_size)); + CHECK_RETHROW(append_dsdt(hook_body_aml, hook_body_size)); + +cleanup: + return err; +} + +/** + * Renames an AML method in the DSDT. + * + * Since all AML methods have the same length, this doesn't create a new DSDT. + */ +static err_t rename_method(const char* original_name, const char* modified_name) { + err_t err = SUCCESS; + + CHECK(strlen(original_name) == AML_METHOD_NAME_LEN); + CHECK(strlen(modified_name) == AML_METHOD_NAME_LEN); + + size_t method_start = -1; + struct acpi_pkg_length method_pkg_length; + CHECK_RETHROW(find_method(original_name, &method_start, &method_pkg_length)); + + // The method declaration is encoded as: + // DefMethod := MethodOp PkgLength NameString ... + size_t method_name_offset = method_start + 1 + method_pkg_length.encoded_size; + + // Make sure we don't overflow when modifying the name. + CHECK(method_name_offset + AML_METHOD_NAME_LEN <= DSDT_AML_SIZE(*g_dsdt)); + + memcpy(g_dsdt->definition_block + method_name_offset, modified_name, AML_METHOD_NAME_LEN); + + fix_table_checksum((struct acpi_table_header*)g_dsdt); + +cleanup: + return err; +} + +err_t hook_method(const char* name, const char* modified_name, const uint8_t* hook_header_aml, size_t hook_header_size, + const uint8_t* hook_body_aml, size_t hook_body_size) { + err_t err = SUCCESS; + + CHECK_RETHROW(rename_method(name, modified_name)); + + // Append the hook function. It is assumed to be named `name`, and contain a call to the original hook if needed by + // calling `modified_name`. + CHECK_RETHROW(append_method(hook_header_aml, hook_header_size, hook_body_aml, hook_body_size)); + +cleanup: + return err; +} diff --git a/uefi/acpi/patch/method.h b/uefi/acpi/patch/method.h new file mode 100644 index 0000000..acc7285 --- /dev/null +++ b/uefi/acpi/patch/method.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +#include "error.h" +#include "pkg_length.h" + +/** + * Locates an AML method by name in the DSDT. + * + * @param start_out - The offset to the AML method opcode that declares this method. + * @param pkg_length_out - The parsed PkgLength object in the method's declaration. + * @return - Success if the method was found and parsed, or a failure otherwise. + */ +err_t find_method(const char* name, size_t* start_out, struct acpi_pkg_length* pkg_length_out); + +/** + * Appends a method to the end of the DSDT table. + * + * This function accepts the method's header and body. This is to be compliant with IASL's AML output. + */ +err_t append_method(const uint8_t* hook_header_aml, size_t hook_header_size, const uint8_t* hook_body_aml, + size_t hook_body_size); + +/** + * Hooks an existing AML method in the DSDT. + * + * The hook is performed by renaming the AML method to `modified_name`, and appending a new method with the original + * name. + * The appended method now becomes the real method, and it can decide whether to call the original method by calling + * `modified_name`. + */ +err_t hook_method(const char* name, const char* modified_name, const uint8_t* hook_header_aml, size_t hook_header_size, + const uint8_t* hook_body_aml, size_t hook_body_size); diff --git a/uefi/acpi/patch/patch_utils.c b/uefi/acpi/patch/patch_utils.c new file mode 100644 index 0000000..1f702a0 --- /dev/null +++ b/uefi/acpi/patch/patch_utils.c @@ -0,0 +1,16 @@ +#include "patch_utils.h" + +#include + +void fix_table_checksum(struct acpi_table_header* table) { + // Reset the checksum field before calculating the checksum, so the previous checksum isn't counted. + table->checksum = 0; + + uint8_t checksum = 0; + for (size_t i = 0; i < table->length; i++) { + checksum += ((uint8_t*)table)[i]; + } + + // The checksum must fulfill `table->checksum + CHECKSUM(table) == 0`. + table->checksum = -checksum; +} diff --git a/uefi/acpi/patch/patch_utils.h b/uefi/acpi/patch/patch_utils.h new file mode 100644 index 0000000..aa2310d --- /dev/null +++ b/uefi/acpi/patch/patch_utils.h @@ -0,0 +1,8 @@ +#pragma once + +#include "../tables.h" + +/** + * Fixes an ACPI table's checksum after modifying it. + */ +void fix_table_checksum(struct acpi_table_header* table); diff --git a/uefi/acpi/patch/pkg_length.c b/uefi/acpi/patch/pkg_length.c new file mode 100644 index 0000000..a362e7f --- /dev/null +++ b/uefi/acpi/patch/pkg_length.c @@ -0,0 +1,85 @@ +#include "pkg_length.h" + +#include "mem.h" + +err_t build_pkg_length(size_t size, struct acpi_pkg_length* pkg_length_out) { + err_t err = SUCCESS; + + CHECK_TRACE(size <= MAX_PKG_LENGTH_SIZE, "The given size for the PkgLength is too large\n"); + + if (size <= 63) { + // A PkgLeadByte can solely represent PkgLengths up to size 63. + pkg_length_out->encoded_size = 1; + pkg_length_out->aml[0] = size; + } else { + // Additional bytes need to follow the PkgLeadByte to encode packages longer than 63. + + // Bits 0-3 of the PkgLeadByte represent the 4 LSBs of the size. + pkg_length_out->aml[0] = size & 0b1111; + size >>= 4; + + // Each following byte encodes the next 8 LSBs of the size. + size_t byte_count = 1; + while (size != 0) { + byte_count++; + CHECK(byte_count <= MAX_PKG_LENGTH_ENCODED_SIZE); + pkg_length_out->aml[byte_count - 1] = size & 0xff; + size >>= 8; + } + + // Bits 6-7 of the PkgLeadByte represent the number of following bytes. + pkg_length_out->aml[0] = (byte_count - 1) << 6; + + pkg_length_out->encoded_size = byte_count; + } + + pkg_length_out->size = size; + +cleanup: + return err; +} + +err_t parse_pkg_length(const uint8_t* aml, size_t aml_size, struct acpi_pkg_length* pkg_length_out) { + err_t err = SUCCESS; + + CHECK_SILENT(aml_size > 0); + + uint8_t pkg_lead_byte = aml[0]; + if (pkg_lead_byte <= 63) { + // If the PkgLeadByte is less than 64, the PkgLength is represented solely by it. + pkg_length_out->encoded_size = 1; + pkg_length_out->size = pkg_lead_byte; + } else { + // Additional bytes need to follow the PkgLeadByte to encode packages longer than 63. + + // Bits 4-5 in the PkgLeadByte are reserved and must be 0, + CHECK_SILENT((pkg_lead_byte & 0b110000) == 0); + + // Bits 0-3 of the PkgLeadByte represent the 4 LSBs of the size. + uint8_t pkg_length_nybble = pkg_lead_byte & 0b1111; + // Bits 6-7 of the PkgLeadByte represent the number of following bytes. + uint8_t bytedata_count = (pkg_lead_byte & 0b11000000) >> 6; + + // Bytedata count must be between 0-3. + CHECK_SILENT(bytedata_count <= 3); + // Make sure we won't overflow. + CHECK_SILENT(bytedata_count <= aml_size); + + // Each following byte encodes the next 8 LSBs of the size. Concatenate the next `bytedata_count` bytes into the + // final size. + size_t size = pkg_length_nybble; + for (size_t i = 0; i < bytedata_count; i++) { + size |= (aml[i + 1] << ((8 * i) + 4)); + } + + // The encoded size of the PkgLength is the lead byte and the bytedatas. + pkg_length_out->encoded_size = bytedata_count + 1; + pkg_length_out->size = size; + } + + memset(pkg_length_out->aml, 0, sizeof(pkg_length_out->aml)); + memcpy(pkg_length_out->aml, aml, pkg_length_out->encoded_size); + +cleanup: + return err; +} diff --git a/uefi/acpi/patch/pkg_length.h b/uefi/acpi/patch/pkg_length.h new file mode 100644 index 0000000..5f4e9d0 --- /dev/null +++ b/uefi/acpi/patch/pkg_length.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +#include "error.h" + +// A PkgLength is made of a PkgLeadByte and up to 3 additional bytes. Therefore, it can only span up to 4 bytes. +#define MAX_PKG_LENGTH_ENCODED_SIZE 4 + +#define MAX_PKG_LENGTH_SIZE (1 << 28) + +// This struct represents a parsed AML PkgLength object. +struct acpi_pkg_length { + /// The package length represented by this PkgLength object. Bounded by `MAX_PKG_LENGTH_SIZE`. + size_t size; + + /// The number of bytes this PkgLength object spans across. Bounded by `MAX_PKG_LENGTH_ENCODED_SIZE`. + /// Can be used as the array size to the `aml` field. + size_t encoded_size; + + /// The AML encoding of this PkgLength object. + uint8_t aml[MAX_PKG_LENGTH_ENCODED_SIZE]; +}; + +/** + * Builds a new PkgLength object that represents a package of length `size`. + */ +err_t build_pkg_length(size_t size, struct acpi_pkg_length* pkg_length_out); + +/** + * Parses AML code that encodes a PkgLength object into a `acpi_pkg_length` struct. + * + * Returns an error if the AML code doesn't represent a valid PkgLength object. + * + * NOTE: the `CHECKS` in this function are silent, so it can be used to locate AML code that represents a PkgLength + * without spamming traces. + */ +err_t parse_pkg_length(const uint8_t* aml, size_t aml_size, struct acpi_pkg_length* pkg_length_out); diff --git a/uefi/acpi/tables.h b/uefi/acpi/tables.h index 2698e23..09f1c5c 100644 --- a/uefi/acpi/tables.h +++ b/uefi/acpi/tables.h @@ -113,7 +113,6 @@ struct acpi_fadt { uint64_t hypervisor_vendor_identity; } __attribute__((packed)); -#define ACPI_GLOBAL_LOCK_OWNED (1 << 1) struct acpi_facs { char signature[4]; uint32_t length; diff --git a/uefi/app.c b/uefi/app.c index 62f9da7..bd4bc30 100644 --- a/uefi/app.c +++ b/uefi/app.c @@ -3,6 +3,7 @@ #include #include +#include "acpi/hook_pts.h" #include "acpi/tables.h" #include "core_header_utils.h" #include "core_loader.h" @@ -24,6 +25,9 @@ efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE* SystemTable) { TRACE("Hooking UEFI services...\n"); CHECK_RETHROW(hook_services()); + TRACE("Hooking the _PTS aml method...\n"); + CHECK_RETHROW(create_or_hook_pts()); + TRACE("Filling core header...\n"); CHECK_RETHROW(fill_core_header(core_header)); From 5425b7176d80ade5e17f089cfc30b9b18a04a00c Mon Sep 17 00:00:00 2001 From: Alon Livne <2005alonlivne@gmail.com> Date: Tue, 23 Sep 2025 19:28:16 +0300 Subject: [PATCH 7/7] fix: install iasl in ci --- .github/workflows/build.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 4b839fd..d10ad4e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -33,6 +33,9 @@ jobs: wget ${{ matrix.linux-version.deb.all }} wget ${{ matrix.linux-version.deb.generic }} sudo apt install -y ./*.deb + - name: Install dependencies + run: | + sudo apt install -y iasl - name: Install GNU-EFI run: | git clone https://git.code.sf.net/p/gnu-efi/code ${{ github.workspace }}/gnu-efi