Deskriptory virtuálních adres 2: pod povrchem adresového prosotru

Druhá část seriálu o interní struktuře a reprezentaci privátní části virtuálních adresových prostorů ve Windows. Dozvíte se v ní mimo jiné, co to vlastně ten deskriptor virtálních adres je.

Než se pomocí pomyslného vrtáku probouráme pod povrch virtuálního adresového prostoru, dovolím si upozornit, že se dostáváme do málo známých vod jádra operačního systému, které se navíc odlišují v závislosti na konkrétní verzi. V této části článku se budu opírat převážně o poznatky zjištěné při hrátkách s ukázkovou aplikací Vad.

Následující odstavce obsahují mnohdy neúplné, nepřesné a možná i úplně chybné informace. Jedná se spíše o prezentaci mých pozorování a mého porozumění tomu, jak daná oblast jádra funguje, než o tvrdá fakta, jenž najdete v dokumentacích. K tomuto tématu jsem však žádné rozumné manuály nenašel a i moje oblíbená publikace Windows Internals je skoupá na slovo.

Správce paměti si vnitřně reprezentuje bloky rezervovaných či alokovaných stránek pomocí tzv. deskriptorů virtuálních adres (Virtual Address Descriptor – VAD). Moje pozorování ukazují, že každý deskriptor popisuje právě jeden blok stránek prvotně alokovaný (či rezervovaný) některou z funkcí Windows API (VirtualAlloc, MapViewOfFile…). Operace, které pro rutinu VirtualQuery (VirtualQueryEx) vedou k rozdělení bloku na více částí, neznamenají rozdělení deskriptoru. Jak se dozvíte později, deskriptor virtuálních adres některé své údaje neupravuje podle toho, co se s daným blokem po alokaci děje. Bloky volných stránek nejsou reprezentovány žádným deskriptorem.

Jednotlivé deskriptory jsou spojeny do struktury stromu AVL. Klíč tvoří adresa počátku bloku, který daný deskriptor popisuje. Každý uzel disponuje záznamem MMADDRESS_NODE zajišťujícím propojení v rámci stromu. Jak vidíte z z definice tohoto záznamu níže, deskriptor v něm uchovává odkaz na rodičovský VAD (položka Parent) a odkazy na levého a pravého následníka (členy LeftChild a RightChild). Pozor, dolní dva bity odkazu na rodiče slouží také k uchovávání stupně vyváženosti daného uzlu (prvek Balance), takže je před případným použitím tohoto odkazu musíte vynulovat.

Poznámka: Protože správce haldy přiděluje volné bloky na adresách dělitelných osmi, ne-li dokonce šestnácti, dolní bity ukazatelů mohou být využity k uchovávání dalších informací.

Součástí struktury MMADDRESS_NODE jsou také čísla první a poslední virtuální stránky bloku, který daný deskriptor popisuje (členy StartingVpn a EndingVpn). Pro transformaci těchto hodnot na adresy je nutné je vynásobit minimální velikostí stránky podporované na dané architektuře.

typedef struct _MMADDRESS_NODE {
  union {
    LONG_PTR Balance : 2;
    struct _MMADDRESS_NODE *Parent;
  } u1;
 
  struct _MMADDRESS_NODE *LeftChild;
  struct _MMADDRESS_NODE *RightChild;
  ULONG_PTR StartingVPN;
  ULONG_PTR EdningVPN;
} MMADDRESS_NODE, *PMMADDRESS_NODE;

Vstupní bod do stromu AVL s deskriptory se nachází v položce VadRoot struktury EPROCESS. Z této struktury vyrůstá ještě jeden strom AVL, konkrétně z položky PhysicalVadRoot. Neobsahuje však deskriptory virtuálních adres. V jeho položkách jsou zakódovány informace o přímých mapováních do fyzické paměti.

Vstupní bod stromu AVL reprezentuje záznam MM_AVL_TABLE. Obsahuje jednu strukturu MMADDRESS_NODE s názvem BalancedRoot, jejíž člen RightChild odkazuje na kořen daného stromu. Vývojáři zřejmě zvolili tento způsob nepřímého přístupu z důvodu možných změn kořenového uzlu při přidávání a odebírání uzlů.

Záznam MM_AVL_TABLE dále obsahuje aktuální hloubku stromu (položka DepthOfTree) a aktuální počet uzlů (NumberOfGenericEntries). Úloze, kterou plní zbývající položky, zatím ještě dostatečně nerozumím.

typedef struct _MM_AVL_TABLE {
  MMADDRESS_NODE BalancedRoot;
  ULONG_PTR DepthOfTree: 5;
  ULONG_PTR Unused: 3;
  // 56 bitu na 64bitovych platformach
  ULONG_PTR NumberOfGenericEntries: 24; 
  PVOID NodeHint;
  PVOID NodeFreeHint;
} MM_AVL_TABLE, *PMM_AVL_TABLE;

Deskriptory virtuálních adres se rozdělují do několika typů podle toho, k jakému účelu slouží blok virtuálních stránek, který popisují. Všechny možnosti definuje výčet _MI_VAD_TYPE. V tabulce níže najdete i krátký popis účelu jednotlivých typů deskriptorů.

typedef enum _MI_VAD_TYPE {
  VadNone,
  VadDevicePhysicalMemory,
  VadImageMap,
  VadAwe,
  VadWriteWatch,         
  VadLargePages,
  VadRotatePhysical,           
  VadLargePageSection
} MI_VAD_TYPE, *PMI_VAD_TYPE;

