BABALAR SELAMUN ALEYKÜÜM VMM'i de yazarım demiştim işte yazıyorum BABALAR SÖZÜNÜ TUTAAAR
PMM yazısını okumadıysan önce onu oku çünkü burda sürekli pmm_alloc_page() kullanıcaz yine aynı şekilde yazarken bi yandan Narc'ın VMM'ini yazıyorum (YAZIYORUZZZZ)
Ders 1: Sanal bellek nedir?
Kodda bir pointer kullandığında mesela 0xffffffff80001234, CPU direkt RAM'de o adrese gitmiyor önce bu adresi bir tablodan geçirip fiziksel adrese çeviriyor bu tablolara page table diyoruz
Sanal adres ---page table---> Fiziksel adres
bu sayede her process kendi adres alanını görebiliyor, bir sayfayı salt okunur yapabiliyorsun, map etmediğin bi yere erişilince CPU page fault atıyor vs
VMM'in işi de bu tabloları yönetmek hangi sanal adres hangi fiziksel sayfaya gidecek onu VMM ayarlıyor
1. 4 seviyeli paging
x86_64'te sanal adresler 4 tane tablodan geçiyor:
PML4 -> PDPT -> PD -> PT -> 4 KiB'lık sayfa
her tablo 4096 byte ve içinde 512 tane 8 byte'lık entry var yani bir tablo = PMM'den bir sayfa, güzel oturuyor
Sanal adresi şöyle parçalıyoruz:
47 39 38 30 29 21 20 12 11 0
+---------+----------+----------+----------+------------+
| PML4 | PDPT | PD | PT | offset |
+---------+----------+----------+----------+------------+
Her biri 9 bit yani 0 ile 511 arası bir index son 12 bit de sayfanın içindeki konum
2. CR3
CPU en üstteki tabloyu (PML4) nerden biliyor? CR3 register'ından CR3'te PML4'ün fiziksel adresi duruyor
şu an limine bize zaten bi page table kurmuş ve CR3'e yazmış önce onu okuyalım
3. vmm.c
uint64_t vmm_read_cr3(void) {
uint64_t cr3;
__asm__ volatile ("mov %%cr3, %0" : "=r"(cr3));
return cr3;
}CR3'ü C'den okuyamıyoruz o yüzden inline assembly
4. Hex yazdırma
Narc'ta kprint sadece string basıyor adres basmak için hex fonksiyonu lazm terminal.c'ye ekledim:
void kprint_hex(uint64_t n) {
char buf[19];
const char *digits = "0123456789ABCDEF";
buf[0] = '0';
buf[1] = 'x';
for (int i = 0; i < 16; i++) {
buf[17 - i] = digits[n & 0xF];
n >>= 4;
}
buf[18] = '\0';
kprint(buf);
}n & 0xF ile en sağdaki hex basamağını alıp sağa kaydırıyoruz buffer'ı sağdan sola dolduruyoruz
kernel.c'de sti'den sonra:
kprint("VMM: CR3 = ");
kprint_hex(vmm_read_cr3());
kprint("\n");ve çalıştırıyorumm

