diff mbox series

[1/2] monitor: Implement a generic x86 page table iterator

Message ID 20240415160818.2316599-2-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>

This iterator provides a way to traverse 32-bit, PAE, and 64-bit page
tables by abstracting them as n-ary trees.  A struct describes the
full range of x86 page table layouts and the iterator builds on this
to provide a "successor" function for efficiently traversing the page
table tree.

This code is currently unused, but provides the groundwork for the
following "info pg" patch.  It could also be used to unify and
simplify the implementations of "info mem" and "info tlb".

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>
---
 target/i386/monitor.c | 149 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 149 insertions(+)
diff mbox series

Patch

diff --git a/target/i386/monitor.c b/target/i386/monitor.c
index 3a281dab02..7924de6520 100644
--- a/target/i386/monitor.c
+++ b/target/i386/monitor.c
@@ -34,6 +34,155 @@ 
 #include "qapi/qapi-commands-misc-target.h"
 #include "qapi/qapi-commands-misc.h"
 
+/**
+ * PTLayout describes the layout of an x86 page table in enough detail
+ * to fully decode up to a 4-level 64-bit page table tree.
+ */
+typedef struct PTLayout {
+    int levels, entsize;
+    int entries[4];             /* Entries in each table level */
+    int shift[4];               /* VA bit shift each each level */
+    bool pse[4];                /* Whether PSE bit is valid */
+    const char *names[4];
+    int vaw, paw;               /* VA and PA width in characters */
+} PTLayout;
+
+/**
+ * PTIter provides a generic way to traverse and decode an x86 page
+ * table tree.
+ */
+typedef struct PTIter {
+    const PTLayout *layout;
+    bool pse;                   /* PSE enabled */
+    int level;                  /* Current level */
+    int i[4];                   /* Index at each level */
+    hwaddr base[4];             /* Physical base pointer */
+
+    uint64_t ent;               /* Current entry */
+    bool present, leaf;
+    target_ulong va;
+    hwaddr pa;
+    target_ulong  size;
+} PTIter;
+
+static bool ptiter_succ(PTIter *it);
+
+/**
+ * Initialize a PTIter to point to the first entry of the page table's
+ * 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 = {
+        2, 4, {1024, 1024}, {22, 12}, {1, 0}, {"PDE", "PTE"}, 8, 8
+    };
+    static const PTLayout lpae = {
+        3, 8, {4, 512, 512}, {30, 21, 12}, {0, 1, 0},
+        {"PDP", "PDE", "PTE"}, 8, 13
+    };
+#ifdef TARGET_X86_64
+    static const PTLayout l64 = {
+        4, 8, {512, 512, 512, 512}, {39, 30, 21, 12}, {0, 1, 1, 0},
+        {"PML4", "PDP", "PDE", "PTE"}, 12, 13
+    };
+#endif
+    CPUArchState *env;
+
+    env = mon_get_cpu_env(mon);
+
+    if (!(env->cr[0] & CR0_PG_MASK)) {
+        monitor_printf(mon, "PG disabled\n");
+        return false;
+    }
+
+    memset(it, 0, sizeof(*it));
+    if (env->cr[4] & CR4_PAE_MASK) {
+#ifdef TARGET_X86_64
+        if (env->hflags & HF_LMA_MASK) {
+            it->layout = &l64;
+            it->base[0] = env->cr[3] & 0x3fffffffff000ULL;
+        } else
+#endif
+        {
+            it->layout = &lpae;
+            it->base[0] = env->cr[3] & ~0x1f;
+        }
+        it->pse = true;
+    } else {
+        it->layout = &l32;
+        it->base[0] = env->cr[3] & ~0xfff;
+        it->pse = (env->cr[4] & CR4_PSE_MASK);
+    }
+
+    /* Trick ptiter_succ into doing the hard initialization. */
+    it->i[0] = -1;
+    it->leaf = true;
+    ptiter_succ(it);
+    return true;
+}
+
+/**
+ * Move a PTIter to the successor of the current entry.  Specifically:
+ * if the iterator points to a leaf, move to its next sibling, or to
+ * the next sibling of a parent if it has no more siblings.  If the
+ * iterator points to a non-leaf, move to its first child.  If there
+ * is no successor, return false.
+ *
+ * Note that the resulting entry may not be marked present, though
+ * non-present entries are always leafs (within a page
+ * table/directory/etc, this will always visit all entries).
+ */
+static bool ptiter_succ(PTIter *it)
+{
+    int i, l, entsize;
+    uint64_t ent64;
+    uint32_t ent32;
+    bool large;
+
+    if (it->level < 0) {
+        return false;
+    } else if (!it->leaf) {
+        /* Move to this entry's first child */
+        it->level++;
+        it->base[it->level] = it->pa;
+        it->i[it->level] = 0;
+    } else {
+        /* Move forward and, if we hit the end of this level, up */
+        while (++it->i[it->level] == it->layout->entries[it->level]) {
+            if (it->level-- == 0) {
+                /* We're out of page table */
+                return false;
+            }
+        }
+    }
+
+    /* Read this entry */
+    l = it->level;
+    entsize = it->layout->entsize;
+    cpu_physical_memory_read(it->base[l] + it->i[l] * entsize,
+                             entsize == 4 ? (void *)&ent32 : (void *)&ent64,
+                             entsize);
+    if (entsize == 4) {
+        it->ent = le32_to_cpu(ent32);
+    } else {
+        it->ent = le64_to_cpu(ent64);
+    }
+
+    /* Decode the entry */
+    large = (it->pse && it->layout->pse[l] && (it->ent & PG_PSE_MASK));
+    it->present = it->ent & PG_PRESENT_MASK;
+    it->leaf = (large || !it->present || ((l + 1) == it->layout->levels));
+    it->va = 0;
+    for (i = 0; i <= l; i++) {
+        it->va |= (uint64_t)it->i[i] << it->layout->shift[i];
+    }
+    it->pa = it->ent & (large ? 0x3ffffffffc000ULL : 0x3fffffffff000ULL);
+    it->size = 1 << it->layout->shift[l];
+    return true;
+}
+
 /* Perform linear address sign extension */
 static hwaddr addr_canonical(CPUArchState *env, hwaddr addr)
 {