Typ deskriptoru Popis
VadNone Blok privátní paměti, nebo paměťově mapovaný soubor. Skládá ze stránek standardní velikosti (na x86 a x64 4 KB).
VadDevicePhysicalMemory Popisuje bloky paměti mapované do „zařízení“ DevicePhysicalMemory, které reprezentuje fyzickou paměť počítače.
VadImageMap Do bloku virtuální paměti je namapován soubor formátu PE (například hlavní modul programu či knihovna DLL).
VadAwe Virtuální stránky jsou namapovány přímo do rámců fyzické paměti alokovaných prostřednictvím rozšíření Address Windowing Extension (AWE).
VadWriteWatch Deskriptory tohoto typu reprezentují bloky paměti, do jejichž stránek operační systém zaznamenává zápisy. Takový blok lze rezervovat například pomocí funkce VirtualAlloc.
VadLargePages Blok privátní paměti skládající se z velkých stránek (na x86 a x64 2 MB nebo 4 MB).
VadRotatePhysical Popisují bloky virtuálních stránek, jejichž mapování do fyzické paměti (či stránkovacího souboru) se často mění. Jinak řečeno, mapování „rotuje“ mezi několika oblastmi, jako je standardní fyzická paměť a paměť grafické karty či jiného zařízení. Proto tyto deskriptory najdou užití zejména u aplikací, které potřebují přímo komunikovat s grafickou kartou. Podle knihy Windows Internals správce paměti tyto deskriptory využívá až od Windows Vista. Bohužel se mi je zatím nepodařilo pozorovat ani uměle vytvořit.
VadLargePageSection Paměťově mapovaný soubor tvořený velkými stránkami.

Správce paměti reprezentuje deskriptory virtuálních adres třemi různě velkými strukturami. Všechny tři typy struktur se mohou objevit v rámci jednoho stromu AVL. Přitom platí, že správce paměti může strukturu určitého deskriptoru změnit i za běhu. Děje se tak v případě, že aplikace (nebo ovladač) provede s blokem stránek (nebo jeho částí), které daný deskriptor reprezentuje, operaci vyžadující uložení informací, jenž se do aktuální struktury deskriptoru nevejdou. Mezi takové operace patří například volání rutiny MmSecureVirtualMemory.

Podle své interní struktury se deskriptory virtuálních adres rozdělují na krátké (short), normální (normal) a dlouhé (long). Krátké deskriptory reprezentují bloky privátní paměti procesu a popisuje je struktura MMVAD_SHORT. Normální deskriptory správce charakterizuje záznamem MMVAD a reprezentuje jimi oblasti patřící paměťově mapovaným souborům (včetně souborů formátu PE). Deskriptory poslední zmíněné skupiny (reprezentované strukturou MMVAD_LONG) mohou plnit oba zmiňované účely. Uchovávají však ještě další informace, například o tom, které části bloku jsou zabezpečeny proti dealokaci a změně oprávnění.

Definice všech tří struktur deskriptorů vidíte na výpisu níže. Všimněte si, že na strukturách lze téměř zadefinovat podmnožinové uspořádání. Normální deskriptor v sobě obsahuje deskriptor krátký, dlouhý deskriptor je „téměř“ nadmnožinou normálního.

typedef struct _MMVAD_SHORT {
  MMADDRESS_NODE Node;
  union {
    ULONG_PTR LongFlags;
    MMVAD_FLAGS VadFlags;
  } u;
 
  PVOID PushLock;
  union {
    ULONG_PTR LongFlags3;
    MMVAD_FLAGS VadFlags3;
  } u5;
} MMVAD_SHORT, *PMMVAD_SHORT;
 
typedef struct _MMVAD {
  MMADDRESS_NODE Node;
  union {
    ULONG_PTR LongFlags;
    MMVAD_FLAGS VadFlags;
  } u;
 
  PVOID PushLock;
  union {
    ULONG_PTR LongFlags3;
    MMVAD_FLAGS3 VadFlags3;
  } u5;
 
  union {
    ULONG LongFlags2;
    MMVAD_FLAGS2 VadFlags2;
  } u2;
 
  PVOID SubSection;
  PVOID MappedSubSection;
  PVOID FirstPrototypePte;
  PVOID LastContignuousPte;
  LIST_ENTRY ViewLinks;
  PEPROCESS VadProcess;
} MMVAD, *PMMVAD;
 
typedef struct _MMVAD_LONG {
  MMADDRESS_NODE Node;
  union {
    ULONG_PTR LongFlags;
    MMVAD_FLAGS VadFlags;
  } u;
 
  PVOID PushLock;
  union {
    ULONG_PTR LongFlags3;
    MMVAD_FLAGS3 VadFlags3;
  } u5;
 
  union {
    ULONG LongFlags2;
    MMVAD_FLAGS2 VadFlags2;
  } u2;
 
  PVOID SubSection;
  PVOID FirstPrototypePte;
  PVOID LastContignuousPte;
  LIST_ENTRY ViewLinks;
  PEPROCESS VadProcess;
  union {
    LIST_ENTRY SecureList;
    MMADDRESS_LIST Secured;
  } u3;
 
  union {
    PVOID Banked;
    PVOID ExtendedInfo;
  } u4;
} MMVAD_LONG, *PMMVAD_LONG;

Vidíte, že ve strukturách MMVAD_XXX se nachází mnoho položek s potenciálně zajímavým účelem. V dalším díle tohoto seriálu si účel některých položek vysvětlíme. Zaměříme se zejména na skupiny příznaků (položky VadFlags, VadFlags2 a VadFlags3)

Leave a Reply

Your email address will not be published. Required fields are marked *