VE CR3'Ü OKUDUKKK 0xFF8E000 yani limine'nin PML4'ü fiziksel ramde burda duruyomuş ahahah
Ders 2: Page table'ı elle gezelim
CPU'nun her bellek erişiminde yaptığı şeyi biz C'de yapıcaz sanal adresi verip tabloları tek tek gezip fiziksel adresi bulucaz bunu anlarsan VMM'in çoğunu anlamış olursun
1. Entry'nin içinde ne var
Her entry 64 bit, bizim için önemli bitler:
- bit 0 → Present, 0 ise bu entry yok
- bit 1 → yazılabilir mi
- bit 2 → userspace erişebilir mi
- bit 7 → huge page (aşağıda)
- bit 63 → NX, 1 ise burdaki kod çalıştırılamaz
- 12 ile 51 arası → bir sonraki tablonun (ya da sayfanın) fiziksel adresi
vmm.h:
#define PTE_PRESENT (1ull << 0)
#define PTE_WRITE (1ull << 1)
#define PTE_USER (1ull << 2)
#define PTE_HUGE (1ull << 7)
#define PTE_NX (1ull << 63)
#define PTE_ADDR_MASK 0x000FFFFFFFFFF000ull2. Index'ler
#define PML4_INDEX(a) (((a) >> 39) & 0x1FF)
#define PDPT_INDEX(a) (((a) >> 30) & 0x1FF)
#define PD_INDEX(a) (((a) >> 21) & 0x1FF)
#define PT_INDEX(a) (((a) >> 12) & 0x1FF)0x1FF = 511, sağa kaydırıp alt 9 biti alıyoruz
3. Tablolar fiziksel adreste
Entry'deki adresler fiziksel, pointer yapamıyoruz PMM'den hatırlarsın HHDM offset'ini ekliyoruz:
static uint64_t hhdm = 0;
void vmm_init(uint64_t hhdm_offset) {
hhdm = hhdm_offset;
}
static uint64_t *phys_to_table(uint64_t phys) {
return (uint64_t *)(phys + hhdm);
}4. Huge page
Limine HHDM'i 4 KiB sayfalarla map etmiyor yoksa milyon tane entry lazım olurdu PDPT ya da PD entry'sinde bit 7 açıksa o entry alt tabloyu değil direkt büyük bir sayfayı gösteriyor:
- PDPT'de → 1 GiB sayfa
- PD'de → 2 MiB sayfa
Bunu kontrol etmezsen sayfanın kendisini tablo sanıp saçma şeyler okursnu
5. vmm_virt_to_phys
uint64_t vmm_virt_to_phys(uint64_t virt) {
uint64_t *pml4 = phys_to_table(vmm_read_cr3() & PTE_ADDR_MASK);
uint64_t e = pml4[PML4_INDEX(virt)];
if (!(e & PTE_PRESENT))
return 0;
uint64_t *pdpt = phys_to_table(e & PTE_ADDR_MASK);
e = pdpt[PDPT_INDEX(virt)];
if (!(e & PTE_PRESENT))
return 0;
if (e & PTE_HUGE)
return (e & 0x000FFFFFC0000000ull) + (virt & 0x3FFFFFFF);
uint64_t *pd = phys_to_table(e & PTE_ADDR_MASK);
e = pd[PD_INDEX(virt)];
if (!(e & PTE_PRESENT))
return 0;
if (e & PTE_HUGE)
return (e & 0x000FFFFFFFE00000ull) + (virt & 0x1FFFFF);
uint64_t *pt = phys_to_table(e & PTE_ADDR_MASK);
e = pt[PT_INDEX(virt)];
if (!(e & PTE_PRESENT))
return 0;
return (e & PTE_ADDR_MASK) + (virt & 0xFFF);
}uzun duruyo ama aynı şeyi 4 kere yapıyoruz: entry'yi al, present değilse 0 dön, huge ise hesaplayıp dön değilse bir alt tabloya geç en sonda fiziksel sayfaya offset'i ekliyoruz
(0 dönmek kötü bir hata işareti biliyorum fiziksel 0 da geçerli adres ama şimdilik idare eder)
6. Test
uint64_t test_virt = (uint64_t)&kmain;
kprint("VMM: kmain virt = ");
kprint_hex(test_virt);
kprint(" phys = ");
kprint_hex(vmm_virt_to_phys(test_virt));
kprint("\n");
uint64_t test_page = pmm_alloc_page();
kprint("VMM: pmm page = ");
kprint_hex(test_page);
kprint(" hhdm walk = ");
kprint_hex(vmm_virt_to_phys(test_page + boot.hhdm_offset));
kprint("\n");
pmm_free_page(test_page);kmain fiziksel olarak nerde duruyor bir de PMM'den aldığım sayfanın HHDM adresini walk edince aynı fiziksel adresi geri alıyormuyum ona bakıyorum

