Спасибо, Майкл.

шено ли для одного доступа охватывать границу между0 а также0xFFFFFF... в х861?

Например, учитывая, чтоeax (rax в 64-битном) равен нулю, разрешен ли следующий доступ:

mov ebx, DWORD [eax - 2]

Мне интересны как x86 (32-битные), так и x86-64, если ответы разные.

1 Конечно, учитывая, что регион отображается в вашем процессе и т. Д.

 harold07 дек. 2017 г., 21:34
Я не могу найти что-либо об этом в руководстве, но если я попробую, то это сработает.
 Michael Petch09 дек. 2017 г., 00:10
Так что же произойдет, если мы немного убьем реальный режим. Включите защищенный режим и настройте GDT с 16-битным дескриптором с основанием 0xffffffff (используйте ограничение в 0xffff). УстановленES в этот дескриптор затем отключите защищенный режим без перезагрузки ES (процессор все равно будет использовать кэшированный базовый адрес, если вы не перезагрузите ES со значением). Если мы записываем слово в 0x0000, запись завершается успешно, и операция с памятью завершается. Реальный режим ведет себя так же, как защищенный режим.
 Michael Petch08 дек. 2017 г., 23:45
Во втором эксперименте в моем последнем комментарии, хотя (случай, когда основание равно 1), и я делаю запись слова с помощью селектора, используя этот дескриптор (то есть ES) со смещением 0xfffffffe, это не будет ошибкой и будет завершено. Таким образом, проверка базового + эффективного адреса перед выполнением доступа к памяти не может быть перенесена, но после этого, если сама запись пересекает конец памяти, она переносится.
 Margaret Bloom08 дек. 2017 г., 14:42
Я тестировал как реальный, так и защищенный режим и первые ошибки, а последние нет. Однако 32-битный случай зависит от конкретной реализации. Я не думаю, что это когда-либо изменится в основных процессорах Intel, но для других брендов или дополнительных архитектур это может произойти.
 Michael Petch08 дек. 2017 г., 23:32
Я провел некоторые эксперименты. На моем процессоре, если я нахожусь в 32-разрядном защищенном режиме, и я использую обычную модель памяти с плоским 4 ГБ (где база равна 0), и я пишу слово в конец памяти, нет ошибки. Как другой эксперимент Если я изменю базу в дескрипторе на 1 (вместо нормального нуля), сохраняя ограничение в 4 ГБ, и я попытаюсь записать в селектор, используя этот дескриптор (то есть ES) со смещением 0xffffffff, это приведет к ошибке. Кажется, что с ненулевым основанием, если его добавить к эффективному адресу операнда памяти, произойдет сбой (он не переносится в 0), если вычисленный адрес равен 2 ^ 32 или выше.

Ответы на вопрос(2)

Решение Вопроса

как и ожидалось.) Если вы хотите воспроизвести этот результат, вам потребуется реализация efi_printf или другой способ просмотра результата.

#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;
}

Если это работает как ожидалось, последние 4 строки должны быть:

[0] = 12341234
[fffffffffffc] = ABCDABCD
p[0] = 5678ABCD
p[1] = 12345678

Значение 0x56785678 записывается, начиная с последнего 16-разрядного слова памяти, и должно переноситься на первое 16-разрядное слово памяти.

Примечание:p должна быть глобальной переменной, иначе GCC изменился*(p+4) вud2

 Peter Cordes07 дек. 2017 г., 23:04
я нашел что составление только-Og вместо-O2 или выше получил GCC, чтобы сделать асм я хотел. Но, конечно, глобальный тоже работает, чтобы не дать оптимизатору увидеть константу времени компиляции.

но он был слишком большим для комментария. Это код @ prl, преобразованный таким образом, чтобы он работал с основнымиgnu-efiпакет доступен на многих дистрибутивах Linux. файл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 это должно работать на 64-битной Ubuntu, а 64-битный Debian может выглядеть так:

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 [email protected] -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) $^ [email protected]

Написанный код будет работать правильно только в том случае, если он скомпилирован для x86-64. Вы можете сделать это приложение EFI с помощью команды:

make ARCH=x86_64

Полученный файл должен бытьwraptest.efi это можно скопировать в системный раздел EFI. Файл make был основан наУчебник Родерика Смита

 prl08 дек. 2017 г., 09:14
Спасибо, Майкл.

Ваш ответ на вопрос