VirtualBox

source: vbox/trunk/src/VBox/VMM/VMMR3/PGMDbg.cpp@ 63648

Last change on this file since 63648 was 63560, checked in by vboxsync, 8 years ago

scm: cleaning up todos

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 104.3 KB
Line 
1/* $Id: PGMDbg.cpp 63560 2016-08-16 14:01:20Z vboxsync $ */
2/** @file
3 * PGM - Page Manager and Monitor - Debugger & Debugging APIs.
4 */
5
6/*
7 * Copyright (C) 2006-2016 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#define LOG_GROUP LOG_GROUP_PGM
23#include <VBox/vmm/pgm.h>
24#include <VBox/vmm/stam.h>
25#include "PGMInternal.h"
26#include <VBox/vmm/vm.h>
27#include <VBox/vmm/uvm.h>
28#include "PGMInline.h"
29#include <iprt/assert.h>
30#include <iprt/asm.h>
31#include <iprt/string.h>
32#include <VBox/log.h>
33#include <VBox/param.h>
34#include <VBox/err.h>
35
36
37/*********************************************************************************************************************************
38* Defined Constants And Macros *
39*********************************************************************************************************************************/
40/** The max needle size that we will bother searching for
41 * This must not be more than half a page! */
42#define MAX_NEEDLE_SIZE 256
43
44
45/*********************************************************************************************************************************
46* Structures and Typedefs *
47*********************************************************************************************************************************/
48/**
49 * State structure for the paging hierarchy dumpers.
50 */
51typedef struct PGMR3DUMPHIERARCHYSTATE
52{
53 /** Pointer to the VM. */
54 PVM pVM;
55 /** Output helpers. */
56 PCDBGFINFOHLP pHlp;
57 /** Set if PSE, PAE or long mode is enabled. */
58 bool fPse;
59 /** Set if PAE or long mode is enabled. */
60 bool fPae;
61 /** Set if long mode is enabled. */
62 bool fLme;
63 /** Set if nested paging. */
64 bool fNp;
65 /** Set if EPT. */
66 bool fEpt;
67 /** Set if NXE is enabled. */
68 bool fNxe;
69 /** The number or chars the address needs. */
70 uint8_t cchAddress;
71 /** The last reserved bit. */
72 uint8_t uLastRsvdBit;
73 /** Dump the page info as well (shadow page summary / guest physical
74 * page summary). */
75 bool fDumpPageInfo;
76 /** Whether or not to print the header. */
77 bool fPrintHeader;
78 /** Whether to print the CR3 value */
79 bool fPrintCr3;
80 /** Padding*/
81 bool afReserved[5];
82 /** The current address. */
83 uint64_t u64Address;
84 /** The last address to dump structures for. */
85 uint64_t u64FirstAddress;
86 /** The last address to dump structures for. */
87 uint64_t u64LastAddress;
88 /** Mask with the high reserved bits set. */
89 uint64_t u64HighReservedBits;
90 /** The number of leaf entries that we've printed. */
91 uint64_t cLeaves;
92} PGMR3DUMPHIERARCHYSTATE;
93/** Pointer to the paging hierarchy dumper state. */
94typedef PGMR3DUMPHIERARCHYSTATE *PPGMR3DUMPHIERARCHYSTATE;
95
96
97/**
98 * Assembly scanning function.
99 *
100 * @returns Pointer to possible match or NULL.
101 * @param pvHaystack Pointer to what we search in.
102 * @param cbHaystack Number of bytes to search.
103 * @param pvNeedle Pointer to what we search for.
104 * @param cbNeedle Size of what we're searching for.
105 */
106
107typedef DECLCALLBACK(uint8_t const *) FNPGMR3DBGFIXEDMEMSCAN(void const *pvHaystack, uint32_t cbHaystack,
108 void const *pvNeedle, size_t cbNeedle);
109/** Pointer to an fixed size and step assembly scanner function. */
110typedef FNPGMR3DBGFIXEDMEMSCAN *PFNPGMR3DBGFIXEDMEMSCAN;
111
112
113/*********************************************************************************************************************************
114* Internal Functions *
115*********************************************************************************************************************************/
116DECLASM(uint8_t const *) pgmR3DbgFixedMemScan8Wide8Step(void const *, uint32_t, void const *, size_t cbNeedle);
117DECLASM(uint8_t const *) pgmR3DbgFixedMemScan4Wide4Step(void const *, uint32_t, void const *, size_t cbNeedle);
118DECLASM(uint8_t const *) pgmR3DbgFixedMemScan2Wide2Step(void const *, uint32_t, void const *, size_t cbNeedle);
119DECLASM(uint8_t const *) pgmR3DbgFixedMemScan1Wide1Step(void const *, uint32_t, void const *, size_t cbNeedle);
120DECLASM(uint8_t const *) pgmR3DbgFixedMemScan4Wide1Step(void const *, uint32_t, void const *, size_t cbNeedle);
121DECLASM(uint8_t const *) pgmR3DbgFixedMemScan8Wide1Step(void const *, uint32_t, void const *, size_t cbNeedle);
122
123
124/**
125 * Converts a R3 pointer to a GC physical address.
126 *
127 * Only for the debugger.
128 *
129 * @returns VBox status code.
130 * @retval VINF_SUCCESS on success, *pGCPhys is set.
131 * @retval VERR_INVALID_POINTER if the pointer is not within the GC physical memory.
132 *
133 * @param pUVM The user mode VM handle.
134 * @param R3Ptr The R3 pointer to convert.
135 * @param pGCPhys Where to store the GC physical address on success.
136 */
137VMMR3DECL(int) PGMR3DbgR3Ptr2GCPhys(PUVM pUVM, RTR3PTR R3Ptr, PRTGCPHYS pGCPhys)
138{
139 NOREF(pUVM); NOREF(R3Ptr);
140 *pGCPhys = NIL_RTGCPHYS;
141 return VERR_NOT_IMPLEMENTED;
142}
143
144
145/**
146 * Converts a R3 pointer to a HC physical address.
147 *
148 * Only for the debugger.
149 *
150 * @returns VBox status code.
151 * @retval VINF_SUCCESS on success, *pHCPhys is set.
152 * @retval VERR_PGM_PHYS_PAGE_RESERVED it it's a valid GC physical page but has no physical backing.
153 * @retval VERR_INVALID_POINTER if the pointer is not within the GC physical memory.
154 *
155 * @param pUVM The user mode VM handle.
156 * @param R3Ptr The R3 pointer to convert.
157 * @param pHCPhys Where to store the HC physical address on success.
158 */
159VMMR3DECL(int) PGMR3DbgR3Ptr2HCPhys(PUVM pUVM, RTR3PTR R3Ptr, PRTHCPHYS pHCPhys)
160{
161 NOREF(pUVM); NOREF(R3Ptr);
162 *pHCPhys = NIL_RTHCPHYS;
163 return VERR_NOT_IMPLEMENTED;
164}
165
166
167/**
168 * Converts a HC physical address to a GC physical address.
169 *
170 * Only for the debugger.
171 *
172 * @returns VBox status code
173 * @retval VINF_SUCCESS on success, *pGCPhys is set.
174 * @retval VERR_INVALID_POINTER if the HC physical address is not within the GC physical memory.
175 *
176 * @param pUVM The user mode VM handle.
177 * @param HCPhys The HC physical address to convert.
178 * @param pGCPhys Where to store the GC physical address on success.
179 */
180VMMR3DECL(int) PGMR3DbgHCPhys2GCPhys(PUVM pUVM, RTHCPHYS HCPhys, PRTGCPHYS pGCPhys)
181{
182 UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE);
183 VM_ASSERT_VALID_EXT_RETURN(pUVM->pVM, VERR_INVALID_VM_HANDLE);
184
185 /*
186 * Validate and adjust the input a bit.
187 */
188 if (HCPhys == NIL_RTHCPHYS)
189 return VERR_INVALID_POINTER;
190 unsigned off = HCPhys & PAGE_OFFSET_MASK;
191 HCPhys &= X86_PTE_PAE_PG_MASK;
192 if (HCPhys == 0)
193 return VERR_INVALID_POINTER;
194
195 for (PPGMRAMRANGE pRam = pUVM->pVM->pgm.s.CTX_SUFF(pRamRangesX);
196 pRam;
197 pRam = pRam->CTX_SUFF(pNext))
198 {
199 uint32_t iPage = pRam->cb >> PAGE_SHIFT;
200 while (iPage-- > 0)
201 if (PGM_PAGE_GET_HCPHYS(&pRam->aPages[iPage]) == HCPhys)
202 {
203 *pGCPhys = pRam->GCPhys + (iPage << PAGE_SHIFT) + off;
204 return VINF_SUCCESS;
205 }
206 }
207 return VERR_INVALID_POINTER;
208}
209
210
211/**
212 * Read physical memory API for the debugger, similar to
213 * PGMPhysSimpleReadGCPhys.
214 *
215 * @returns VBox status code.
216 *
217 * @param pVM The cross context VM structure.
218 * @param pvDst Where to store what's read.
219 * @param GCPhysSrc Where to start reading from.
220 * @param cb The number of bytes to attempt reading.
221 * @param fFlags Flags, MBZ.
222 * @param pcbRead For store the actual number of bytes read, pass NULL if
223 * partial reads are unwanted.
224 * @todo Unused?
225 */
226VMMR3_INT_DECL(int) PGMR3DbgReadGCPhys(PVM pVM, void *pvDst, RTGCPHYS GCPhysSrc, size_t cb, uint32_t fFlags, size_t *pcbRead)
227{
228 /* validate */
229 AssertReturn(!fFlags, VERR_INVALID_PARAMETER);
230 AssertReturn(pVM, VERR_INVALID_PARAMETER);
231
232 /* try simple first. */
233 int rc = PGMPhysSimpleReadGCPhys(pVM, pvDst, GCPhysSrc, cb);
234 if (RT_SUCCESS(rc) || !pcbRead)
235 return rc;
236
237 /* partial read that failed, chop it up in pages. */
238 *pcbRead = 0;
239 rc = VINF_SUCCESS;
240 while (cb > 0)
241 {
242 size_t cbChunk = PAGE_SIZE;
243 cbChunk -= GCPhysSrc & PAGE_OFFSET_MASK;
244 if (cbChunk > cb)
245 cbChunk = cb;
246
247 rc = PGMPhysSimpleReadGCPhys(pVM, pvDst, GCPhysSrc, cbChunk);
248
249 /* advance */
250 if (RT_FAILURE(rc))
251 break;
252 *pcbRead += cbChunk;
253 cb -= cbChunk;
254 GCPhysSrc += cbChunk;
255 pvDst = (uint8_t *)pvDst + cbChunk;
256 }
257
258 return *pcbRead && RT_FAILURE(rc) ? -rc : rc;
259}
260
261
262/**
263 * Write physical memory API for the debugger, similar to
264 * PGMPhysSimpleWriteGCPhys.
265 *
266 * @returns VBox status code.
267 *
268 * @param pVM The cross context VM structure.
269 * @param GCPhysDst Where to start writing.
270 * @param pvSrc What to write.
271 * @param cb The number of bytes to attempt writing.
272 * @param fFlags Flags, MBZ.
273 * @param pcbWritten For store the actual number of bytes written, pass NULL
274 * if partial writes are unwanted.
275 * @todo Unused?
276 */
277VMMR3_INT_DECL(int) PGMR3DbgWriteGCPhys(PVM pVM, RTGCPHYS GCPhysDst, const void *pvSrc, size_t cb, uint32_t fFlags, size_t *pcbWritten)
278{
279 /* validate */
280 AssertReturn(!fFlags, VERR_INVALID_PARAMETER);
281 AssertReturn(pVM, VERR_INVALID_PARAMETER);
282
283 /* try simple first. */
284 int rc = PGMPhysSimpleWriteGCPhys(pVM, GCPhysDst, pvSrc, cb);
285 if (RT_SUCCESS(rc) || !pcbWritten)
286 return rc;
287
288 /* partial write that failed, chop it up in pages. */
289 *pcbWritten = 0;
290 rc = VINF_SUCCESS;
291 while (cb > 0)
292 {
293 size_t cbChunk = PAGE_SIZE;
294 cbChunk -= GCPhysDst & PAGE_OFFSET_MASK;
295 if (cbChunk > cb)
296 cbChunk = cb;
297
298 rc = PGMPhysSimpleWriteGCPhys(pVM, GCPhysDst, pvSrc, cbChunk);
299
300 /* advance */
301 if (RT_FAILURE(rc))
302 break;
303 *pcbWritten += cbChunk;
304 cb -= cbChunk;
305 GCPhysDst += cbChunk;
306 pvSrc = (uint8_t const *)pvSrc + cbChunk;
307 }
308
309 return *pcbWritten && RT_FAILURE(rc) ? -rc : rc;
310
311}
312
313
314/**
315 * Read virtual memory API for the debugger, similar to PGMPhysSimpleReadGCPtr.
316 *
317 * @returns VBox status code.
318 *
319 * @param pVM The cross context VM structure.
320 * @param pvDst Where to store what's read.
321 * @param GCPtrSrc Where to start reading from.
322 * @param cb The number of bytes to attempt reading.
323 * @param fFlags Flags, MBZ.
324 * @param pcbRead For store the actual number of bytes read, pass NULL if
325 * partial reads are unwanted.
326 * @todo Unused?
327 */
328VMMR3_INT_DECL(int) PGMR3DbgReadGCPtr(PVM pVM, void *pvDst, RTGCPTR GCPtrSrc, size_t cb, uint32_t fFlags, size_t *pcbRead)
329{
330 /* validate */
331 AssertReturn(!fFlags, VERR_INVALID_PARAMETER);
332 AssertReturn(pVM, VERR_INVALID_PARAMETER);
333
334 /** @todo SMP support! */
335 PVMCPU pVCpu = &pVM->aCpus[0];
336
337/** @todo deal with HMA */
338 /* try simple first. */
339 int rc = PGMPhysSimpleReadGCPtr(pVCpu, pvDst, GCPtrSrc, cb);
340 if (RT_SUCCESS(rc) || !pcbRead)
341 return rc;
342
343 /* partial read that failed, chop it up in pages. */
344 *pcbRead = 0;
345 rc = VINF_SUCCESS;
346 while (cb > 0)
347 {
348 size_t cbChunk = PAGE_SIZE;
349 cbChunk -= GCPtrSrc & PAGE_OFFSET_MASK;
350 if (cbChunk > cb)
351 cbChunk = cb;
352
353 rc = PGMPhysSimpleReadGCPtr(pVCpu, pvDst, GCPtrSrc, cbChunk);
354
355 /* advance */
356 if (RT_FAILURE(rc))
357 break;
358 *pcbRead += cbChunk;
359 cb -= cbChunk;
360 GCPtrSrc += cbChunk;
361 pvDst = (uint8_t *)pvDst + cbChunk;
362 }
363
364 return *pcbRead && RT_FAILURE(rc) ? -rc : rc;
365
366}
367
368
369/**
370 * Write virtual memory API for the debugger, similar to
371 * PGMPhysSimpleWriteGCPtr.
372 *
373 * @returns VBox status code.
374 *
375 * @param pVM The cross context VM structure.
376 * @param GCPtrDst Where to start writing.
377 * @param pvSrc What to write.
378 * @param cb The number of bytes to attempt writing.
379 * @param fFlags Flags, MBZ.
380 * @param pcbWritten For store the actual number of bytes written, pass NULL
381 * if partial writes are unwanted.
382 * @todo Unused?
383 */
384VMMR3_INT_DECL(int) PGMR3DbgWriteGCPtr(PVM pVM, RTGCPTR GCPtrDst, void const *pvSrc, size_t cb, uint32_t fFlags, size_t *pcbWritten)
385{
386 /* validate */
387 AssertReturn(!fFlags, VERR_INVALID_PARAMETER);
388 AssertReturn(pVM, VERR_INVALID_PARAMETER);
389
390 /** @todo SMP support! */
391 PVMCPU pVCpu = &pVM->aCpus[0];
392
393/** @todo deal with HMA */
394 /* try simple first. */
395 int rc = PGMPhysSimpleWriteGCPtr(pVCpu, GCPtrDst, pvSrc, cb);
396 if (RT_SUCCESS(rc) || !pcbWritten)
397 return rc;
398
399 /* partial write that failed, chop it up in pages. */
400 *pcbWritten = 0;
401 rc = VINF_SUCCESS;
402 while (cb > 0)
403 {
404 size_t cbChunk = PAGE_SIZE;
405 cbChunk -= GCPtrDst & PAGE_OFFSET_MASK;
406 if (cbChunk > cb)
407 cbChunk = cb;
408
409 rc = PGMPhysSimpleWriteGCPtr(pVCpu, GCPtrDst, pvSrc, cbChunk);
410
411 /* advance */
412 if (RT_FAILURE(rc))
413 break;
414 *pcbWritten += cbChunk;
415 cb -= cbChunk;
416 GCPtrDst += cbChunk;
417 pvSrc = (uint8_t const *)pvSrc + cbChunk;
418 }
419
420 return *pcbWritten && RT_FAILURE(rc) ? -rc : rc;
421
422}
423
424
425/**
426 * memchr() with alignment considerations.
427 *
428 * @returns Pointer to matching byte, NULL if none found.
429 * @param pb Where to search. Aligned.
430 * @param b What to search for.
431 * @param cb How much to search .
432 * @param uAlign The alignment restriction of the result.
433 */
434static const uint8_t *pgmR3DbgAlignedMemChr(const uint8_t *pb, uint8_t b, size_t cb, uint32_t uAlign)
435{
436 const uint8_t *pbRet;
437 if (uAlign <= 32)
438 {
439 pbRet = (const uint8_t *)memchr(pb, b, cb);
440 if ((uintptr_t)pbRet & (uAlign - 1))
441 {
442 do
443 {
444 pbRet++;
445 size_t cbLeft = cb - (pbRet - pb);
446 if (!cbLeft)
447 {
448 pbRet = NULL;
449 break;
450 }
451 pbRet = (const uint8_t *)memchr(pbRet, b, cbLeft);
452 } while ((uintptr_t)pbRet & (uAlign - 1));
453 }
454 }
455 else
456 {
457 pbRet = NULL;
458 if (cb)
459 {
460 for (;;)
461 {
462 if (*pb == b)
463 {
464 pbRet = pb;
465 break;
466 }
467 if (cb <= uAlign)
468 break;
469 cb -= uAlign;
470 pb += uAlign;
471 }
472 }
473 }
474 return pbRet;
475}
476
477
478/**
479 * Scans a page for a byte string, keeping track of potential
480 * cross page matches.
481 *
482 * @returns true and *poff on match.
483 * false on mismatch.
484 * @param pbPage Pointer to the current page.
485 * @param poff Input: The offset into the page (aligned).
486 * Output: The page offset of the match on success.
487 * @param cb The number of bytes to search, starting of *poff.
488 * @param uAlign The needle alignment. This is of course less than a page.
489 * @param pabNeedle The byte string to search for.
490 * @param cbNeedle The length of the byte string.
491 * @param pfnFixedMemScan Pointer to assembly scan function, if available for
492 * the given needle and alignment combination.
493 * @param pabPrev The buffer that keeps track of a partial match that we
494 * bring over from the previous page. This buffer must be
495 * at least cbNeedle - 1 big.
496 * @param pcbPrev Input: The number of partial matching bytes from the previous page.
497 * Output: The number of partial matching bytes from this page.
498 * Initialize to 0 before the first call to this function.
499 */
500static bool pgmR3DbgScanPage(const uint8_t *pbPage, int32_t *poff, uint32_t cb, uint32_t uAlign,
501 const uint8_t *pabNeedle, size_t cbNeedle, PFNPGMR3DBGFIXEDMEMSCAN pfnFixedMemScan,
502 uint8_t *pabPrev, size_t *pcbPrev)
503{
504 /*
505 * Try complete any partial match from the previous page.
506 */
507 if (*pcbPrev > 0)
508 {
509 size_t cbPrev = *pcbPrev;
510 Assert(!*poff);
511 Assert(cbPrev < cbNeedle);
512 if (!memcmp(pbPage, pabNeedle + cbPrev, cbNeedle - cbPrev))
513 {
514 if (cbNeedle - cbPrev > cb)
515 return false;
516 *poff = -(int32_t)cbPrev;
517 return true;
518 }
519
520 /* check out the remainder of the previous page. */
521 const uint8_t *pb = pabPrev;
522 for (;;)
523 {
524 if (cbPrev <= uAlign)
525 break;
526 cbPrev -= uAlign;
527 pb = pgmR3DbgAlignedMemChr(pb + uAlign, *pabNeedle, cbPrev, uAlign);
528 if (!pb)
529 break;
530 cbPrev = *pcbPrev - (pb - pabPrev);
531 if ( !memcmp(pb + 1, &pabNeedle[1], cbPrev - 1)
532 && !memcmp(pbPage, pabNeedle + cbPrev, cbNeedle - cbPrev))
533 {
534 if (cbNeedle - cbPrev > cb)
535 return false;
536 *poff = -(int32_t)cbPrev;
537 return true;
538 }
539 }
540
541 *pcbPrev = 0;
542 }
543
544 /*
545 * Match the body of the page.
546 */
547 const uint8_t *pb = pbPage + *poff;
548 const uint8_t * const pbEnd = pb + cb;
549 for (;;)
550 {
551 AssertMsg(((uintptr_t)pb & (uAlign - 1)) == 0, ("%#p %#x\n", pb, uAlign));
552 if (pfnFixedMemScan)
553 pb = pfnFixedMemScan(pb, cb, pabNeedle, cbNeedle);
554 else
555 pb = pgmR3DbgAlignedMemChr(pb, *pabNeedle, cb, uAlign);
556 if (!pb)
557 break;
558 cb = pbEnd - pb;
559 if (cb >= cbNeedle)
560 {
561 /* match? */
562 if (!memcmp(pb + 1, &pabNeedle[1], cbNeedle - 1))
563 {
564 *poff = pb - pbPage;
565 return true;
566 }
567 }
568 else
569 {
570 /* partial match at the end of the page? */
571 if (!memcmp(pb + 1, &pabNeedle[1], cb - 1))
572 {
573 /* We're copying one byte more that we really need here, but wtf. */
574 memcpy(pabPrev, pb, cb);
575 *pcbPrev = cb;
576 return false;
577 }
578 }
579
580 /* no match, skip ahead. */
581 if (cb <= uAlign)
582 break;
583 pb += uAlign;
584 cb -= uAlign;
585 }
586
587 return false;
588}
589
590
591static void pgmR3DbgSelectMemScanFunction(PFNPGMR3DBGFIXEDMEMSCAN *ppfnMemScan, uint32_t GCPhysAlign, size_t cbNeedle)
592{
593 *ppfnMemScan = NULL;
594 switch (GCPhysAlign)
595 {
596 case 1:
597 if (cbNeedle >= 8)
598 *ppfnMemScan = pgmR3DbgFixedMemScan8Wide1Step;
599 else if (cbNeedle >= 4)
600 *ppfnMemScan = pgmR3DbgFixedMemScan4Wide1Step;
601 else
602 *ppfnMemScan = pgmR3DbgFixedMemScan1Wide1Step;
603 break;
604 case 2:
605 if (cbNeedle >= 2)
606 *ppfnMemScan = pgmR3DbgFixedMemScan2Wide2Step;
607 break;
608 case 4:
609 if (cbNeedle >= 4)
610 *ppfnMemScan = pgmR3DbgFixedMemScan4Wide4Step;
611 break;
612 case 8:
613 if (cbNeedle >= 8)
614 *ppfnMemScan = pgmR3DbgFixedMemScan8Wide8Step;
615 break;
616 }
617}
618
619
620
621/**
622 * Scans guest physical memory for a byte string.
623 *
624 * @returns VBox status codes:
625 * @retval VINF_SUCCESS and *pGCPtrHit on success.
626 * @retval VERR_DBGF_MEM_NOT_FOUND if not found.
627 * @retval VERR_INVALID_POINTER if any of the pointer arguments are invalid.
628 * @retval VERR_INVALID_ARGUMENT if any other arguments are invalid.
629 *
630 * @param pVM The cross context VM structure.
631 * @param GCPhys Where to start searching.
632 * @param cbRange The number of bytes to search.
633 * @param GCPhysAlign The alignment of the needle. Must be a power of two
634 * and less or equal to 4GB.
635 * @param pabNeedle The byte string to search for.
636 * @param cbNeedle The length of the byte string. Max 256 bytes.
637 * @param pGCPhysHit Where to store the address of the first occurrence on success.
638 */
639VMMR3_INT_DECL(int) PGMR3DbgScanPhysical(PVM pVM, RTGCPHYS GCPhys, RTGCPHYS cbRange, RTGCPHYS GCPhysAlign,
640 const uint8_t *pabNeedle, size_t cbNeedle, PRTGCPHYS pGCPhysHit)
641{
642 /*
643 * Validate and adjust the input a bit.
644 */
645 if (!VALID_PTR(pGCPhysHit))
646 return VERR_INVALID_POINTER;
647 *pGCPhysHit = NIL_RTGCPHYS;
648
649 if ( !VALID_PTR(pabNeedle)
650 || GCPhys == NIL_RTGCPHYS)
651 return VERR_INVALID_POINTER;
652 if (!cbNeedle)
653 return VERR_INVALID_PARAMETER;
654 if (cbNeedle > MAX_NEEDLE_SIZE)
655 return VERR_INVALID_PARAMETER;
656
657 if (!cbRange)
658 return VERR_DBGF_MEM_NOT_FOUND;
659 if (GCPhys + cbNeedle - 1 < GCPhys)
660 return VERR_DBGF_MEM_NOT_FOUND;
661
662 if (!GCPhysAlign)
663 return VERR_INVALID_PARAMETER;
664 if (GCPhysAlign > UINT32_MAX)
665 return VERR_NOT_POWER_OF_TWO;
666 if (GCPhysAlign & (GCPhysAlign - 1))
667 return VERR_INVALID_PARAMETER;
668
669 if (GCPhys & (GCPhysAlign - 1))
670 {
671 RTGCPHYS Adj = GCPhysAlign - (GCPhys & (GCPhysAlign - 1));
672 if ( cbRange <= Adj
673 || GCPhys + Adj < GCPhys)
674 return VERR_DBGF_MEM_NOT_FOUND;
675 GCPhys += Adj;
676 cbRange -= Adj;
677 }
678
679 const bool fAllZero = ASMMemIsZero(pabNeedle, cbNeedle);
680 const uint32_t cIncPages = GCPhysAlign <= PAGE_SIZE
681 ? 1
682 : GCPhysAlign >> PAGE_SHIFT;
683 const RTGCPHYS GCPhysLast = GCPhys + cbRange - 1 >= GCPhys
684 ? GCPhys + cbRange - 1
685 : ~(RTGCPHYS)0;
686
687 PFNPGMR3DBGFIXEDMEMSCAN pfnMemScan;
688 pgmR3DbgSelectMemScanFunction(&pfnMemScan, (uint32_t)GCPhysAlign, cbNeedle);
689
690 /*
691 * Search the memory - ignore MMIO and zero pages, also don't
692 * bother to match across ranges.
693 */
694 pgmLock(pVM);
695 for (PPGMRAMRANGE pRam = pVM->pgm.s.CTX_SUFF(pRamRangesX);
696 pRam;
697 pRam = pRam->CTX_SUFF(pNext))
698 {
699 /*
700 * If the search range starts prior to the current ram range record,
701 * adjust the search range and possibly conclude the search.
702 */
703 RTGCPHYS off;
704 if (GCPhys < pRam->GCPhys)
705 {
706 if (GCPhysLast < pRam->GCPhys)
707 break;
708 GCPhys = pRam->GCPhys;
709 off = 0;
710 }
711 else
712 off = GCPhys - pRam->GCPhys;
713 if (off < pRam->cb)
714 {
715 /*
716 * Iterate the relevant pages.
717 */
718 uint8_t abPrev[MAX_NEEDLE_SIZE];
719 size_t cbPrev = 0;
720 const uint32_t cPages = pRam->cb >> PAGE_SHIFT;
721 uint32_t iPage = off >> PAGE_SHIFT;
722 uint32_t offPage = GCPhys & PAGE_OFFSET_MASK;
723 GCPhys &= ~(RTGCPHYS)PAGE_OFFSET_MASK;
724 for (;; offPage = 0)
725 {
726 PPGMPAGE pPage = &pRam->aPages[iPage];
727 if ( ( !PGM_PAGE_IS_ZERO(pPage)
728 || fAllZero)
729 && !PGM_PAGE_IS_MMIO_OR_ALIAS(pPage)
730 && !PGM_PAGE_IS_BALLOONED(pPage))
731 {
732 void const *pvPage;
733 PGMPAGEMAPLOCK Lock;
734 int rc = PGMPhysGCPhys2CCPtrReadOnly(pVM, GCPhys, &pvPage, &Lock);
735 if (RT_SUCCESS(rc))
736 {
737 int32_t offHit = offPage;
738 bool fRc;
739 if (GCPhysAlign < PAGE_SIZE)
740 {
741 uint32_t cbSearch = (GCPhys ^ GCPhysLast) & ~(RTGCPHYS)PAGE_OFFSET_MASK
742 ? PAGE_SIZE - (uint32_t)offPage
743 : (GCPhysLast & PAGE_OFFSET_MASK) + 1 - (uint32_t)offPage;
744 fRc = pgmR3DbgScanPage((uint8_t const *)pvPage, &offHit, cbSearch, (uint32_t)GCPhysAlign,
745 pabNeedle, cbNeedle, pfnMemScan, &abPrev[0], &cbPrev);
746 }
747 else
748 fRc = memcmp(pvPage, pabNeedle, cbNeedle) == 0
749 && (GCPhysLast - GCPhys) >= cbNeedle;
750 PGMPhysReleasePageMappingLock(pVM, &Lock);
751 if (fRc)
752 {
753 *pGCPhysHit = GCPhys + offHit;
754 pgmUnlock(pVM);
755 return VINF_SUCCESS;
756 }
757 }
758 else
759 cbPrev = 0; /* ignore error. */
760 }
761 else
762 cbPrev = 0;
763
764 /* advance to the next page. */
765 GCPhys += (RTGCPHYS)cIncPages << PAGE_SHIFT;
766 if (GCPhys >= GCPhysLast) /* (may not always hit, but we're run out of ranges.) */
767 {
768 pgmUnlock(pVM);
769 return VERR_DBGF_MEM_NOT_FOUND;
770 }
771 iPage += cIncPages;
772 if ( iPage < cIncPages
773 || iPage >= cPages)
774 break;
775 }
776 }
777 }
778 pgmUnlock(pVM);
779 return VERR_DBGF_MEM_NOT_FOUND;
780}
781
782
783/**
784 * Scans (guest) virtual memory for a byte string.
785 *
786 * @returns VBox status codes:
787 * @retval VINF_SUCCESS and *pGCPtrHit on success.
788 * @retval VERR_DBGF_MEM_NOT_FOUND if not found.
789 * @retval VERR_INVALID_POINTER if any of the pointer arguments are invalid.
790 * @retval VERR_INVALID_ARGUMENT if any other arguments are invalid.
791 *
792 * @param pVM The cross context VM structure.
793 * @param pVCpu The cross context virtual CPU structure of the CPU
794 * context to search from.
795 * @param GCPtr Where to start searching.
796 * @param GCPtrAlign The alignment of the needle. Must be a power of two
797 * and less or equal to 4GB.
798 * @param cbRange The number of bytes to search. Max 256 bytes.
799 * @param pabNeedle The byte string to search for.
800 * @param cbNeedle The length of the byte string.
801 * @param pGCPtrHit Where to store the address of the first occurrence on success.
802 */
803VMMR3_INT_DECL(int) PGMR3DbgScanVirtual(PVM pVM, PVMCPU pVCpu, RTGCPTR GCPtr, RTGCPTR cbRange, RTGCPTR GCPtrAlign,
804 const uint8_t *pabNeedle, size_t cbNeedle, PRTGCUINTPTR pGCPtrHit)
805{
806 VMCPU_ASSERT_EMT(pVCpu);
807
808 /*
809 * Validate and adjust the input a bit.
810 */
811 if (!VALID_PTR(pGCPtrHit))
812 return VERR_INVALID_POINTER;
813 *pGCPtrHit = 0;
814
815 if (!VALID_PTR(pabNeedle))
816 return VERR_INVALID_POINTER;
817 if (!cbNeedle)
818 return VERR_INVALID_PARAMETER;
819 if (cbNeedle > MAX_NEEDLE_SIZE)
820 return VERR_INVALID_PARAMETER;
821
822 if (!cbRange)
823 return VERR_DBGF_MEM_NOT_FOUND;
824 if (GCPtr + cbNeedle - 1 < GCPtr)
825 return VERR_DBGF_MEM_NOT_FOUND;
826
827 if (!GCPtrAlign)
828 return VERR_INVALID_PARAMETER;
829 if (GCPtrAlign > UINT32_MAX)
830 return VERR_NOT_POWER_OF_TWO;
831 if (GCPtrAlign & (GCPtrAlign - 1))
832 return VERR_INVALID_PARAMETER;
833
834 if (GCPtr & (GCPtrAlign - 1))
835 {
836 RTGCPTR Adj = GCPtrAlign - (GCPtr & (GCPtrAlign - 1));
837 if ( cbRange <= Adj
838 || GCPtr + Adj < GCPtr)
839 return VERR_DBGF_MEM_NOT_FOUND;
840 GCPtr += Adj;
841 cbRange -= Adj;
842 }
843
844 /* Only paged protected mode or long mode here, use the physical scan for
845 the other modes. */
846 PGMMODE enmMode = PGMGetGuestMode(pVCpu);
847 AssertReturn(PGMMODE_WITH_PAGING(enmMode), VERR_PGM_NOT_USED_IN_MODE);
848
849 /*
850 * Search the memory - ignore MMIO, zero and not-present pages.
851 */
852 const bool fAllZero = ASMMemIsZero(pabNeedle, cbNeedle);
853 RTGCPTR GCPtrMask = PGMMODE_IS_LONG_MODE(enmMode) ? UINT64_MAX : UINT32_MAX;
854 uint8_t abPrev[MAX_NEEDLE_SIZE];
855 size_t cbPrev = 0;
856 const uint32_t cIncPages = GCPtrAlign <= PAGE_SIZE
857 ? 1
858 : GCPtrAlign >> PAGE_SHIFT;
859 const RTGCPTR GCPtrLast = GCPtr + cbRange - 1 >= GCPtr
860 ? (GCPtr + cbRange - 1) & GCPtrMask
861 : GCPtrMask;
862 RTGCPTR cPages = (((GCPtrLast - GCPtr) + (GCPtr & PAGE_OFFSET_MASK)) >> PAGE_SHIFT) + 1;
863 uint32_t offPage = GCPtr & PAGE_OFFSET_MASK;
864 GCPtr &= ~(RTGCPTR)PAGE_OFFSET_MASK;
865
866 PFNPGMR3DBGFIXEDMEMSCAN pfnMemScan;
867 pgmR3DbgSelectMemScanFunction(&pfnMemScan, (uint32_t)GCPtrAlign, cbNeedle);
868
869 uint32_t cYieldCountDown = 4096;
870 pgmLock(pVM);
871 for (;; offPage = 0)
872 {
873 PGMPTWALKGST Walk;
874 int rc = pgmGstPtWalk(pVCpu, GCPtr, &Walk);
875 if (RT_SUCCESS(rc) && Walk.u.Core.fSucceeded)
876 {
877 PPGMPAGE pPage = pgmPhysGetPage(pVM, Walk.u.Core.GCPhys);
878 if ( pPage
879 && ( !PGM_PAGE_IS_ZERO(pPage)
880 || fAllZero)
881 && !PGM_PAGE_IS_MMIO_OR_ALIAS(pPage)
882 && !PGM_PAGE_IS_BALLOONED(pPage))
883 {
884 void const *pvPage;
885 PGMPAGEMAPLOCK Lock;
886 rc = PGMPhysGCPhys2CCPtrReadOnly(pVM, Walk.u.Core.GCPhys, &pvPage, &Lock);
887 if (RT_SUCCESS(rc))
888 {
889 int32_t offHit = offPage;
890 bool fRc;
891 if (GCPtrAlign < PAGE_SIZE)
892 {
893 uint32_t cbSearch = cPages > 0
894 ? PAGE_SIZE - (uint32_t)offPage
895 : (GCPtrLast & PAGE_OFFSET_MASK) + 1 - (uint32_t)offPage;
896 fRc = pgmR3DbgScanPage((uint8_t const *)pvPage, &offHit, cbSearch, (uint32_t)GCPtrAlign,
897 pabNeedle, cbNeedle, pfnMemScan, &abPrev[0], &cbPrev);
898 }
899 else
900 fRc = memcmp(pvPage, pabNeedle, cbNeedle) == 0
901 && (GCPtrLast - GCPtr) >= cbNeedle;
902 PGMPhysReleasePageMappingLock(pVM, &Lock);
903 if (fRc)
904 {
905 *pGCPtrHit = GCPtr + offHit;
906 pgmUnlock(pVM);
907 return VINF_SUCCESS;
908 }
909 }
910 else
911 cbPrev = 0; /* ignore error. */
912 }
913 else
914 cbPrev = 0;
915 }
916 else
917 {
918 Assert(Walk.enmType != PGMPTWALKGSTTYPE_INVALID);
919 Assert(!Walk.u.Core.fSucceeded);
920 cbPrev = 0; /* ignore error. */
921
922 /*
923 * Try skip as much as possible. No need to figure out that a PDE
924 * is not present 512 times!
925 */
926 uint64_t cPagesCanSkip;
927 switch (Walk.u.Core.uLevel)
928 {
929 case 1:
930 /* page level, use cIncPages */
931 cPagesCanSkip = 1;
932 break;
933 case 2:
934 if (Walk.enmType == PGMPTWALKGSTTYPE_32BIT)
935 {
936 cPagesCanSkip = X86_PG_ENTRIES - ((GCPtr >> X86_PT_SHIFT) & X86_PT_MASK);
937 Assert(!((GCPtr + ((RTGCPTR)cPagesCanSkip << X86_PT_PAE_SHIFT)) & (RT_BIT_64(X86_PD_SHIFT) - 1)));
938 }
939 else
940 {
941 cPagesCanSkip = X86_PG_PAE_ENTRIES - ((GCPtr >> X86_PT_PAE_SHIFT) & X86_PT_PAE_MASK);
942 Assert(!((GCPtr + ((RTGCPTR)cPagesCanSkip << X86_PT_PAE_SHIFT)) & (RT_BIT_64(X86_PD_PAE_SHIFT) - 1)));
943 }
944 break;
945 case 3:
946 cPagesCanSkip = (X86_PG_PAE_ENTRIES - ((GCPtr >> X86_PD_PAE_SHIFT) & X86_PD_PAE_MASK)) * X86_PG_PAE_ENTRIES
947 - ((GCPtr >> X86_PT_PAE_SHIFT) & X86_PT_PAE_MASK);
948 Assert(!((GCPtr + ((RTGCPTR)cPagesCanSkip << X86_PT_PAE_SHIFT)) & (RT_BIT_64(X86_PDPT_SHIFT) - 1)));
949 break;
950 case 4:
951 cPagesCanSkip = (X86_PG_PAE_ENTRIES - ((GCPtr >> X86_PDPT_SHIFT) & X86_PDPT_MASK_AMD64))
952 * X86_PG_PAE_ENTRIES * X86_PG_PAE_ENTRIES
953 - ((((GCPtr >> X86_PD_PAE_SHIFT) & X86_PD_PAE_MASK)) * X86_PG_PAE_ENTRIES)
954 - (( GCPtr >> X86_PT_PAE_SHIFT) & X86_PT_PAE_MASK);
955 Assert(!((GCPtr + ((RTGCPTR)cPagesCanSkip << X86_PT_PAE_SHIFT)) & (RT_BIT_64(X86_PML4_SHIFT) - 1)));
956 break;
957 case 8:
958 /* The CR3 value is bad, forget the whole search. */
959 cPagesCanSkip = cPages;
960 break;
961 default:
962 AssertMsgFailed(("%d\n", Walk.u.Core.uLevel));
963 cPagesCanSkip = 0;
964 break;
965 }
966 if (cPages <= cPagesCanSkip)
967 break;
968 if (cPagesCanSkip >= cIncPages)
969 {
970 cPages -= cPagesCanSkip;
971 GCPtr += (RTGCPTR)cPagesCanSkip << X86_PT_PAE_SHIFT;
972 continue;
973 }
974 }
975
976 /* advance to the next page. */
977 if (cPages <= cIncPages)
978 break;
979 cPages -= cIncPages;
980 GCPtr += (RTGCPTR)cIncPages << X86_PT_PAE_SHIFT;
981
982 /* Yield the PGM lock every now and then. */
983 if (!--cYieldCountDown)
984 {
985 PDMR3CritSectYield(&pVM->pgm.s.CritSectX);
986 cYieldCountDown = 4096;
987 }
988 }
989 pgmUnlock(pVM);
990 return VERR_DBGF_MEM_NOT_FOUND;
991}
992
993
994/**
995 * Initializes the dumper state.
996 *
997 * @param pState The state to initialize.
998 * @param pVM The cross context VM structure.
999 * @param fFlags The flags.
1000 * @param u64FirstAddr The first address.
1001 * @param u64LastAddr The last address.
1002 * @param pHlp The output helpers.
1003 */
1004static void pgmR3DumpHierarchyInitState(PPGMR3DUMPHIERARCHYSTATE pState, PVM pVM, uint32_t fFlags,
1005 uint64_t u64FirstAddr, uint64_t u64LastAddr, PCDBGFINFOHLP pHlp)
1006{
1007 pState->pVM = pVM;
1008 pState->pHlp = pHlp ? pHlp : DBGFR3InfoLogHlp();
1009 pState->fPse = !!(fFlags & (DBGFPGDMP_FLAGS_PSE | DBGFPGDMP_FLAGS_PAE | DBGFPGDMP_FLAGS_LME));
1010 pState->fPae = !!(fFlags & (DBGFPGDMP_FLAGS_PAE | DBGFPGDMP_FLAGS_LME));
1011 pState->fLme = !!(fFlags & DBGFPGDMP_FLAGS_LME);
1012 pState->fNp = !!(fFlags & DBGFPGDMP_FLAGS_NP);
1013 pState->fEpt = !!(fFlags & DBGFPGDMP_FLAGS_EPT);
1014 pState->fNxe = !!(fFlags & DBGFPGDMP_FLAGS_NXE);
1015 pState->cchAddress = pState->fLme ? 16 : 8;
1016 pState->uLastRsvdBit = pState->fNxe ? 62 : 63;
1017 pState->fDumpPageInfo = !!(fFlags & DBGFPGDMP_FLAGS_PAGE_INFO);
1018 pState->fPrintHeader = !!(fFlags & DBGFPGDMP_FLAGS_HEADER);
1019 pState->fPrintCr3 = !!(fFlags & DBGFPGDMP_FLAGS_PRINT_CR3);
1020 pState->afReserved[0] = false;
1021 pState->afReserved[1] = false;
1022 pState->afReserved[2] = false;
1023 pState->afReserved[3] = false;
1024 pState->afReserved[4] = false;
1025 pState->u64Address = u64FirstAddr;
1026 pState->u64FirstAddress = u64FirstAddr;
1027 pState->u64LastAddress = u64LastAddr;
1028 pState->u64HighReservedBits = pState->uLastRsvdBit == 62 ? UINT64_C(0x7ff) << 52 : UINT64_C(0xfff) << 52;
1029 pState->cLeaves = 0;
1030}
1031
1032
1033/**
1034 * The simple way out, too tired to think of a more elegant solution.
1035 *
1036 * @returns The base address of this page table/directory/whatever.
1037 * @param pState The state where we get the current address.
1038 * @param cShift The shift count for the table entries.
1039 * @param cEntries The number of table entries.
1040 * @param piFirst Where to return the table index of the first
1041 * entry to dump.
1042 * @param piLast Where to return the table index of the last
1043 * entry.
1044 */
1045static uint64_t pgmR3DumpHierarchyCalcRange(PPGMR3DUMPHIERARCHYSTATE pState, uint32_t cShift, uint32_t cEntries,
1046 uint32_t *piFirst, uint32_t *piLast)
1047{
1048 const uint64_t iBase = (pState->u64Address >> cShift) & ~(uint64_t)(cEntries - 1);
1049 const uint64_t iFirst = pState->u64FirstAddress >> cShift;
1050 const uint64_t iLast = pState->u64LastAddress >> cShift;
1051
1052 if ( iBase >= iFirst
1053 && iBase + cEntries - 1 <= iLast)
1054 {
1055 /* full range. */
1056 *piFirst = 0;
1057 *piLast = cEntries - 1;
1058 }
1059 else if ( iBase + cEntries - 1 < iFirst
1060 || iBase > iLast)
1061 {
1062 /* no match */
1063 *piFirst = cEntries;
1064 *piLast = 0;
1065 }
1066 else
1067 {
1068 /* partial overlap */
1069 *piFirst = iBase <= iFirst
1070 ? iFirst - iBase
1071 : 0;
1072 *piLast = iBase + cEntries - 1 <= iLast
1073 ? cEntries - 1
1074 : iLast - iBase;
1075 }
1076
1077 return iBase << cShift;
1078}
1079
1080
1081/**
1082 * Maps/finds the shadow page.
1083 *
1084 * @returns VBox status code.
1085 * @param pState The dumper state.
1086 * @param HCPhys The physical address of the shadow page.
1087 * @param pszDesc The description.
1088 * @param fIsMapping Set if it's a mapping.
1089 * @param ppv Where to return the pointer.
1090 */
1091static int pgmR3DumpHierarchyShwMapPage(PPGMR3DUMPHIERARCHYSTATE pState, RTHCPHYS HCPhys, const char *pszDesc,
1092 bool fIsMapping, void const **ppv)
1093{
1094 void *pvPage;
1095 if (!fIsMapping)
1096 {
1097 int rc = MMPagePhys2PageTry(pState->pVM, HCPhys, &pvPage);
1098 if (RT_FAILURE(rc))
1099 {
1100 pState->pHlp->pfnPrintf(pState->pHlp, "%0*llx error! %s at HCPhys=%RHp was not found in the page pool!\n",
1101 pState->cchAddress, pState->u64Address, pszDesc, HCPhys);
1102 return rc;
1103 }
1104 }
1105 else
1106 {
1107 pvPage = NULL;
1108 for (PPGMMAPPING pMap = pState->pVM->pgm.s.pMappingsR3; pMap; pMap = pMap->pNextR3)
1109 {
1110 uint64_t off = pState->u64Address - pMap->GCPtr;
1111 if (off < pMap->cb)
1112 {
1113 const int iPDE = (uint32_t)(off >> X86_PD_SHIFT);
1114 const int iSub = (int)((off >> X86_PD_PAE_SHIFT) & 1); /* MSC is a pain sometimes */
1115 if ((iSub ? pMap->aPTs[iPDE].HCPhysPaePT1 : pMap->aPTs[iPDE].HCPhysPaePT0) != HCPhys)
1116 pState->pHlp->pfnPrintf(pState->pHlp,
1117 "%0*llx error! Mapping error! PT %d has HCPhysPT=%RHp not %RHp is in the PD.\n",
1118 pState->cchAddress, pState->u64Address, iPDE,
1119 iSub ? pMap->aPTs[iPDE].HCPhysPaePT1 : pMap->aPTs[iPDE].HCPhysPaePT0, HCPhys);
1120 pvPage = &pMap->aPTs[iPDE].paPaePTsR3[iSub];
1121 break;
1122 }
1123 }
1124 if (!pvPage)
1125 {
1126 pState->pHlp->pfnPrintf(pState->pHlp, "%0*llx error! PT mapping %s at HCPhys=%RHp was not found in the page pool!\n",
1127 pState->cchAddress, pState->u64Address, pszDesc, HCPhys);
1128 return VERR_INVALID_PARAMETER;
1129 }
1130 }
1131 *ppv = pvPage;
1132 return VINF_SUCCESS;
1133}
1134
1135
1136/**
1137 * Dumps the a shadow page summary or smth.
1138 *
1139 * @param pState The dumper state.
1140 * @param HCPhys The page address.
1141 */
1142static void pgmR3DumpHierarchyShwTablePageInfo(PPGMR3DUMPHIERARCHYSTATE pState, RTHCPHYS HCPhys)
1143{
1144 pgmLock(pState->pVM);
1145 char szPage[80];
1146 PPGMPOOLPAGE pPage = pgmPoolQueryPageForDbg(pState->pVM->pgm.s.CTX_SUFF(pPool), HCPhys);
1147 if (pPage)
1148 RTStrPrintf(szPage, sizeof(szPage), " idx=0i%u", pPage->idx);
1149 else
1150 {
1151 /* probably a mapping */
1152 strcpy(szPage, " not found");
1153 for (PPGMMAPPING pMap = pState->pVM->pgm.s.pMappingsR3; pMap; pMap = pMap->pNextR3)
1154 {
1155 uint64_t off = pState->u64Address - pMap->GCPtr;
1156 if (off < pMap->cb)
1157 {
1158 const int iPDE = (uint32_t)(off >> X86_PD_SHIFT);
1159 if (pMap->aPTs[iPDE].HCPhysPT == HCPhys)
1160 RTStrPrintf(szPage, sizeof(szPage), " #%u: %s", iPDE, pMap->pszDesc);
1161 else if (pMap->aPTs[iPDE].HCPhysPaePT0 == HCPhys)
1162 RTStrPrintf(szPage, sizeof(szPage), " #%u/0: %s", iPDE, pMap->pszDesc);
1163 else if (pMap->aPTs[iPDE].HCPhysPaePT1 == HCPhys)
1164 RTStrPrintf(szPage, sizeof(szPage), " #%u/1: %s", iPDE, pMap->pszDesc);
1165 else
1166 continue;
1167 break;
1168 }
1169 }
1170 }
1171 pgmUnlock(pState->pVM);
1172 pState->pHlp->pfnPrintf(pState->pHlp, "%s", szPage);
1173}
1174
1175
1176/**
1177 * Figures out which guest page this is and dumps a summary.
1178 *
1179 * @param pState The dumper state.
1180 * @param HCPhys The page address.
1181 * @param cbPage The page size.
1182 */
1183static void pgmR3DumpHierarchyShwGuestPageInfo(PPGMR3DUMPHIERARCHYSTATE pState, RTHCPHYS HCPhys, uint32_t cbPage)
1184{
1185 char szPage[80];
1186 RTGCPHYS GCPhys;
1187 int rc = PGMR3DbgHCPhys2GCPhys(pState->pVM->pUVM, HCPhys, &GCPhys);
1188 if (RT_SUCCESS(rc))
1189 {
1190 pgmLock(pState->pVM);
1191 PCPGMPAGE pPage = pgmPhysGetPage(pState->pVM, GCPhys);
1192 if (pPage)
1193 RTStrPrintf(szPage, sizeof(szPage), "%R[pgmpage]", pPage);
1194 else
1195 strcpy(szPage, "not found");
1196 pgmUnlock(pState->pVM);
1197 pState->pHlp->pfnPrintf(pState->pHlp, " -> %RGp %s", GCPhys, szPage);
1198 }
1199 else
1200 {
1201 /* check the heap */
1202 uint32_t cbAlloc;
1203 rc = MMR3HyperQueryInfoFromHCPhys(pState->pVM, HCPhys, szPage, sizeof(szPage), &cbAlloc);
1204 if (RT_SUCCESS(rc))
1205 pState->pHlp->pfnPrintf(pState->pHlp, " %s %#x bytes", szPage, cbAlloc);
1206 else
1207 pState->pHlp->pfnPrintf(pState->pHlp, " not found");
1208 }
1209 NOREF(cbPage);
1210}
1211
1212
1213/**
1214 * Dumps a PAE shadow page table.
1215 *
1216 * @returns VBox status code (VINF_SUCCESS).
1217 * @param pState The dumper state.
1218 * @param HCPhys The page table address.
1219 * @param fIsMapping Whether it is a mapping.
1220 */
1221static int pgmR3DumpHierarchyShwPaePT(PPGMR3DUMPHIERARCHYSTATE pState, RTHCPHYS HCPhys, bool fIsMapping)
1222{
1223 PCPGMSHWPTPAE pPT;
1224 int rc = pgmR3DumpHierarchyShwMapPage(pState, HCPhys, "Page table", fIsMapping, (void const **)&pPT);
1225 if (RT_FAILURE(rc))
1226 return rc;
1227
1228 uint32_t iFirst, iLast;
1229 uint64_t u64BaseAddress = pgmR3DumpHierarchyCalcRange(pState, X86_PT_PAE_SHIFT, X86_PG_PAE_ENTRIES, &iFirst, &iLast);
1230 for (uint32_t i = iFirst; i <= iLast; i++)
1231 if (PGMSHWPTEPAE_GET_U(pPT->a[i]) & X86_PTE_P)
1232 {
1233 pState->u64Address = u64BaseAddress + ((uint64_t)i << X86_PT_PAE_SHIFT);
1234 if (PGMSHWPTEPAE_IS_P(pPT->a[i]))
1235 {
1236 X86PTEPAE Pte;
1237 Pte.u = PGMSHWPTEPAE_GET_U(pPT->a[i]);
1238 pState->pHlp->pfnPrintf(pState->pHlp,
1239 pState->fLme /*P R S A D G WT CD AT NX 4M a p ? */
1240 ? "%016llx 3 | P %c %c %c %c %c %s %s %s %s 4K %c%c%c %016llx"
1241 : "%08llx 2 | P %c %c %c %c %c %s %s %s %s 4K %c%c%c %016llx",
1242 pState->u64Address,
1243 Pte.n.u1Write ? 'W' : 'R',
1244 Pte.n.u1User ? 'U' : 'S',
1245 Pte.n.u1Accessed ? 'A' : '-',
1246 Pte.n.u1Dirty ? 'D' : '-',
1247 Pte.n.u1Global ? 'G' : '-',
1248 Pte.n.u1WriteThru ? "WT" : "--",
1249 Pte.n.u1CacheDisable? "CD" : "--",
1250 Pte.n.u1PAT ? "AT" : "--",
1251 Pte.n.u1NoExecute ? "NX" : "--",
1252 Pte.u & PGM_PTFLAGS_TRACK_DIRTY ? 'd' : '-',
1253 Pte.u & RT_BIT(10) ? '1' : '0',
1254 Pte.u & PGM_PTFLAGS_CSAM_VALIDATED? 'v' : '-',
1255 Pte.u & X86_PTE_PAE_PG_MASK);
1256 if (pState->fDumpPageInfo)
1257 pgmR3DumpHierarchyShwGuestPageInfo(pState, Pte.u & X86_PTE_PAE_PG_MASK, _4K);
1258 if ((Pte.u >> 52) & 0x7ff)
1259 pState->pHlp->pfnPrintf(pState->pHlp, " 62:52=%03llx%s", (Pte.u >> 52) & 0x7ff, pState->fLme ? "" : "!");
1260 pState->pHlp->pfnPrintf(pState->pHlp, "\n");
1261 }
1262 else if ( (PGMSHWPTEPAE_GET_U(pPT->a[i]) & (pState->pVM->pgm.s.HCPhysInvMmioPg | X86_PTE_PAE_MBZ_MASK_NO_NX))
1263 == (pState->pVM->pgm.s.HCPhysInvMmioPg | X86_PTE_PAE_MBZ_MASK_NO_NX))
1264 pState->pHlp->pfnPrintf(pState->pHlp,
1265 pState->fLme
1266 ? "%016llx 3 | invalid / MMIO optimization\n"
1267 : "%08llx 2 | invalid / MMIO optimization\n",
1268 pState->u64Address);
1269 else
1270 pState->pHlp->pfnPrintf(pState->pHlp,
1271 pState->fLme
1272 ? "%016llx 3 | invalid: %RX64\n"
1273 : "%08llx 2 | invalid: %RX64\n",
1274 pState->u64Address, PGMSHWPTEPAE_GET_U(pPT->a[i]));
1275 pState->cLeaves++;
1276 }
1277 return VINF_SUCCESS;
1278}
1279
1280
1281/**
1282 * Dumps a PAE shadow page directory table.
1283 *
1284 * @returns VBox status code (VINF_SUCCESS).
1285 * @param pState The dumper state.
1286 * @param HCPhys The physical address of the page directory table.
1287 * @param cMaxDepth The maximum depth.
1288 */
1289static int pgmR3DumpHierarchyShwPaePD(PPGMR3DUMPHIERARCHYSTATE pState, RTHCPHYS HCPhys, unsigned cMaxDepth)
1290{
1291 PCX86PDPAE pPD;
1292 int rc = pgmR3DumpHierarchyShwMapPage(pState, HCPhys, "Page directory", false, (void const **)&pPD);
1293 if (RT_FAILURE(rc))
1294 return rc;
1295
1296 Assert(cMaxDepth > 0);
1297 cMaxDepth--;
1298
1299 uint32_t iFirst, iLast;
1300 uint64_t u64BaseAddress = pgmR3DumpHierarchyCalcRange(pState, X86_PD_PAE_SHIFT, X86_PG_PAE_ENTRIES, &iFirst, &iLast);
1301 for (uint32_t i = iFirst; i <= iLast; i++)
1302 {
1303 X86PDEPAE Pde = pPD->a[i];
1304 if (Pde.n.u1Present)
1305 {
1306 pState->u64Address = u64BaseAddress + ((uint64_t)i << X86_PD_PAE_SHIFT);
1307 if (Pde.b.u1Size)
1308 {
1309 pState->pHlp->pfnPrintf(pState->pHlp,
1310 pState->fLme /*P R S A D G WT CD AT NX 2M a p ? phys*/
1311 ? "%016llx 2 | P %c %c %c %c %c %s %s %s %s 2M %c%c%c %016llx"
1312 : "%08llx 1 | P %c %c %c %c %c %s %s %s %s 2M %c%c%c %016llx",
1313 pState->u64Address,
1314 Pde.b.u1Write ? 'W' : 'R',
1315 Pde.b.u1User ? 'U' : 'S',
1316 Pde.b.u1Accessed ? 'A' : '-',
1317 Pde.b.u1Dirty ? 'D' : '-',
1318 Pde.b.u1Global ? 'G' : '-',
1319 Pde.b.u1WriteThru ? "WT" : "--",
1320 Pde.b.u1CacheDisable? "CD" : "--",
1321 Pde.b.u1PAT ? "AT" : "--",
1322 Pde.b.u1NoExecute ? "NX" : "--",
1323 Pde.u & RT_BIT_64(9) ? '1' : '0',
1324 Pde.u & PGM_PDFLAGS_MAPPING ? 'm' : '-',
1325 Pde.u & PGM_PDFLAGS_TRACK_DIRTY ? 'd' : '-',
1326 Pde.u & X86_PDE2M_PAE_PG_MASK);
1327 if (pState->fDumpPageInfo)
1328 pgmR3DumpHierarchyShwGuestPageInfo(pState, Pde.u & X86_PDE2M_PAE_PG_MASK, _2M);
1329 if ((Pde.u >> 52) & 0x7ff)
1330 pState->pHlp->pfnPrintf(pState->pHlp, " 62:52=%03llx%s", (Pde.u >> 52) & 0x7ff, pState->fLme ? "" : "!");
1331 if ((Pde.u >> 13) & 0xff)
1332 pState->pHlp->pfnPrintf(pState->pHlp, " 20:13=%02llx%s", (Pde.u >> 13) & 0x0ff, pState->fLme ? "" : "!");
1333 pState->pHlp->pfnPrintf(pState->pHlp, "\n");
1334
1335 pState->cLeaves++;
1336 }
1337 else
1338 {
1339 pState->pHlp->pfnPrintf(pState->pHlp,
1340 pState->fLme /*P R S A D G WT CD AT NX 4M a p ? phys */
1341 ? "%016llx 2 | P %c %c %c %c %c %s %s .. %s .. %c%c%c %016llx"
1342 : "%08llx 1 | P %c %c %c %c %c %s %s .. %s .. %c%c%c %016llx",
1343 pState->u64Address,
1344 Pde.n.u1Write ? 'W' : 'R',
1345 Pde.n.u1User ? 'U' : 'S',
1346 Pde.n.u1Accessed ? 'A' : '-',
1347 Pde.n.u1Reserved0 ? '?' : '.', /* ignored */
1348 Pde.n.u1Reserved1 ? '?' : '.', /* ignored */
1349 Pde.n.u1WriteThru ? "WT" : "--",
1350 Pde.n.u1CacheDisable? "CD" : "--",
1351 Pde.n.u1NoExecute ? "NX" : "--",
1352 Pde.u & RT_BIT_64(9) ? '1' : '0',
1353 Pde.u & PGM_PDFLAGS_MAPPING ? 'm' : '-',
1354 Pde.u & PGM_PDFLAGS_TRACK_DIRTY ? 'd' : '-',
1355 Pde.u & X86_PDE_PAE_PG_MASK);
1356 if (pState->fDumpPageInfo)
1357 pgmR3DumpHierarchyShwTablePageInfo(pState, Pde.u & X86_PDE_PAE_PG_MASK);
1358 if ((Pde.u >> 52) & 0x7ff)
1359 pState->pHlp->pfnPrintf(pState->pHlp, " 62:52=%03llx!", (Pde.u >> 52) & 0x7ff);
1360 pState->pHlp->pfnPrintf(pState->pHlp, "\n");
1361
1362 if (cMaxDepth)
1363 {
1364 int rc2 = pgmR3DumpHierarchyShwPaePT(pState, Pde.u & X86_PDE_PAE_PG_MASK, !!(Pde.u & PGM_PDFLAGS_MAPPING));
1365 if (rc2 < rc && RT_SUCCESS(rc))
1366 rc = rc2;
1367 }
1368 else
1369 pState->cLeaves++;
1370 }
1371 }
1372 }
1373 return rc;
1374}
1375
1376
1377/**
1378 * Dumps a PAE shadow page directory pointer table.
1379 *
1380 * @returns VBox status code (VINF_SUCCESS).
1381 * @param pState The dumper state.
1382 * @param HCPhys The physical address of the page directory pointer table.
1383 * @param cMaxDepth The maximum depth.
1384 */
1385static int pgmR3DumpHierarchyShwPaePDPT(PPGMR3DUMPHIERARCHYSTATE pState, RTHCPHYS HCPhys, unsigned cMaxDepth)
1386{
1387 /* Fend of addresses that are out of range in PAE mode - simplifies the code below. */
1388 if (!pState->fLme && pState->u64Address >= _4G)
1389 return VINF_SUCCESS;
1390
1391 PCX86PDPT pPDPT;
1392 int rc = pgmR3DumpHierarchyShwMapPage(pState, HCPhys, "Page directory pointer table", false, (void const **)&pPDPT);
1393 if (RT_FAILURE(rc))
1394 return rc;
1395
1396 Assert(cMaxDepth > 0);
1397 cMaxDepth--;
1398
1399 uint32_t iFirst, iLast;
1400 uint64_t u64BaseAddress = pgmR3DumpHierarchyCalcRange(pState, X86_PDPT_SHIFT,
1401 pState->fLme ? X86_PG_AMD64_PDPE_ENTRIES : X86_PG_PAE_PDPE_ENTRIES,
1402 &iFirst, &iLast);
1403 for (uint32_t i = iFirst; i <= iLast; i++)
1404 {
1405 X86PDPE Pdpe = pPDPT->a[i];
1406 if (Pdpe.n.u1Present)
1407 {
1408 pState->u64Address = u64BaseAddress + ((uint64_t)i << X86_PDPT_SHIFT);
1409 if (pState->fLme)
1410 {
1411 pState->pHlp->pfnPrintf(pState->pHlp, /*P R S A D G WT CD AT NX .. a p ? */
1412 "%016llx 1 | P %c %c %c %c %c %s %s %s %s .. %c%c%c %016llx",
1413 pState->u64Address,
1414 Pdpe.lm.u1Write ? 'W' : 'R',
1415 Pdpe.lm.u1User ? 'U' : 'S',
1416 Pdpe.lm.u1Accessed ? 'A' : '-',
1417 Pdpe.lm.u3Reserved & 1? '?' : '.', /* ignored */
1418 Pdpe.lm.u3Reserved & 4? '!' : '.', /* mbz */
1419 Pdpe.lm.u1WriteThru ? "WT" : "--",
1420 Pdpe.lm.u1CacheDisable? "CD" : "--",
1421 Pdpe.lm.u3Reserved & 2? "!" : "..",/* mbz */
1422 Pdpe.lm.u1NoExecute ? "NX" : "--",
1423 Pdpe.u & RT_BIT(9) ? '1' : '0',
1424 Pdpe.u & PGM_PLXFLAGS_PERMANENT ? 'p' : '-',
1425 Pdpe.u & RT_BIT(11) ? '1' : '0',
1426 Pdpe.u & X86_PDPE_PG_MASK);
1427 if (pState->fDumpPageInfo)
1428 pgmR3DumpHierarchyShwTablePageInfo(pState, Pdpe.u & X86_PDPE_PG_MASK);
1429 if ((Pdpe.u >> 52) & 0x7ff)
1430 pState->pHlp->pfnPrintf(pState->pHlp, " 62:52=%03llx", (Pdpe.u >> 52) & 0x7ff);
1431 }
1432 else
1433 {
1434 pState->pHlp->pfnPrintf(pState->pHlp,/*P R S A D G WT CD AT NX .. a p ? */
1435 "%08llx 0 | P %c %c %c %c %c %s %s %s %s .. %c%c%c %016llx",
1436 pState->u64Address,
1437 Pdpe.n.u2Reserved & 1? '!' : '.', /* mbz */
1438 Pdpe.n.u2Reserved & 2? '!' : '.', /* mbz */
1439 Pdpe.n.u4Reserved & 1? '!' : '.', /* mbz */
1440 Pdpe.n.u4Reserved & 2? '!' : '.', /* mbz */
1441 Pdpe.n.u4Reserved & 8? '!' : '.', /* mbz */
1442 Pdpe.n.u1WriteThru ? "WT" : "--",
1443 Pdpe.n.u1CacheDisable? "CD" : "--",
1444 Pdpe.n.u4Reserved & 2? "!" : "..",/* mbz */
1445 Pdpe.lm.u1NoExecute ? "!!" : "..",/* mbz */
1446 Pdpe.u & RT_BIT(9) ? '1' : '0',
1447 Pdpe.u & PGM_PLXFLAGS_PERMANENT ? 'p' : '-',
1448 Pdpe.u & RT_BIT(11) ? '1' : '0',
1449 Pdpe.u & X86_PDPE_PG_MASK);
1450 if (pState->fDumpPageInfo)
1451 pgmR3DumpHierarchyShwTablePageInfo(pState, Pdpe.u & X86_PDPE_PG_MASK);
1452 if ((Pdpe.u >> 52) & 0xfff)
1453 pState->pHlp->pfnPrintf(pState->pHlp, " 63:52=%03llx!", (Pdpe.u >> 52) & 0xfff);
1454 }
1455 pState->pHlp->pfnPrintf(pState->pHlp, "\n");
1456
1457 if (cMaxDepth)
1458 {
1459 int rc2 = pgmR3DumpHierarchyShwPaePD(pState, Pdpe.u & X86_PDPE_PG_MASK, cMaxDepth);
1460 if (rc2 < rc && RT_SUCCESS(rc))
1461 rc = rc2;
1462 }
1463 else
1464 pState->cLeaves++;
1465 }
1466 }
1467 return rc;
1468}
1469
1470
1471/**
1472 * Dumps a 32-bit shadow page table.
1473 *
1474 * @returns VBox status code (VINF_SUCCESS).
1475 * @param pState The dumper state.
1476 * @param HCPhys The physical address of the table.
1477 * @param cMaxDepth The maximum depth.
1478 */
1479static int pgmR3DumpHierarchyShwPaePML4(PPGMR3DUMPHIERARCHYSTATE pState, RTHCPHYS HCPhys, unsigned cMaxDepth)
1480{
1481 PCX86PML4 pPML4;
1482 int rc = pgmR3DumpHierarchyShwMapPage(pState, HCPhys, "Page map level 4", false, (void const **)&pPML4);
1483 if (RT_FAILURE(rc))
1484 return rc;
1485
1486 Assert(cMaxDepth);
1487 cMaxDepth--;
1488
1489 /*
1490 * This is a bit tricky as we're working on unsigned addresses while the
1491 * AMD64 spec uses signed tricks.
1492 */
1493 uint32_t iFirst = (pState->u64FirstAddress >> X86_PML4_SHIFT) & X86_PML4_MASK;
1494 uint32_t iLast = (pState->u64LastAddress >> X86_PML4_SHIFT) & X86_PML4_MASK;
1495 if ( pState->u64LastAddress <= UINT64_C(0x00007fffffffffff)
1496 || pState->u64FirstAddress >= UINT64_C(0xffff800000000000))
1497 { /* Simple, nothing to adjust */ }
1498 else if (pState->u64FirstAddress <= UINT64_C(0x00007fffffffffff))
1499 iLast = X86_PG_AMD64_ENTRIES / 2 - 1;
1500 else if (pState->u64LastAddress >= UINT64_C(0xffff800000000000))
1501 iFirst = X86_PG_AMD64_ENTRIES / 2;
1502 else
1503 iFirst = X86_PG_AMD64_ENTRIES; /* neither address is canonical */
1504
1505 for (uint32_t i = iFirst; i <= iLast; i++)
1506 {
1507 X86PML4E Pml4e = pPML4->a[i];
1508 if (Pml4e.n.u1Present)
1509 {
1510 pState->u64Address = ((uint64_t)i << X86_PML4_SHIFT)
1511 | (i >= RT_ELEMENTS(pPML4->a) / 2 ? UINT64_C(0xffff000000000000) : 0);
1512 pState->pHlp->pfnPrintf(pState->pHlp, /*P R S A D G WT CD AT NX 4M a p ? */
1513 "%016llx 0 | P %c %c %c %c %c %s %s %s %s .. %c%c%c %016llx",
1514 pState->u64Address,
1515 Pml4e.n.u1Write ? 'W' : 'R',
1516 Pml4e.n.u1User ? 'U' : 'S',
1517 Pml4e.n.u1Accessed ? 'A' : '-',
1518 Pml4e.n.u3Reserved & 1? '?' : '.', /* ignored */
1519 Pml4e.n.u3Reserved & 4? '!' : '.', /* mbz */
1520 Pml4e.n.u1WriteThru ? "WT" : "--",
1521 Pml4e.n.u1CacheDisable? "CD" : "--",
1522 Pml4e.n.u3Reserved & 2? "!" : "..",/* mbz */
1523 Pml4e.n.u1NoExecute ? "NX" : "--",
1524 Pml4e.u & RT_BIT(9) ? '1' : '0',
1525 Pml4e.u & PGM_PLXFLAGS_PERMANENT ? 'p' : '-',
1526 Pml4e.u & RT_BIT(11) ? '1' : '0',
1527 Pml4e.u & X86_PML4E_PG_MASK);
1528 if (pState->fDumpPageInfo)
1529 pgmR3DumpHierarchyShwTablePageInfo(pState, Pml4e.u & X86_PML4E_PG_MASK);
1530 if ((Pml4e.u >> 52) & 0x7ff)
1531 pState->pHlp->pfnPrintf(pState->pHlp, " 62:52=%03llx!", (Pml4e.u >> 52) & 0x7ff);
1532 pState->pHlp->pfnPrintf(pState->pHlp, "\n");
1533
1534 if (cMaxDepth)
1535 {
1536 int rc2 = pgmR3DumpHierarchyShwPaePDPT(pState, Pml4e.u & X86_PML4E_PG_MASK, cMaxDepth);
1537 if (rc2 < rc && RT_SUCCESS(rc))
1538 rc = rc2;
1539 }
1540 else
1541 pState->cLeaves++;
1542 }
1543 }
1544 return rc;
1545}
1546
1547
1548/**
1549 * Dumps a 32-bit shadow page table.
1550 *
1551 * @returns VBox status code (VINF_SUCCESS).
1552 * @param pState The dumper state.
1553 * @param HCPhys The physical address of the table.
1554 * @param fMapping Set if it's a guest mapping.
1555 */
1556static int pgmR3DumpHierarchyShw32BitPT(PPGMR3DUMPHIERARCHYSTATE pState, RTHCPHYS HCPhys, bool fMapping)
1557{
1558 PCX86PT pPT;
1559 int rc = pgmR3DumpHierarchyShwMapPage(pState, HCPhys, "Page table", fMapping, (void const **)&pPT);
1560 if (RT_FAILURE(rc))
1561 return rc;
1562
1563 uint32_t iFirst, iLast;
1564 uint64_t u64BaseAddress = pgmR3DumpHierarchyCalcRange(pState, X86_PT_SHIFT, X86_PG_ENTRIES, &iFirst, &iLast);
1565 for (uint32_t i = iFirst; i <= iLast; i++)
1566 {
1567 X86PTE Pte = pPT->a[i];
1568 if (Pte.n.u1Present)
1569 {
1570 pState->u64Address = u64BaseAddress + (i << X86_PT_SHIFT);
1571 pState->pHlp->pfnPrintf(pState->pHlp,/*P R S A D G WT CD AT NX 4M a m d */
1572 "%08llx 1 | P %c %c %c %c %c %s %s %s .. 4K %c%c%c %08x",
1573 pState->u64Address,
1574 Pte.n.u1Write ? 'W' : 'R',
1575 Pte.n.u1User ? 'U' : 'S',
1576 Pte.n.u1Accessed ? 'A' : '-',
1577 Pte.n.u1Dirty ? 'D' : '-',
1578 Pte.n.u1Global ? 'G' : '-',
1579 Pte.n.u1WriteThru ? "WT" : "--",
1580 Pte.n.u1CacheDisable? "CD" : "--",
1581 Pte.n.u1PAT ? "AT" : "--",
1582 Pte.u & PGM_PTFLAGS_TRACK_DIRTY ? 'd' : '-',
1583 Pte.u & RT_BIT(10) ? '1' : '0',
1584 Pte.u & PGM_PTFLAGS_CSAM_VALIDATED ? 'v' : '-',
1585 Pte.u & X86_PDE_PG_MASK);
1586 if (pState->fDumpPageInfo)
1587 pgmR3DumpHierarchyShwGuestPageInfo(pState, Pte.u & X86_PDE_PG_MASK, _4K);
1588 pState->pHlp->pfnPrintf(pState->pHlp, "\n");
1589 }
1590 }
1591 return VINF_SUCCESS;
1592}
1593
1594
1595/**
1596 * Dumps a 32-bit shadow page directory and page tables.
1597 *
1598 * @returns VBox status code (VINF_SUCCESS).
1599 * @param pState The dumper state.
1600 * @param HCPhys The physical address of the table.
1601 * @param cMaxDepth The maximum depth.
1602 */
1603static int pgmR3DumpHierarchyShw32BitPD(PPGMR3DUMPHIERARCHYSTATE pState, RTHCPHYS HCPhys, unsigned cMaxDepth)
1604{
1605 if (pState->u64Address >= _4G)
1606 return VINF_SUCCESS;
1607
1608 PCX86PD pPD;
1609 int rc = pgmR3DumpHierarchyShwMapPage(pState, HCPhys, "Page directory", false, (void const **)&pPD);
1610 if (RT_FAILURE(rc))
1611 return rc;
1612
1613 Assert(cMaxDepth > 0);
1614 cMaxDepth--;
1615
1616 uint32_t iFirst, iLast;
1617 pgmR3DumpHierarchyCalcRange(pState, X86_PD_SHIFT, X86_PG_ENTRIES, &iFirst, &iLast);
1618 for (uint32_t i = iFirst; i <= iLast; i++)
1619 {
1620 X86PDE Pde = pPD->a[i];
1621 if (Pde.n.u1Present)
1622 {
1623 pState->u64Address = (uint32_t)i << X86_PD_SHIFT;
1624 if (Pde.b.u1Size && pState->fPse)
1625 {
1626 uint64_t u64Phys = ((uint64_t)(Pde.u & X86_PDE4M_PG_HIGH_MASK) << X86_PDE4M_PG_HIGH_SHIFT)
1627 | (Pde.u & X86_PDE4M_PG_MASK);
1628 pState->pHlp->pfnPrintf(pState->pHlp,/*P R S A D G WT CD AT NX 4M a m d phys */
1629 "%08llx 0 | P %c %c %c %c %c %s %s %s .. 4M %c%c%c %08llx",
1630 pState->u64Address,
1631 Pde.b.u1Write ? 'W' : 'R',
1632 Pde.b.u1User ? 'U' : 'S',
1633 Pde.b.u1Accessed ? 'A' : '-',
1634 Pde.b.u1Dirty ? 'D' : '-',
1635 Pde.b.u1Global ? 'G' : '-',
1636 Pde.b.u1WriteThru ? "WT" : "--",
1637 Pde.b.u1CacheDisable? "CD" : "--",
1638 Pde.b.u1PAT ? "AT" : "--",
1639 Pde.u & RT_BIT_32(9) ? '1' : '0',
1640 Pde.u & PGM_PDFLAGS_MAPPING ? 'm' : '-',
1641 Pde.u & PGM_PDFLAGS_TRACK_DIRTY ? 'd' : '-',
1642 u64Phys);
1643 if (pState->fDumpPageInfo)
1644 pgmR3DumpHierarchyShwGuestPageInfo(pState, u64Phys, _4M);
1645 pState->pHlp->pfnPrintf(pState->pHlp, "\n");
1646 pState->cLeaves++;
1647 }
1648 else
1649 {
1650 pState->pHlp->pfnPrintf(pState->pHlp,/*P R S A D G WT CD AT NX 4M a m d phys */
1651 "%08llx 0 | P %c %c %c %c %c %s %s .. .. 4K %c%c%c %08x",
1652 pState->u64Address,
1653 Pde.n.u1Write ? 'W' : 'R',
1654 Pde.n.u1User ? 'U' : 'S',
1655 Pde.n.u1Accessed ? 'A' : '-',
1656 Pde.n.u1Reserved0 ? '?' : '.', /* ignored */
1657 Pde.n.u1Reserved1 ? '?' : '.', /* ignored */
1658 Pde.n.u1WriteThru ? "WT" : "--",
1659 Pde.n.u1CacheDisable? "CD" : "--",
1660 Pde.u & RT_BIT_32(9) ? '1' : '0',
1661 Pde.u & PGM_PDFLAGS_MAPPING ? 'm' : '-',
1662 Pde.u & PGM_PDFLAGS_TRACK_DIRTY ? 'd' : '-',
1663 Pde.u & X86_PDE_PG_MASK);
1664 if (pState->fDumpPageInfo)
1665 pgmR3DumpHierarchyShwTablePageInfo(pState, Pde.u & X86_PDE_PG_MASK);
1666 pState->pHlp->pfnPrintf(pState->pHlp, "\n");
1667
1668 if (cMaxDepth)
1669 {
1670 int rc2 = pgmR3DumpHierarchyShw32BitPT(pState, Pde.u & X86_PDE_PG_MASK, !!(Pde.u & PGM_PDFLAGS_MAPPING));
1671 if (rc2 < rc && RT_SUCCESS(rc))
1672 rc = rc2;
1673 }
1674 else
1675 pState->cLeaves++;
1676 }
1677 }
1678 }
1679
1680 return rc;
1681}
1682
1683
1684/**
1685 * Internal worker that initiates the actual dump.
1686 *
1687 * @returns VBox status code.
1688 * @param pState The dumper state.
1689 * @param cr3 The CR3 value.
1690 * @param cMaxDepth The max depth.
1691 */
1692static int pgmR3DumpHierarchyShwDoIt(PPGMR3DUMPHIERARCHYSTATE pState, uint64_t cr3, unsigned cMaxDepth)
1693{
1694 int rc;
1695 unsigned const cch = pState->cchAddress;
1696 uint64_t const cr3Mask = pState->fEpt ? X86_CR3_AMD64_PAGE_MASK
1697 : pState->fLme ? X86_CR3_AMD64_PAGE_MASK
1698 : pState->fPae ? X86_CR3_PAE_PAGE_MASK
1699 : X86_CR3_PAGE_MASK;
1700 if (pState->fPrintCr3)
1701 {
1702 const char * const pszMode = pState->fEpt ? "Extended Page Tables"
1703 : pState->fLme ? "Long Mode"
1704 : pState->fPae ? "PAE Mode"
1705 : pState->fPse ? "32-bit w/ PSE"
1706 : "32-bit";
1707 pState->pHlp->pfnPrintf(pState->pHlp, "cr3=%0*llx", cch, cr3);
1708 if (pState->fDumpPageInfo)
1709 pgmR3DumpHierarchyShwTablePageInfo(pState, cr3 & X86_CR3_AMD64_PAGE_MASK);
1710 pState->pHlp->pfnPrintf(pState->pHlp, " %s%s%s\n",
1711 pszMode,
1712 pState->fNp ? " + Nested Paging" : "",
1713 pState->fNxe ? " + NX" : "");
1714 }
1715
1716
1717 if (pState->fEpt)
1718 {
1719 if (pState->fPrintHeader)
1720 pState->pHlp->pfnPrintf(pState->pHlp,
1721 "%-*s R - Readable\n"
1722 "%-*s | W - Writeable\n"
1723 "%-*s | | X - Executable\n"
1724 "%-*s | | | EMT - EPT memory type\n"
1725 "%-*s | | | | PAT - Ignored PAT?\n"
1726 "%-*s | | | | | AVL1 - 4 available bits\n"
1727 "%-*s | | | | | | AVL2 - 12 available bits\n"
1728 "%-*s Level | | | | | | | page \n"
1729 /* xxxx n **** R W X EMT PAT AVL1 AVL2 xxxxxxxxxxxxx
1730 R W X 7 0 f fff 0123456701234567 */
1731 ,
1732 cch, "", cch, "", cch, "", cch, "", cch, "", cch, "", cch, "", cch, "Address");
1733
1734 pState->pHlp->pfnPrintf(pState->pHlp, "EPT dumping is not yet implemented, sorry.\n");
1735 /** @todo implemented EPT dumping. */
1736 rc = VERR_NOT_IMPLEMENTED;
1737 }
1738 else
1739 {
1740 if (pState->fPrintHeader)
1741 pState->pHlp->pfnPrintf(pState->pHlp,
1742 "%-*s P - Present\n"
1743 "%-*s | R/W - Read (0) / Write (1)\n"
1744 "%-*s | | U/S - User (1) / Supervisor (0)\n"
1745 "%-*s | | | A - Accessed\n"
1746 "%-*s | | | | D - Dirty\n"
1747 "%-*s | | | | | G - Global\n"
1748 "%-*s | | | | | | WT - Write thru\n"
1749 "%-*s | | | | | | | CD - Cache disable\n"
1750 "%-*s | | | | | | | | AT - Attribute table (PAT)\n"
1751 "%-*s | | | | | | | | | NX - No execute (K8)\n"
1752 "%-*s | | | | | | | | | | 4K/4M/2M - Page size.\n"
1753 "%-*s | | | | | | | | | | | AVL - a=allocated; m=mapping; d=track dirty;\n"
1754 "%-*s | | | | | | | | | | | | p=permanent; v=validated;\n"
1755 "%-*s Level | | | | | | | | | | | | Page\n"
1756 /* xxxx n **** P R S A D G WT CD AT NX 4M AVL xxxxxxxxxxxxx
1757 - W U - - - -- -- -- -- -- 010 */
1758 ,
1759 cch, "", cch, "", cch, "", cch, "", cch, "", cch, "", cch, "",
1760 cch, "", cch, "", cch, "", cch, "", cch, "", cch, "", cch, "Address");
1761 if (pState->fLme)
1762 rc = pgmR3DumpHierarchyShwPaePML4(pState, cr3 & cr3Mask, cMaxDepth);
1763 else if (pState->fPae)
1764 rc = pgmR3DumpHierarchyShwPaePDPT(pState, cr3 & cr3Mask, cMaxDepth);
1765 else
1766 rc = pgmR3DumpHierarchyShw32BitPD(pState, cr3 & cr3Mask, cMaxDepth);
1767 }
1768
1769 if (!pState->cLeaves)
1770 pState->pHlp->pfnPrintf(pState->pHlp, "not present\n");
1771 return rc;
1772}
1773
1774
1775/**
1776 * dbgfR3PagingDumpEx worker.
1777 *
1778 * @returns VBox status code.
1779 * @param pVM The cross context VM structure.
1780 * @param cr3 The CR3 register value.
1781 * @param fFlags The flags, DBGFPGDMP_FLAGS_XXX.
1782 * @param u64FirstAddr The start address.
1783 * @param u64LastAddr The address to stop after.
1784 * @param cMaxDepth The max depth.
1785 * @param pHlp The output callbacks. Defaults to log if NULL.
1786 *
1787 * @internal
1788 */
1789VMMR3_INT_DECL(int) PGMR3DumpHierarchyShw(PVM pVM, uint64_t cr3, uint32_t fFlags, uint64_t u64FirstAddr, uint64_t u64LastAddr,
1790 uint32_t cMaxDepth, PCDBGFINFOHLP pHlp)
1791{
1792 /* Minimal validation as we're only supposed to service DBGF. */
1793 AssertReturn(~(fFlags & ~DBGFPGDMP_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER);
1794 AssertReturn(!(fFlags & (DBGFPGDMP_FLAGS_CURRENT_MODE | DBGFPGDMP_FLAGS_CURRENT_CR3)), VERR_INVALID_PARAMETER);
1795 AssertReturn(fFlags & DBGFPGDMP_FLAGS_SHADOW, VERR_INVALID_PARAMETER);
1796
1797 PGMR3DUMPHIERARCHYSTATE State;
1798 pgmR3DumpHierarchyInitState(&State, pVM, fFlags, u64FirstAddr, u64LastAddr, pHlp);
1799 return pgmR3DumpHierarchyShwDoIt(&State, cr3, cMaxDepth);
1800}
1801
1802
1803/**
1804 * Dumps a page table hierarchy use only physical addresses and cr4/lm flags.
1805 *
1806 * @returns VBox status code (VINF_SUCCESS).
1807 * @param pVM The cross context VM structure.
1808 * @param cr3 The root of the hierarchy.
1809 * @param cr4 The cr4, only PAE and PSE is currently used.
1810 * @param fLongMode Set if long mode, false if not long mode.
1811 * @param cMaxDepth Number of levels to dump.
1812 * @param pHlp Pointer to the output functions.
1813 *
1814 * @deprecated Use DBGFR3PagingDumpEx.
1815 */
1816VMMR3DECL(int) PGMR3DumpHierarchyHC(PVM pVM, uint64_t cr3, uint64_t cr4, bool fLongMode, unsigned cMaxDepth, PCDBGFINFOHLP pHlp)
1817{
1818 if (!cMaxDepth)
1819 return VINF_SUCCESS;
1820
1821 PVMCPU pVCpu = VMMGetCpu(pVM);
1822 if (!pVCpu)
1823 pVCpu = &pVM->aCpus[0];
1824
1825 uint32_t fFlags = DBGFPGDMP_FLAGS_HEADER | DBGFPGDMP_FLAGS_PRINT_CR3 | DBGFPGDMP_FLAGS_PAGE_INFO | DBGFPGDMP_FLAGS_SHADOW;
1826 fFlags |= cr4 & (X86_CR4_PAE | X86_CR4_PSE);
1827 if (fLongMode)
1828 fFlags |= DBGFPGDMP_FLAGS_LME;
1829
1830 return DBGFR3PagingDumpEx(pVM->pUVM, pVCpu->idCpu, fFlags, cr3, 0, fLongMode ? UINT64_MAX : UINT32_MAX, cMaxDepth, pHlp);
1831}
1832
1833
1834/**
1835 * Maps the guest page.
1836 *
1837 * @returns VBox status code.
1838 * @param pState The dumper state.
1839 * @param GCPhys The physical address of the guest page.
1840 * @param pszDesc The description.
1841 * @param ppv Where to return the pointer.
1842 * @param pLock Where to return the mapping lock. Hand this to
1843 * PGMPhysReleasePageMappingLock when done.
1844 */
1845static int pgmR3DumpHierarchyGstMapPage(PPGMR3DUMPHIERARCHYSTATE pState, RTGCPHYS GCPhys, const char *pszDesc,
1846 void const **ppv, PPGMPAGEMAPLOCK pLock)
1847{
1848 int rc = PGMPhysGCPhys2CCPtrReadOnly(pState->pVM, GCPhys, ppv, pLock);
1849 if (RT_FAILURE(rc))
1850 {
1851 pState->pHlp->pfnPrintf(pState->pHlp, "%0*llx error! Failed to map %s at GCPhys=%RGp: %Rrc!\n",
1852 pState->cchAddress, pState->u64Address, pszDesc, GCPhys, rc);
1853 return rc;
1854 }
1855 return VINF_SUCCESS;
1856}
1857
1858
1859/**
1860 * Figures out which guest page this is and dumps a summary.
1861 *
1862 * @param pState The dumper state.
1863 * @param GCPhys The page address.
1864 * @param cbPage The page size.
1865 */
1866static void pgmR3DumpHierarchyGstPageInfo(PPGMR3DUMPHIERARCHYSTATE pState, RTGCPHYS GCPhys, uint32_t cbPage)
1867{
1868 char szPage[80];
1869 pgmLock(pState->pVM);
1870 PCPGMPAGE pPage = pgmPhysGetPage(pState->pVM, GCPhys);
1871 if (pPage)
1872 RTStrPrintf(szPage, sizeof(szPage), " %R[pgmpage]", pPage);
1873 else
1874 strcpy(szPage, " not found");
1875 pgmUnlock(pState->pVM);
1876 pState->pHlp->pfnPrintf(pState->pHlp, "%s", szPage);
1877 NOREF(cbPage);
1878}
1879
1880
1881/**
1882 * Checks the entry for reserved bits.
1883 *
1884 * @param pState The dumper state.
1885 * @param u64Entry The entry to check.
1886 */
1887static void pgmR3DumpHierarchyGstCheckReservedHighBits(PPGMR3DUMPHIERARCHYSTATE pState, uint64_t u64Entry)
1888{
1889 uint32_t uRsvd = (u64Entry & pState->u64HighReservedBits) >> 52;
1890 if (uRsvd)
1891 pState->pHlp->pfnPrintf(pState->pHlp, " %u:52=%03x%s",
1892 pState->uLastRsvdBit, uRsvd, pState->fLme ? "" : "!");
1893 /** @todo check the valid physical bits as well. */
1894}
1895
1896
1897/**
1898 * Dumps a PAE shadow page table.
1899 *
1900 * @returns VBox status code (VINF_SUCCESS).
1901 * @param pState The dumper state.
1902 * @param GCPhys The page table address.
1903 */
1904static int pgmR3DumpHierarchyGstPaePT(PPGMR3DUMPHIERARCHYSTATE pState, RTGCPHYS GCPhys)
1905{
1906 PCX86PTPAE pPT;
1907 PGMPAGEMAPLOCK Lock;
1908 int rc = pgmR3DumpHierarchyGstMapPage(pState, GCPhys, "Page table", (void const **)&pPT, &Lock);
1909 if (RT_FAILURE(rc))
1910 return rc;
1911
1912 uint32_t iFirst, iLast;
1913 uint64_t u64BaseAddress = pgmR3DumpHierarchyCalcRange(pState, X86_PT_PAE_SHIFT, X86_PG_PAE_ENTRIES, &iFirst, &iLast);
1914 for (uint32_t i = iFirst; i <= iLast; i++)
1915 {
1916 X86PTEPAE Pte = pPT->a[i];
1917 if (Pte.n.u1Present)
1918 {
1919 pState->u64Address = u64BaseAddress + ((uint64_t)i << X86_PT_PAE_SHIFT);
1920 pState->pHlp->pfnPrintf(pState->pHlp,
1921 pState->fLme /*P R S A D G WT CD AT NX 4M a p ? */
1922 ? "%016llx 3 | P %c %c %c %c %c %s %s %s %s 4K %c%c%c %016llx"
1923 : "%08llx 2 | P %c %c %c %c %c %s %s %s %s 4K %c%c%c %016llx",
1924 pState->u64Address,
1925 Pte.n.u1Write ? 'W' : 'R',
1926 Pte.n.u1User ? 'U' : 'S',
1927 Pte.n.u1Accessed ? 'A' : '-',
1928 Pte.n.u1Dirty ? 'D' : '-',
1929 Pte.n.u1Global ? 'G' : '-',
1930 Pte.n.u1WriteThru ? "WT" : "--",
1931 Pte.n.u1CacheDisable? "CD" : "--",
1932 Pte.n.u1PAT ? "AT" : "--",
1933 Pte.n.u1NoExecute ? "NX" : "--",
1934 Pte.u & RT_BIT(9) ? '1' : '0',
1935 Pte.u & RT_BIT(10) ? '1' : '0',
1936 Pte.u & RT_BIT(11) ? '1' : '0',
1937 Pte.u & X86_PTE_PAE_PG_MASK);
1938 if (pState->fDumpPageInfo)
1939 pgmR3DumpHierarchyGstPageInfo(pState, Pte.u & X86_PTE_PAE_PG_MASK, _4K);
1940 pgmR3DumpHierarchyGstCheckReservedHighBits(pState, Pte.u);
1941 pState->pHlp->pfnPrintf(pState->pHlp, "\n");
1942 pState->cLeaves++;
1943 }
1944 }
1945
1946 PGMPhysReleasePageMappingLock(pState->pVM, &Lock);
1947 return VINF_SUCCESS;
1948}
1949
1950
1951/**
1952 * Dumps a PAE shadow page directory table.
1953 *
1954 * @returns VBox status code (VINF_SUCCESS).
1955 * @param pState The dumper state.
1956 * @param GCPhys The physical address of the table.
1957 * @param cMaxDepth The maximum depth.
1958 */
1959static int pgmR3DumpHierarchyGstPaePD(PPGMR3DUMPHIERARCHYSTATE pState, RTGCPHYS GCPhys, unsigned cMaxDepth)
1960{
1961 PCX86PDPAE pPD;
1962 PGMPAGEMAPLOCK Lock;
1963 int rc = pgmR3DumpHierarchyGstMapPage(pState, GCPhys, "Page directory", (void const **)&pPD, &Lock);
1964 if (RT_FAILURE(rc))
1965 return rc;
1966
1967 Assert(cMaxDepth > 0);
1968 cMaxDepth--;
1969
1970 uint32_t iFirst, iLast;
1971 uint64_t u64BaseAddress = pgmR3DumpHierarchyCalcRange(pState, X86_PD_PAE_SHIFT, X86_PG_PAE_ENTRIES, &iFirst, &iLast);
1972 for (uint32_t i = iFirst; i <= iLast; i++)
1973 {
1974 X86PDEPAE Pde = pPD->a[i];
1975 if (Pde.n.u1Present)
1976 {
1977 pState->u64Address = u64BaseAddress + ((uint64_t)i << X86_PD_PAE_SHIFT);
1978 if (Pde.b.u1Size)
1979 {
1980 pState->pHlp->pfnPrintf(pState->pHlp,
1981 pState->fLme /*P R S A D G WT CD AT NX 2M a p ? phys*/
1982 ? "%016llx 2 | P %c %c %c %c %c %s %s %s %s 2M %c%c%c %016llx"
1983 : "%08llx 1 | P %c %c %c %c %c %s %s %s %s 2M %c%c%c %016llx",
1984 pState->u64Address,
1985 Pde.b.u1Write ? 'W' : 'R',
1986 Pde.b.u1User ? 'U' : 'S',
1987 Pde.b.u1Accessed ? 'A' : '-',
1988 Pde.b.u1Dirty ? 'D' : '-',
1989 Pde.b.u1Global ? 'G' : '-',
1990 Pde.b.u1WriteThru ? "WT" : "--",
1991 Pde.b.u1CacheDisable ? "CD" : "--",
1992 Pde.b.u1PAT ? "AT" : "--",
1993 Pde.b.u1NoExecute ? "NX" : "--",
1994 Pde.u & RT_BIT_64(9) ? '1' : '0',
1995 Pde.u & RT_BIT_64(10) ? '1' : '0',
1996 Pde.u & RT_BIT_64(11) ? '1' : '0',
1997 Pde.u & X86_PDE2M_PAE_PG_MASK);
1998 if (pState->fDumpPageInfo)
1999 pgmR3DumpHierarchyGstPageInfo(pState, Pde.u & X86_PDE2M_PAE_PG_MASK, _2M);
2000 pgmR3DumpHierarchyGstCheckReservedHighBits(pState, Pde.u);
2001 if ((Pde.u >> 13) & 0xff)
2002 pState->pHlp->pfnPrintf(pState->pHlp, " 20:13=%02llx%s", (Pde.u >> 13) & 0x0ff, pState->fLme ? "" : "!");
2003 pState->pHlp->pfnPrintf(pState->pHlp, "\n");
2004
2005 pState->cLeaves++;
2006 }
2007 else
2008 {
2009 pState->pHlp->pfnPrintf(pState->pHlp,
2010 pState->fLme /*P R S A D G WT CD AT NX 4M a p ? phys */
2011 ? "%016llx 2 | P %c %c %c %c %c %s %s .. %s .. %c%c%c %016llx"
2012 : "%08llx 1 | P %c %c %c %c %c %s %s .. %s .. %c%c%c %016llx",
2013 pState->u64Address,
2014 Pde.n.u1Write ? 'W' : 'R',
2015 Pde.n.u1User ? 'U' : 'S',
2016 Pde.n.u1Accessed ? 'A' : '-',
2017 Pde.n.u1Reserved0 ? '?' : '.', /* ignored */
2018 Pde.n.u1Reserved1 ? '?' : '.', /* ignored */
2019 Pde.n.u1WriteThru ? "WT" : "--",
2020 Pde.n.u1CacheDisable ? "CD" : "--",
2021 Pde.n.u1NoExecute ? "NX" : "--",
2022 Pde.u & RT_BIT_64(9) ? '1' : '0',
2023 Pde.u & RT_BIT_64(10) ? '1' : '0',
2024 Pde.u & RT_BIT_64(11) ? '1' : '0',
2025 Pde.u & X86_PDE_PAE_PG_MASK);
2026 if (pState->fDumpPageInfo)
2027 pgmR3DumpHierarchyGstPageInfo(pState, Pde.u & X86_PDE_PAE_PG_MASK, _4K);
2028 pgmR3DumpHierarchyGstCheckReservedHighBits(pState, Pde.u);
2029 pState->pHlp->pfnPrintf(pState->pHlp, "\n");
2030
2031 if (cMaxDepth)
2032 {
2033 int rc2 = pgmR3DumpHierarchyGstPaePT(pState, Pde.u & X86_PDE_PAE_PG_MASK);
2034 if (rc2 < rc && RT_SUCCESS(rc))
2035 rc = rc2;
2036 }
2037 else
2038 pState->cLeaves++;
2039 }
2040 }
2041 }
2042
2043 PGMPhysReleasePageMappingLock(pState->pVM, &Lock);
2044 return rc;
2045}
2046
2047
2048/**
2049 * Dumps a PAE shadow page directory pointer table.
2050 *
2051 * @returns VBox status code (VINF_SUCCESS).
2052 * @param pState The dumper state.
2053 * @param GCPhys The physical address of the table.
2054 * @param cMaxDepth The maximum depth.
2055 */
2056static int pgmR3DumpHierarchyGstPaePDPT(PPGMR3DUMPHIERARCHYSTATE pState, RTGCPHYS GCPhys, unsigned cMaxDepth)
2057{
2058 /* Fend of addresses that are out of range in PAE mode - simplifies the code below. */
2059 if (!pState->fLme && pState->u64Address >= _4G)
2060 return VINF_SUCCESS;
2061
2062 PCX86PDPT pPDPT;
2063 PGMPAGEMAPLOCK Lock;
2064 int rc = pgmR3DumpHierarchyGstMapPage(pState, GCPhys, "Page directory pointer table", (void const **)&pPDPT, &Lock);
2065 if (RT_FAILURE(rc))
2066 return rc;
2067
2068 Assert(cMaxDepth > 0);
2069 cMaxDepth--;
2070
2071 uint32_t iFirst, iLast;
2072 uint64_t u64BaseAddress = pgmR3DumpHierarchyCalcRange(pState, X86_PDPT_SHIFT,
2073 pState->fLme ? X86_PG_AMD64_PDPE_ENTRIES : X86_PG_PAE_PDPE_ENTRIES,
2074 &iFirst, &iLast);
2075 for (uint32_t i = iFirst; i <= iLast; i++)
2076 {
2077 X86PDPE Pdpe = pPDPT->a[i];
2078 if (Pdpe.n.u1Present)
2079 {
2080 pState->u64Address = u64BaseAddress + ((uint64_t)i << X86_PDPT_SHIFT);
2081 if (pState->fLme)
2082 {
2083 /** @todo Do 1G pages. */
2084 pState->pHlp->pfnPrintf(pState->pHlp, /*P R S A D G WT CD AT NX .. a p ? */
2085 "%016llx 1 | P %c %c %c %c %c %s %s %s %s .. %c%c%c %016llx",
2086 pState->u64Address,
2087 Pdpe.lm.u1Write ? 'W' : 'R',
2088 Pdpe.lm.u1User ? 'U' : 'S',
2089 Pdpe.lm.u1Accessed ? 'A' : '-',
2090 Pdpe.lm.u3Reserved & 1 ? '?' : '.', /* ignored */
2091 Pdpe.lm.u3Reserved & 4 ? '!' : '.', /* mbz */
2092 Pdpe.lm.u1WriteThru ? "WT" : "--",
2093 Pdpe.lm.u1CacheDisable ? "CD" : "--",
2094 Pdpe.lm.u3Reserved & 2 ? "!" : "..",/* mbz */
2095 Pdpe.lm.u1NoExecute ? "NX" : "--",
2096 Pdpe.u & RT_BIT_64(9) ? '1' : '0',
2097 Pdpe.u & RT_BIT_64(10) ? '1' : '0',
2098 Pdpe.u & RT_BIT_64(11) ? '1' : '0',
2099 Pdpe.u & X86_PDPE_PG_MASK);
2100 if (pState->fDumpPageInfo)
2101 pgmR3DumpHierarchyGstPageInfo(pState, Pdpe.u & X86_PDPE_PG_MASK, _4K);
2102 pgmR3DumpHierarchyGstCheckReservedHighBits(pState, Pdpe.u);
2103 }
2104 else
2105 {
2106 pState->pHlp->pfnPrintf(pState->pHlp,/*P R S A D G WT CD AT NX .. a p ? */
2107 "%08llx 0 | P %c %c %c %c %c %s %s %s %s .. %c%c%c %016llx",
2108 pState->u64Address,
2109 Pdpe.n.u2Reserved & 1 ? '!' : '.', /* mbz */
2110 Pdpe.n.u2Reserved & 2 ? '!' : '.', /* mbz */
2111 Pdpe.n.u4Reserved & 1 ? '!' : '.', /* mbz */
2112 Pdpe.n.u4Reserved & 2 ? '!' : '.', /* mbz */
2113 Pdpe.n.u4Reserved & 8 ? '!' : '.', /* mbz */
2114 Pdpe.n.u1WriteThru ? "WT" : "--",
2115 Pdpe.n.u1CacheDisable ? "CD" : "--",
2116 Pdpe.n.u4Reserved & 2 ? "!" : "..", /* mbz */
2117 Pdpe.lm.u1NoExecute ? "!!" : "..",/* mbz */
2118 Pdpe.u & RT_BIT_64(9) ? '1' : '0',
2119 Pdpe.u & RT_BIT_64(10) ? '1' : '0',
2120 Pdpe.u & RT_BIT_64(11) ? '1' : '0',
2121 Pdpe.u & X86_PDPE_PG_MASK);
2122 if (pState->fDumpPageInfo)
2123 pgmR3DumpHierarchyGstPageInfo(pState, Pdpe.u & X86_PDPE_PG_MASK, _4K);
2124 pgmR3DumpHierarchyGstCheckReservedHighBits(pState, Pdpe.u);
2125 }
2126 pState->pHlp->pfnPrintf(pState->pHlp, "\n");
2127
2128 if (cMaxDepth)
2129 {
2130 int rc2 = pgmR3DumpHierarchyGstPaePD(pState, Pdpe.u & X86_PDPE_PG_MASK, cMaxDepth);
2131 if (rc2 < rc && RT_SUCCESS(rc))
2132 rc = rc2;
2133 }
2134 else
2135 pState->cLeaves++;
2136 }
2137 }
2138
2139 PGMPhysReleasePageMappingLock(pState->pVM, &Lock);
2140 return rc;
2141}
2142
2143
2144/**
2145 * Dumps a 32-bit shadow page table.
2146 *
2147 * @returns VBox status code (VINF_SUCCESS).
2148 * @param pState The dumper state.
2149 * @param GCPhys The physical address of the table.
2150 * @param cMaxDepth The maximum depth.
2151 */
2152static int pgmR3DumpHierarchyGstPaePML4(PPGMR3DUMPHIERARCHYSTATE pState, RTHCPHYS GCPhys, unsigned cMaxDepth)
2153{
2154 PCX86PML4 pPML4;
2155 PGMPAGEMAPLOCK Lock;
2156 int rc = pgmR3DumpHierarchyGstMapPage(pState, GCPhys, "Page map level 4", (void const **)&pPML4, &Lock);
2157 if (RT_FAILURE(rc))
2158 return rc;
2159
2160 Assert(cMaxDepth);
2161 cMaxDepth--;
2162
2163 /*
2164 * This is a bit tricky as we're working on unsigned addresses while the
2165 * AMD64 spec uses signed tricks.
2166 */
2167 uint32_t iFirst = (pState->u64FirstAddress >> X86_PML4_SHIFT) & X86_PML4_MASK;
2168 uint32_t iLast = (pState->u64LastAddress >> X86_PML4_SHIFT) & X86_PML4_MASK;
2169 if ( pState->u64LastAddress <= UINT64_C(0x00007fffffffffff)
2170 || pState->u64FirstAddress >= UINT64_C(0xffff800000000000))
2171 { /* Simple, nothing to adjust */ }
2172 else if (pState->u64FirstAddress <= UINT64_C(0x00007fffffffffff))
2173 iLast = X86_PG_AMD64_ENTRIES / 2 - 1;
2174 else if (pState->u64LastAddress >= UINT64_C(0xffff800000000000))
2175 iFirst = X86_PG_AMD64_ENTRIES / 2;
2176 else
2177 iFirst = X86_PG_AMD64_ENTRIES; /* neither address is canonical */
2178
2179 for (uint32_t i = iFirst; i <= iLast; i++)
2180 {
2181 X86PML4E Pml4e = pPML4->a[i];
2182 if (Pml4e.n.u1Present)
2183 {
2184 pState->u64Address = ((uint64_t)i << X86_PML4_SHIFT)
2185 | (i >= RT_ELEMENTS(pPML4->a) / 2 ? UINT64_C(0xffff000000000000) : 0);
2186 pState->pHlp->pfnPrintf(pState->pHlp, /*P R S A D G WT CD AT NX 4M a p ? */
2187 "%016llx 0 | P %c %c %c %c %c %s %s %s %s .. %c%c%c %016llx",
2188 pState->u64Address,
2189 Pml4e.n.u1Write ? 'W' : 'R',
2190 Pml4e.n.u1User ? 'U' : 'S',
2191 Pml4e.n.u1Accessed ? 'A' : '-',
2192 Pml4e.n.u3Reserved & 1 ? '?' : '.', /* ignored */
2193 Pml4e.n.u3Reserved & 4 ? '!' : '.', /* mbz */
2194 Pml4e.n.u1WriteThru ? "WT" : "--",
2195 Pml4e.n.u1CacheDisable ? "CD" : "--",
2196 Pml4e.n.u3Reserved & 2 ? "!" : "..",/* mbz */
2197 Pml4e.n.u1NoExecute ? "NX" : "--",
2198 Pml4e.u & RT_BIT_64(9) ? '1' : '0',
2199 Pml4e.u & RT_BIT_64(10) ? '1' : '0',
2200 Pml4e.u & RT_BIT_64(11) ? '1' : '0',
2201 Pml4e.u & X86_PML4E_PG_MASK);
2202 if (pState->fDumpPageInfo)
2203 pgmR3DumpHierarchyGstPageInfo(pState, Pml4e.u & X86_PML4E_PG_MASK, _4K);
2204 pgmR3DumpHierarchyGstCheckReservedHighBits(pState, Pml4e.u);
2205 pState->pHlp->pfnPrintf(pState->pHlp, "\n");
2206
2207 if (cMaxDepth)
2208 {
2209 int rc2 = pgmR3DumpHierarchyGstPaePDPT(pState, Pml4e.u & X86_PML4E_PG_MASK, cMaxDepth);
2210 if (rc2 < rc && RT_SUCCESS(rc))
2211 rc = rc2;
2212 }
2213 else
2214 pState->cLeaves++;
2215 }
2216 }
2217
2218 PGMPhysReleasePageMappingLock(pState->pVM, &Lock);
2219 return rc;
2220}
2221
2222
2223/**
2224 * Dumps a 32-bit shadow page table.
2225 *
2226 * @returns VBox status code (VINF_SUCCESS).
2227 * @param pState The dumper state.
2228 * @param GCPhys The physical address of the table.
2229 */
2230static int pgmR3DumpHierarchyGst32BitPT(PPGMR3DUMPHIERARCHYSTATE pState, RTHCPHYS GCPhys)
2231{
2232 PCX86PT pPT;
2233 PGMPAGEMAPLOCK Lock;
2234 int rc = pgmR3DumpHierarchyGstMapPage(pState, GCPhys, "Page table", (void const **)&pPT, &Lock);
2235 if (RT_FAILURE(rc))
2236 return rc;
2237
2238 uint32_t iFirst, iLast;
2239 uint64_t u64BaseAddress = pgmR3DumpHierarchyCalcRange(pState, X86_PT_SHIFT, X86_PG_ENTRIES, &iFirst, &iLast);
2240 for (uint32_t i = iFirst; i <= iLast; i++)
2241 {
2242 X86PTE Pte = pPT->a[i];
2243 if (Pte.n.u1Present)
2244 {
2245 pState->u64Address = u64BaseAddress + (i << X86_PT_SHIFT);
2246 pState->pHlp->pfnPrintf(pState->pHlp,/*P R S A D G WT CD AT NX 4M a m d */
2247 "%08llx 1 | P %c %c %c %c %c %s %s %s .. 4K %c%c%c %08x",
2248 pState->u64Address,
2249 Pte.n.u1Write ? 'W' : 'R',
2250 Pte.n.u1User ? 'U' : 'S',
2251 Pte.n.u1Accessed ? 'A' : '-',
2252 Pte.n.u1Dirty ? 'D' : '-',
2253 Pte.n.u1Global ? 'G' : '-',
2254 Pte.n.u1WriteThru ? "WT" : "--",
2255 Pte.n.u1CacheDisable ? "CD" : "--",
2256 Pte.n.u1PAT ? "AT" : "--",
2257 Pte.u & RT_BIT_32(9) ? '1' : '0',
2258 Pte.u & RT_BIT_32(10) ? '1' : '0',
2259 Pte.u & RT_BIT_32(11) ? '1' : '0',
2260 Pte.u & X86_PDE_PG_MASK);
2261 if (pState->fDumpPageInfo)
2262 pgmR3DumpHierarchyGstPageInfo(pState, Pte.u & X86_PDE_PG_MASK, _4K);
2263 pState->pHlp->pfnPrintf(pState->pHlp, "\n");
2264 }
2265 }
2266
2267 PGMPhysReleasePageMappingLock(pState->pVM, &Lock);
2268 return VINF_SUCCESS;
2269}
2270
2271
2272/**
2273 * Dumps a 32-bit shadow page directory and page tables.
2274 *
2275 * @returns VBox status code (VINF_SUCCESS).
2276 * @param pState The dumper state.
2277 * @param GCPhys The physical address of the table.
2278 * @param cMaxDepth The maximum depth.
2279 */
2280static int pgmR3DumpHierarchyGst32BitPD(PPGMR3DUMPHIERARCHYSTATE pState, RTHCPHYS GCPhys, unsigned cMaxDepth)
2281{
2282 if (pState->u64Address >= _4G)
2283 return VINF_SUCCESS;
2284
2285 PCX86PD pPD;
2286 PGMPAGEMAPLOCK Lock;
2287 int rc = pgmR3DumpHierarchyGstMapPage(pState, GCPhys, "Page directory", (void const **)&pPD, &Lock);
2288 if (RT_FAILURE(rc))
2289 return rc;
2290
2291 Assert(cMaxDepth > 0);
2292 cMaxDepth--;
2293
2294 uint32_t iFirst, iLast;
2295 pgmR3DumpHierarchyCalcRange(pState, X86_PD_SHIFT, X86_PG_ENTRIES, &iFirst, &iLast);
2296 for (uint32_t i = iFirst; i <= iLast; i++)
2297 {
2298 X86PDE Pde = pPD->a[i];
2299 if (Pde.n.u1Present)
2300 {
2301 pState->u64Address = (uint32_t)i << X86_PD_SHIFT;
2302 if (Pde.b.u1Size && pState->fPse)
2303 {
2304 uint64_t u64Phys = ((uint64_t)(Pde.u & X86_PDE4M_PG_HIGH_MASK) << X86_PDE4M_PG_HIGH_SHIFT)
2305 | (Pde.u & X86_PDE4M_PG_MASK);
2306 pState->pHlp->pfnPrintf(pState->pHlp,/*P R S A D G WT CD AT NX 4M a m d phys */
2307 "%08llx 0 | P %c %c %c %c %c %s %s %s .. 4M %c%c%c %08llx",
2308 pState->u64Address,
2309 Pde.b.u1Write ? 'W' : 'R',
2310 Pde.b.u1User ? 'U' : 'S',
2311 Pde.b.u1Accessed ? 'A' : '-',
2312 Pde.b.u1Dirty ? 'D' : '-',
2313 Pde.b.u1Global ? 'G' : '-',
2314 Pde.b.u1WriteThru ? "WT" : "--",
2315 Pde.b.u1CacheDisable ? "CD" : "--",
2316 Pde.b.u1PAT ? "AT" : "--",
2317 Pde.u & RT_BIT_32(9) ? '1' : '0',
2318 Pde.u & RT_BIT_32(10) ? '1' : '0',
2319 Pde.u & RT_BIT_32(11) ? '1' : '0',
2320 u64Phys);
2321 if (pState->fDumpPageInfo)
2322 pgmR3DumpHierarchyGstPageInfo(pState, u64Phys, _4M);
2323 pState->pHlp->pfnPrintf(pState->pHlp, "\n");
2324 pState->cLeaves++;
2325 }
2326 else
2327 {
2328 pState->pHlp->pfnPrintf(pState->pHlp,/*P R S A D G WT CD AT NX 4M a m d phys */
2329 "%08llx 0 | P %c %c %c %c %c %s %s .. .. .. %c%c%c %08x",
2330 pState->u64Address,
2331 Pde.n.u1Write ? 'W' : 'R',
2332 Pde.n.u1User ? 'U' : 'S',
2333 Pde.n.u1Accessed ? 'A' : '-',
2334 Pde.n.u1Reserved0 ? '?' : '.', /* ignored */
2335 Pde.n.u1Reserved1 ? '?' : '.', /* ignored */
2336 Pde.n.u1WriteThru ? "WT" : "--",
2337 Pde.n.u1CacheDisable ? "CD" : "--",
2338 Pde.u & RT_BIT_32(9) ? '1' : '0',
2339 Pde.u & RT_BIT_32(10) ? '1' : '0',
2340 Pde.u & RT_BIT_32(11) ? '1' : '0',
2341 Pde.u & X86_PDE_PG_MASK);
2342 if (pState->fDumpPageInfo)
2343 pgmR3DumpHierarchyGstPageInfo(pState, Pde.u & X86_PDE_PG_MASK, _4K);
2344 pState->pHlp->pfnPrintf(pState->pHlp, "\n");
2345
2346 if (cMaxDepth)
2347 {
2348 int rc2 = pgmR3DumpHierarchyGst32BitPT(pState, Pde.u & X86_PDE_PG_MASK);
2349 if (rc2 < rc && RT_SUCCESS(rc))
2350 rc = rc2;
2351 }
2352 else
2353 pState->cLeaves++;
2354 }
2355 }
2356 }
2357
2358 PGMPhysReleasePageMappingLock(pState->pVM, &Lock);
2359 return rc;
2360}
2361
2362
2363/**
2364 * Internal worker that initiates the actual dump.
2365 *
2366 * @returns VBox status code.
2367 * @param pState The dumper state.
2368 * @param cr3 The CR3 value.
2369 * @param cMaxDepth The max depth.
2370 */
2371static int pgmR3DumpHierarchyGstDoIt(PPGMR3DUMPHIERARCHYSTATE pState, uint64_t cr3, unsigned cMaxDepth)
2372{
2373 int rc;
2374 unsigned const cch = pState->cchAddress;
2375 uint64_t const cr3Mask = pState->fEpt ? X86_CR3_AMD64_PAGE_MASK
2376 : pState->fLme ? X86_CR3_AMD64_PAGE_MASK
2377 : pState->fPae ? X86_CR3_PAE_PAGE_MASK
2378 : X86_CR3_PAGE_MASK;
2379 if (pState->fPrintCr3)
2380 {
2381 const char * const pszMode = pState->fEpt ? "Extended Page Tables"
2382 : pState->fLme ? "Long Mode"
2383 : pState->fPae ? "PAE Mode"
2384 : pState->fPse ? "32-bit w/ PSE"
2385 : "32-bit";
2386 pState->pHlp->pfnPrintf(pState->pHlp, "cr3=%0*llx", cch, cr3);
2387 if (pState->fDumpPageInfo)
2388 pgmR3DumpHierarchyGstPageInfo(pState, cr3 & X86_CR3_AMD64_PAGE_MASK, _4K);
2389 pState->pHlp->pfnPrintf(pState->pHlp, " %s%s%s\n",
2390 pszMode,
2391 pState->fNp ? " + Nested Paging" : "",
2392 pState->fNxe ? " + NX" : "");
2393 }
2394
2395
2396 if (pState->fEpt)
2397 {
2398 if (pState->fPrintHeader)
2399 pState->pHlp->pfnPrintf(pState->pHlp,
2400 "%-*s R - Readable\n"
2401 "%-*s | W - Writeable\n"
2402 "%-*s | | X - Executable\n"
2403 "%-*s | | | EMT - EPT memory type\n"
2404 "%-*s | | | | PAT - Ignored PAT?\n"
2405 "%-*s | | | | | AVL1 - 4 available bits\n"
2406 "%-*s | | | | | | AVL2 - 12 available bits\n"
2407 "%-*s Level | | | | | | | page \n"
2408 /* xxxx n **** R W X EMT PAT AVL1 AVL2 xxxxxxxxxxxxx
2409 R W X 7 0 f fff 0123456701234567 */
2410 ,
2411 cch, "", cch, "", cch, "", cch, "", cch, "", cch, "", cch, "", cch, "Address");
2412
2413 pState->pHlp->pfnPrintf(pState->pHlp, "EPT dumping is not yet implemented, sorry.\n");
2414 /** @todo implemented EPT dumping. */
2415 rc = VERR_NOT_IMPLEMENTED;
2416 }
2417 else
2418 {
2419 if (pState->fPrintHeader)
2420 pState->pHlp->pfnPrintf(pState->pHlp,
2421 "%-*s P - Present\n"
2422 "%-*s | R/W - Read (0) / Write (1)\n"
2423 "%-*s | | U/S - User (1) / Supervisor (0)\n"
2424 "%-*s | | | A - Accessed\n"
2425 "%-*s | | | | D - Dirty\n"
2426 "%-*s | | | | | G - Global\n"
2427 "%-*s | | | | | | WT - Write thru\n"
2428 "%-*s | | | | | | | CD - Cache disable\n"
2429 "%-*s | | | | | | | | AT - Attribute table (PAT)\n"
2430 "%-*s | | | | | | | | | NX - No execute (K8)\n"
2431 "%-*s | | | | | | | | | | 4K/4M/2M - Page size.\n"
2432 "%-*s | | | | | | | | | | | AVL - 3 available bits.\n"
2433 "%-*s Level | | | | | | | | | | | | Page\n"
2434 /* xxxx n **** P R S A D G WT CD AT NX 4M AVL xxxxxxxxxxxxx
2435 - W U - - - -- -- -- -- -- 010 */
2436 ,
2437 cch, "", cch, "", cch, "", cch, "", cch, "", cch, "", cch, "",
2438 cch, "", cch, "", cch, "", cch, "", cch, "", cch, "Address");
2439 if (pState->fLme)
2440 rc = pgmR3DumpHierarchyGstPaePML4(pState, cr3 & cr3Mask, cMaxDepth);
2441 else if (pState->fPae)
2442 rc = pgmR3DumpHierarchyGstPaePDPT(pState, cr3 & cr3Mask, cMaxDepth);
2443 else
2444 rc = pgmR3DumpHierarchyGst32BitPD(pState, cr3 & cr3Mask, cMaxDepth);
2445 }
2446
2447 if (!pState->cLeaves)
2448 pState->pHlp->pfnPrintf(pState->pHlp, "not present\n");
2449 return rc;
2450}
2451
2452
2453/**
2454 * dbgfR3PagingDumpEx worker.
2455 *
2456 * @returns VBox status code.
2457 * @param pVM The cross context VM structure.
2458 * @param cr3 The CR3 register value.
2459 * @param fFlags The flags, DBGFPGDMP_FLAGS_XXX.
2460 * @param FirstAddr The start address.
2461 * @param LastAddr The address to stop after.
2462 * @param cMaxDepth The max depth.
2463 * @param pHlp The output callbacks. Defaults to log if NULL.
2464 *
2465 * @internal
2466 */
2467VMMR3_INT_DECL(int) PGMR3DumpHierarchyGst(PVM pVM, uint64_t cr3, uint32_t fFlags, RTGCPTR FirstAddr, RTGCPTR LastAddr,
2468 uint32_t cMaxDepth, PCDBGFINFOHLP pHlp)
2469{
2470 /* Minimal validation as we're only supposed to service DBGF. */
2471 AssertReturn(~(fFlags & ~DBGFPGDMP_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER);
2472 AssertReturn(!(fFlags & (DBGFPGDMP_FLAGS_CURRENT_MODE | DBGFPGDMP_FLAGS_CURRENT_CR3)), VERR_INVALID_PARAMETER);
2473 AssertReturn(fFlags & DBGFPGDMP_FLAGS_GUEST, VERR_INVALID_PARAMETER);
2474
2475 PGMR3DUMPHIERARCHYSTATE State;
2476 pgmR3DumpHierarchyInitState(&State, pVM, fFlags, FirstAddr, LastAddr, pHlp);
2477 return pgmR3DumpHierarchyGstDoIt(&State, cr3, cMaxDepth);
2478}
2479
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette