|
@@ -12,6 +12,7 @@
|
|
|
#include "cpu.h"
|
|
|
#include "exec/address-spaces.h"
|
|
|
#include "exec/ioport.h"
|
|
|
+#include "exec/gdbstub.h"
|
|
|
#include "qemu/accel.h"
|
|
|
#include "sysemu/whpx.h"
|
|
|
#include "sysemu/cpus.h"
|
|
@@ -147,6 +148,87 @@ struct whpx_register_set {
|
|
|
WHV_REGISTER_VALUE values[RTL_NUMBER_OF(whpx_register_names)];
|
|
|
};
|
|
|
|
|
|
+/*
|
|
|
+ * The current implementation of instruction stepping sets the TF flag
|
|
|
+ * in RFLAGS, causing the CPU to raise an INT1 after each instruction.
|
|
|
+ * This corresponds to the WHvX64ExceptionTypeDebugTrapOrFault exception.
|
|
|
+ *
|
|
|
+ * This approach has a few limitations:
|
|
|
+ * 1. Stepping over a PUSHF/SAHF instruction will save the TF flag
|
|
|
+ * along with the other flags, possibly restoring it later. It would
|
|
|
+ * result in another INT1 when the flags are restored, triggering
|
|
|
+ * a stop in gdb that could be cleared by doing another step.
|
|
|
+ *
|
|
|
+ * Stepping over a POPF/LAHF instruction will let it overwrite the
|
|
|
+ * TF flags, ending the stepping mode.
|
|
|
+ *
|
|
|
+ * 2. Stepping over an instruction raising an exception (e.g. INT, DIV,
|
|
|
+ * or anything that could result in a page fault) will save the flags
|
|
|
+ * to the stack, clear the TF flag, and let the guest execute the
|
|
|
+ * handler. Normally, the guest will restore the original flags,
|
|
|
+ * that will continue single-stepping.
|
|
|
+ *
|
|
|
+ * 3. Debuggers running on the guest may wish to set TF to do instruction
|
|
|
+ * stepping. INT1 events generated by it would be intercepted by us,
|
|
|
+ * as long as the gdb is connected to QEMU.
|
|
|
+ *
|
|
|
+ * In practice this means that:
|
|
|
+ * 1. Stepping through flags-modifying instructions may cause gdb to
|
|
|
+ * continue or stop in unexpected places. This will be fully recoverable
|
|
|
+ * and will not crash the target.
|
|
|
+ *
|
|
|
+ * 2. Stepping over an instruction that triggers an exception will step
|
|
|
+ * over the exception handler, not into it.
|
|
|
+ *
|
|
|
+ * 3. Debugging the guest via gdb, while running debugger on the guest
|
|
|
+ * at the same time may lead to unexpected effects. Removing all
|
|
|
+ * breakpoints set via QEMU will prevent any further interference
|
|
|
+ * with the guest-level debuggers.
|
|
|
+ *
|
|
|
+ * The limitations can be addressed as shown below:
|
|
|
+ * 1. PUSHF/SAHF/POPF/LAHF/IRET instructions can be emulated instead of
|
|
|
+ * stepping through them. The exact semantics of the instructions is
|
|
|
+ * defined in the "Combined Volume Set of Intel 64 and IA-32
|
|
|
+ * Architectures Software Developer's Manuals", however it involves a
|
|
|
+ * fair amount of corner cases due to compatibility with real mode,
|
|
|
+ * virtual 8086 mode, and differences between 64-bit and 32-bit modes.
|
|
|
+ *
|
|
|
+ * 2. We could step into the guest's exception handlers using the following
|
|
|
+ * sequence:
|
|
|
+ * a. Temporarily enable catching of all exception types via
|
|
|
+ * whpx_set_exception_exit_bitmap().
|
|
|
+ * b. Once an exception is intercepted, read the IDT/GDT and locate
|
|
|
+ * the original handler.
|
|
|
+ * c. Patch the original handler, injecting an INT3 at the beginning.
|
|
|
+ * d. Update the exception exit bitmap to only catch the
|
|
|
+ * WHvX64ExceptionTypeBreakpointTrap exception.
|
|
|
+ * e. Let the affected CPU run in the exclusive mode.
|
|
|
+ * f. Restore the original handler and the exception exit bitmap.
|
|
|
+ * Note that handling all corner cases related to IDT/GDT is harder
|
|
|
+ * than it may seem. See x86_cpu_get_phys_page_attrs_debug() for a
|
|
|
+ * rough idea.
|
|
|
+ *
|
|
|
+ * 3. In order to properly support guest-level debugging in parallel with
|
|
|
+ * the QEMU-level debugging, we would need to be able to pass some INT1
|
|
|
+ * events to the guest. This could be done via the following methods:
|
|
|
+ * a. Using the WHvRegisterPendingEvent register. As of Windows 21H1,
|
|
|
+ * it seems to only work for interrupts and not software
|
|
|
+ * exceptions.
|
|
|
+ * b. Locating and patching the original handler by parsing IDT/GDT.
|
|
|
+ * This involves relatively complex logic outlined in the previous
|
|
|
+ * paragraph.
|
|
|
+ * c. Emulating the exception invocation (i.e. manually updating RIP,
|
|
|
+ * RFLAGS, and pushing the old values to stack). This is even more
|
|
|
+ * complicated than the previous option, since it involves checking
|
|
|
+ * CPL, gate attributes, and doing various adjustments depending
|
|
|
+ * on the current CPU mode, whether the CPL is changing, etc.
|
|
|
+ */
|
|
|
+typedef enum WhpxStepMode {
|
|
|
+ WHPX_STEP_NONE = 0,
|
|
|
+ /* Halt other VCPUs */
|
|
|
+ WHPX_STEP_EXCLUSIVE,
|
|
|
+} WhpxStepMode;
|
|
|
+
|
|
|
struct whpx_vcpu {
|
|
|
WHV_EMULATOR_HANDLE emulator;
|
|
|
bool window_registered;
|
|
@@ -785,6 +867,517 @@ static int whpx_handle_portio(CPUState *cpu,
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+/*
|
|
|
+ * Controls whether we should intercept various exceptions on the guest,
|
|
|
+ * namely breakpoint/single-step events.
|
|
|
+ *
|
|
|
+ * The 'exceptions' argument accepts a bitmask, e.g:
|
|
|
+ * (1 << WHvX64ExceptionTypeDebugTrapOrFault) | (...)
|
|
|
+ */
|
|
|
+static HRESULT whpx_set_exception_exit_bitmap(UINT64 exceptions)
|
|
|
+{
|
|
|
+ struct whpx_state *whpx = &whpx_global;
|
|
|
+ WHV_PARTITION_PROPERTY prop = { 0, };
|
|
|
+ HRESULT hr;
|
|
|
+
|
|
|
+ if (exceptions == whpx->exception_exit_bitmap) {
|
|
|
+ return S_OK;
|
|
|
+ }
|
|
|
+
|
|
|
+ prop.ExceptionExitBitmap = exceptions;
|
|
|
+
|
|
|
+ hr = whp_dispatch.WHvSetPartitionProperty(
|
|
|
+ whpx->partition,
|
|
|
+ WHvPartitionPropertyCodeExceptionExitBitmap,
|
|
|
+ &prop,
|
|
|
+ sizeof(WHV_PARTITION_PROPERTY));
|
|
|
+
|
|
|
+ if (SUCCEEDED(hr)) {
|
|
|
+ whpx->exception_exit_bitmap = exceptions;
|
|
|
+ }
|
|
|
+
|
|
|
+ return hr;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/*
|
|
|
+ * This function is called before/after stepping over a single instruction.
|
|
|
+ * It will update the CPU registers to arm/disarm the instruction stepping
|
|
|
+ * accordingly.
|
|
|
+ */
|
|
|
+static HRESULT whpx_vcpu_configure_single_stepping(CPUState *cpu,
|
|
|
+ bool set,
|
|
|
+ uint64_t *exit_context_rflags)
|
|
|
+{
|
|
|
+ WHV_REGISTER_NAME reg_name;
|
|
|
+ WHV_REGISTER_VALUE reg_value;
|
|
|
+ HRESULT hr;
|
|
|
+ struct whpx_state *whpx = &whpx_global;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If we are trying to step over a single instruction, we need to set the
|
|
|
+ * TF bit in rflags. Otherwise, clear it.
|
|
|
+ */
|
|
|
+ reg_name = WHvX64RegisterRflags;
|
|
|
+ hr = whp_dispatch.WHvGetVirtualProcessorRegisters(
|
|
|
+ whpx->partition,
|
|
|
+ cpu->cpu_index,
|
|
|
+ ®_name,
|
|
|
+ 1,
|
|
|
+ ®_value);
|
|
|
+
|
|
|
+ if (FAILED(hr)) {
|
|
|
+ error_report("WHPX: Failed to get rflags, hr=%08lx", hr);
|
|
|
+ return hr;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (exit_context_rflags) {
|
|
|
+ assert(*exit_context_rflags == reg_value.Reg64);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (set) {
|
|
|
+ /* Raise WHvX64ExceptionTypeDebugTrapOrFault after each instruction */
|
|
|
+ reg_value.Reg64 |= TF_MASK;
|
|
|
+ } else {
|
|
|
+ reg_value.Reg64 &= ~TF_MASK;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (exit_context_rflags) {
|
|
|
+ *exit_context_rflags = reg_value.Reg64;
|
|
|
+ }
|
|
|
+
|
|
|
+ hr = whp_dispatch.WHvSetVirtualProcessorRegisters(
|
|
|
+ whpx->partition,
|
|
|
+ cpu->cpu_index,
|
|
|
+ ®_name,
|
|
|
+ 1,
|
|
|
+ ®_value);
|
|
|
+
|
|
|
+ if (FAILED(hr)) {
|
|
|
+ error_report("WHPX: Failed to set rflags,"
|
|
|
+ " hr=%08lx",
|
|
|
+ hr);
|
|
|
+ return hr;
|
|
|
+ }
|
|
|
+
|
|
|
+ reg_name = WHvRegisterInterruptState;
|
|
|
+ reg_value.Reg64 = 0;
|
|
|
+
|
|
|
+ /* Suspend delivery of hardware interrupts during single-stepping. */
|
|
|
+ reg_value.InterruptState.InterruptShadow = set != 0;
|
|
|
+
|
|
|
+ hr = whp_dispatch.WHvSetVirtualProcessorRegisters(
|
|
|
+ whpx->partition,
|
|
|
+ cpu->cpu_index,
|
|
|
+ ®_name,
|
|
|
+ 1,
|
|
|
+ ®_value);
|
|
|
+
|
|
|
+ if (FAILED(hr)) {
|
|
|
+ error_report("WHPX: Failed to set InterruptState,"
|
|
|
+ " hr=%08lx",
|
|
|
+ hr);
|
|
|
+ return hr;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!set) {
|
|
|
+ /*
|
|
|
+ * We have just finished stepping over a single instruction,
|
|
|
+ * and intercepted the INT1 generated by it.
|
|
|
+ * We need to now hide the INT1 from the guest,
|
|
|
+ * as it would not be expecting it.
|
|
|
+ */
|
|
|
+
|
|
|
+ reg_name = WHvX64RegisterPendingDebugException;
|
|
|
+ hr = whp_dispatch.WHvGetVirtualProcessorRegisters(
|
|
|
+ whpx->partition,
|
|
|
+ cpu->cpu_index,
|
|
|
+ ®_name,
|
|
|
+ 1,
|
|
|
+ ®_value);
|
|
|
+
|
|
|
+ if (FAILED(hr)) {
|
|
|
+ error_report("WHPX: Failed to get pending debug exceptions,"
|
|
|
+ "hr=%08lx", hr);
|
|
|
+ return hr;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (reg_value.PendingDebugException.SingleStep) {
|
|
|
+ reg_value.PendingDebugException.SingleStep = 0;
|
|
|
+
|
|
|
+ hr = whp_dispatch.WHvSetVirtualProcessorRegisters(
|
|
|
+ whpx->partition,
|
|
|
+ cpu->cpu_index,
|
|
|
+ ®_name,
|
|
|
+ 1,
|
|
|
+ ®_value);
|
|
|
+
|
|
|
+ if (FAILED(hr)) {
|
|
|
+ error_report("WHPX: Failed to clear pending debug exceptions,"
|
|
|
+ "hr=%08lx", hr);
|
|
|
+ return hr;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ return S_OK;
|
|
|
+}
|
|
|
+
|
|
|
+/* Tries to find a breakpoint at the specified address. */
|
|
|
+static struct whpx_breakpoint *whpx_lookup_breakpoint_by_addr(uint64_t address)
|
|
|
+{
|
|
|
+ struct whpx_state *whpx = &whpx_global;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ if (whpx->breakpoints.breakpoints) {
|
|
|
+ for (i = 0; i < whpx->breakpoints.breakpoints->used; i++) {
|
|
|
+ if (address == whpx->breakpoints.breakpoints->data[i].address) {
|
|
|
+ return &whpx->breakpoints.breakpoints->data[i];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Linux uses int3 (0xCC) during startup (see int3_selftest()) and for
|
|
|
+ * debugging user-mode applications. Since the WHPX API does not offer
|
|
|
+ * an easy way to pass the intercepted exception back to the guest, we
|
|
|
+ * resort to using INT1 instead, and let the guest always handle INT3.
|
|
|
+ */
|
|
|
+static const uint8_t whpx_breakpoint_instruction = 0xF1;
|
|
|
+
|
|
|
+/*
|
|
|
+ * The WHPX QEMU backend implements breakpoints by writing the INT1
|
|
|
+ * instruction into memory (ignoring the DRx registers). This raises a few
|
|
|
+ * issues that need to be carefully handled:
|
|
|
+ *
|
|
|
+ * 1. Although unlikely, other parts of QEMU may set multiple breakpoints
|
|
|
+ * at the same location, and later remove them in arbitrary order.
|
|
|
+ * This should not cause memory corruption, and should only remove the
|
|
|
+ * physical breakpoint instruction when the last QEMU breakpoint is gone.
|
|
|
+ *
|
|
|
+ * 2. Writing arbitrary virtual memory may fail if it's not mapped to a valid
|
|
|
+ * physical location. Hence, physically adding/removing a breakpoint can
|
|
|
+ * theoretically fail at any time. We need to keep track of it.
|
|
|
+ *
|
|
|
+ * The function below rebuilds a list of low-level breakpoints (one per
|
|
|
+ * address, tracking the original instruction and any errors) from the list of
|
|
|
+ * high-level breakpoints (set via cpu_breakpoint_insert()).
|
|
|
+ *
|
|
|
+ * In order to optimize performance, this function stores the list of
|
|
|
+ * high-level breakpoints (a.k.a. CPU breakpoints) used to compute the
|
|
|
+ * low-level ones, so that it won't be re-invoked until these breakpoints
|
|
|
+ * change.
|
|
|
+ *
|
|
|
+ * Note that this function decides which breakpoints should be inserted into,
|
|
|
+ * memory, but doesn't actually do it. The memory accessing is done in
|
|
|
+ * whpx_apply_breakpoints().
|
|
|
+ */
|
|
|
+static void whpx_translate_cpu_breakpoints(
|
|
|
+ struct whpx_breakpoints *breakpoints,
|
|
|
+ CPUState *cpu,
|
|
|
+ int cpu_breakpoint_count)
|
|
|
+{
|
|
|
+ CPUBreakpoint *bp;
|
|
|
+ int cpu_bp_index = 0;
|
|
|
+
|
|
|
+ breakpoints->original_addresses =
|
|
|
+ g_renew(vaddr, breakpoints->original_addresses, cpu_breakpoint_count);
|
|
|
+
|
|
|
+ breakpoints->original_address_count = cpu_breakpoint_count;
|
|
|
+
|
|
|
+ int max_breakpoints = cpu_breakpoint_count +
|
|
|
+ (breakpoints->breakpoints ? breakpoints->breakpoints->used : 0);
|
|
|
+
|
|
|
+ struct whpx_breakpoint_collection *new_breakpoints =
|
|
|
+ (struct whpx_breakpoint_collection *)g_malloc0(
|
|
|
+ sizeof(struct whpx_breakpoint_collection) +
|
|
|
+ max_breakpoints * sizeof(struct whpx_breakpoint));
|
|
|
+
|
|
|
+ new_breakpoints->allocated = max_breakpoints;
|
|
|
+ new_breakpoints->used = 0;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * 1. Preserve all old breakpoints that could not be automatically
|
|
|
+ * cleared when the CPU got stopped.
|
|
|
+ */
|
|
|
+ if (breakpoints->breakpoints) {
|
|
|
+ int i;
|
|
|
+ for (i = 0; i < breakpoints->breakpoints->used; i++) {
|
|
|
+ if (breakpoints->breakpoints->data[i].state != WHPX_BP_CLEARED) {
|
|
|
+ new_breakpoints->data[new_breakpoints->used++] =
|
|
|
+ breakpoints->breakpoints->data[i];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 2. Map all CPU breakpoints to WHPX breakpoints */
|
|
|
+ QTAILQ_FOREACH(bp, &cpu->breakpoints, entry) {
|
|
|
+ int i;
|
|
|
+ bool found = false;
|
|
|
+
|
|
|
+ /* This will be used to detect changed CPU breakpoints later. */
|
|
|
+ breakpoints->original_addresses[cpu_bp_index++] = bp->pc;
|
|
|
+
|
|
|
+ for (i = 0; i < new_breakpoints->used; i++) {
|
|
|
+ /*
|
|
|
+ * WARNING: This loop has O(N^2) complexity, where N is the
|
|
|
+ * number of breakpoints. It should not be a bottleneck in
|
|
|
+ * real-world scenarios, since it only needs to run once after
|
|
|
+ * the breakpoints have been modified.
|
|
|
+ * If this ever becomes a concern, it can be optimized by storing
|
|
|
+ * high-level breakpoint objects in a tree or hash map.
|
|
|
+ */
|
|
|
+
|
|
|
+ if (new_breakpoints->data[i].address == bp->pc) {
|
|
|
+ /* There was already a breakpoint at this address. */
|
|
|
+ if (new_breakpoints->data[i].state == WHPX_BP_CLEAR_PENDING) {
|
|
|
+ new_breakpoints->data[i].state = WHPX_BP_SET;
|
|
|
+ } else if (new_breakpoints->data[i].state == WHPX_BP_SET) {
|
|
|
+ new_breakpoints->data[i].state = WHPX_BP_SET_PENDING;
|
|
|
+ }
|
|
|
+
|
|
|
+ found = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!found && new_breakpoints->used < new_breakpoints->allocated) {
|
|
|
+ /* No WHPX breakpoint at this address. Create one. */
|
|
|
+ new_breakpoints->data[new_breakpoints->used].address = bp->pc;
|
|
|
+ new_breakpoints->data[new_breakpoints->used].state =
|
|
|
+ WHPX_BP_SET_PENDING;
|
|
|
+ new_breakpoints->used++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (breakpoints->breakpoints) {
|
|
|
+ /*
|
|
|
+ * Free the previous breakpoint list. This can be optimized by keeping
|
|
|
+ * it as shadow buffer for the next computation instead of freeing
|
|
|
+ * it immediately.
|
|
|
+ */
|
|
|
+ g_free(breakpoints->breakpoints);
|
|
|
+ }
|
|
|
+
|
|
|
+ breakpoints->breakpoints = new_breakpoints;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Physically inserts/removes the breakpoints by reading and writing the
|
|
|
+ * physical memory, keeping a track of the failed attempts.
|
|
|
+ *
|
|
|
+ * Passing resuming=true will try to set all previously unset breakpoints.
|
|
|
+ * Passing resuming=false will remove all inserted ones.
|
|
|
+ */
|
|
|
+static void whpx_apply_breakpoints(
|
|
|
+ struct whpx_breakpoint_collection *breakpoints,
|
|
|
+ CPUState *cpu,
|
|
|
+ bool resuming)
|
|
|
+{
|
|
|
+ int i, rc;
|
|
|
+ if (!breakpoints) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (i = 0; i < breakpoints->used; i++) {
|
|
|
+ /* Decide what to do right now based on the last known state. */
|
|
|
+ WhpxBreakpointState state = breakpoints->data[i].state;
|
|
|
+ switch (state) {
|
|
|
+ case WHPX_BP_CLEARED:
|
|
|
+ if (resuming) {
|
|
|
+ state = WHPX_BP_SET_PENDING;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case WHPX_BP_SET_PENDING:
|
|
|
+ if (!resuming) {
|
|
|
+ state = WHPX_BP_CLEARED;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case WHPX_BP_SET:
|
|
|
+ if (!resuming) {
|
|
|
+ state = WHPX_BP_CLEAR_PENDING;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case WHPX_BP_CLEAR_PENDING:
|
|
|
+ if (resuming) {
|
|
|
+ state = WHPX_BP_SET;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (state == WHPX_BP_SET_PENDING) {
|
|
|
+ /* Remember the original instruction. */
|
|
|
+ rc = cpu_memory_rw_debug(cpu,
|
|
|
+ breakpoints->data[i].address,
|
|
|
+ &breakpoints->data[i].original_instruction,
|
|
|
+ 1,
|
|
|
+ false);
|
|
|
+
|
|
|
+ if (!rc) {
|
|
|
+ /* Write the breakpoint instruction. */
|
|
|
+ rc = cpu_memory_rw_debug(cpu,
|
|
|
+ breakpoints->data[i].address,
|
|
|
+ (void *)&whpx_breakpoint_instruction,
|
|
|
+ 1,
|
|
|
+ true);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!rc) {
|
|
|
+ state = WHPX_BP_SET;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if (state == WHPX_BP_CLEAR_PENDING) {
|
|
|
+ /* Restore the original instruction. */
|
|
|
+ rc = cpu_memory_rw_debug(cpu,
|
|
|
+ breakpoints->data[i].address,
|
|
|
+ &breakpoints->data[i].original_instruction,
|
|
|
+ 1,
|
|
|
+ true);
|
|
|
+
|
|
|
+ if (!rc) {
|
|
|
+ state = WHPX_BP_CLEARED;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ breakpoints->data[i].state = state;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * This function is called when the a VCPU is about to start and no other
|
|
|
+ * VCPUs have been started so far. Since the VCPU start order could be
|
|
|
+ * arbitrary, it doesn't have to be VCPU#0.
|
|
|
+ *
|
|
|
+ * It is used to commit the breakpoints into memory, and configure WHPX
|
|
|
+ * to intercept debug exceptions.
|
|
|
+ *
|
|
|
+ * Note that whpx_set_exception_exit_bitmap() cannot be called if one or
|
|
|
+ * more VCPUs are already running, so this is the best place to do it.
|
|
|
+ */
|
|
|
+static int whpx_first_vcpu_starting(CPUState *cpu)
|
|
|
+{
|
|
|
+ struct whpx_state *whpx = &whpx_global;
|
|
|
+ HRESULT hr;
|
|
|
+
|
|
|
+ g_assert(qemu_mutex_iothread_locked());
|
|
|
+
|
|
|
+ if (!QTAILQ_EMPTY(&cpu->breakpoints) ||
|
|
|
+ (whpx->breakpoints.breakpoints &&
|
|
|
+ whpx->breakpoints.breakpoints->used)) {
|
|
|
+ CPUBreakpoint *bp;
|
|
|
+ int i = 0;
|
|
|
+ bool update_pending = false;
|
|
|
+
|
|
|
+ QTAILQ_FOREACH(bp, &cpu->breakpoints, entry) {
|
|
|
+ if (i >= whpx->breakpoints.original_address_count ||
|
|
|
+ bp->pc != whpx->breakpoints.original_addresses[i]) {
|
|
|
+ update_pending = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ i++;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (i != whpx->breakpoints.original_address_count) {
|
|
|
+ update_pending = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (update_pending) {
|
|
|
+ /*
|
|
|
+ * The CPU breakpoints have changed since the last call to
|
|
|
+ * whpx_translate_cpu_breakpoints(). WHPX breakpoints must
|
|
|
+ * now be recomputed.
|
|
|
+ */
|
|
|
+ whpx_translate_cpu_breakpoints(&whpx->breakpoints, cpu, i);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Actually insert the breakpoints into the memory. */
|
|
|
+ whpx_apply_breakpoints(whpx->breakpoints.breakpoints, cpu, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ uint64_t exception_mask;
|
|
|
+ if (whpx->step_pending ||
|
|
|
+ (whpx->breakpoints.breakpoints &&
|
|
|
+ whpx->breakpoints.breakpoints->used)) {
|
|
|
+ /*
|
|
|
+ * We are either attempting to single-step one or more CPUs, or
|
|
|
+ * have one or more breakpoints enabled. Both require intercepting
|
|
|
+ * the WHvX64ExceptionTypeBreakpointTrap exception.
|
|
|
+ */
|
|
|
+
|
|
|
+ exception_mask = 1UL << WHvX64ExceptionTypeDebugTrapOrFault;
|
|
|
+ } else {
|
|
|
+ /* Let the guest handle all exceptions. */
|
|
|
+ exception_mask = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ hr = whpx_set_exception_exit_bitmap(exception_mask);
|
|
|
+ if (!SUCCEEDED(hr)) {
|
|
|
+ error_report("WHPX: Failed to update exception exit mask,"
|
|
|
+ "hr=%08lx.", hr);
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * This function is called when the last VCPU has finished running.
|
|
|
+ * It is used to remove any previously set breakpoints from memory.
|
|
|
+ */
|
|
|
+static int whpx_last_vcpu_stopping(CPUState *cpu)
|
|
|
+{
|
|
|
+ whpx_apply_breakpoints(whpx_global.breakpoints.breakpoints, cpu, false);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* Returns the address of the next instruction that is about to be executed. */
|
|
|
+static vaddr whpx_vcpu_get_pc(CPUState *cpu, bool exit_context_valid)
|
|
|
+{
|
|
|
+ if (cpu->vcpu_dirty) {
|
|
|
+ /* The CPU registers have been modified by other parts of QEMU. */
|
|
|
+ CPUArchState *env = (CPUArchState *)(cpu->env_ptr);
|
|
|
+ return env->eip;
|
|
|
+ } else if (exit_context_valid) {
|
|
|
+ /*
|
|
|
+ * The CPU registers have not been modified by neither other parts
|
|
|
+ * of QEMU, nor this port by calling WHvSetVirtualProcessorRegisters().
|
|
|
+ * This is the most common case.
|
|
|
+ */
|
|
|
+ struct whpx_vcpu *vcpu = get_whpx_vcpu(cpu);
|
|
|
+ return vcpu->exit_ctx.VpContext.Rip;
|
|
|
+ } else {
|
|
|
+ /*
|
|
|
+ * The CPU registers have been modified by a call to
|
|
|
+ * WHvSetVirtualProcessorRegisters() and must be re-queried from
|
|
|
+ * the target.
|
|
|
+ */
|
|
|
+ WHV_REGISTER_VALUE reg_value;
|
|
|
+ WHV_REGISTER_NAME reg_name = WHvX64RegisterRip;
|
|
|
+ HRESULT hr;
|
|
|
+ struct whpx_state *whpx = &whpx_global;
|
|
|
+
|
|
|
+ hr = whp_dispatch.WHvGetVirtualProcessorRegisters(
|
|
|
+ whpx->partition,
|
|
|
+ cpu->cpu_index,
|
|
|
+ ®_name,
|
|
|
+ 1,
|
|
|
+ ®_value);
|
|
|
+
|
|
|
+ if (FAILED(hr)) {
|
|
|
+ error_report("WHPX: Failed to get PC, hr=%08lx", hr);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ return reg_value.Reg64;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
static int whpx_handle_halt(CPUState *cpu)
|
|
|
{
|
|
|
CPUX86State *env = cpu->env_ptr;
|
|
@@ -996,17 +1589,75 @@ static int whpx_vcpu_run(CPUState *cpu)
|
|
|
HRESULT hr;
|
|
|
struct whpx_state *whpx = &whpx_global;
|
|
|
struct whpx_vcpu *vcpu = get_whpx_vcpu(cpu);
|
|
|
+ struct whpx_breakpoint *stepped_over_bp = NULL;
|
|
|
+ WhpxStepMode exclusive_step_mode = WHPX_STEP_NONE;
|
|
|
int ret;
|
|
|
|
|
|
- whpx_vcpu_process_async_events(cpu);
|
|
|
- if (cpu->halted && !whpx_apic_in_platform()) {
|
|
|
- cpu->exception_index = EXCP_HLT;
|
|
|
- qatomic_set(&cpu->exit_request, false);
|
|
|
- return 0;
|
|
|
+ g_assert(qemu_mutex_iothread_locked());
|
|
|
+
|
|
|
+ if (whpx->running_cpus++ == 0) {
|
|
|
+ /* Insert breakpoints into memory, update exception exit bitmap. */
|
|
|
+ ret = whpx_first_vcpu_starting(cpu);
|
|
|
+ if (ret != 0) {
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (whpx->breakpoints.breakpoints &&
|
|
|
+ whpx->breakpoints.breakpoints->used > 0)
|
|
|
+ {
|
|
|
+ uint64_t pc = whpx_vcpu_get_pc(cpu, true);
|
|
|
+ stepped_over_bp = whpx_lookup_breakpoint_by_addr(pc);
|
|
|
+ if (stepped_over_bp && stepped_over_bp->state != WHPX_BP_SET) {
|
|
|
+ stepped_over_bp = NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (stepped_over_bp) {
|
|
|
+ /*
|
|
|
+ * We are trying to run the instruction overwritten by an active
|
|
|
+ * breakpoint. We will temporarily disable the breakpoint, suspend
|
|
|
+ * other CPUs, and step over the instruction.
|
|
|
+ */
|
|
|
+ exclusive_step_mode = WHPX_STEP_EXCLUSIVE;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (exclusive_step_mode == WHPX_STEP_NONE) {
|
|
|
+ whpx_vcpu_process_async_events(cpu);
|
|
|
+ if (cpu->halted && !whpx_apic_in_platform()) {
|
|
|
+ cpu->exception_index = EXCP_HLT;
|
|
|
+ qatomic_set(&cpu->exit_request, false);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
qemu_mutex_unlock_iothread();
|
|
|
- cpu_exec_start(cpu);
|
|
|
+
|
|
|
+ if (exclusive_step_mode != WHPX_STEP_NONE) {
|
|
|
+ start_exclusive();
|
|
|
+ g_assert(cpu == current_cpu);
|
|
|
+ g_assert(!cpu->running);
|
|
|
+ cpu->running = true;
|
|
|
+
|
|
|
+ hr = whpx_set_exception_exit_bitmap(
|
|
|
+ 1UL << WHvX64ExceptionTypeDebugTrapOrFault);
|
|
|
+ if (!SUCCEEDED(hr)) {
|
|
|
+ error_report("WHPX: Failed to update exception exit mask, "
|
|
|
+ "hr=%08lx.", hr);
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (stepped_over_bp) {
|
|
|
+ /* Temporarily disable the triggered breakpoint. */
|
|
|
+ cpu_memory_rw_debug(cpu,
|
|
|
+ stepped_over_bp->address,
|
|
|
+ &stepped_over_bp->original_instruction,
|
|
|
+ 1,
|
|
|
+ true);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ cpu_exec_start(cpu);
|
|
|
+ }
|
|
|
|
|
|
do {
|
|
|
if (cpu->vcpu_dirty) {
|
|
@@ -1014,10 +1665,16 @@ static int whpx_vcpu_run(CPUState *cpu)
|
|
|
cpu->vcpu_dirty = false;
|
|
|
}
|
|
|
|
|
|
- whpx_vcpu_pre_run(cpu);
|
|
|
+ if (exclusive_step_mode == WHPX_STEP_NONE) {
|
|
|
+ whpx_vcpu_pre_run(cpu);
|
|
|
+
|
|
|
+ if (qatomic_read(&cpu->exit_request)) {
|
|
|
+ whpx_vcpu_kick(cpu);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- if (qatomic_read(&cpu->exit_request)) {
|
|
|
- whpx_vcpu_kick(cpu);
|
|
|
+ if (exclusive_step_mode != WHPX_STEP_NONE || cpu->singlestep_enabled) {
|
|
|
+ whpx_vcpu_configure_single_stepping(cpu, true, NULL);
|
|
|
}
|
|
|
|
|
|
hr = whp_dispatch.WHvRunVirtualProcessor(
|
|
@@ -1031,6 +1688,12 @@ static int whpx_vcpu_run(CPUState *cpu)
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
+ if (exclusive_step_mode != WHPX_STEP_NONE || cpu->singlestep_enabled) {
|
|
|
+ whpx_vcpu_configure_single_stepping(cpu,
|
|
|
+ false,
|
|
|
+ &vcpu->exit_ctx.VpContext.Rflags);
|
|
|
+ }
|
|
|
+
|
|
|
whpx_vcpu_post_run(cpu);
|
|
|
|
|
|
switch (vcpu->exit_ctx.ExitReason) {
|
|
@@ -1054,6 +1717,10 @@ static int whpx_vcpu_run(CPUState *cpu)
|
|
|
break;
|
|
|
|
|
|
case WHvRunVpExitReasonX64Halt:
|
|
|
+ /*
|
|
|
+ * WARNING: as of build 19043.1526 (21H1), this exit reason is no
|
|
|
+ * longer used.
|
|
|
+ */
|
|
|
ret = whpx_handle_halt(cpu);
|
|
|
break;
|
|
|
|
|
@@ -1152,10 +1819,19 @@ static int whpx_vcpu_run(CPUState *cpu)
|
|
|
}
|
|
|
|
|
|
case WHvRunVpExitReasonCanceled:
|
|
|
- cpu->exception_index = EXCP_INTERRUPT;
|
|
|
- ret = 1;
|
|
|
+ if (exclusive_step_mode != WHPX_STEP_NONE) {
|
|
|
+ /*
|
|
|
+ * We are trying to step over a single instruction, and
|
|
|
+ * likely got a request to stop from another thread.
|
|
|
+ * Delay it until we are done stepping
|
|
|
+ * over.
|
|
|
+ */
|
|
|
+ ret = 0;
|
|
|
+ } else {
|
|
|
+ cpu->exception_index = EXCP_INTERRUPT;
|
|
|
+ ret = 1;
|
|
|
+ }
|
|
|
break;
|
|
|
-
|
|
|
case WHvRunVpExitReasonX64MsrAccess: {
|
|
|
WHV_REGISTER_VALUE reg_values[3] = {0};
|
|
|
WHV_REGISTER_NAME reg_names[3];
|
|
@@ -1259,11 +1935,36 @@ static int whpx_vcpu_run(CPUState *cpu)
|
|
|
ret = 0;
|
|
|
break;
|
|
|
}
|
|
|
+ case WHvRunVpExitReasonException:
|
|
|
+ whpx_get_registers(cpu);
|
|
|
+
|
|
|
+ if ((vcpu->exit_ctx.VpException.ExceptionType ==
|
|
|
+ WHvX64ExceptionTypeDebugTrapOrFault) &&
|
|
|
+ (vcpu->exit_ctx.VpException.InstructionByteCount >= 1) &&
|
|
|
+ (vcpu->exit_ctx.VpException.InstructionBytes[0] ==
|
|
|
+ whpx_breakpoint_instruction)) {
|
|
|
+ /* Stopped at a software breakpoint. */
|
|
|
+ cpu->exception_index = EXCP_DEBUG;
|
|
|
+ } else if ((vcpu->exit_ctx.VpException.ExceptionType ==
|
|
|
+ WHvX64ExceptionTypeDebugTrapOrFault) &&
|
|
|
+ !cpu->singlestep_enabled) {
|
|
|
+ /*
|
|
|
+ * Just finished stepping over a breakpoint, but the
|
|
|
+ * gdb does not expect us to do single-stepping.
|
|
|
+ * Don't do anything special.
|
|
|
+ */
|
|
|
+ cpu->exception_index = EXCP_INTERRUPT;
|
|
|
+ } else {
|
|
|
+ /* Another exception or debug event. Report it to GDB. */
|
|
|
+ cpu->exception_index = EXCP_DEBUG;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = 1;
|
|
|
+ break;
|
|
|
case WHvRunVpExitReasonNone:
|
|
|
case WHvRunVpExitReasonUnrecoverableException:
|
|
|
case WHvRunVpExitReasonInvalidVpRegisterValue:
|
|
|
case WHvRunVpExitReasonUnsupportedFeature:
|
|
|
- case WHvRunVpExitReasonException:
|
|
|
default:
|
|
|
error_report("WHPX: Unexpected VP exit code %d",
|
|
|
vcpu->exit_ctx.ExitReason);
|
|
@@ -1276,10 +1977,32 @@ static int whpx_vcpu_run(CPUState *cpu)
|
|
|
|
|
|
} while (!ret);
|
|
|
|
|
|
- cpu_exec_end(cpu);
|
|
|
+ if (stepped_over_bp) {
|
|
|
+ /* Restore the breakpoint we stepped over */
|
|
|
+ cpu_memory_rw_debug(cpu,
|
|
|
+ stepped_over_bp->address,
|
|
|
+ (void *)&whpx_breakpoint_instruction,
|
|
|
+ 1,
|
|
|
+ true);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (exclusive_step_mode != WHPX_STEP_NONE) {
|
|
|
+ g_assert(cpu_in_exclusive_context(cpu));
|
|
|
+ cpu->running = false;
|
|
|
+ end_exclusive();
|
|
|
+
|
|
|
+ exclusive_step_mode = WHPX_STEP_NONE;
|
|
|
+ } else {
|
|
|
+ cpu_exec_end(cpu);
|
|
|
+ }
|
|
|
+
|
|
|
qemu_mutex_lock_iothread();
|
|
|
current_cpu = cpu;
|
|
|
|
|
|
+ if (--whpx->running_cpus == 0) {
|
|
|
+ whpx_last_vcpu_stopping(cpu);
|
|
|
+ }
|
|
|
+
|
|
|
qatomic_set(&cpu->exit_request, false);
|
|
|
|
|
|
return ret < 0;
|
|
@@ -1339,6 +2062,11 @@ void whpx_cpu_synchronize_pre_loadvm(CPUState *cpu)
|
|
|
run_on_cpu(cpu, do_whpx_cpu_synchronize_pre_loadvm, RUN_ON_CPU_NULL);
|
|
|
}
|
|
|
|
|
|
+void whpx_cpu_synchronize_pre_resume(bool step_pending)
|
|
|
+{
|
|
|
+ whpx_global.step_pending = step_pending;
|
|
|
+}
|
|
|
+
|
|
|
/*
|
|
|
* Vcpu support.
|
|
|
*/
|
|
@@ -1838,6 +2566,7 @@ static int whpx_accel_init(MachineState *ms)
|
|
|
memset(&prop, 0, sizeof(WHV_PARTITION_PROPERTY));
|
|
|
prop.ExtendedVmExits.X64MsrExit = 1;
|
|
|
prop.ExtendedVmExits.X64CpuidExit = 1;
|
|
|
+ prop.ExtendedVmExits.ExceptionExit = 1;
|
|
|
if (whpx_apic_in_platform()) {
|
|
|
prop.ExtendedVmExits.X64ApicInitSipiExitTrap = 1;
|
|
|
}
|
|
@@ -1866,6 +2595,19 @@ static int whpx_accel_init(MachineState *ms)
|
|
|
goto error;
|
|
|
}
|
|
|
|
|
|
+ /*
|
|
|
+ * We do not want to intercept any exceptions from the guest,
|
|
|
+ * until we actually start debugging with gdb.
|
|
|
+ */
|
|
|
+ whpx->exception_exit_bitmap = -1;
|
|
|
+ hr = whpx_set_exception_exit_bitmap(0);
|
|
|
+
|
|
|
+ if (FAILED(hr)) {
|
|
|
+ error_report("WHPX: Failed to set exception exit bitmap, hr=%08lx", hr);
|
|
|
+ ret = -EINVAL;
|
|
|
+ goto error;
|
|
|
+ }
|
|
|
+
|
|
|
hr = whp_dispatch.WHvSetupPartition(whpx->partition);
|
|
|
if (FAILED(hr)) {
|
|
|
error_report("WHPX: Failed to setup partition, hr=%08lx", hr);
|