diff mbox series

[2/2] monitor: Add an "info pg" command that prints the current page tables

Message ID 20240415160818.2316599-3-porter@cs.unc.edu (mailing list archive)
State New, archived
Headers show
Series [1/2] monitor: Implement a generic x86 page table iterator | expand

Commit Message

Don Porter April 15, 2024, 4:08 p.m. UTC
From: Austin Clements <aclements@csail.mit.edu>

The new "info pg" monitor command prints the current page table,
including virtual address ranges, flag bits, and snippets of physical
page numbers.  Completely filled regions of the page table with
compatible flags are "folded", with the result that the complete
output for a freshly booted x86-64 Linux VM can fit in a single
terminal window.  The output looks like this:

VPN range             Entry         Flags            Physical page
[7f0000000-7f0000000] PML4[0fe]     ---DA--UWP
  [7f28c0000-7f28fffff]  PDP[0a3]     ---DA--UWP
    [7f28c4600-7f28c47ff]  PDE[023]     ---DA--UWP
      [7f28c4655-7f28c4656]  PTE[055-056] X--D---U-P 0000007f14-0000007f15
      [7f28c465b-7f28c465b]  PTE[05b]     ----A--U-P 0000001cfc
...
[ff8000000-ff8000000] PML4[1ff]     ---DA--UWP
  [ffff80000-ffffbffff]  PDP[1fe]     ---DA---WP
    [ffff81000-ffff81dff]  PDE[008-00e] -GSDA---WP 0000001000-0000001dff
  [ffffc0000-fffffffff]  PDP[1ff]     ---DA--UWP
    [ffffff400-ffffff5ff]  PDE[1fa]     ---DA--UWP
      [ffffff5fb-ffffff5fc]  PTE[1fb-1fc] XG-DACT-WP 00000fec00 00000fee00
    [ffffff600-ffffff7ff]  PDE[1fb]     ---DA--UWP
      [ffffff600-ffffff600]  PTE[000]     -G-DA--U-P 0000001467

Signed-off-by: Austin Clements <aclements@csail.mit.edu>
[geofft@ldpreload.com: Rebased on top of 2.9.0]
Signed-off-by: Geoffrey Thomas <geofft@ldpreload.com>
Signed-off-by: Don Porter <porter@cs.unc.edu>
---
 hmp-commands-info.hx         |  15 +++
 include/monitor/hmp-target.h |   2 +
 target/i386/monitor.c        | 179 ++++++++++++++++++++++++++++++++++-
 3 files changed, 195 insertions(+), 1 deletion(-)
diff mbox series

Patch

diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx
index ad1b1306e3..ae7de74041 100644
--- a/hmp-commands-info.hx
+++ b/hmp-commands-info.hx
@@ -239,6 +239,21 @@  SRST
     Show the active virtual memory mappings.
 ERST
 
