Using Page Tables Directly


Winstation space cannot be viewed in kd because the debugger is timid in its use of page tables. If its page and page table are resident, a virtual address can always be found and viewed by using the page tables directly. The mapping from virtual to physical can be done by hand by first indexing into the Page Directory of any process participating in the Winstation session, then indexing into the Page Table pointed to by the Page Directory.

Although it is fun to do this once or twice by hand, after that a kd extension is desireable. Code for an extension is included below.

Diagram

                Page Directory

   PD_pa  ---->  +---------+
                 |  PDE's  |
                 +---------+
                 |         |
                 +---------+
                 |   ...   |            Page Table
                 +---------+
   PDE_pa --->   |  PT_pa  |  ------>  +---------+
                 +---------+           |  PTE's  |
                 |   ...   |           +---------+
                 +---------+           |         |
                                       +---------+
                                       |   ...   |              Page
                                       +---------+
                         PTE_pa ---->  |  PA_pa  |  ------>  +---------+ 
                                       +---------+           |         |
                                       |   ...   |           +---------+
                                       +---------+           |   ...   |
                                                             +---------+
                                                VA_pa ---->  |  DWORD  |
                                                             +---------+
                                                             |   ...   |
                                                             +---------+

Variables

Calculations

  1. Use !process to get DirBase. This is a page frame index, the physical address of the page directory. Extract the physical address of the page directory, PD_pa, from DirBase.

    PD_pa = (DirBase & 0xfffff000 )

  2. The higher order 10 bits of the virtual address is an index into the page directory. Each PDE in the page directory is 4 bytes, so multiply by 4. This amounts to choping off the 5 rightmost hex digits and zeroing the two low order bits of the result. Add the offset to the physical address of the page directory to get the physical address of the PDE:

    PDE_pa = PD_pa + (( VA & 0xffc00000 ) >> 20 )

  3. !dd at this address to get the PDE (!dd goes to the physical address).

    PDE = !dd PDE_pa

    Make sure the PDE is odd. The low order bit of a PTE or PDE is the valid bit. If it is not set, then the page table for the page containing the virtual adderss is not resident.

    if ( ! (0x01 & PDE) ) { return( FOO_BAR ) ; }

  4. Else extract the physical address of the page in the page table containing the PTE for the virtual address:

    PT_pa = ( PDE & 0xfffff000 )

  5. Calculate the physical addres of the PTE which maps the virtual address. The middle ten bits of the virtual address index into the PTE. Extract them then multiply by 4. Add the result to the physical base of the page table page. This calculation is tricky to do by hand, since multiplication by 4 is not obvious in hex notation.

    PTE_pa = PT_pa + (( VA & 0x003ff000 ) >> 10 )

  6. !dd at this address to get the PTE:

    PTE = !dd PTE_pa

    Again, make sure the PTE at this address is odd. If not, the page is not resident. The game is over.

    if ( ! (0x01 & PTE) ) { return( FOO_BAR ) ; }

  7. Else extract the physical address of the page containing he virtual address:

    PA_pa = ( PTE & 0xfffff000 )

  8. Offset into this page by the 12 low order bits of the virtual address:

    VA_pa = PA_pa + ( VA & 0x00000fff )

  9. This is the physical address of the sought after virtual address. Use !dd to access its contents:

    !dd VA_pa

KD Extension code


/*
 * kd extension to chase virtual memory address
 * through the page tables.
 *
 * Author: burtonr@citrix.com
 * Date:   1 March 2000
 *
 */

#include <nt.h>
#include <windef.h>
#include <ntkdexts.h>
#include <string.h>

static PNTKD_READ_PHYSICAL_MEMORY ReadPhysical ;
static PNTKD_OUTPUT_ROUTINE OutputRoutine  ;
static PNTKD_GET_EXPRESSION GetExpressionRoutine ;

#define HELP_VATOPA "vatopa page_directory_phy_addr virtual_addr"

enum _VirtualToPhysicalReturnStatus {
     Ok,
     CantAccessMemory,
     PdeInvalid,
     PteInvalid,
     LargePage } ;

