[PATCH] target/i386: show nested VMX peer state in info registers

Zhengrong Li <[email protected]>
Newsgroups org.nongnu.qemu-devel
Message-ID <[email protected]>
>From c8f876a1feeddcb0a65fb49bc8c01573efbc018f Mon Sep 17 00:00:00 2001
From: Zhengrong Li <[email protected]>
Date: Fri, 14 Aug 2026 23:25:54 +0800
Subject: [PATCH] target/i386: show nested VMX peer state in info registers

When a vCPU is running a nested guest (L2), QEMU's "info registers"
only shows L2 state.  There is no way to see where L1 was executing.
Similarly, when L1 is running and has configured an L2 guest via
VMPTRLD, the L2 guest-state area of vmcs12 is not displayed.

The vmcs12 blob returned by KVM_GET_NESTED_STATE already contains
both the GUEST area (L2 state configured by L1) and the HOST area
(L1 state restored on VM-exit from L2).  Parse these fields directly
from the existing vmcs12 byte array using well-known offsets from the
kernel's struct vmcs12 layout, which is locked by live-migration
compatibility and compile-time CHECK_OFFSET assertions.

Two cases:
- CPU in L2 (KVM_STATE_NESTED_GUEST_MODE set): append L1 partial
  state from vmcs12 HOST area (RIP, RSP, CR0/3/4, EFER, PAT,
  segment selectors, FS/GS/TR bases, GDTR/IDTR, SYSENTER).
- CPU in L1 (guest mode not set): append L2 partial state from
  vmcs12 GUEST area (full registers, segments with limit/ar,
  GDTR/IDTR with limit, DR7, PDPTR, SYSENTER).

A VMCS12_REVISION sanity check at offset 0 guards against layout
mismatch.  Only VMX is handled; SVM is skipped.

Signed-off-by: Zhengrong Li <[email protected]>
---
 target/i386/cpu-dump.c | 252 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 252 insertions(+)

diff --git a/target/i386/cpu-dump.c b/target/i386/cpu-dump.c
index 9e290c0b9b..81500932a6 100644
--- a/target/i386/cpu-dump.c
+++ b/target/i386/cpu-dump.c
@@ -23,6 +23,9 @@
 #ifndef CONFIG_USER_ONLY
 #include "hw/i386/apic_internal.h"
 #endif
+#ifdef CONFIG_KVM
+#include <linux/kvm.h>
+#endif
 
 /***********************************************************/
 /* x86 debug */
@@ -343,6 +346,250 @@ void x86_cpu_dump_local_apic_state(CPUState *cs, int flags)
 #define DUMP_CODE_BYTES_TOTAL    50
 #define DUMP_CODE_BYTES_BACKWARD 20
 