İKİSİDE ÇALIŞTIIII hayatımda bu kadar sevinmedim ya kmain'in son 3 hanesi (A20) iki tarafta da aynı çünkü offset değişmiyo sadece sayfa değişiyo, pmm 0x7E000 verdi walk da 0x7E000 buldu AYNISIII
Ders 3: Kendi sayfamızı map edelim
Şimdiye kadar sadece okudk şimdi yazıcaz PMM'den bir fiziksel sayfa alıp bizim seçtiğimiz bir sanal adrese map edicez
Adres olarak 0xFFFF900000000000 seçtim HHDM'in PML4 index'i 256, kernel 511 bu 288 çıkıyor yani kimseyle çakışmıyor
1. Eksik tablolar
Bu adres için daha önce hiç tablo yok Walk ederken tablo yoksa PMM'den sayfa alıp sıfırlayıp üst tabloya yazıcaz:
static uint64_t *get_next_table(uint64_t *table, uint64_t index) {
if (table[index] & PTE_PRESENT) {
if (table[index] & PTE_HUGE)
return NULL;
return phys_to_table(table[index] & PTE_ADDR_MASK);
}
uint64_t page = pmm_alloc_page();
if (page == 0)
return NULL;
memset(phys_to_table(page), 0, PAGE_SIZE);
table[index] = page | PTE_PRESENT | PTE_WRITE;
return phys_to_table(page);
}sıfırlamak şart yoksa içindeki çöp değerleri present sanabilir. memset için mem.h include ettim
Üst tablolara PTE_WRITE koyuyorum çünkü CPU izinleri bütün seviyelerde kontrol ediyor, PML4'te yazma kapalıysa altında ne yazarsan yaz salt okunur oluyor asıl izni en alttaki PT'de veriyoruz
2. vmm_map_page
Hangi PML4'ü düzenlediğimizi tutan bi değişken ekledim şimdilik limine'ninki:
static uint64_t kernel_pml4 = 0;(vmm_init'te kernel_pml4 = vmm_read_cr3() & PTE_ADDR_MASK; yapıyorum)
bool vmm_map_page(uint64_t virt, uint64_t phys, uint64_t flags) {
if ((virt & 0xFFF) || (phys & 0xFFF))
return false;
uint64_t *pml4 = phys_to_table(kernel_pml4);
uint64_t *pdpt = get_next_table(pml4, PML4_INDEX(virt));
if (!pdpt) return false;
uint64_t *pd = get_next_table(pdpt, PDPT_INDEX(virt));
if (!pd) return false;
uint64_t *pt = get_next_table(pd, PD_INDEX(virt));
if (!pt) return false;
pt[PT_INDEX(virt)] = phys | flags | PTE_PRESENT;
__asm__ volatile ("invlpg (%0)" : : "r"(virt) : "memory");
return true;
}Adresler 4096'ya hizalı mı bakıyoruz sonra aşağı iniyoruz, en sonda PT entry'sine fiziksel adresi ve flagleri yazıyoruz
invlpg ne derseniz CPU çevirdiği adresleri TLB denen bir cache'te tutuyor tabloyu değiştirince CPU'nun haberi olmuyor, invlpg o adresin TLB kaydını siliyor
3. Test
uint64_t my_virt = 0xFFFF900000000000;
uint64_t my_phys = pmm_alloc_page();
if (!vmm_map_page(my_virt, my_phys, PTE_WRITE)) {
kprint("VMM: map failed!\n");
khalt();
}
kprint("VMM: mapped ");
kprint_hex(my_virt);
kprint(" -> ");
kprint_hex(vmm_virt_to_phys(my_virt));
kprint("\n");
volatile uint32_t *p1 = (volatile uint32_t *)my_virt;
volatile uint32_t *p2 = (volatile uint32_t *)(my_phys + boot.hhdm_offset);
*p1 = 0x4E415243;
kprint("VMM: write via my_virt, read via hhdm = ");
kprint_hex(*p2);
kprint("\n");
if (*p2 == 0x4E415243)
kprint("VMM: SAME PAGE!!! map works\n");Aynı fiziksel sayfaya artık iki adresten erişebiliyoruz, p1 bizim map ettiğimiz adres p2 aynı sayfanın HHDM adresi p1'e yazıp p2'den okuyorum aynı gelirse ikisi aynı RAM'e bakıyor demek. 0x4E415243 da ASCII'de "NARC" :D
VE BUNU ÇALIŞTIRDIĞIMIZDA::::

OOOLDUUUUU AHAHAHAHAH bir taraftan yazdık öbür taraftan okuduk aynı geldi NARC ARTIK KENDI SAYFALARINI MAP EDEBILIYOR
Ders 4: Kendi page table'ımızı kuralım
Şu ana kadar limine'nin tablolarının üstüne yazdık bu kötü çünkü:
- limine'nin tabloları bootloader reclaimable bölgede, ileride o bölgeyi PMM'e geri verirsek tablolar da gider
- limine her şeyi rwx map ediyor yani kernel kendi kodunun üstüne yazabiliyor kimse bişey demiyor
O yüzden yeni bir PML4 açıp her şeyi kendimiz map edip CR3'e yazıcaz
1. Neleri map etmemiz lazım
CR3'ü değiştirdiğimiz anda kullandığımız her şey yeni tablolarda olmalı yoksa triple fault yer makine resetlenir Kullandıklarımız:
- kernel'in kendisi
- HHDM (PMM bitmap'i, page table'lar, Limine'nin verdiği stack ve GDT hepsi HHDM'den erişiliyor)
- framebuffer
Limine'nin dokümanına baktım base revision 4'te HHDM'e reserved ve bad memory hariç her şeyi map ediyor (reserved mapped diye bi tip daha var o map ediliyor) boot.h'ta o tip yoktu MEM_REGION_RESERVED_MAPPED diye ekledim limine.c'de de convert_type'a koydum
2. Kernel fiziksel olarak nerde
kernelı map etmek için fiziksel adresini bilmemiz lazım, limine.c'ye bir istek daha:
__attribute__((used, section(".limine_requests")))
static volatile struct limine_executable_address_request kernel_address_request = {
.id = LIMINE_EXECUTABLE_ADDRESS_REQUEST_ID,
.revision = 0
};boot_info'ya kernel_phys_base ve kernel_virt_base ekledim kernel'deki bir adresin fiziksel karşılığı virt - kernel_virt_base + kernel_phys_base
3. Kernel'i parça parça map etmek
Asıl olay burda kernel'i tek parça map etmicez:
.text(kod) → okunur ve çalıştırılabilir, yazılamaz.rodata→ sadece okunur.datave.bss→ okunur yazılır ama çalıştırılamaz
böylece bir bug yüzünden kodun üstüne yazarsak ya da veriyi çalıştırmaya kalkarsak direkt page fault yiyoruz
Bölümlerin nerde başlayıp bittiğini bilmek için linker.ld'ye sembol ekledim:
. = 0xffffffff80000000;
__kernel_start = .;
...
__text_start = .;
.text : { *(.text .text.*) } :text
...
__rodata_start = .;
.rodata : { *(.rodata .rodata.*) } :rodata
...
__data_start = .;
.data : { *(.data .data.*) } :data
.bss : { *(.bss .bss.*) *(COMMON) } :data
. = ALIGN(CONSTANT(MAXPAGESIZE));
__kernel_end = .;
her bölümden önce zaten ALIGN vardı yani her bölüm sayfa başında başlıyor bu önemli çünkü izinler sayfa sayfa veriliyor
C tarafında:
extern char __kernel_start[], __text_start[], __rodata_start[], __data_start[], __kernel_end[];bunlar değişken deil direkt adres
static bool map_kernel_part(struct boot_info *boot, char *start, char *end, uint64_t flags) {
for (uint64_t v = (uint64_t)start; v < (uint64_t)end; v += PAGE_SIZE) {
uint64_t p = v - boot->kernel_virt_base + boot->kernel_phys_base;
if (!vmm_map_page(v, p, flags))
return false;
}
return true;
}4. HHDM'i 2 MiB sayfalarla map etmek
HHDM'i 4 KiB sayfalarla yaparsak her 2 MiB için bir PT gidiyor, 16 GiB RAM'li bilgisayarda 32 MiB sadece page table o yüzden adres 2 MiB'a hizalıysa PD seviyesinde huge page koyuyoruz:
static bool map_2mb(uint64_t virt, uint64_t phys, uint64_t flags) {
uint64_t *pml4 = phys_to_table(kernel_pml4);
uint64_t *pdpt = get_next_table(pml4, PML4_INDEX(virt));
if (!pdpt) return false;
uint64_t *pd = get_next_table(pdpt, PDPT_INDEX(virt));
if (!pd) return false;
if ((pd[PD_INDEX(virt)] & PTE_PRESENT) && !(pd[PD_INDEX(virt)] & PTE_HUGE))
return false;
pd[PD_INDEX(virt)] = phys | flags | PTE_HUGE | PTE_PRESENT;
return true;
}
static bool map_hhdm_region(uint64_t start, uint64_t end) {
uint64_t p = start;
while (p < end) {
if (p % MB2 == 0 && end - p >= MB2) {
if (!map_2mb(p + hhdm, p, PTE_WRITE | PTE_NX))
return false;
p += MB2;
} else {
if (!vmm_map_page(p + hhdm, p, PTE_WRITE | PTE_NX))
return false;
p += PAGE_SIZE;
}
}
return true;
}MB2 = 0x200000 hizalıysa ve en az 2 MiB yer kaldıysa huge page değilse normal sayfa map_2mb'de orda zaten bir PT varsa üstüne yazmıyoruz yoksa o PT'yi kaybederiz
5. Framebuffer
Framebuffer'ı normal RAM gibi map edersen yazdığın pikseller cache'te kalıp ekrana geç çıkabiliyor, cache'i tamamen kapatırsan da çok yavaş oluyor limine framebuffer'ı write-combining (WC) map ediyor biz de öyle yapıcaz
Cache tipi entry'deki PAT, PCD, PWT bitlerinden seçiliyor, Limine'nin dokümanına göre WC 5 numara yani PAT=1 PCD=0 PWT=1:
#define PTE_PWT (1ull << 3)
#define PTE_PAT (1ull << 7)saçma bi detay PAT biti 4 KiB sayfada bit 7 ama 2 MiB sayfada bit 12 (çünkü orda bit 7 huge biti) ben uğraşmamk için framebuffer'ı sadece 4 KiB sayfalarla map ettim
6. vmm_init
bool vmm_init(struct boot_info *boot) {
hhdm = boot->hhdm_offset;
kernel_pml4 = pmm_alloc_page();
if (kernel_pml4 == 0)
return false;
uint64_t *pml4 = phys_to_table(kernel_pml4);
memset(pml4, 0, PAGE_SIZE);
for (int i = 256; i < 512; i++) {
if (!get_next_table(pml4, i))
return false;
}
for (uint64_t i = 0; i < boot->memmap_count; i++) {
const struct mem_region *r = &boot->memmap[i];
if (r->type == MEM_REGION_RESERVED || r->type == MEM_REGION_BAD)
continue;
uint64_t start = r->base & ~0xFFFull;
uint64_t end = (r->base + r->length + 0xFFF) & ~0xFFFull;
if (r->type == MEM_REGION_FRAMEBUFFER) {
for (uint64_t p = start; p < end; p += PAGE_SIZE) {
if (!vmm_map_page(p + hhdm, p, PTE_WRITE | PTE_NX | PTE_PAT | PTE_PWT))
return false;
}
} else {
if (!map_hhdm_region(start, end))
return false;
}
}
if (!map_kernel_part(boot, __kernel_start, __text_start, PTE_WRITE | PTE_NX)) return false;
if (!map_kernel_part(boot, __text_start, __rodata_start, 0)) return false;
if (!map_kernel_part(boot, __rodata_start, __data_start, PTE_NX)) return false;
if (!map_kernel_part(boot, __data_start, __kernel_end, PTE_WRITE | PTE_NX)) return false;
vmm_write_cr3(kernel_pml4);
return true;
}Sırayla:
- yeni PML4 al sıfırla, kernel_pml4'ü hemen bu yap ki map fonksiyonları buna yazsın CR3 hala limine'de ama tabloları HHDM'den düzenleyebiliyoruz
- üst yarının (256-511) hepsine şimdiden PDPT açıyoruz İleride her process'in PML4'üne kernel yarısını kopyalıcaz bu PDPT'ler hiç değişmezse herkes aynı kernel'i görür. 1 MiB gidiyor ama değer
- memory map'i gez reserved ve bad hariç HHDM'e map et, framebuffer'a WC ekle
- kernel'i 4 parça map et (
.text'in flagi 0 yani yazılamaz ve NX yok) - CR3'e yaz
void vmm_write_cr3(uint64_t cr3) {
__asm__ volatile ("mov %0, %%cr3" : : "r"(cr3) : "memory");
}NX bitinin çalışması için EFER'de NXE açık olmalı, Limine açık bırakıyor CR0'da WP de açık o sayede kernel bile salt okunur sayfaya yazamıyor
7. Test
kernel.c:
uint64_t limine_cr3 = vmm_read_cr3();
uint64_t free_before = pmm_get_free_pages();
if (!vmm_init(&boot)) {
kprint("VMM: init failed!\n");
khalt();
}
kprint("VMM: limine CR3 = ");
kprint_hex(limine_cr3);
kprint("\n");
kprint("VMM: narc CR3 = ");
kprint_hex(vmm_read_cr3());
kprint("\n");
kprint("VMM: page tables used ");
kprint_dec(free_before - pmm_get_free_pages());
kprint(" pages\n");
kprint("VMM: still alive after CR3 switch :D\n");sayfa sayısını hex okumak acı verdiği için kprint_dec de yazdım:
void kprint_dec(uint64_t n) {
char buf[21];
int i = 20;
buf[i] = '\0';
do {
buf[--i] = '0' + n % 10;
n /= 10;
} while (n > 0);
kprint(&buf[i]);
}Triple fault yerim diye bekliyodum ama:

YAŞIYORUZZZZ AHAHAHAH TRIPLE FAULT YOK cr3 0xFF8E000'dan 0x7E000'a geçti kernel hala yazı basıyo artık BÜTÜN TABLOLAR BİZİM limine sg. 275 sayfa gitti 256'sı üst yarının PDPT'leri
make run-uefi ile UEFI'de de denedim orda da çalıştı
Ders 5: Unmap ve Page Fault
1. vmm_unmap_page
Yine walk ediyoruz ama en alttaki entry'yi 0 yapıyoruz PT tamamen boşaldıysa onu da PMM'e geri veriyoruz yoksa her map unmap'ta sayfa leak olur:
static bool table_empty(uint64_t *t) {
for (int i = 0; i < 512; i++)
if (t[i]) return false;
return true;
}
uint64_t vmm_unmap_page(uint64_t virt) {
uint64_t *pml4 = phys_to_table(kernel_pml4);
uint64_t e = pml4[PML4_INDEX(virt)];
if (!(e & PTE_PRESENT)) return 0;
uint64_t *pdpt = phys_to_table(e & PTE_ADDR_MASK);
e = pdpt[PDPT_INDEX(virt)];
if (!(e & PTE_PRESENT) || (e & PTE_HUGE)) return 0;
uint64_t pd_phys = e & PTE_ADDR_MASK;
uint64_t *pd = phys_to_table(pd_phys);
e = pd[PD_INDEX(virt)];
if (!(e & PTE_PRESENT) || (e & PTE_HUGE)) return 0;
uint64_t pt_phys = e & PTE_ADDR_MASK;
uint64_t *pt = phys_to_table(pt_phys);
uint64_t page = pt[PT_INDEX(virt)];
if (!(page & PTE_PRESENT)) return 0;
pt[PT_INDEX(virt)] = 0;
__asm__ volatile ("invlpg (%0)" : : "r"(virt) : "memory");
if (table_empty(pt)) {
pd[PD_INDEX(virt)] = 0;
pmm_free_page(pt_phys);
if (table_empty(pd)) {
pdpt[PDPT_INDEX(virt)] = 0;
pmm_free_page(pd_phys);
}
}
return page & PTE_ADDR_MASK;
}PDPT'leri free etmiyoruz Ders 4'te dediğim gibi onlar sabit kalmalı
fiziksel sayfanın kendisini free etmiyoz adresini dönüyoruz çünkü aynı sayfa başka yerde de map'li olabilir (Ders 3'teki gibi) çağıran isterse kendisi free etsin
burda invlpg kesinlikle lazım yoksa CPU eski çeviriyi kullanır ve unmap ettiğin sayfaya hala erişirsin sonra o sayfa başkasına verilir iki yer aynı RAM'e yazar o bug'ı bulmaya çalışırken kafayı yersin
2. Leak testi
Ders 3'teki testin başına ve sonuna boş sayfa sayısını koydum:
uint64_t free_map = pmm_get_free_pages();
/* ... Ders 3 testi ... */
uint64_t freed = vmm_unmap_page(my_virt);
pmm_free_page(freed);
kprint("VMM: unmapped, walk now = ");
kprint_hex(vmm_virt_to_phys(my_virt));
kprint("\n");
kprint("VMM: free pages before map = ");
kprint_dec(free_map);
kprint(" after unmap = ");
kprint_dec(pmm_get_free_pages());
kprint("\n");Map ederken PD, PT ve sayfanın kendisi gidiyor unmap'ten sonra üçü de geri gelmeli

