Virtuális – fizikai címfordítás Windows alatt x64-en

Az Operációs rendszerek tárgyból a héten a Windows memóriakezeléséről tartottam előadást, és gondoltam idén nemcsak azt mutatom meg, hogy Process Explorerben meg a Feladatkezelőben hogyan lehet megnézni a felhasznált és szabad memória mértékét, hanem kicsit mélyebbre ásunk, és megnézzük hogyan kezeli a Windows kernel a fizikai és virtuális memóriát.

Ennek a leírása a legrészletesebben a Windows Internals könyvben olvasható (aminek az ötödik, Vistáról szóló kiadása reméljük még a Windows 7 előtt megjelenik, január óta csúszik:). Hasznos még a részletek megértéséhez az Intel leírása az x86 és x64 architektúráról (Intel 64 and IA-32 Architectures Software Developer’s Manuals), ebből a Volume 3A foglalkozik a memóriakezeléssel részletesebben.

Egy olyan példát akartam mutatni, ahol egy folyamat egy virtuális címére elvégezzük a cím leképezést, és megnézzük, hogy az így kapott fizikai címen tényleg ugyanaz a tartalom található-e. Ilyenre példát láthatunk a WinDbg leírásában (Converting Virtual Addresses to Physical Addresses) vagy pl. ebben a blog bejegyzésben (How to manually translate virtual addresses into physical ones). Ki is próbáltam ezeket, de sehogy se akart összejönni. Először egy Windows 7 Beta virtuális gépet használtam, de ott valamiért nem látta a kernel szimbólumfájljait, holott le voltak töltve. Utána egy Windows XP virtuális gépet vizsgáltam, de ott folyamatosan nem érvényes címeket kaptam. Ráadásul a laptáblák címe is valahogy nagyon gyanús volt, és kezdtem arra gyanakodni, hogy a VMware Tools valamit nagyon megkever. (Holott a memóriánál nem kéne paravirtualizációt használnia legjobb tudomásom szerint. Később ki is derült, hogy nem volt köze a virtualizációnak ehhez, hanem a PAE-vel volt a gond, de erről majd esetleg egy másik bejegyzésben.)

Így aztán nem volt más lehetőségem a gazda operációs rendszeremnek mentem neki a kernel debuggerrel:) [Tanulság: amikor fut a lokális kernel debugger, ne indítsatok Skype-ot. Nagyon csúnya lefagyás lett az eredmény:-] Egyetlen egy gond volt, hogy a fizikai gépemen futó Vista viszont 64 bites volt, úgyhogy meg kellett nézni az x64-es címleképezés menetét, és a fenti példákat adaptálni kellett 64 bites címekre.

1. Érvényes logikai cím meghatározása

A fenti példák mind úgy indulnak, hogy akkor van egy érvényes logikai címünk, és nézzük akkor ezt meg. Igen ám, de honnan lesz ilyenünk?:) Én azt csináltam, hogy írtam egy primitív kis C programot, lefordítottam a Windows SDK-ban lévő C fordítóval, és azt indítottam el WinDbg alatt. A C program mindössze ennyit csinált:

#include <stdio.h>

int main(){  char text[] = {"virtual"};

  printf("%s\n", text);

  getchar();

  return 0;
}

A text változó címére leszünk majd kíváncsiak futás során. Elindítottam WinDbg alatt a programot:

Microsoft (R) Windows Debugger Version 6.11.0001.402 AMD64
Copyright (c) Microsoft Corporation. All rights reserved.

CommandLine: C:\temp\LH_X64_DEBUG\vas.exe
Symbol search path is: SRV*c:\symbols*http://msdl.microsoft.com/download/symbols
Executable search path is:
ModLoad: 00000001`3fad0000 00000001`3fb3f000   vas.exe
ModLoad: 00000000`77b40000 00000000`77cc0000   ntdll.dll
ModLoad: 00000000`77a10000 00000000`77b3c000   C:\Windows\system32\kernel32.dll