+#ifdef CONFIG_KVM
+/*
+ * Byte offsets into the vmcs12 blob returned by KVM_GET_NESTED_STATE.
+ * Taken from struct vmcs12 in arch/x86/kvm/vmx/vmcs12.h (kernel v6.16).
+ * The layout is locked by live-migration compatibility and compile-time
+ * CHECK_OFFSET assertions in the kernel, so existing offsets are stable.
+ * Only new fields may be appended at the end; existing fields cannot be
+ * moved, deleted, or have their type changed.
+ */
+#define VMCS12_REVISION                  0x11e57ed0
+#define VMCS12_GUEST_IA32_PAT            192
+#define VMCS12_GUEST_IA32_EFER           200
+#define VMCS12_HOST_IA32_PAT             256
+#define VMCS12_HOST_IA32_EFER            264
+#define VMCS12_GUEST_CR0                 424
+#define VMCS12_GUEST_CR3                 432
+#define VMCS12_GUEST_CR4                 440
+#define VMCS12_GUEST_ES_BASE             448
+#define VMCS12_GUEST_CS_BASE             456
+#define VMCS12_GUEST_SS_BASE             464
+#define VMCS12_GUEST_DS_BASE             472
+#define VMCS12_GUEST_FS_BASE             480
+#define VMCS12_GUEST_GS_BASE             488
+#define VMCS12_GUEST_LDTR_BASE           496
+#define VMCS12_GUEST_TR_BASE             504
+#define VMCS12_GUEST_GDTR_BASE           512
+#define VMCS12_GUEST_IDTR_BASE           520
+#define VMCS12_GUEST_DR7                 528
+#define VMCS12_GUEST_RSP                 536
+#define VMCS12_GUEST_RIP                 544
+#define VMCS12_GUEST_RFLAGS              552
+#define VMCS12_GUEST_SYSENTER_ESP        568
+#define VMCS12_GUEST_SYSENTER_EIP        576
+#define VMCS12_HOST_CR0                  584
+#define VMCS12_HOST_CR3                  592
+#define VMCS12_HOST_CR4                  600
+#define VMCS12_HOST_FS_BASE              608
+#define VMCS12_HOST_GS_BASE              616
+#define VMCS12_HOST_TR_BASE              624
+#define VMCS12_HOST_GDTR_BASE            632
+#define VMCS12_HOST_IDTR_BASE            640
+#define VMCS12_HOST_IA32_SYSENTER_ESP    648
+#define VMCS12_HOST_IA32_SYSENTER_EIP    656
+#define VMCS12_HOST_RSP                  664
+#define VMCS12_HOST_RIP                  672
+#define VMCS12_GUEST_ES_LIMIT            840
+#define VMCS12_GUEST_CS_LIMIT            844
+#define VMCS12_GUEST_SS_LIMIT            848
+#define VMCS12_GUEST_DS_LIMIT            852
+#define VMCS12_GUEST_FS_LIMIT            856
+#define VMCS12_GUEST_GS_LIMIT            860
+#define VMCS12_GUEST_LDTR_LIMIT          864
+#define VMCS12_GUEST_TR_LIMIT            868
+#define VMCS12_GUEST_GDTR_LIMIT          872
+#define VMCS12_GUEST_IDTR_LIMIT          876
+#define VMCS12_GUEST_ES_AR_BYTES         880
+#define VMCS12_GUEST_CS_AR_BYTES         884
+#define VMCS12_GUEST_SS_AR_BYTES         888
+#define VMCS12_GUEST_DS_AR_BYTES         892
+#define VMCS12_GUEST_FS_AR_BYTES         896
+#define VMCS12_GUEST_GS_AR_BYTES         900
+#define VMCS12_GUEST_LDTR_AR_BYTES       904
+#define VMCS12_GUEST_TR_AR_BYTES         908
+#define VMCS12_GUEST_SYSENTER_CS         920
+#define VMCS12_HOST_IA32_SYSENTER_CS     924
+#define VMCS12_GUEST_ES_SELECTOR         964
+#define VMCS12_GUEST_CS_SELECTOR         966
+#define VMCS12_GUEST_SS_SELECTOR         968
+#define VMCS12_GUEST_DS_SELECTOR         970
+#define VMCS12_GUEST_FS_SELECTOR         972
+#define VMCS12_GUEST_GS_SELECTOR         974
+#define VMCS12_GUEST_LDTR_SELECTOR       976
+#define VMCS12_GUEST_TR_SELECTOR         978
+#define VMCS12_HOST_ES_SELECTOR          982
+#define VMCS12_HOST_CS_SELECTOR          984
+#define VMCS12_HOST_SS_SELECTOR          986
+#define VMCS12_HOST_DS_SELECTOR          988
+#define VMCS12_HOST_FS_SELECTOR          990
+#define VMCS12_HOST_GS_SELECTOR          992
+#define VMCS12_HOST_TR_SELECTOR          994
+
+static inline uint64_t vmcs12_read64(const uint8_t *vmcs12, size_t off)
+{
+    uint64_t val;
+    memcpy(&val, vmcs12 + off, sizeof(val));
+    return val;
+}
+
+static inline uint32_t vmcs12_read32(const uint8_t *vmcs12, size_t off)
+{
+    uint32_t val;
+    memcpy(&val, vmcs12 + off, sizeof(val));
+    return val;
+}
+
+static inline uint16_t vmcs12_read16(const uint8_t *vmcs12, size_t off)
+{
+    uint16_t val;
+    memcpy(&val, vmcs12 + off, sizeof(val));
+    return val;
+}
+
+static void dump_vmcs12_nested_state(CPUX86State *env, FILE *f,
+                                     const char *seg_name[6])
+{
+    const uint8_t *vmcs12;
+    uint64_t base[8];
+    uint32_t limit[8], ar[8];
+    uint16_t sel[8];
+    int i;
+
+    if (!env->nested_state ||
+        env->nested_state->format != KVM_STATE_NESTED_FORMAT_VMX) {
+        return;
+    }
+
+    vmcs12 = env->nested_state->data.vmx[0].vmcs12;
+
+    /* Sanity check: vmcs12 revision must match expected layout */
+    if (vmcs12_read32(vmcs12, 0) != VMCS12_REVISION) {
+        return;
+    }
+
+    if (env->hflags & HF_GUEST_MASK) {
+        /*
+         * CPU is in L2 (VMX non-root). Show L1 state from vmcs12 HOST
+         * area -- this is the context L1 set up for VM-exit from L2.
+         */
+        qemu_fprintf(f, "\nL1 state (from vmcs12 HOST area):\n");
+        qemu_fprintf(f, "RIP=%016" PRIx64 " RSP=%016" PRIx64 "\n",
+                     vmcs12_read64(vmcs12, VMCS12_HOST_RIP),
+                     vmcs12_read64(vmcs12, VMCS12_HOST_RSP));
+        qemu_fprintf(f, "CR0=%016" PRIx64 " CR3=%016" PRIx64
+                     " CR4=%016" PRIx64 "\n",
+                     vmcs12_read64(vmcs12, VMCS12_HOST_CR0),
+                     vmcs12_read64(vmcs12, VMCS12_HOST_CR3),
+                     vmcs12_read64(vmcs12, VMCS12_HOST_CR4));
+        qemu_fprintf(f, "EFER=%016" PRIx64 " PAT=%016" PRIx64 "\n",
+                     vmcs12_read64(vmcs12, VMCS12_HOST_IA32_EFER),
+                     vmcs12_read64(vmcs12, VMCS12_HOST_IA32_PAT));
+
+        sel[0] = vmcs12_read16(vmcs12, VMCS12_HOST_ES_SELECTOR);
+        sel[1] = vmcs12_read16(vmcs12, VMCS12_HOST_CS_SELECTOR);
+        sel[2] = vmcs12_read16(vmcs12, VMCS12_HOST_SS_SELECTOR);
+        sel[3] = vmcs12_read16(vmcs12, VMCS12_HOST_DS_SELECTOR);
+        sel[4] = vmcs12_read16(vmcs12, VMCS12_HOST_FS_SELECTOR);
+        sel[5] = vmcs12_read16(vmcs12, VMCS12_HOST_GS_SELECTOR);
+        for (i = 0; i < 6; i++) {
+            qemu_fprintf(f, "%-4s sel=%04x\n", seg_name[i], sel[i]);
+        }
+        qemu_fprintf(f, "TR   sel=%04x base=%016" PRIx64 "\n",
+                     vmcs12_read16(vmcs12, VMCS12_HOST_TR_SELECTOR),
+                     vmcs12_read64(vmcs12, VMCS12_HOST_TR_BASE));
+        qemu_fprintf(f, "FS base=%016" PRIx64 " GS base=%016" PRIx64 "\n",
+                     vmcs12_read64(vmcs12, VMCS12_HOST_FS_BASE),
+                     vmcs12_read64(vmcs12, VMCS12_HOST_GS_BASE));
+        qemu_fprintf(f, "GDT=     %016" PRIx64 "\n",
+                     vmcs12_read64(vmcs12, VMCS12_HOST_GDTR_BASE));
+        qemu_fprintf(f, "IDT=     %016" PRIx64 "\n",
+                     vmcs12_read64(vmcs12, VMCS12_HOST_IDTR_BASE));
+        qemu_fprintf(f, "SYSENTER cs=%08x esp=%016" PRIx64
+                     " eip=%016" PRIx64 "\n",
+                     vmcs12_read32(vmcs12, VMCS12_HOST_IA32_SYSENTER_CS),
+                     vmcs12_read64(vmcs12, VMCS12_HOST_IA32_SYSENTER_ESP),
+                     vmcs12_read64(vmcs12, VMCS12_HOST_IA32_SYSENTER_EIP));
+    } else {
+        /*
+         * CPU is in L1. Show L2 state from vmcs12 GUEST area -- this
+         * is the state L1 configured for L2 via VMWRITE.
+         */
+        qemu_fprintf(f, "\nL2 state (from vmcs12 GUEST area):\n");
+        qemu_fprintf(f, "RIP=%016" PRIx64 " RSP=%016" PRIx64
+                     " RFL=%016" PRIx64 "\n",
+                     vmcs12_read64(vmcs12, VMCS12_GUEST_RIP),
+                     vmcs12_read64(vmcs12, VMCS12_GUEST_RSP),
+                     vmcs12_read64(vmcs12, VMCS12_GUEST_RFLAGS));
+        qemu_fprintf(f, "CR0=%016" PRIx64 " CR3=%016" PRIx64
+                     " CR4=%016" PRIx64 "\n",
+                     vmcs12_read64(vmcs12, VMCS12_GUEST_CR0),
+                     vmcs12_read64(vmcs12, VMCS12_GUEST_CR3),
+                     vmcs12_read64(vmcs12, VMCS12_GUEST_CR4));
+        qemu_fprintf(f, "DR7=%016" PRIx64 " EFER=%016" PRIx64
+                     " PAT=%016" PRIx64 "\n",
+                     vmcs12_read64(vmcs12, VMCS12_GUEST_DR7),
+                     vmcs12_read64(vmcs12, VMCS12_GUEST_IA32_EFER),
+                     vmcs12_read64(vmcs12, VMCS12_GUEST_IA32_PAT));
+
+        sel[0] = vmcs12_read16(vmcs12, VMCS12_GUEST_ES_SELECTOR);
+        sel[1] = vmcs12_read16(vmcs12, VMCS12_GUEST_CS_SELECTOR);
+        sel[2] = vmcs12_read16(vmcs12, VMCS12_GUEST_SS_SELECTOR);
+        sel[3] = vmcs12_read16(vmcs12, VMCS12_GUEST_DS_SELECTOR);
+        sel[4] = vmcs12_read16(vmcs12, VMCS12_GUEST_FS_SELECTOR);
+        sel[5] = vmcs12_read16(vmcs12, VMCS12_GUEST_GS_SELECTOR);
+        base[0] = vmcs12_read64(vmcs12, VMCS12_GUEST_ES_BASE);
+        base[1] = vmcs12_read64(vmcs12, VMCS12_GUEST_CS_BASE);
+        base[2] = vmcs12_read64(vmcs12, VMCS12_GUEST_SS_BASE);
+        base[3] = vmcs12_read64(vmcs12, VMCS12_GUEST_DS_BASE);
+        base[4] = vmcs12_read64(vmcs12, VMCS12_GUEST_FS_BASE);
+        base[5] = vmcs12_read64(vmcs12, VMCS12_GUEST_GS_BASE);
+        limit[0] = vmcs12_read32(vmcs12, VMCS12_GUEST_ES_LIMIT);
+        limit[1] = vmcs12_read32(vmcs12, VMCS12_GUEST_CS_LIMIT);
+        limit[2] = vmcs12_read32(vmcs12, VMCS12_GUEST_SS_LIMIT);
+        limit[3] = vmcs12_read32(vmcs12, VMCS12_GUEST_DS_LIMIT);
+        limit[4] = vmcs12_read32(vmcs12, VMCS12_GUEST_FS_LIMIT);
+        limit[5] = vmcs12_read32(vmcs12, VMCS12_GUEST_GS_LIMIT);
+        ar[0] = vmcs12_read32(vmcs12, VMCS12_GUEST_ES_AR_BYTES);
+        ar[1] = vmcs12_read32(vmcs12, VMCS12_GUEST_CS_AR_BYTES);
+        ar[2] = vmcs12_read32(vmcs12, VMCS12_GUEST_SS_AR_BYTES);
+        ar[3] = vmcs12_read32(vmcs12, VMCS12_GUEST_DS_AR_BYTES);
+        ar[4] = vmcs12_read32(vmcs12, VMCS12_GUEST_FS_AR_BYTES);
+        ar[5] = vmcs12_read32(vmcs12, VMCS12_GUEST_GS_AR_BYTES);
+        for (i = 0; i < 6; i++) {
+            qemu_fprintf(f, "%-4s sel=%04x base=%016" PRIx64
+                         " limit=%08x ar=%08x\n",
+                         seg_name[i], sel[i], base[i], limit[i], ar[i]);
+        }
+        qemu_fprintf(f, "LDT  sel=%04x base=%016" PRIx64
+                     " limit=%08x ar=%08x\n",
+                     vmcs12_read16(vmcs12, VMCS12_GUEST_LDTR_SELECTOR),
+                     vmcs12_read64(vmcs12, VMCS12_GUEST_LDTR_BASE),
+                     vmcs12_read32(vmcs12, VMCS12_GUEST_LDTR_LIMIT),
+                     vmcs12_read32(vmcs12, VMCS12_GUEST_LDTR_AR_BYTES));
+        qemu_fprintf(f, "TR   sel=%04x base=%016" PRIx64
+                     " limit=%08x ar=%08x\n",
+                     vmcs12_read16(vmcs12, VMCS12_GUEST_TR_SELECTOR),
+                     vmcs12_read64(vmcs12, VMCS12_GUEST_TR_BASE),
+                     vmcs12_read32(vmcs12, VMCS12_GUEST_TR_LIMIT),
+                     vmcs12_read32(vmcs12, VMCS12_GUEST_TR_AR_BYTES));
+
+        qemu_fprintf(f, "GDT=     %016" PRIx64 " %08x\n",
+                     vmcs12_read64(vmcs12, VMCS12_GUEST_GDTR_BASE),
+                     vmcs12_read32(vmcs12, VMCS12_GUEST_GDTR_LIMIT));
+        qemu_fprintf(f, "IDT=     %016" PRIx64 " %08x\n",
+                     vmcs12_read64(vmcs12, VMCS12_GUEST_IDTR_BASE),
+                     vmcs12_read32(vmcs12, VMCS12_GUEST_IDTR_LIMIT));
+        qemu_fprintf(f, "SYSENTER cs=%08x esp=%016" PRIx64
+                     " eip=%016" PRIx64 "\n",
+                     vmcs12_read32(vmcs12, VMCS12_GUEST_SYSENTER_CS),
+                     vmcs12_read64(vmcs12, VMCS12_GUEST_SYSENTER_ESP),
+                     vmcs12_read64(vmcs12, VMCS12_GUEST_SYSENTER_EIP));
+    }
+}
+#endif /* CONFIG_KVM */
+
 void x86_cpu_dump_state(CPUState *cs, FILE *f, int flags)
 {
     X86CPU *cpu = X86_CPU(cs);
@@ -595,4 +842,9 @@ void x86_cpu_dump_state(CPUState *cs, FILE *f, int flags)
         }
         qemu_fprintf(f, "\n");
     }
+
+#ifdef CONFIG_KVM
+    /* Show nested peer state from vmcs12 when applicable */
+    dump_vmcs12_nested_state(env, f, seg_name);
+#endif
 }
-- 
2.43.7
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.