enum _VirtualToPhysicalReturnStatus
VirtualToPhysicalUsingDirbase( 
    DWORD dir_base, 
    DWORD virtual_address,
    DWORD * physical_address,
    DWORD verbose )
{
    DWORD PDE, PTE ;
    PHYSICAL_ADDRESS pPDE = { 0L, 0L } ;
    PHYSICAL_ADDRESS pPTE = { 0L, 0L } ;
    PHYSICAL_ADDRESS pVA  = { 0L, 0L } ;
    enum _VirtualToPhysicalReturnStatus rs = Ok ;

    do {
    
        pPDE.LowPart = ( dir_base & 0xfffff000 ) | ((virtual_address & 0xffc00000) >> 20 ) ;
    
        if ( !ReadPhysical( pPDE, (LPVOID)&PDE, sizeof(PDE), NULL ) )
        {
            rs = CantAccessMemory ;
            break ;
        }
        if ( !(PDE & 1) ) 
        { 
            rs = PdeInvalid ;
            break ;
        }
        if ( (PDE & 0x80) ) 
        { 
            rs = LargePage ;
            pVA.LowPart = ( PDE & 0xffc00000 ) | (virtual_address & 0x003fffff) ;
            break ;
        }
    
        pPTE.LowPart = ( PDE & 0xfffff000 ) | ((virtual_address & 0x003ff000) >> 10 ) ;
    
        if (!ReadPhysical( pPTE, &PTE, sizeof(PTE), NULL ) ) 
        {
            rs = CantAccessMemory ;
            break ;
        }
        if ( !(PTE & 1) ) 
        { 
            rs = PteInvalid ;
            break ;
        }
    
        pVA.LowPart = ( PTE & 0xfffff000 ) | (virtual_address & 0x00000fff) ;
    
    } while ( 0 ) ;

    if ( physical_address ) *physical_address = pVA.LowPart ;
    if ( verbose )
    {
        OutputRoutine("\n       Phys. Adr.     Data\n") ;
        OutputRoutine("  PDE: 0x%08x  0x%08x\n", pPDE.LowPart, PDE ) ;
        OutputRoutine("  PTE: 0x%08x  0x%08x\n\n", pPTE.LowPart, PTE ) ;
    }

    return rs ;
}

void
vatopa(
    DWORD dwCurrentPc,
    PNTKD_EXTENSION_APIS lpExtensionApis,
    LPSTR lpArgumentString
    )
{
    DWORD page_directory, virtual_address, physical_address ;
    LPSTR arg1, arg2 ;

    ReadPhysical =  lpExtensionApis->lpReadPhysicalMemRoutine ;
    OutputRoutine = lpExtensionApis->lpOutputRoutine ;
    GetExpressionRoutine = lpExtensionApis->lpGetExpressionRoutine ;

    if ( (arg1 = strtok( lpArgumentString, " " ))
         && (arg2 = strtok( NULL, " " )) )
    {
       page_directory = GetExpressionRoutine( arg1 ) ;
       virtual_address = GetExpressionRoutine( arg2 ) ;
    }
    else 
    {
       OutputRoutine("usage: %s\n", HELP_VATOPA) ;
       return ;
    }

    switch ( VirtualToPhysicalUsingDirbase( page_directory, 
                                        virtual_address, 
                                        &physical_address,
                                        1 ))
    {
    case CantAccessMemory:
        OutputRoutine("error: can't access memory.\n") ;
        break ;

    case PteInvalid:
        OutputRoutine("PTE marked invalid. Page not resident.\n") ;
        break ;

    case PdeInvalid:
        OutputRoutine("PDE marked invalid. Page table not resident.\n") ;
        break ;

    case LargePage:
        OutputRoutine("PDE marked Large. No Page Table needed.\n") ;
        /* fall through to case Ok */

    case Ok:
    default:
       OutputRoutine("Physical Address: 0x%08x\n", physical_address ) ;

    }
}

void
help(

    DWORD dwCurrentPc,
    PNTKD_EXTENSION_APIS lpExtensionApis,
    LPSTR lpArgumentString
    )
{

    OutputRoutine = lpExtensionApis->lpOutputRoutine ;
    OutputRoutine("%s\n", HELP_VATOPA) ;
    OutputRoutine("%s\n", "Ex: vatopa cr3 c0300c00\n" ) ;
}


Burton Rosenberg
1 March 2000