Страничная адресация intel x86

В процессе настройки страничной адресации столкнулся с проблемами.
Для их описания я буду использовать этот код на Си:

uint32_t pde[1024] __attribute__((aligned(4096)));
uint32_t first_pte[1024] __attribute__((aligned(4096)));

static void paging_set_page_directory(uint32_t pde_addr)
{
    asm volatile ("mov %0, %%cr3"::"r"(pde_addr));
    uint32_t cr0;
    asm volatile ("mov %%cr0, %0": "=r"(cr0));
    cr0 |= 0x80000000;
    asm volatile ("mov %0, %%cr0":: "r"(cr0));
}

static void paging_init()
{
    memset(&pde, 0, sizeof(pde));
    memset(&first_pte, 0, sizeof(first_pte));

    for(uint32_t i = 0; i < 1024; i++)
        first_pte[i] = (i * 0x1000) | 1; //present, readonly, supervisor...

    pde[0] = ((uint32_t)&first_pte) | 1; //present, readonly, supervisor...

    paging_set_page_directory((uint32_t)&pde);
}

Все тесты работоспособности я проводил на виртуальной машине VirtualBox. Во всех случаях под ОШИБКОЙ я подразумеваю критическую ошибку VirtualBox.

Почему:

  1. Я не могу использовать каталог без страниц. Т.е. при таком изменении цикла for(uint32_t i = 0; i < 0; i++) возникает ошибка. Я немного поэкспериментировал и выяснил, что ошибка возникает при i < 1024/4 + 10.
  2. Я могу писать в память после установки каталога, хотя страницы (и таблицы) помечены атрибутом readonly. Добавление этой строки *(uint32_t*)(0x3) = 77; после paging_init() не вызывает никакой реакции системы (я ожидал page fault).
  3. Я не могу сдвинуть страницы на фрейм, т.е. изменение строки на first_pte[i] = (0x1000*(i + 1)) | 1; приводит к ошибке.

Я только встал на путь низкоуровневого программирования, поэтому буду рад любым советам и ответам. Спасибо!


Ответы (1 шт):

Автор решения: Аленя́
  1. В каталоге должны быть определены как минимум те страницы, которые использует программа в данный момент. Предполагаю, что для примера выше это страницы, содержащие стек и код.

  2. Страницы должны тождественно отражать фреймы: [0] -> 0x00000000, [1] -> 0x00001000 и т.д. Иначе при обращении к ранее определённому адресу, например, 0x00000000, можно обратиться, например, к 0x00001000, и получить непредсказуемый результат.

  3. Привилегия supervisor позволяет игнорировать состояние флага rw. Чтобы отключить это поведение, нужно установить флаг RW в регистре CR0: cr0 |= (1 << 31) | (1 << 16);.

→ Ссылка