From: "Ivan Shcherbakov" <ivan@sysprogs.com>
To: <qemu-devel@nongnu.org>
Cc: armbru@redhat.com, mst@redhat.com
Subject: [PATCH 3/3] whpx: Added support for breakpoints and stepping
Date: Tue, 22 Feb 2022 21:25:51 -0800 [thread overview]
Message-ID: <010e01d82875$d3cc0ec0$7b642c40$@sysprogs.com> (raw)
This adds support for breakpoints and stepping when debugging
WHPX-accelerated guests with gdb.
It enables reliable debugging of the Linux kernel in both single-CPU and SMP
modes.
Signed-off-by: Ivan Shcherbakov <ivan@sysprogs.com>
---
gdbstub.c | 10 +
include/exec/gdbstub.h | 8 +
target/i386/whpx/whpx-all.c | 689 ++++++++++++++++++++++++++++++-
target/i386/whpx/whpx-internal.h | 29 ++
4 files changed, 721 insertions(+), 15 deletions(-)
diff --git a/gdbstub.c b/gdbstub.c
index 3c14c6a038..d30cbfa478 100644
--- a/gdbstub.c
+++ b/gdbstub.c
@@ -373,6 +373,12 @@ typedef struct GDBState {
} GDBState;
static GDBState gdbserver_state;
+static bool gdbserver_is_connected;
+
+bool gdb_is_connected(void)
+{
+ return gdbserver_is_connected;
+}
static void init_gdbserver_state(void)
{
@@ -3410,6 +3416,10 @@ static void gdb_chr_event(void *opaque, QEMUChrEvent
event)
vm_stop(RUN_STATE_PAUSED);
replay_gdb_attached();
gdb_has_xml = false;
+ gdbserver_is_connected = true;
+ break;
+ case CHR_EVENT_CLOSED:
+ gdbserver_is_connected = false;
break;
default:
break;
diff --git a/include/exec/gdbstub.h b/include/exec/gdbstub.h
index a024a0350d..0ef54cdeb5 100644
--- a/include/exec/gdbstub.h
+++ b/include/exec/gdbstub.h
@@ -188,4 +188,12 @@ extern bool gdb_has_xml;
/* in gdbstub-xml.c, generated by scripts/feature_to_c.sh */
extern const char *const xml_builtin[][2];
+/**
+ * gdb_is_connected: Check whether gdb is currently connected.
+ * This function is used to determine if gdb is currently connected to
qemu.
+ * It is used by the WHPX engine to enable interception of debug-related
+ * exceptions, when debugging with gdb, and pass them to the guest
otherwise.
+ */
+bool gdb_is_connected(void);
+
#endif
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index 8a8b5d55d1..030988fa51 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -12,6 +12,7 @@
#include "cpu.h"
#include "exec/address-spaces.h"
#include "exec/ioport.h"
+#include "exec/gdbstub.h"
#include "qemu-common.h"
#include "qemu/accel.h"
#include "sysemu/whpx.h"
@@ -148,6 +149,12 @@ struct whpx_register_set {
WHV_REGISTER_VALUE values[RTL_NUMBER_OF(whpx_register_names)];
};
+enum whpx_step_mode {
+ whpx_step_none = 0,
+ /* Halt other VCPUs */
+ whpx_step_exclusive,
+};
+
struct whpx_vcpu {
WHV_EMULATOR_HANDLE emulator;
bool window_registered;
@@ -156,7 +163,6 @@ struct whpx_vcpu {
uint64_t tpr;
uint64_t apic_base;
bool interruption_pending;
-
/* Must be the last field as it may have a tail */
WHV_RUN_VP_EXIT_CONTEXT exit_ctx;
};
@@ -793,6 +799,515 @@ 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. */
+ enum whpx_breakpoint_state 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 (gdb_is_connected()) {
+ /*
+ * GDB is connected. Intercept breakpoint/step events.
+ * Since we are using INT1 rather than INT3 for breakpoints,
+ * we don't need to intercept WHvX64ExceptionTypeBreakpointTrap.
+ */
+
+ exception_mask = 1UL << WHvX64ExceptionTypeDebugTrapOrFault;
+ } else {
+ /* GDB is not connected. 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. */
+ struct CPUX86State *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)
{
struct CPUX86State *env = (CPUArchState *)(cpu->env_ptr);
@@ -1004,17 +1519,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;
+ enum whpx_step_mode 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) {
@@ -1022,10 +1595,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(
@@ -1039,6 +1618,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) {
@@ -1062,6 +1647,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;
@@ -1160,10 +1749,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];
@@ -1267,11 +1865,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);
@@ -1284,10 +1907,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;
@@ -1846,6 +2491,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;
}
@@ -1874,6 +2520,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);
diff --git a/target/i386/whpx/whpx-internal.h
b/target/i386/whpx/whpx-internal.h
index 908ababf6d..8f8e02cf9a 100644
--- a/target/i386/whpx/whpx-internal.h
+++ b/target/i386/whpx/whpx-internal.h
@@ -5,9 +5,38 @@
#include <WinHvPlatform.h>
#include <WinHvEmulation.h>
+enum whpx_breakpoint_state {
+ whpx_bp_cleared = 0,
+ whpx_bp_set_pending,
+ whpx_bp_set,
+ whpx_bp_clear_pending,
+};
+
+struct whpx_breakpoint {
+ vaddr address;
+ enum whpx_breakpoint_state state;
+ uint8_t original_instruction;
+};
+
+struct whpx_breakpoint_collection {
+ int allocated, used;
+ struct whpx_breakpoint data[0];
+};
+
+struct whpx_breakpoints {
+ int original_address_count;
+ vaddr *original_addresses;
+
+ struct whpx_breakpoint_collection *breakpoints;
+};
+
struct whpx_state {
uint64_t mem_quota;
WHV_PARTITION_HANDLE partition;
+ uint64_t exception_exit_bitmap;
+ int32_t running_cpus;
+ struct whpx_breakpoints breakpoints;
+
bool kernel_irqchip_allowed;
bool kernel_irqchip_required;
bool apic_in_platform;
--
next reply other threads:[~2022-02-23 5:26 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-02-23 5:25 Ivan Shcherbakov [this message]
2022-02-23 9:36 ` [PATCH 3/3] whpx: Added support for breakpoints and stepping Paolo Bonzini
2022-02-23 20:05 ` Ivan Shcherbakov
2022-02-24 9:35 ` Peter Maydell
2022-02-24 11:22 ` Alex Bennée
2022-02-24 15:54 ` Ivan Shcherbakov
2022-02-28 4:31 ` Ivan Shcherbakov
2022-02-28 10:28 ` Alex Bennée
2022-03-01 2:08 ` Ivan Shcherbakov
2022-03-02 2:06 ` Ivan Shcherbakov
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to='010e01d82875$d3cc0ec0$7b642c40$@sysprogs.com' \
--to=ivan@sysprogs.com \
--cc=armbru@redhat.com \
--cc=mst@redhat.com \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).