Is it allowed for a single access to span the bounary between 0
and 0xFFFFFF...
in x861?
For example given that eax
This is not really a new answer but was too big for a comment. This is @prl's code converted so that it should run with the basic gnu-efi
package available on many Linux distros. File wraptest.c
:
#include <efi.h>
#include <efiapi.h>
#include <efilib.h>
#include <inttypes.h>
#include <stdint.h>
uint8_t *p = (uint8_t *)0xfffffffffffffffcULL;
EFI_STATUS
EFIAPI
efi_main (EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
{
uint64_t cr3;
InitializeLib(ImageHandle, SystemTable);
asm("mov %%cr3, %0" : "=r"(cr3));
uint64_t *pml4 = (uint64_t *)(cr3 & ~0xfffULL);
Print(L"cr3 %lx\n", cr3);
Print(L"pml4[0] %lx\n", pml4[0]);
uint64_t *pdpt = (uint64_t *)(pml4[0] & ~0xfffULL);
Print(L"pdpt[0] %lx\n", pdpt[0]);
if (!(pdpt[0] & 1)) {
uefi_call_wrapper(BS->AllocatePages, 4, AllocateAnyPages, \
EfiBootServicesData, 1, &pdpt[0]);
pdpt[0] |= 0x03;
Print(L"pdpt[0] %lx\n", pdpt[0]);
}
uint64_t *pd = (uint64_t *)(pdpt[0] & ~0xfffULL);
Print(L"pd[0] %lx\n", pd[0]);
if (!(pd[0] & 1)) {
uefi_call_wrapper(BS->AllocatePages, 4, AllocateAnyPages, \
EfiBootServicesData, 1, &pd[0]);
pd[0] |= 0x03;
Print(L"pd[0] %lx\n", pd[0]);
}
if (!(pd[0] & 0x80)) {
uint64_t *pt = (uint64_t *)(pd[0] & ~0xfffULL);
Print(L"pt[0] %lx\n", pt[0]);
if (!(pt[0] & 1)) {
uefi_call_wrapper(BS->AllocatePages, 4, AllocateAnyPages, \
EfiBootServicesData, 1, &pt[0]);
pt[0] |= 0x03;
Print(L"pt[0] %lx\n", pt[0]);
}
}
Print(L"[0] = %08x\n", *(uint32_t *)(p+4));
Print(L"pml4[0x1ff] %lx\n", pml4[0x1ff]);
if (pml4[0x1ff] == 0) {
uint64_t *pt;
uefi_call_wrapper(BS->AllocatePages, 4, AllocateAnyPages, \
EfiBootServicesData, 4, &pt);
uint64_t x = (uint64_t)pt;
Print(L"pt = %lx\n", pt);
pml4[0x1ff] = x | 0x3;
pt[0x1ff] = (x + 0x1000) | 0x3;
pt[0x3ff] = (x + 0x2000) | 0x3;
pt[0x5ff] = (x + 0x3000) | 0x3;
*(uint32_t *)p = 0xabcdabcd;
*(uint32_t *)(p + 4) = 0x12341234;
Print(L"[0] = %08x\n", *(uint32_t *)(p+4));
Print(L"[fffffffffffc] = %08x\n", *(uint32_t *)(x + 0x3ffc));
/* This write should place 0x5678 in the last 16-bit word of memory
* and 0x5678 at the first 16-bit word in memory. If the wrapping
* works as expected p[0] should be 0x5678ABCD and
* p[1] should be 0x12345678 when displayed. */
*(uint32_t *)(p + 2) = 0x56785678;
Print(L"p[0] = %08x\n", ((uint32_t *)p)[0]);
Print(L"p[1] = %08x\n", ((uint32_t *)p)[1]);
}
return 0;
}
A Makefile
that should work on 64-bit Ubuntu and 64-bit Debian could look like this:
ARCH ?= $(shell uname -m | sed s,i[3456789]86,ia32,)
ifneq ($(ARCH),x86_64)
LIBDIR = /usr/lib32
else
LIBDIR = /usr/lib
endif
OBJS = wraptest.o
TARGET = wraptest.efi
EFIINC = /usr/include/efi
EFIINCS = -I$(EFIINC) -I$(EFIINC)/$(ARCH) -I$(EFIINC)/protocol
LIB = $(LIBDIR)
EFILIB = $(LIBDIR)
EFI_CRT_OBJS = $(EFILIB)/crt0-efi-$(ARCH).o
EFI_LDS = $(EFILIB)/elf_$(ARCH)_efi.lds
CFLAGS = $(EFIINCS) -fno-stack-protector -fpic \
-fshort-wchar -mno-red-zone -Wall -O3
ifeq ($(ARCH),x86_64)
CFLAGS += -DEFI_FUNCTION_WRAPPER
endif
LDFLAGS = -nostdlib -znocombreloc -T $(EFI_LDS) -shared \
-Bsymbolic -L $(EFILIB) -L $(LIB) $(EFI_CRT_OBJS)
all: $(TARGET)
wraptest.so: $(OBJS)
ld $(LDFLAGS) $(OBJS) -o $@ -lefi -lgnuefi
%.efi: %.so
objcopy -j .text -j .sdata -j .data -j .dynamic \
-j .dynsym -j .rel -j .rela -j .reloc \
--target=efi-app-$(ARCH) $^ $@
The code as written will only work properly if compiled for x86-64. You can make this EFI application with the command:
make ARCH=x86_64
The resulting file should be wraptest.efi
that can be copied to your EFI System Partition. The make file was based on Roderick Smith's tutorial
I just tested with this EFI program. (And it worked, as expected.) If you want to reproduce this result, you would need an implementation of efi_printf, or another way to view the result.
#include <stdint.h>
#include "efi.h"
uint8_t *p = (uint8_t *)0xfffffffffffffffcULL;
int main()
{
uint64_t cr3;
asm("mov %%cr3, %0" : "=r"(cr3));
uint64_t *pml4 = (uint64_t *)(cr3 & ~0xfffULL);
efi_printf("cr3 %lx\n", cr3);
efi_printf("pml4[0] %lx\n", pml4[0]);
uint64_t *pdpt = (uint64_t *)(pml4[0] & ~0xfffULL);
efi_printf("pdpt[0] %lx\n", pdpt[0]);
if (!(pdpt[0] & 1)) {
pdpt[0] = (uint64_t)efi_alloc_pages(EFI_BOOT_SERVICES_DATA, 1) | 0x03;
efi_printf("pdpt[0] %lx\n", pdpt[0]);
}
uint64_t *pd = (uint64_t *)(pdpt[0] & ~0xfffULL);
efi_printf("pd[0] %lx\n", pd[0]);
if (!(pd[0] & 1)) {
pd[0] = (uint64_t)efi_alloc_pages(EFI_BOOT_SERVICES_DATA, 1) | 0x03;
efi_printf("pd[0] %lx\n", pd[0]);
}
if (!(pd[0] & 0x80)) {
uint64_t *pt = (uint64_t *)(pd[0] & ~0xfffULL);
efi_printf("pt[0] %lx\n", pt[0]);
if (!(pt[0] & 1)) {
pt[0] = (uint64_t)efi_alloc_pages(EFI_BOOT_SERVICES_DATA, 1) | 0x03;
efi_printf("pt[0] %lx\n", pt[0]);
}
}
efi_printf("[0] = %08x\n", *(uint32_t *)(p+4));
efi_printf("pml4[0x1ff] %lx\n", pml4[0x1ff]);
if (pml4[0x1ff] == 0) {
uint64_t *pt = (uint64_t *)efi_alloc_pages(EFI_BOOT_SERVICES_DATA, 4);
uint64_t x = (uint64_t)pt;
efi_printf("pt = %p\n", pt);
pml4[0x1ff] = x | 0x3;
pt[0x1ff] = x + 0x1000 | 0x3;
pt[0x3ff] = x + 0x2000 | 0x3;
pt[0x5ff] = x + 0x3000 | 0x3;
*(uint32_t *)p = 0xabcdabcd;
*(uint32_t *)(p + 4) = 0x12341234;
efi_printf("[0] = %08x\n", *(uint32_t *)(p+4));
efi_printf("[fffffffffffc] = %08x\n", *(uint32_t *)(x + 0x3ffc));
*(uint32_t *)(p + 2) = 0x56785678;
efi_printf("p[0] = %08x\n", ((uint32_t *)p)[0]);
efi_printf("p[1] = %08x\n", ((uint32_t *)p)[1]);
}
return 0;
}
If it works as expected, the last 4 lines should be:
[0] = 12341234 [fffffffffffc] = ABCDABCD p[0] = 5678ABCD p[1] = 12345678
A value of 0x56785678 is written starting in the last 16-bit word of memory and should wrap to the first 16-bit word of memory.
Note: p
needed to be a global variable, otherwise GCC changed *(p+4)
into ud2