51437 = 51437 TEK BİR SAYFA BİLE LEAK YOK BEN DAHİYİM
3. Page fault handler
IDT'yi yazarken page fault handler koymuştum ama sadece "Page Fault!" yazıyordu neresi patladı bilmiyorduk CPU patlayan adresi CR2'ye yazıyor ayrıca bir error code veriyor, handler'ı şöyle yaptım (idt.c):
__attribute__((interrupt))
static void page_fault_handler(
interrupt_frame_t *frame,
uint64_t error_code
) {
uint64_t cr2;
__asm__ volatile ("mov %%cr2, %0" : "=r"(cr2));
kprint("\nPage Fault!\n");
kprint(" address: ");
kprint_hex(cr2);
kprint("\n error: ");
kprint_hex(error_code);
kprint("\n rip: ");
kprint_hex(frame->rip);
kprint("\n");
for (;;) {
__asm__ volatile ("cli; hlt");
}
}error code bitleri:
- bit 0 → 0 ise sayfa yok, 1 ise sayfa var ama izin yok
- bit 1 → 1 ise yazarken oldu
- bit 2 → 1 ise userspace'te oldu
- bit 4 → 1 ise kod çalıştırırken oldu (NX)
rip'i de objdump -d build/kernel.elf çıktısında aratınca hangi satırda patladığını buluyorsun çok işe yarıyo
4. Unmap ettiğimiz sayfaya dokunalım
kprint("VMM: touching unmapped page...\n");
kprint_hex(*p1);buna page fault atması lazım atmazsa bişeyi batırdık