+#if defined(TARGET_I386)
+    {
+        .name       = "pg",
+        .args_type  = "",
+        .params     = "",
+        .help       = "show the page table",
+        .cmd        = hmp_info_pg,
+    },
+#endif
+
+SRST                                                                               |
+  ``info pg``                                                                      |
+    Show the active page table.                                                    |
+ERST
+
     {
         .name       = "mtree",
         .args_type  = "flatview:-f,dispatch_tree:-d,owner:-o,disabled:-D",
diff --git a/include/monitor/hmp-target.h b/include/monitor/hmp-target.h
index d78e979f05..68f58d2a31 100644
--- a/include/monitor/hmp-target.h
+++ b/include/monitor/hmp-target.h
@@ -48,6 +48,8 @@  void hmp_info_mem(Monitor *mon, const QDict *qdict);
 void hmp_info_tlb(Monitor *mon, const QDict *qdict);
 void hmp_mce(Monitor *mon, const QDict *qdict);
 void hmp_info_local_apic(Monitor *mon, const QDict *qdict);
+void hmp_info_io_apic(Monitor *mon, const QDict *qdict);
+void hmp_info_pg(Monitor *mon, const QDict *qdict);
 void hmp_info_sev(Monitor *mon, const QDict *qdict);
 void hmp_info_sgx(Monitor *mon, const QDict *qdict);
 void hmp_info_via(Monitor *mon, const QDict *qdict);
diff --git a/target/i386/monitor.c b/target/i386/monitor.c
index 7924de6520..4cf39a4140 100644
--- a/target/i386/monitor.c
+++ b/target/i386/monitor.c
@@ -72,7 +72,6 @@  static bool ptiter_succ(PTIter *it);
  * top level.  On failure, prints a message to mon and returns false.
  */
 static bool
-__attribute__ ((unused))
 ptiter_init(Monitor *mon, PTIter *it)
 {
     static const PTLayout l32 = {
@@ -88,10 +87,16 @@  ptiter_init(Monitor *mon, PTIter *it)
         {"PML4", "PDP", "PDE", "PTE"}, 12, 13
     };
 #endif
+
     CPUArchState *env;
 
     env = mon_get_cpu_env(mon);
 
+    if (!env) {
+        monitor_printf(mon, "No CPU available\n");
+        return false;
+    }
+
     if (!(env->cr[0] & CR0_PG_MASK)) {
         monitor_printf(mon, "PG disabled\n");
         return false;
@@ -200,6 +205,178 @@  static hwaddr addr_canonical(CPUArchState *env, hwaddr addr)
     return addr;
 }
 
+/**
+ * Return true if the page tree rooted at iter is complete and
+ * compatible with compat.  last will be filled with the last entry at
+ * each level.  If false, does not change iter and last can be filled
+ * with anything; if true, returns with iter at the next entry on the
+ * same level, or the next parent entry if iter is on the last entry
+ * of this level.
+ */
+static bool pg_complete(PTIter *root, const PTIter compat[], PTIter last[])
+{
+    PTIter iter = *root;
+
+    if ((root->ent & 0xfff) != (compat[root->level].ent & 0xfff)) {
+        return false;
+    }
+
+    last[root->level] = *root;
+    ptiter_succ(&iter);
+    if (!root->leaf) {
+        /* Are all of the direct children of root complete? */
+        while (iter.level == root->level + 1) {
+            if (!pg_complete(&iter, compat, last)) {
+                return false;
+            }
+        }
+    }
+    assert(iter.level <= root->level);
+    assert(iter.level == root->level ?
+           iter.i[iter.level] == root->i[iter.level] + 1 : 1);
+    *root = iter;
+    return true;
+}
+
+static char *pg_bits(uint64_t ent)
+{
+    static char buf[32];
+    sprintf(buf, "%c%c%c%c%c%c%c%c%c%c",
+            /* TODO: Some of these change depending on level */
+            ent & PG_NX_MASK ? 'X' : '-',
+            ent & PG_GLOBAL_MASK ? 'G' : '-',
+            ent & PG_PSE_MASK ? 'S' : '-',
+            ent & PG_DIRTY_MASK ? 'D' : '-',
+            ent & PG_ACCESSED_MASK ? 'A' : '-',
+            ent & PG_PCD_MASK ? 'C' : '-',
+            ent & PG_PWT_MASK ? 'T' : '-',
+            ent & PG_USER_MASK ? 'U' : '-',
+            ent & PG_RW_MASK ? 'W' : '-',
+            ent & PG_PRESENT_MASK ? 'P' : '-');
+    return buf;
+}
+
+static void pg_print(Monitor *mon, PTIter *s, PTIter *l)
+{
+    int lev = s->level;
+    char buf[128];
+    char *pos = buf, *end = buf + sizeof(buf);
+
+    /* VFN range */
+    pos += sprintf(pos, "%*s[%0*"PRIx64"-%0*"PRIx64"] ",
+                   lev * 2, "",
+                   s->layout->vaw - 3, (uint64_t)s->va >> 12,
+                   s->layout->vaw - 3, ((uint64_t)l->va + l->size - 1) >> 12);
+
+    /* Slot */
+    if (s->i[lev] == l->i[lev]) {
+        pos += sprintf(pos, "%4s[%03x]    ",
+                       s->layout->names[lev], s->i[lev]);
+    } else {
+        pos += sprintf(pos, "%4s[%03x-%03x]",
+                       s->layout->names[lev], s->i[lev], l->i[lev]);
+    }
+
+    /* Flags */
+    pos += sprintf(pos, " %s", pg_bits(s->ent));
+
+    /* Range-compressed PFN's */
+    if (s->leaf) {
+        PTIter iter = *s;
+        int i = 0;
+        bool exhausted = false;
+        while (!exhausted && i++ < 10) {
+            hwaddr pas = iter.pa, pae = iter.pa + iter.size;
+            while (ptiter_succ(&iter) && iter.va <= l->va) {
+                if (iter.level == s->level) {
+                    if (iter.pa == pae) {
+                        pae = iter.pa + iter.size;
+                    } else {
+                        goto print;
+                    }
+                }
+            }
+            exhausted = true;
+
+print:
+            if (pas >> 12 == (pae - 1) >> 12) {
+                pos += snprintf(pos, end - pos, " %0*"PRIx64,
+                                s->layout->paw - 3, (uint64_t)pas >> 12);
+            } else {
+                pos += snprintf(pos, end - pos, " %0*"PRIx64"-%0*"PRIx64,
+                                s->layout->paw - 3, (uint64_t)pas >> 12,
+                                s->layout->paw - 3, (uint64_t)(pae - 1) >> 12);
+            }
+            pos = MIN(pos, end);
+        }
+    }
+
+    /* Trim line to fit screen */
+    if (pos - buf > 79) {
+        strcpy(buf + 77, "..");
+    }
+
+    monitor_printf(mon, "%s\n", buf);
+}
+
+void hmp_info_pg(Monitor *mon, const QDict *qdict)
+{
+    PTIter iter;
+
+    if (!ptiter_init(mon, &iter)) {
+        return;
+    }
+
+    /* Header line */
+    monitor_printf(mon, "%-*s %-13s %-10s %*s%s\n",
+                   3 + 2 * (iter.layout->vaw - 3), "VPN range",
+                   "Entry", "Flags",
+                   2 * (iter.layout->levels - 1), "", "Physical page");
+
+    while (iter.level >= 0) {
+        int i, startLevel, maxLevel;
+        PTIter start[4], last[4], nlast[4];
+        bool compressed = false;
+
+        /* Skip to the next present entry */
+        do { } while (!iter.present && ptiter_succ(&iter));
+        if (iter.level < 0) {
+            break;
+        }
+
+        /*
+         * Find a run of complete entries starting at iter and staying
+         * on the same level.
+         */
+        startLevel = iter.level;
+        memset(start, 0, sizeof(start));
+        do {
+            start[iter.level] = iter;
+        } while (!iter.leaf && ptiter_succ(&iter));
+        maxLevel = iter.level;
+        iter = start[startLevel];
+        while (iter.level == startLevel && pg_complete(&iter, start, nlast)) {
+            compressed = true;
+            memcpy(last, nlast, sizeof(last));
+        }
+
+        if (compressed) {
+            /*
+             * We found a run we can show as a range spanning
+             * [startLevel, maxLevel].  start stores the first entry
+             * at each level and last stores the last entry.
+             */
+            for (i = startLevel; i <= maxLevel; i++) {
+                pg_print(mon, &start[i], &last[i]);
+            }
+        } else {
+            /* No luck finding a range.  Iter hasn't moved. */
+            pg_print(mon, &iter, &iter);
+            ptiter_succ(&iter);
+        }
+    }
+}
+
 static void print_pte(Monitor *mon, CPUArchState *env, hwaddr addr,
                       hwaddr pte, hwaddr mask)
 {