[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