(d40.334): Break instruction exception - code 80000003
(first chance)ntdll!DbgBreakPoint:00000000`77b84ea0 cc              int     3

Ha nem töltené be a privát debug szombólumokat a WinDbg a vas.exe-hez (ez volt a példaprogramom neve), akkor ezt meg kell még tenni ahhoz, hogy a lokális változók nevét majd lássa a debugger:

0:000> .sympath+ C:\temp\LH_X64_DEBUG
Symbol search path is: SRV*c:\symbols*http://msdl.microsoft.com/download/symbols;C:\temp\LH_X64_DEBUG
Expanded Symbol search path is: srv*c:\symbols*http://msdl.microsoft.com/download/symbols;c:\temp\lh_x64_debug
0:000> ld vas
Symbols loaded for vas
0:000> lm
start             end                 module name
00000000`77a10000 00000000`77b3c000   kernel32   (deferred)
00000000`77b40000 00000000`77cc0000   ntdll      (pdb symbols)          c:\symbols\ntdll.pdb\C43EA3129CCD4F36AEE8C7BADA1D24C32\ntdll.pdb
00000001`3fad0000 00000001`3fb3f000   vas        (private pdb symbols)  c:\temp\lh_x64_debug\vas.pdb

A .sympath+ hozzáad egy könyvtárat a symbol search path-hoz, az ld betölti egy modul szimbólumait, az lm pedig kilistázza az aktuális modulokat.

Első lépésként nézzük meg, milyen assembly kódra fordult le a rendkívül bonyolult programunk (uf – unassemble function):

0:000> uf vas!mainvas!main
[c:\temp\vas-test\vas.c @ 4]:
4 00000001`3fad1000 4883ec48        sub     rsp,48h
4 00000001`3fad1004 488b050d600600  mov     rax,qword ptr [vas!__security_cookie (00000001`3fb37018)]    4 00000001`3fad100b 4833c4          xor     rax,rsp    4 00000001`3fad100e 4889442430      mov     qword ptr [rsp+30h],rax    5 00000001`3fad1013 488b05ee5f0600  mov     rax,qword ptr [vas!_NULL_IMPORT_DESCRIPTOR <PERF> (vas+0x67008) (00000001`3fb37008)]    5 00000001`3fad101a 4889442428      mov     qword ptr [rsp+28h],rax    8 00000001`3fad101f 488d542428      lea     rdx,[rsp+28h]    8 00000001`3fad1024 488d0de55f0600  lea     rcx,[vas!_NULL_IMPORT_DESCRIPTOR <PERF> (vas+0x67010) (00000001`3fb37010)]    8 00000001`3fad102b e890010000      call    vas!printf (00000001`3fad11c0)   10 00000001`3fad1030 e87b010000      call    vas!getchar (00000001`3fad11b0)   12 00000001`3fad1035 33c0            xor     eax,eax   13 00000001`3fad1037 488b4c2430      mov     rcx,qword ptr [rsp+30h]   13 00000001`3fad103c 4833cc          xor     rcx,rsp   13 00000001`3fad103f e8bc040000      call    vas!__security_check_cookie (00000001`3fad1500)   13 00000001`3fad1044 4883c448        add     rsp,48h   13 00000001`3fad1048 c3              ret

Várjuk meg, amíg belép a main függvénybe (bp – set breakpoint, g – go):

0:000> bp vas!main0:000> g
Breakpoint 0 hit
vas!main:
00000001`3fad1000 4883ec48        sub     rsp,48h

Léptessük el a programot a printf elé, akkorra már beállította a text változó értékét (ta – trace address, r – registers, d – display memory):

0:000> ta 00000001`3fad1024...0:000> r rdx
rdx=00000000002ffc38
0:000> db [rdx]
00000000`002ffc38  76 69 72 74 75 61 6c 00-58 b4 26 21 da d0 00 00  virtual.X.&!....
00000000`002ffc48  12 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00000000`002ffc58  27 16 ad 3f 01 00 00 00-01 00 00 00 00 00 00 00  '..?............
00000000`002ffc68  64 7a da 00 00 00 00 00-00 00 00 00 00 00 00 00  dz..............
00000000`002ffc78  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00000000`002ffc88  00 00 00 00 da d0 00 00-54 5d a3 74 06 00 00 00  ........T].t....
00000000`002ffc98  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00000000`002ffca8  2e 15 ad 3f 01 00 00 00-00 00 00 00 00 00 00 00  ...?............

Az rdx regiszterbe tölti be a text változó címét, ezt tudjuk megnézni az r rdx paranccsal. Után a db megmutatja egy adott memóriacím tartalmát, úgy, hogy byte értékekként és ASCII karakterekként írja ki a tartalmat. Látszik is a text változó értéke, a virtual string. Ezt egyébként meg tudtuk volna nézni egyszerűbben a WinDbg Locals ablakában:

image

Érdemes még megnézni, hogy mit lát a folyamat a saját virtuális címteréből, milyen részek vannak lefoglalva, stb.

0:000> !vadump
BaseAddress:       0000000000000000
RegionSize:        0000000000010000
State:             00010000  MEM_FREE
Protect:           00000001  PAGE_NOACCESS
...
BaseAddress:       00000000002fd000
RegionSize:        0000000000003000
State:             00001000  MEM_COMMIT
Protect:           00000004  PAGE_READWRITE
Type:              00020000  MEM_PRIVATE
...
BaseAddress:       0000000077a11000
RegionSize:        00000000000bc000
State:             00001000  MEM_COMMIT
Protect:           00000020  PAGE_EXECUTE_READ
Type:              01000000  MEM_IMAGE
...

Látszik szépen, hogy melyik privát tartományban van benne a text változónk. Most hogy megvan egy adott változó érvényes logikai címe, átválthatunk a kernel debuggerre, és megnézhetjük hol tárolódik ez a memóriában.

2. A logikai címhez tartozó fizikai cím megkeresése kernel debuggerben

WinDbg-ban a Ctrl+K kombinációval előjövő menüben a Local-t választottam:

Microsoft (R) Windows Debugger Version 6.11.0001.402 AMD64
Copyright (c) Microsoft Corporation. All rights reserved.

Connected to Windows Server 2008/Windows Vista 6001 x64 target at (Sat May  9 21:34:03.746 2009 (GMT+2)), ptr64 TRUE
Symbol search path is: SRV*c:\symbols*http://msdl.microsoft.com/download/symbols
Executable search path is:
Windows Server 2008/Windows Vista Kernel Version 6001 (Service Pack 1) MP (2 procs) Free x64
Product: WinNt, suite: TerminalServer SingleUserTS
Built by: 6001.18226.amd64fre.vistasp1_gdr.090302-1506
Machine Name:
Kernel base = 0xfffff800`01e16000 PsLoadedModuleList = 0xfffff800`01fdbdb0
Debug session time: Sat May  9 21:34:04.090 2009 (GMT+2)
System Uptime: 0 days 0:37:49.796GetContextState failed, 0x80004001

Nézzük akkor meg, hogy innen mit látunk a keresett címen:

lkd> db 00000000002ffc38
00000000`002ffc38  1d cb dd 02 5c d8 aa de-16 cb 4a 02 5c d8 ab de  ....\.....J.\...
00000000`002ffc48  1c cc dd 02 5c d8 ac de-0d ca 05 02 5c d8 ad de  ....\.......\...
00000000`002ffc58  1d cb da 02 5c d8 ae de-0c cb aa 02 5c d8 af de  ....\.......\...
00000000`002ffc68  3c ca 08 02 5c d8 b0 de-2a cb 3b 02 5c d8 b1 de  <...\...*.;.\...
00000000`002ffc78  1e cb 65 02 5c d8 b2 de-1e cb 0b 02 5c d8 b3 de  ..e.\.......\...
00000000`002ffc88  1d cb 8c 02 5c d8 b4 de-1e cb 62 02 5c d8 b5 de  ....\.....b.\...
00000000`002ffc98  1e cb 20 02 5c d8 b6 de-1e cb 14 02 5c d8 b7 de  .. .\.......\...
00000000`002ffca8  1e cb 35 02 5c d8 b8 de-1d cb 95 02 5c d8 b9 de  ..5.\.......\...

Hát ez nem az, ami az előbb volt. Még szerencse, hisz most egy másik folyamat kontextusában vagyunk, így annak a virtuális címterét látjuk. Ebben a címtérben az adott címen természetesen teljesen más van. Keressük meg a példaprogramunkhoz tartozó folyamatot:

lkd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
PROCESS fffffa8004398c10
    SessionId: 1  Cid: 0b80    Peb: 7fffffdf000  ParentCid: 051c
    DirBase: dac57000  ObjectTable: fffff88008d1f7c0  HandleCount: 391.
    Image: windbg.exe

PROCESS fffffa80047c5040
    SessionId: 1  Cid: 0d40    Peb: 7fffffdc000  ParentCid: 0b80
    DirBase: 436b1000  ObjectTable: fffff8800fa9c730  HandleCount:   6.
    Image: vas.exe
...

Innen most minket két dolog érdekel, a PROCESS után szerepel a folyamat címe, a DirBase pedig a legfelső szintű laptáblára mutató fizikai cím (x64 esetén ezt page map level 4 table-nek hívják). Váltsunk át a vas.exe folyamat kontextusára, így ha egy virtuális címet használunk, akkor az az ő címtartományában lesz értelmezve.

lkd> .process /p fffffa80047c5040
Implicit process is now fffffa80`047c5040
GetContextState failed, 0x80004001

Most már csak meg kéne tudnunk az ehhez tartozó fizikai címet. Szerencsére a címfordítás lépéseit elvégzi helyettünk a !vtop parancs (virtual to physical):

lkd> !vtop 436b1000 00000000002ffc38
Amd64VtoP: Virt 00000000`002ffc38, pagedir 436b1000
Amd64VtoP: PML4E 436b1000
Amd64VtoP: PDPE ac484000
Amd64VtoP: PDE 1`19287008
Amd64VtoP: PTE 1`0eb0b7f8
Amd64VtoP: Mapped phys 138d5c38
Virtual address 2ffc38 translates to physical address 138d5c38.

FIGYELEM: A !vtop 2009. januári dokumentációja rosszul írja le a parancs szintaxisát, az első paraméter nem PFN (Page Frame Number, fizikai lapszám, azaz a fizikai címből levéve az utolsó 3 számjegyet hexában, hisz 4KB-os lapok vannak), hanem a BaseDir.

FIGYELEM2: A 64 bites címekben a WinDbg gyakran használja a ` szeparátort a cím felében, hogy könnyebb legyen olvasni. Viszont ezt nem szereti a !vtop, ezt szedjük ki. Ezzel viszonylag sok időm elment, mire rájöttem:)

