I'm trying to understand how virtual addresses are translated to physical addresses.
I started with the simple case: x86, non PAE.
In this case things should work as follows:
- CR3 contains the physical address of the page directory base
- a Virtual Address is split in 3: highest 10 bits are the Page Directory Index, next 10 are the Page Table Index and the last 12 are the offset in the page.
- PageDirectory[PageDirectoryIndex] will give you a Page Directory Entry. A PDE is split in 2: highest 22 bits point to the Page Table, the rest are control bits. In my particular case I care about bit 0 (Present bit, must be 1) and bit 7 (Size bit, 1 if large page, 0 is small page; must be 0).
- PageTable[PageTableIndex] represents a Page Table Entry. A PTE is similar to a PDE. We care only about bit 0 (the Present bit). The upper 20 bits give us the Page Frame, representing the Physical Page in which our VA is located.
- Finally, PageFrame[PageOffset] is the Physical Address corresponding to our VA.
I tried to write a small driver that makes this translation, but it reports that the present bit is 0 for every VA it tries to translate.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
|
BOOLEAN
VirtualToPhysical() // normal x86
{
DWORD_PTR virtualAddress = g_VirtualAdr;
DWORD_PTR pageDirectory = 0;
DWORD_PTR pageDirectoryIndex = 0;
DWORD_PTR pageDirectoryEntry = 0;
DWORD_PTR pageTable = 0;
DWORD_PTR pageTableIndex = 0;
DWORD_PTR pageTableEntry = 0;
DWORD_PTR physicalPage = 0;
PHYSICAL_ADDRESS cr3;
// THIS MIGHT BE WRONG
cr3.QuadPart = 0;
cr3.HighPart = 0;
cr3.LowPart = __readcr3();
pageDirectory = (DWORD_PTR)MmMapIoSpace(
cr3,
1024,
MmNonCached);
// pageDirectory = 0xC0300000;
DbgPrint("CR3 = 0x%x\n", cr3.LowPart);
if( 0 == pageDirectory )
{
DbgPrint("MmMapIoSpace() failed\n");
return FALSE;
}
DbgPrint("PageDirectory = 0x%x\n",
pageDirectory);
pageDirectoryIndex = virtualAddress >> 22;
pageTableIndex = (virtualAddress << 10) >> 22;
pageDirectoryEntry = pageDirectory + pageDirectoryIndex * 4;
if( 0 == pageDirectoryEntry )
{
DbgPrint("PDE = 0\n");
return FALSE;
}
DbgPrint("PDE = 0x%x\n",
pageDirectoryEntry);
if( pageDirectoryEntry & 0x80 ) // Large Page
{
DbgPrint("Large page\n");
return FALSE;
}
else // small page
{
DbgPrint("Small page\n");
if( pageDirectoryEntry & 0x1 ) // Present
{
pageTable = pageDirectoryEntry & 0x0FFFFF000;
if( 0 == pageTable )
{
DbgPrint("PT = 0\n");
return FALSE;
}
pageTableEntry = pageTable + pageTableIndex * 4;
if( pageTableEntry & 0x01 ) // Present
{
physicalPage = pageTableEntry & 0x0FFFFF000;
DbgPrint("Physical Page = 0x%x\n",
physicalPage);
return TRUE;
}
else
{
DbgPrint("PT.P = 0\n");
return FALSE;
}
}
else // not present
{
DbgPrint("PDE.P = 0\n");
return FALSE;
}
}
return TRUE;
}
|
I might not use
correctly, but I don't know how to check that.
is called by a function that checks if PAE is active or not:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
BOOLEAN
DetectPagingMode()
{
// __int64 msr = 0; // later
DWORD cr4 = 0;
/*
msr = __readmsr(0xC0000080); // EFER
if( msr & 0x0500 ) // Long Mode Enabled & Active
{
DbgPrint("Long Mode\n");
return FALSE;
}
*/
cr4 = __readcr4();
if( cr4 & 0x020 ) // PAE Active
{
DbgPrint("PAE is active\n");
return FALSE; // TO DO
}
DbgPrint("PAE is not active\n");
return VirtualToPhysical();
}
|
I plan to have it detect if Long Mode is active also, but now I'm testing only on x86, so I commented that part.
Side-note: I forgot to MmUnmapIoSpace before returning from VirtualToPhysical().
EDIT: no answers, but if anyone lands here in the future: the problem was that I forgot to map the Page Table.
In essence, you need to zero the last 12 bytes of the PDE and then use
to map that physical address to a virtual address.