Blame SOURCES/kvm-target-i386-kvm-Add-support-for-KVM_CAP_EXCEPTION_PA.patch

b38b0f
From 05a54f3fc44598f917d72a1f2570c43ec042cdb8 Mon Sep 17 00:00:00 2001
b38b0f
From: Paolo Bonzini <pbonzini@redhat.com>
b38b0f
Date: Mon, 22 Jul 2019 18:22:16 +0100
b38b0f
Subject: [PATCH 35/39] target/i386: kvm: Add support for
b38b0f
 KVM_CAP_EXCEPTION_PAYLOAD
b38b0f
b38b0f
RH-Author: Paolo Bonzini <pbonzini@redhat.com>
b38b0f
Message-id: <20190722182220.19374-15-pbonzini@redhat.com>
b38b0f
Patchwork-id: 89631
b38b0f
O-Subject: [RHEL-8.1.0 PATCH qemu-kvm v3 14/18] target/i386: kvm: Add support for KVM_CAP_EXCEPTION_PAYLOAD
b38b0f
Bugzilla: 1689269
b38b0f
RH-Acked-by: Peter Xu <zhexu@redhat.com>
b38b0f
RH-Acked-by: Laurent Vivier <lvivier@redhat.com>
b38b0f
RH-Acked-by: Dr. David Alan Gilbert <dgilbert@redhat.com>
b38b0f
b38b0f
From: Liran Alon <liran.alon@oracle.com>
b38b0f
b38b0f
Kernel commit c4f55198c7c2 ("kvm: x86: Introduce KVM_CAP_EXCEPTION_PAYLOAD")
b38b0f
introduced a new KVM capability which allows userspace to correctly
b38b0f
distinguish between pending and injected exceptions.
b38b0f
b38b0f
This distinguish is important in case of nested virtualization scenarios
b38b0f
because a L2 pending exception can still be intercepted by the L1 hypervisor
b38b0f
while a L2 injected exception cannot.
b38b0f
b38b0f
Furthermore, when an exception is attempted to be injected by QEMU,
b38b0f
QEMU should specify the exception payload (CR2 in case of #PF or
b38b0f
DR6 in case of #DB) instead of having the payload already delivered in
b38b0f
the respective vCPU register. Because in case exception is injected to
b38b0f
L2 guest and is intercepted by L1 hypervisor, then payload needs to be
b38b0f
reported to L1 intercept (VMExit handler) while still preserving
b38b0f
respective vCPU register unchanged.
b38b0f
b38b0f
This commit adds support for QEMU to properly utilise this new KVM
b38b0f
capability (KVM_CAP_EXCEPTION_PAYLOAD).
b38b0f
b38b0f
Reviewed-by: Nikita Leshenko <nikita.leshchenko@oracle.com>
b38b0f
Signed-off-by: Liran Alon <liran.alon@oracle.com>
b38b0f
Message-Id: <20190619162140.133674-10-liran.alon@oracle.com>
b38b0f
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
b38b0f
(cherry picked from commit fd13f23b8c95311eff74426921557eee592b0ed3)
b38b0f
Signed-off-by: Danilo C. L. de Paula <ddepaula@redhat.com>
b38b0f
---
b38b0f
 target/i386/cpu.c        |   6 ++-
b38b0f
 target/i386/cpu.h        |   6 ++-
b38b0f
 target/i386/hvf/hvf.c    |  10 +++--
b38b0f
 target/i386/hvf/x86hvf.c |   4 +-
b38b0f
 target/i386/kvm.c        | 101 +++++++++++++++++++++++++++++++++++++++--------
b38b0f
 target/i386/machine.c    |  84 ++++++++++++++++++++++++++++++++++++++-
b38b0f
 6 files changed, 187 insertions(+), 24 deletions(-)
b38b0f
b38b0f
diff --git a/target/i386/cpu.c b/target/i386/cpu.c
b38b0f
index bd0b784..f71b044 100644
b38b0f
--- a/target/i386/cpu.c
b38b0f
+++ b/target/i386/cpu.c
b38b0f
@@ -4645,7 +4645,11 @@ static void x86_cpu_reset(CPUState *s)
b38b0f
     memset(env->mtrr_fixed, 0, sizeof(env->mtrr_fixed));
b38b0f
 
b38b0f
     env->interrupt_injected = -1;
b38b0f
-    env->exception_injected = -1;
b38b0f
+    env->exception_nr = -1;
b38b0f
+    env->exception_pending = 0;
b38b0f
+    env->exception_injected = 0;
b38b0f
+    env->exception_has_payload = false;
b38b0f
+    env->exception_payload = 0;
b38b0f
     env->nmi_injected = false;
b38b0f
 #if !defined(CONFIG_USER_ONLY)
b38b0f
     /* We hard-wire the BSP to the first CPU. */
b38b0f
diff --git a/target/i386/cpu.h b/target/i386/cpu.h
b38b0f
index 86f3d98..d120f62 100644
b38b0f
--- a/target/i386/cpu.h
b38b0f
+++ b/target/i386/cpu.h
b38b0f
@@ -1325,10 +1325,14 @@ typedef struct CPUX86State {
b38b0f
 
b38b0f
     /* For KVM */
b38b0f
     uint32_t mp_state;
b38b0f
-    int32_t exception_injected;
b38b0f
+    int32_t exception_nr;
b38b0f
     int32_t interrupt_injected;
b38b0f
     uint8_t soft_interrupt;
b38b0f
+    uint8_t exception_pending;
b38b0f
+    uint8_t exception_injected;
b38b0f
     uint8_t has_error_code;
b38b0f
+    uint8_t exception_has_payload;
b38b0f
+    uint64_t exception_payload;
b38b0f
     uint32_t ins_len;
b38b0f
     uint32_t sipi_vector;
b38b0f
     bool tsc_valid;
b38b0f
diff --git a/target/i386/hvf/hvf.c b/target/i386/hvf/hvf.c
b38b0f
index c367539..acc0bb9 100644
b38b0f
--- a/target/i386/hvf/hvf.c
b38b0f
+++ b/target/i386/hvf/hvf.c
b38b0f
@@ -617,7 +617,9 @@ static void hvf_store_events(CPUState *cpu, uint32_t ins_len, uint64_t idtvec_in
b38b0f
     X86CPU *x86_cpu = X86_CPU(cpu);
b38b0f
     CPUX86State *env = &x86_cpu->env;
b38b0f
 
b38b0f
-    env->exception_injected = -1;
b38b0f
+    env->exception_nr = -1;
b38b0f
+    env->exception_pending = 0;
b38b0f
+    env->exception_injected = 0;
b38b0f
     env->interrupt_injected = -1;
b38b0f
     env->nmi_injected = false;
b38b0f
     if (idtvec_info & VMCS_IDT_VEC_VALID) {
b38b0f
@@ -631,7 +633,8 @@ static void hvf_store_events(CPUState *cpu, uint32_t ins_len, uint64_t idtvec_in
b38b0f
             break;
b38b0f
         case VMCS_IDT_VEC_HWEXCEPTION:
b38b0f
         case VMCS_IDT_VEC_SWEXCEPTION:
b38b0f
-            env->exception_injected = idtvec_info & VMCS_IDT_VEC_VECNUM;
b38b0f
+            env->exception_nr = idtvec_info & VMCS_IDT_VEC_VECNUM;
b38b0f
+            env->exception_injected = 1;
b38b0f
             break;
b38b0f
         case VMCS_IDT_VEC_PRIV_SWEXCEPTION:
b38b0f
         default:
b38b0f
@@ -925,7 +928,8 @@ int hvf_vcpu_exec(CPUState *cpu)
b38b0f
             macvm_set_rip(cpu, rip + ins_len);
b38b0f
             break;
b38b0f
         case VMX_REASON_VMCALL:
b38b0f
-            env->exception_injected = EXCP0D_GPF;
b38b0f
+            env->exception_nr = EXCP0D_GPF;
b38b0f
+            env->exception_injected = 1;
b38b0f
             env->has_error_code = true;
b38b0f
             env->error_code = 0;
b38b0f
             break;
b38b0f
diff --git a/target/i386/hvf/x86hvf.c b/target/i386/hvf/x86hvf.c
b38b0f
index 6c88939..f0e58a8 100644
b38b0f
--- a/target/i386/hvf/x86hvf.c
b38b0f
+++ b/target/i386/hvf/x86hvf.c
b38b0f
@@ -362,8 +362,8 @@ bool hvf_inject_interrupts(CPUState *cpu_state)
b38b0f
     if (env->interrupt_injected != -1) {
b38b0f
         vector = env->interrupt_injected;
b38b0f
         intr_type = VMCS_INTR_T_SWINTR;
b38b0f
-    } else if (env->exception_injected != -1) {
b38b0f
-        vector = env->exception_injected;
b38b0f
+    } else if (env->exception_nr != -1) {
b38b0f
+        vector = env->exception_nr;
b38b0f
         if (vector == EXCP03_INT3 || vector == EXCP04_INTO) {
b38b0f
             intr_type = VMCS_INTR_T_SWEXCEPTION;
b38b0f
         } else {
b38b0f
diff --git a/target/i386/kvm.c b/target/i386/kvm.c
b38b0f
index ddceb7d..aa2d589 100644
b38b0f
--- a/target/i386/kvm.c
b38b0f
+++ b/target/i386/kvm.c
b38b0f
@@ -103,6 +103,7 @@ static uint32_t num_architectural_pmu_fixed_counters;
b38b0f
 static int has_xsave;
b38b0f
 static int has_xcrs;
b38b0f
 static int has_pit_state2;
b38b0f
+static int has_exception_payload;
b38b0f
 
b38b0f
 static bool has_msr_mcg_ext_ctl;
b38b0f
 
b38b0f
@@ -569,15 +570,56 @@ void kvm_arch_on_sigbus_vcpu(CPUState *c, int code, void *addr)
b38b0f
     /* Hope we are lucky for AO MCE */
b38b0f
 }
b38b0f
 
b38b0f
+static void kvm_reset_exception(CPUX86State *env)
b38b0f
+{
b38b0f
+    env->exception_nr = -1;
b38b0f
+    env->exception_pending = 0;
b38b0f
+    env->exception_injected = 0;
b38b0f
+    env->exception_has_payload = false;
b38b0f
+    env->exception_payload = 0;
b38b0f
+}
b38b0f
+
b38b0f
+static void kvm_queue_exception(CPUX86State *env,
b38b0f
+                                int32_t exception_nr,
b38b0f
+                                uint8_t exception_has_payload,
b38b0f
+                                uint64_t exception_payload)
b38b0f
+{
b38b0f
+    assert(env->exception_nr == -1);
b38b0f
+    assert(!env->exception_pending);
b38b0f
+    assert(!env->exception_injected);
b38b0f
+    assert(!env->exception_has_payload);
b38b0f
+
b38b0f
+    env->exception_nr = exception_nr;
b38b0f
+
b38b0f
+    if (has_exception_payload) {
b38b0f
+        env->exception_pending = 1;
b38b0f
+
b38b0f
+        env->exception_has_payload = exception_has_payload;
b38b0f
+        env->exception_payload = exception_payload;
b38b0f
+    } else {
b38b0f
+        env->exception_injected = 1;
b38b0f
+
b38b0f
+        if (exception_nr == EXCP01_DB) {
b38b0f
+            assert(exception_has_payload);
b38b0f
+            env->dr[6] = exception_payload;
b38b0f
+        } else if (exception_nr == EXCP0E_PAGE) {
b38b0f
+            assert(exception_has_payload);
b38b0f
+            env->cr[2] = exception_payload;
b38b0f
+        } else {
b38b0f
+            assert(!exception_has_payload);
b38b0f
+        }
b38b0f
+    }
b38b0f
+}
b38b0f
+
b38b0f
 static int kvm_inject_mce_oldstyle(X86CPU *cpu)
b38b0f
 {
b38b0f
     CPUX86State *env = &cpu->env;
b38b0f
 
b38b0f
-    if (!kvm_has_vcpu_events() && env->exception_injected == EXCP12_MCHK) {
b38b0f
+    if (!kvm_has_vcpu_events() && env->exception_nr == EXCP12_MCHK) {
b38b0f
         unsigned int bank, bank_num = env->mcg_cap & 0xff;
b38b0f
         struct kvm_x86_mce mce;
b38b0f
 
b38b0f
-        env->exception_injected = -1;
b38b0f
+        kvm_reset_exception(env);
b38b0f
 
b38b0f
         /*
b38b0f
          * There must be at least one bank in use if an MCE is pending.
b38b0f
@@ -1458,6 +1500,16 @@ int kvm_arch_init(MachineState *ms, KVMState *s)
b38b0f
     has_pit_state2 = kvm_check_extension(s, KVM_CAP_PIT_STATE2);
b38b0f
 #endif
b38b0f
 
b38b0f
+    has_exception_payload = kvm_check_extension(s, KVM_CAP_EXCEPTION_PAYLOAD);
b38b0f
+    if (has_exception_payload) {
b38b0f
+        ret = kvm_vm_enable_cap(s, KVM_CAP_EXCEPTION_PAYLOAD, 0, true);
b38b0f
+        if (ret < 0) {
b38b0f
+            error_report("kvm: Failed to enable exception payload cap: %s",
b38b0f
+                         strerror(-ret));
b38b0f
+            return ret;
b38b0f
+        }
b38b0f
+    }
b38b0f
+
b38b0f
     ret = kvm_get_supported_msrs(s);
b38b0f
     if (ret < 0) {
b38b0f
         return ret;
b38b0f
@@ -2717,8 +2769,16 @@ static int kvm_put_vcpu_events(X86CPU *cpu, int level)
b38b0f
         return 0;
b38b0f
     }
b38b0f
 
b38b0f
-    events.exception.injected = (env->exception_injected >= 0);
b38b0f
-    events.exception.nr = env->exception_injected;
b38b0f
+    events.flags = 0;
b38b0f
+
b38b0f
+    if (has_exception_payload) {
b38b0f
+        events.flags |= KVM_VCPUEVENT_VALID_PAYLOAD;
b38b0f
+        events.exception.pending = env->exception_pending;
b38b0f
+        events.exception_has_payload = env->exception_has_payload;
b38b0f
+        events.exception_payload = env->exception_payload;
b38b0f
+    }
b38b0f
+    events.exception.nr = env->exception_nr;
b38b0f
+    events.exception.injected = env->exception_injected;
b38b0f
     events.exception.has_error_code = env->has_error_code;
b38b0f
     events.exception.error_code = env->error_code;
b38b0f
 
b38b0f
@@ -2731,7 +2791,6 @@ static int kvm_put_vcpu_events(X86CPU *cpu, int level)
b38b0f
     events.nmi.masked = !!(env->hflags2 & HF2_NMI_MASK);
b38b0f
 
b38b0f
     events.sipi_vector = env->sipi_vector;
b38b0f
-    events.flags = 0;
b38b0f
 
b38b0f
     if (has_msr_smbase) {
b38b0f
         events.smi.smm = !!(env->hflags & HF_SMM_MASK);
b38b0f
@@ -2781,8 +2840,19 @@ static int kvm_get_vcpu_events(X86CPU *cpu)
b38b0f
     if (ret < 0) {
b38b0f
        return ret;
b38b0f
     }
b38b0f
-    env->exception_injected =
b38b0f
-       events.exception.injected ? events.exception.nr : -1;
b38b0f
+
b38b0f
+    if (events.flags & KVM_VCPUEVENT_VALID_PAYLOAD) {
b38b0f
+        env->exception_pending = events.exception.pending;
b38b0f
+        env->exception_has_payload = events.exception_has_payload;
b38b0f
+        env->exception_payload = events.exception_payload;
b38b0f
+    } else {
b38b0f
+        env->exception_pending = 0;
b38b0f
+        env->exception_has_payload = false;
b38b0f
+    }
b38b0f
+    env->exception_injected = events.exception.injected;
b38b0f
+    env->exception_nr =
b38b0f
+        (env->exception_pending || env->exception_injected) ?
b38b0f
+        events.exception.nr : -1;
b38b0f
     env->has_error_code = events.exception.has_error_code;
b38b0f
     env->error_code = events.exception.error_code;
b38b0f
 
b38b0f
@@ -2834,12 +2904,12 @@ static int kvm_guest_debug_workarounds(X86CPU *cpu)
b38b0f
     unsigned long reinject_trap = 0;
b38b0f
 
b38b0f
     if (!kvm_has_vcpu_events()) {
b38b0f
-        if (env->exception_injected == EXCP01_DB) {
b38b0f
+        if (env->exception_nr == EXCP01_DB) {
b38b0f
             reinject_trap = KVM_GUESTDBG_INJECT_DB;
b38b0f
         } else if (env->exception_injected == EXCP03_INT3) {
b38b0f
             reinject_trap = KVM_GUESTDBG_INJECT_BP;
b38b0f
         }
b38b0f
-        env->exception_injected = -1;
b38b0f
+        kvm_reset_exception(env);
b38b0f
     }
b38b0f
 
b38b0f
     /*
b38b0f
@@ -3215,13 +3285,13 @@ int kvm_arch_process_async_events(CPUState *cs)
b38b0f
 
b38b0f
         kvm_cpu_synchronize_state(cs);
b38b0f
 
b38b0f
-        if (env->exception_injected == EXCP08_DBLE) {
b38b0f
+        if (env->exception_nr == EXCP08_DBLE) {
b38b0f
             /* this means triple fault */
b38b0f
             qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
b38b0f
             cs->exit_request = 1;
b38b0f
             return 0;
b38b0f
         }
b38b0f
-        env->exception_injected = EXCP12_MCHK;
b38b0f
+        kvm_queue_exception(env, EXCP12_MCHK, 0, 0);
b38b0f
         env->has_error_code = 0;
b38b0f
 
b38b0f
         cs->halted = 0;
b38b0f
@@ -3436,14 +3506,13 @@ static int kvm_handle_debug(X86CPU *cpu,
b38b0f
     }
b38b0f
     if (ret == 0) {
b38b0f
         cpu_synchronize_state(cs);
b38b0f
-        assert(env->exception_injected == -1);
b38b0f
+        assert(env->exception_nr == -1);
b38b0f
 
b38b0f
         /* pass to guest */
b38b0f
-        env->exception_injected = arch_info->exception;
b38b0f
+        kvm_queue_exception(env, arch_info->exception,
b38b0f
+                            arch_info->exception == EXCP01_DB,
b38b0f
+                            arch_info->dr6);
b38b0f
         env->has_error_code = 0;
b38b0f
-        if (arch_info->exception == EXCP01_DB) {
b38b0f
-            env->dr[6] = arch_info->dr6;
b38b0f
-        }
b38b0f
     }
b38b0f
 
b38b0f
     return ret;
b38b0f
diff --git a/target/i386/machine.c b/target/i386/machine.c
b38b0f
index a2ddbba..5ffee8f 100644
b38b0f
--- a/target/i386/machine.c
b38b0f
+++ b/target/i386/machine.c
b38b0f
@@ -239,6 +239,41 @@ static int cpu_pre_save(void *opaque)
b38b0f
     }
b38b0f
 #endif
b38b0f
 
b38b0f
+    /*
b38b0f
+     * When vCPU is running L2 and exception is still pending,
b38b0f
+     * it can potentially be intercepted by L1 hypervisor.
b38b0f
+     * In contrast to an injected exception which cannot be
b38b0f
+     * intercepted anymore.
b38b0f
+     *
b38b0f
+     * Furthermore, when a L2 exception is intercepted by L1
b38b0f
+     * hypervisor, it's exception payload (CR2/DR6 on #PF/#DB)
b38b0f
+     * should not be set yet in the respective vCPU register.
b38b0f
+     * Thus, in case an exception is pending, it is
b38b0f
+     * important to save the exception payload seperately.
b38b0f
+     *
b38b0f
+     * Therefore, if an exception is not in a pending state
b38b0f
+     * or vCPU is not in guest-mode, it is not important to
b38b0f
+     * distinguish between a pending and injected exception
b38b0f
+     * and we don't need to store seperately the exception payload.
b38b0f
+     *
b38b0f
+     * In order to preserve better backwards-compatabile migration,
b38b0f
+     * convert a pending exception to an injected exception in
b38b0f
+     * case it is not important to distingiush between them
b38b0f
+     * as described above.
b38b0f
+     */
b38b0f
+    if (env->exception_pending && !(env->hflags & HF_GUEST_MASK)) {
b38b0f
+        env->exception_pending = 0;
b38b0f
+        env->exception_injected = 1;
b38b0f
+
b38b0f
+        if (env->exception_has_payload) {
b38b0f
+            if (env->exception_nr == EXCP01_DB) {
b38b0f
+                env->dr[6] = env->exception_payload;
b38b0f
+            } else if (env->exception_nr == EXCP0E_PAGE) {
b38b0f
+                env->cr[2] = env->exception_payload;
b38b0f
+            }
b38b0f
+        }
b38b0f
+    }
b38b0f
+
b38b0f
     return 0;
b38b0f
 }
b38b0f
 
b38b0f
@@ -296,6 +331,23 @@ static int cpu_post_load(void *opaque, int version_id)
b38b0f
     }
b38b0f
 #endif
b38b0f
 
b38b0f
+    /*
b38b0f
+     * There are cases that we can get valid exception_nr with both
b38b0f
+     * exception_pending and exception_injected being cleared.
b38b0f
+     * This can happen in one of the following scenarios:
b38b0f
+     * 1) Source is older QEMU without KVM_CAP_EXCEPTION_PAYLOAD support.
b38b0f
+     * 2) Source is running on kernel without KVM_CAP_EXCEPTION_PAYLOAD support.
b38b0f
+     * 3) "cpu/exception_info" subsection not sent because there is no exception
b38b0f
+     *    pending or guest wasn't running L2 (See comment in cpu_pre_save()).
b38b0f
+     *
b38b0f
+     * In those cases, we can just deduce that a valid exception_nr means
b38b0f
+     * we can treat the exception as already injected.
b38b0f
+     */
b38b0f
+    if ((env->exception_nr != -1) &&
b38b0f
+        !env->exception_pending && !env->exception_injected) {
b38b0f
+        env->exception_injected = 1;
b38b0f
+    }
b38b0f
+
b38b0f
     env->fpstt = (env->fpus_vmstate >> 11) & 7;
b38b0f
     env->fpus = env->fpus_vmstate & ~0x3800;
b38b0f
     env->fptag_vmstate ^= 0xff;
b38b0f
@@ -341,6 +393,35 @@ static bool steal_time_msr_needed(void *opaque)
b38b0f
     return cpu->env.steal_time_msr != 0;
b38b0f
 }
b38b0f
 
b38b0f
+static bool exception_info_needed(void *opaque)
b38b0f
+{
b38b0f
+    X86CPU *cpu = opaque;
b38b0f
+    CPUX86State *env = &cpu->env;
b38b0f
+
b38b0f
+    /*
b38b0f
+     * It is important to save exception-info only in case
b38b0f
+     * we need to distingiush between a pending and injected
b38b0f
+     * exception. Which is only required in case there is a
b38b0f
+     * pending exception and vCPU is running L2.
b38b0f
+     * For more info, refer to comment in cpu_pre_save().
b38b0f
+     */
b38b0f
+    return env->exception_pending && (env->hflags & HF_GUEST_MASK);
b38b0f
+}
b38b0f
+
b38b0f
+static const VMStateDescription vmstate_exception_info = {
b38b0f
+    .name = "cpu/exception_info",
b38b0f
+    .version_id = 1,
b38b0f
+    .minimum_version_id = 1,
b38b0f
+    .needed = exception_info_needed,
b38b0f
+    .fields = (VMStateField[]) {
b38b0f
+        VMSTATE_UINT8(env.exception_pending, X86CPU),
b38b0f
+        VMSTATE_UINT8(env.exception_injected, X86CPU),
b38b0f
+        VMSTATE_UINT8(env.exception_has_payload, X86CPU),
b38b0f
+        VMSTATE_UINT64(env.exception_payload, X86CPU),
b38b0f
+        VMSTATE_END_OF_LIST()
b38b0f
+    }
b38b0f
+};
b38b0f
+
b38b0f
 static const VMStateDescription vmstate_steal_time_msr = {
b38b0f
     .name = "cpu/steal_time_msr",
b38b0f
     .version_id = 1,
b38b0f
@@ -1219,7 +1300,7 @@ VMStateDescription vmstate_x86_cpu = {
b38b0f
         VMSTATE_INT32(env.interrupt_injected, X86CPU),
b38b0f
         VMSTATE_UINT32(env.mp_state, X86CPU),
b38b0f
         VMSTATE_UINT64(env.tsc, X86CPU),
b38b0f
-        VMSTATE_INT32(env.exception_injected, X86CPU),
b38b0f
+        VMSTATE_INT32(env.exception_nr, X86CPU),
b38b0f
         VMSTATE_UINT8(env.soft_interrupt, X86CPU),
b38b0f
         VMSTATE_UINT8(env.nmi_injected, X86CPU),
b38b0f
         VMSTATE_UINT8(env.nmi_pending, X86CPU),
b38b0f
@@ -1243,6 +1324,7 @@ VMStateDescription vmstate_x86_cpu = {
b38b0f
         /* The above list is not sorted /wrt version numbers, watch out! */
b38b0f
     },
b38b0f
     .subsections = (const VMStateDescription*[]) {
b38b0f
+        &vmstate_exception_info,
b38b0f
         &vmstate_async_pf_msr,
b38b0f
         &vmstate_pv_eoi_msr,
b38b0f
         &vmstate_steal_time_msr,
b38b0f
-- 
b38b0f
1.8.3.1
b38b0f