De elérkeztünk a célunkhoz, a kékkel jelölt érték a keresett fizikai cím. Ezt könnyen le is ellenőrizhetjük a !db paranccsal:

lkd> !db 138d5c38
#138d5c38 76 69 72 74 75 61 6c 00-58 b4 26 21 da d0 00 00 virtual.X.&!....
#138d5c48 12 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#138d5c58 27 16 ad 3f 01 00 00 00-01 00 00 00 00 00 00 00 '..?............
#138d5c68 64 7a da 00 00 00 00 00-00 00 00 00 00 00 00 00 dz..............
#138d5c78 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#138d5c88 00 00 00 00 da d0 00 00-54 5d a3 74 06 00 00 00 ........T].t....
#138d5c98 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#138d5ca8 2e 15 ad 3f 01 00 00 00-00 00 00 00 00 00 00 00 ...?............

Bingó, ugyanazt látjuk, mint amikor a logikai cím tartalmát néztük meg. Tehát az operációs rendszer itt tárolja a text változónk tartalmát.

Azt lenne még érdekes megnézni, hogy hogyan néznek ki a laptábla elemek, és hogy lehet kiszámolni kézzel a címet. De nem teljes lokális kernel debug esetén a folyamat kontextus váltás, és a laptáblák tartalmát megnéző !pte parancs a WinDbg folyamat laptábláit használja (talán azért, mert a laptábla legfelső elemére a CR3 regiszter mutat, és live kernel debug esetén regisztereket nem módosíthat a debugger), így ezt a virtuális címet nem erre oldaná fel. Így ezt egy következő bejegyzésre hagyom.

Reklámok
Kategória: Opre | Közvetlen link a könyvjelzőhöz.

Vélemény, hozzászólás?

Adatok megadása vagy bejelentkezés valamelyik ikonnal:

WordPress.com Logo

Hozzászólhat a WordPress.com felhasználói fiók használatával. Kilépés / Módosítás )

Twitter kép

Hozzászólhat a Twitter felhasználói fiók használatával. Kilépés / Módosítás )

Facebook kép

Hozzászólhat a Facebook felhasználói fiók használatával. Kilépés / Módosítás )

Google+ kép

Hozzászólhat a Google+ felhasználói fiók használatával. Kilépés / Módosítás )

Kapcsolódás: %s