PAGE FAULTTTT AHAHAHAH ilk defa bir hataya bu kadar seviniyorum address tam unmap ettiğimiz adres error 0 yani sayfa yok ve okurken oldu TAM İSTEDİĞİMİZ ŞEY
Ders 6: İzinler gerçekten çalışıyo mu
Kağıt üstünde güzel ama.. kırmayı deneyelim
1. .rodata'ya yazmak
extern char __rodata_start[];
kprint("VMM: writing to .rodata...\n");
*(volatile char *)__rodata_start = 'X';
kprint("VMM: uh oh it worked\n");
YAKALANDIIII "uh oh it worked" yazmadı error 0x3 yani sayfa var (bit 0) ama yazma izni yok (bit 1), limine'nin tablolarında bu satır sessizce çalışırdı kimse bişey demezdi
2. .data'dan kod çalıştırmak
kernel.c'nin en üstüne:
static uint8_t evil_code[] = { 0xC3 };0xC3 x86'da ret yani hiçbişey yapmayan bir fonksiyon ama .data'da duruyor
kprint("VMM: running code from .data...\n");
((void (*)(void))evil_code)();
kprint("VMM: uh oh it worked\n");
NX ÇALIŞIYOOOO AHAHAHAH evil_code çalışamadı bile error 0x11 yani sayfa var ama kod çalıştırmaya çalıştın (bit 4) address ve rip ikiside evil_code'un adresi
Bu testlerin hepsini ss aldıktan sonra sildim yoksa kernel her açılışta page fault yiyip donuyor :D
Son
Narc'ın VMM'i artık sanal adresi fiziksel adrese çevirebiliyor, sayfa map/unmap edebiliyor (2 MiB dahil), kendi page table'larını sıfırdan kuruyor, kernel'i doğru izinlerle map ediyor ve page fault olunca nerde olduğunu söylüyor
Sırada bootloader reclaimable bölgeyi PMM'e geri vermek (önce kendi GDT ve stack'imizi kurmamız lazım), huge page bölmek ve kernel heap (kmalloc) var Heap'i yazarsam onu da yazarım