From nobody Mon Sep 28 02:05:28 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=linux.microsoft.com ARC-Seal: i=1; a=rsa-sha256; t=1785162517; cv=none; d=zohomail.com; s=zohoarc; b=C+EI/lasv8HoYAamuKYY72RYVeQio7YUNtrhfLC2ZfE/1+M9sC2T3QNBhgJWlpnE41bEuPkc/uhefzESkACmrF59M0BYWTsotFXT4ItE5C4ObY2QTl5jr0EMYbNtZyuNoL+o9LBIcFnQSudaaDpvx8kVlDx2UoNL00Y3iNHyKEo= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1785162517; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=1xQViwwRbTfUwL4or9CH3iqmCMINBdRcqoXMVtitYvw=; b=VmeLtM/j2kYnVZSmBLLYlV94JLOQUz39U3W+Yf5MVVGilcM6HSAE8CdfpYXd0Ave98HkOLq/FGSfWRTC4RMEyGk7rrXm6mkpetABG8x0lOnHCXFcfbQ2ORGbwIfgoEKvWw5GRxQcsRp43Nv332IVHLI0xxGHftPbhxka/YeGDek= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1785162517583533.8532127356403; Mon, 27 Jul 2026 07:28:37 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1woMJX-0006Ft-Oh; Mon, 27 Jul 2026 10:28:19 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1woMJW-0006FP-K0 for qemu-devel@nongnu.org; Mon, 27 Jul 2026 10:28:18 -0400 Received: from linux.microsoft.com ([13.77.154.182]) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1woMJV-0001vS-3J for qemu-devel@nongnu.org; Mon, 27 Jul 2026 10:28:18 -0400 Received: from laptop.localdomain (unknown [86.121.140.206]) by linux.microsoft.com (Postfix) with ESMTPSA id 8529120B7167; Mon, 27 Jul 2026 07:27:58 -0700 (PDT) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com 8529120B7167 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.microsoft.com; s=default; t=1785162480; bh=1xQViwwRbTfUwL4or9CH3iqmCMINBdRcqoXMVtitYvw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Jj927Rlq2NFFhMZcmAgzJ8MoYgNeIJuvs9TdNV7+YwhFy+pexQz86bitAuqDV2KDJ hxPH1Nxuf2lBB7p7/2afUBv2GLR39h8WqVIKNOBx+wO9eUr1MigbPFu+OqkS/Hz6NW yV7H5F0nqbkiTgi731G8Oy5P67ArTBN46JeRE73o= From: =?UTF-8?q?Doru=20Bl=C3=A2nzeanu?= To: qemu-devel@nongnu.org Cc: Magnus Kulke , =?UTF-8?q?Doru=20Bl=C3=A2nzeanu?= , =?UTF-8?q?Doru=20Bl=C3=A2nzeanu?= , Wei Liu , Wei Liu , Magnus Kulke Subject: [PATCH 1/3] include/hw/hyperv: add ABI for exception intercepts and pending events Date: Mon, 27 Jul 2026 17:28:05 +0300 Message-ID: <20260727142807.84269-2-dblanzeanu@linux.microsoft.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260727142807.84269-1-dblanzeanu@linux.microsoft.com> References: <20260727142807.84269-1-dblanzeanu@linux.microsoft.com> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=13.77.154.182; envelope-from=dblanzeanu@linux.microsoft.com; helo=linux.microsoft.com X-Spam_score_int: -19 X-Spam_score: -2.0 X-Spam_bar: -- X-Spam_report: (-2.0 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @linux.microsoft.com) X-ZM-MESSAGEID: 1785162520083158501 Import the hypervisor definitions needed to intercept guest exceptions and to inject events back into a guest, in preparation for gdbstub debugging support in the MSHV accelerator. Add: - union hv_intercept_parameters, struct hv_input_install_intercept and HVCALL_INSTALL_INTERCEPT, used to install an exception intercept on the partition; - struct hv_x64_exception_intercept_message, the message delivered on an intercepted exception, carrying the faulting RIP, the exception vector and the instruction bytes; - HV_X64_PENDING_EVENT_EXCEPTION, the pending-event type for an exception. These definitions mirror the hypervisor headers and have no functional effect on their own. Signed-off-by: Doru Bl=C3=A2nzeanu Reviewed-by: Magnus Kulke --- include/hw/hyperv/hvgdk_mini.h | 56 ++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/include/hw/hyperv/hvgdk_mini.h b/include/hw/hyperv/hvgdk_mini.h index f8838a31bb..c81928d0c2 100644 --- a/include/hw/hyperv/hvgdk_mini.h +++ b/include/hw/hyperv/hvgdk_mini.h @@ -22,6 +22,9 @@ #define HV_X64_MSR_TSC_FREQUENCY 0x40000022 #define HV_X64_MSR_APIC_FREQUENCY 0x40000023 =20 +/* event_type values for hv_x64_pending_exception_event */ +#define HV_X64_PENDING_EVENT_EXCEPTION 0 + typedef enum hv_register_name { /* Pending Interruption Register */ HV_REGISTER_PENDING_INTERRUPTION =3D 0x00010002, @@ -236,6 +239,29 @@ enum hv_intercept_type { HV_INTERCEPT_TYPE_INVALID =3D 0XFFFFFFFF, }; =20 +union hv_intercept_parameters { + /* HV_INTERCEPT_PARAMETERS is defined to be an 8-byte field. */ + uint64_t as_uint64; + /* HV_INTERCEPT_TYPE_X64_IO_PORT */ + uint16_t io_port; + /* HV_INTERCEPT_TYPE_X64_CPUID */ + uint32_t cpuid_index; + /* HV_INTERCEPT_TYPE_X64_APIC_WRITE */ + uint32_t apic_write_mask; + /* HV_INTERCEPT_TYPE_EXCEPTION */ + uint16_t exception_vector; + /* HV_INTERCEPT_TYPE_X64_MSR_INDEX */ + uint32_t msr_index; + /* N.B. Other intercept types do not have any parameters. */ +}; + +struct hv_input_install_intercept { + uint64_t partition_id; + uint32_t access_type; /* mask */ + uint32_t intercept_type; /* enum hv_intercept_type */ + union hv_intercept_parameters intercept_parameter; +} QEMU_PACKED; + struct hv_u128 { uint64_t low_part; uint64_t high_part; @@ -836,6 +862,35 @@ struct hv_x64_memory_intercept_message { uint8_t instruction_bytes[16]; } QEMU_PACKED; =20 +struct hv_x64_exception_intercept_message { + struct hv_x64_intercept_message_header header; + uint16_t exception_vector; + uint8_t exception_info; + uint8_t instruction_byte_count; + uint32_t error_code; + uint64_t exception_parameter; /* DR6 for #DB, CR2 for #PF */ + uint64_t reserved; + uint8_t instruction_bytes[16]; + struct hv_x64_segment_register ds_segment; + struct hv_x64_segment_register ss_segment; + uint64_t rax; + uint64_t rcx; + uint64_t rdx; + uint64_t rbx; + uint64_t rsp; + uint64_t rbp; + uint64_t rsi; + uint64_t rdi; + uint64_t r8; + uint64_t r9; + uint64_t r10; + uint64_t r11; + uint64_t r12; + uint64_t r13; + uint64_t r14; + uint64_t r15; +} QEMU_PACKED; + union hv_message_flags { uint8_t asu8; struct { @@ -943,6 +998,7 @@ struct hv_cpuid { =20 #define HVCALL_GET_PARTITION_PROPERTY 0x0044 #define HVCALL_SET_PARTITION_PROPERTY 0x0045 +#define HVCALL_INSTALL_INTERCEPT 0x004d #define HVCALL_GET_VP_REGISTERS 0x0050 #define HVCALL_SET_VP_REGISTERS 0x0051 #define HVCALL_TRANSLATE_VIRTUAL_ADDRESS 0x0052 --=20 2.53.0 From nobody Mon Sep 28 02:05:28 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=linux.microsoft.com ARC-Seal: i=1; a=rsa-sha256; t=1785162519; cv=none; d=zohomail.com; s=zohoarc; b=ZdKN07XkBrOf7MWJU4+/nsJrYrPSIzDmghe5+o2IV2mLJxCXONEOzg2Rkd2x0nQ5IgyHa8f3qb6CagwSxHPmURqqbNI0jNlF8iNDlurgb6o+d5dOVUJjOFCKEHYegpj7btbFr0r8+9DjZVvDNMPFIcpvbLj70toG5q9KIcLw+44= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1785162519; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=8YAHzdAMZOSFLjOaSrwOi22ZesU/+9SRPCYzdZqMfX4=; b=mfGd51fXwTrQJTRWtE86ICGE9E8lMuleHicZLx4/UaD0my+YD0Pm2p5rA1m2geWJIbUg/LNN+sKU3RbRTmoQf11pvr+BlBjR3xSS1EpTM4z5osy/UZlDqnLiDUZNYYt6AwkVfomr6it62u0nmHxRUWRkduwGkvSVelBurSX3ZYY= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1785162518989611.0503183709922; Mon, 27 Jul 2026 07:28:38 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1woMJc-0006Iw-6Z; Mon, 27 Jul 2026 10:28:24 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1woMJa-0006I8-BP for qemu-devel@nongnu.org; Mon, 27 Jul 2026 10:28:22 -0400 Received: from linux.microsoft.com ([13.77.154.182]) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1woMJY-0001wB-4A for qemu-devel@nongnu.org; Mon, 27 Jul 2026 10:28:22 -0400 Received: from laptop.localdomain (unknown [86.121.140.206]) by linux.microsoft.com (Postfix) with ESMTPSA id 1432620B7169; Mon, 27 Jul 2026 07:28:00 -0700 (PDT) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com 1432620B7169 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.microsoft.com; s=default; t=1785162483; bh=8YAHzdAMZOSFLjOaSrwOi22ZesU/+9SRPCYzdZqMfX4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=pzTvjVVteiJBQDsRkNEg7QkothuSr4MVOBhBjxV6fIUlMusYGkAsasV8suheZA2zR 5Se/WeA+MnUNSE80rk4OQiTx1dcQOsjdtVLe//LA1qD1ORncUdIh2flmCqTYJ0jGIV FOwBHyD6c7TVAzRb6Tn58Vp5AtdFP1/tRefdg5gg= From: =?UTF-8?q?Doru=20Bl=C3=A2nzeanu?= To: qemu-devel@nongnu.org Cc: Magnus Kulke , =?UTF-8?q?Doru=20Bl=C3=A2nzeanu?= , =?UTF-8?q?Doru=20Bl=C3=A2nzeanu?= , Wei Liu , Wei Liu , Magnus Kulke Subject: [PATCH 2/3] accel/mshv: add gdbstub software breakpoint support Date: Mon, 27 Jul 2026 17:28:06 +0300 Message-ID: <20260727142807.84269-3-dblanzeanu@linux.microsoft.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260727142807.84269-1-dblanzeanu@linux.microsoft.com> References: <20260727142807.84269-1-dblanzeanu@linux.microsoft.com> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=13.77.154.182; envelope-from=dblanzeanu@linux.microsoft.com; helo=linux.microsoft.com X-Spam_score_int: -19 X-Spam_score: -2.0 X-Spam_bar: -- X-Spam_report: (-2.0 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @linux.microsoft.com) X-ZM-MESSAGEID: 1785162520134158500 Implement software breakpoint based guest debugging for the MSHV accelerator, modeled on the WHPX backend. The guest-visible debug registers are not used: a breakpoint is an INT1 (opcode 0xf1) patched into guest memory, which raises a #DB when executed. A partition-wide intercept on #DB is installed the first time a breakpoint (or single step) is requested. The intercept is not uninstalled, so it stays for the lifetime of the VM and every vmexit mshv_handle_debug() decides whether the #DB belongs to gdb (it hit one of our INT1 breakpoints)= or to the guest; a guest-owned #DB is handed back through a pending exception event so the guest takes it through its own IDT. Guest INT3 (#BP) is deliberately left to the guest's own IDT and is never intercepted. Signed-off-by: Doru Bl=C3=A2nzeanu --- accel/mshv/mshv-all.c | 165 +++++++++++++++++++++++++++++++++++- include/system/mshv_int.h | 14 +++ target/i386/mshv/mshv-cpu.c | 86 +++++++++++++++++++ 3 files changed, 264 insertions(+), 1 deletion(-) diff --git a/accel/mshv/mshv-all.c b/accel/mshv/mshv-all.c index 72721d0f0d..afcb87b3b8 100644 --- a/accel/mshv/mshv-all.c +++ b/accel/mshv/mshv-all.c @@ -29,6 +29,7 @@ =20 #include "qemu/accel.h" #include "qemu/guest-random.h" +#include "gdbstub/enums.h" #include "accel/accel-ops.h" #include "accel/accel-cpu-ops.h" #include "exec/cpu-common.h" @@ -583,6 +584,10 @@ static int mshv_init(AccelState *as, MachineState *ms) s->nr_as =3D 1; s->as =3D g_new0(MshvAddressSpace, s->nr_as); =20 + QTAILQ_INIT(&s->sw_breakpoints); + + as->gdbstub.sstep_flags =3D SSTEP_ENABLE; + mshv_state =3D s; =20 mshv_init_irq_routing(s); @@ -607,6 +612,153 @@ static int mshv_destroy_vcpu(CPUState *cpu) return 0; } =20 +struct MshvSwBreakpoint *mshv_find_sw_breakpoint(CPUState *cpu, vaddr pc) +{ + struct MshvSwBreakpoint *bp; + + QTAILQ_FOREACH(bp, &mshv_state->sw_breakpoints, entry) { + if (bp->pc =3D=3D pc) { + return bp; + } + } + return NULL; +} + +static int mshv_install_exception_intercept(int vm_fd, uint16_t vector) +{ + struct hv_input_install_intercept in =3D {0}; + struct mshv_root_hvcall args =3D {0}; + int ret; + + in.access_type =3D 1 << HV_X64_INTERCEPT_ACCESS_TYPE_EXECUTE; + in.intercept_type =3D HV_INTERCEPT_TYPE_EXCEPTION; + in.intercept_parameter.as_uint64 =3D vector; + + args.code =3D HVCALL_INSTALL_INTERCEPT; + args.in_sz =3D sizeof(in); + args.in_ptr =3D (uint64_t)∈ + + ret =3D mshv_hvcall(vm_fd, &args); + if (ret < 0) { + error_report("Failed to install exception intercept for vector %u", + vector); + } + return ret; +} + +/* + * Install the #DB intercept. This is a permanent, partition-wide latch: i= t is + * installed once on the first debug use and stays for the lifetime of the= VM. + */ +static int mshv_init_exception_intercept(void) +{ + int ret; + + if (mshv_state->exception_intercepts_installed) { + return 0; + } + + /* Only #DB is needed: gdb breakpoints are INT1 (0xf1) */ + ret =3D mshv_install_exception_intercept(mshv_state->vm, 1); /* #DB */ + if (ret < 0) { + return ret; + } + + mshv_state->exception_intercepts_installed =3D true; + return 0; +} + +static int mshv_update_guest_debug(CPUState *cpu) +{ + if (cpu_single_stepping(cpu)) { + /* Installs exception intercept only on the first call */ + return mshv_init_exception_intercept(); + } + return 0; +} + +static int mshv_insert_gdbstub_breakpoint(CPUState *cpu, GdbBreakpointType= type, + vaddr addr, vaddr len) +{ + struct MshvSwBreakpoint *bp; + int err; + + /* We don't use the guest's debug registers. */ + if (type !=3D GDB_BREAKPOINT_SW) { + return -ENOSYS; + } + + bp =3D mshv_find_sw_breakpoint(cpu, addr); + if (bp) { + bp->use_count++; + return 0; + } + + bp =3D g_new(struct MshvSwBreakpoint, 1); + bp->pc =3D addr; + bp->use_count =3D 1; + err =3D mshv_arch_insert_sw_breakpoint(cpu, bp); + if (err) { + g_free(bp); + return err; + } + + QTAILQ_INSERT_HEAD(&mshv_state->sw_breakpoints, bp, entry); + + /* Installs exception intercept only on the first call */ + return mshv_init_exception_intercept(); +} + +static int mshv_remove_gdbstub_breakpoint(CPUState *cpu, GdbBreakpointType= type, + vaddr addr, vaddr len) +{ + struct MshvSwBreakpoint *bp; + int err; + + if (type !=3D GDB_BREAKPOINT_SW) { + return -ENOSYS; + } + + bp =3D mshv_find_sw_breakpoint(cpu, addr); + if (!bp) { + return -ENOENT; + } + + if (bp->use_count > 1) { + bp->use_count--; + return 0; + } + + err =3D mshv_arch_remove_sw_breakpoint(cpu, bp); + if (err) { + return err; + } + + QTAILQ_REMOVE(&mshv_state->sw_breakpoints, bp, entry); + g_free(bp); + + return 0; +} + +static void mshv_remove_all_gdbstub_breakpoints(CPUState *cpu) +{ + struct MshvSwBreakpoint *bp, *next; + CPUState *tmpcpu; + + QTAILQ_FOREACH_SAFE(bp, &mshv_state->sw_breakpoints, entry, next) { + if (mshv_arch_remove_sw_breakpoint(cpu, bp) !=3D 0) { + /* Fall back to whichever CPU still has it mapped. */ + CPU_FOREACH(tmpcpu) { + if (mshv_arch_remove_sw_breakpoint(tmpcpu, bp) =3D=3D 0) { + break; + } + } + } + QTAILQ_REMOVE(&mshv_state->sw_breakpoints, bp, entry); + g_free(bp); + } +} + static int mshv_cpu_exec(CPUState *cpu) { hv_message mshv_msg; @@ -637,6 +789,9 @@ static int mshv_cpu_exec(CPUState *cpu) switch (exit_reason) { case MshvVmExitIgnore: break; + case MshvVmExitDebug: + ret =3D EXCP_DEBUG; + break; default: ret =3D EXCP_INTERRUPT; break; @@ -708,7 +863,10 @@ static void *mshv_vcpu_thread(void *arg) do { qemu_process_cpu_events(cpu); if (cpu_can_run(cpu)) { - mshv_cpu_exec(cpu); + ret =3D mshv_cpu_exec(cpu); + if (ret =3D=3D EXCP_DEBUG) { + cpu_handle_guest_debug(cpu); + } } } while (!cpu->unplug || cpu_can_run(cpu)); =20 @@ -852,6 +1010,11 @@ static void mshv_accel_ops_class_init(ObjectClass *oc= , const void *data) ops->synchronize_pre_loadvm =3D mshv_cpu_synchronize_pre_loadvm; ops->cpus_are_resettable =3D mshv_cpus_are_resettable; ops->handle_interrupt =3D generic_handle_interrupt; + + ops->update_guest_debug =3D mshv_update_guest_debug; + ops->insert_gdbstub_breakpoint =3D mshv_insert_gdbstub_breakpoint; + ops->remove_gdbstub_breakpoint =3D mshv_remove_gdbstub_breakpoint; + ops->remove_all_gdbstub_breakpoints =3D mshv_remove_all_gdbstub_breakp= oints; } =20 static const TypeInfo mshv_accel_ops_type =3D { diff --git a/include/system/mshv_int.h b/include/system/mshv_int.h index b91c4d661a..9244915eb6 100644 --- a/include/system/mshv_int.h +++ b/include/system/mshv_int.h @@ -15,6 +15,7 @@ #define QEMU_MSHV_INT_H =20 #include "hw/hyperv/hvhdk.h" +#include "exec/vaddr.h" =20 #define MSHV_MSR_ENTRIES_COUNT 64 =20 @@ -56,6 +57,13 @@ typedef struct MshvAddressSpace { AddressSpace *as; } MshvAddressSpace; =20 +struct MshvSwBreakpoint { + vaddr pc; + vaddr saved_insn; + int use_count; + QTAILQ_ENTRY(MshvSwBreakpoint) entry; +}; + struct MshvState { AccelState parent_obj; int vm; @@ -70,6 +78,8 @@ struct MshvState { unsigned long *used_gsi_bitmap; unsigned int gsi_count; union hv_partition_processor_features processor_features; + QTAILQ_HEAD(, MshvSwBreakpoint) sw_breakpoints; + bool exception_intercepts_installed; }; =20 typedef struct MshvMsiControl { @@ -85,6 +95,7 @@ typedef enum MshvVmExit { MshvVmExitIgnore =3D 0, MshvVmExitShutdown =3D 1, MshvVmExitSpecial =3D 2, + MshvVmExitDebug =3D 3, } MshvVmExit; =20 void mshv_init_mmio_emu(void); @@ -98,6 +109,9 @@ int mshv_get_generic_regs(CPUState *cpu, hv_register_ass= oc *assocs, size_t n_regs); int mshv_arch_store_vcpu_state(const CPUState *cpu); int mshv_arch_load_vcpu_state(CPUState *cpu); +int mshv_arch_insert_sw_breakpoint(CPUState *cpu, struct MshvSwBreakpoint = *bp); +int mshv_arch_remove_sw_breakpoint(CPUState *cpu, struct MshvSwBreakpoint = *bp); +struct MshvSwBreakpoint *mshv_find_sw_breakpoint(CPUState *cpu, vaddr pc); void mshv_arch_init_vcpu(CPUState *cpu); void mshv_arch_destroy_vcpu(CPUState *cpu); void mshv_arch_amend_proc_features( diff --git a/target/i386/mshv/mshv-cpu.c b/target/i386/mshv/mshv-cpu.c index 1c433c408c..2333d3304a 100644 --- a/target/i386/mshv/mshv-cpu.c +++ b/target/i386/mshv/mshv-cpu.c @@ -12,6 +12,7 @@ =20 #include "qemu/osdep.h" #include "qemu/error-report.h" +#include "qemu/main-loop.h" #include "qemu/memalign.h" =20 #include "system/mshv.h" @@ -28,6 +29,7 @@ #include "emulate/x86_decode.h" #include "emulate/x86_emu.h" #include "emulate/x86_flags.h" +#include "gdbstub/enums.h" =20 #include "accel/accel-cpu-target.h" =20 @@ -1932,6 +1934,46 @@ static int handle_pio(CPUState *cpu, const struct hy= perv_message *msg) return handle_pio_non_str(cpu, &info); } =20 +/* Re-inject a guest-owned #DB via a pending event */ +static int reinject_exception(CPUState *cpu, + struct hv_x64_exception_intercept_message *info) +{ + hv_register_assoc assoc =3D { .name =3D HV_REGISTER_PENDING_EVENT0 }; + + assoc.value.pending_exception_event.event_pending =3D 1; + assoc.value.pending_exception_event.event_type =3D + HV_X64_PENDING_EVENT_EXCEPTION; + assoc.value.pending_exception_event.exception_parameter =3D + info->exception_parameter; + assoc.value.pending_exception_event.vector =3D info->exception_vector; + + return mshv_set_generic_regs(cpu, &assoc, 1); +} + +/* Check if the intercepted #DB has been set by gdb. */ +static int handle_debug(CPUState *cpu, hv_message *msg) +{ + struct hv_x64_exception_intercept_message *info =3D (void *)msg->paylo= ad; + + /* Only #DB is intercepted */ + if (info->exception_vector !=3D EXCP01_DB) { + return 0; + } + + /* INT1 hit: RIP points at the breakpoint */ + if (mshv_find_sw_breakpoint(cpu, info->header.rip) !=3D NULL) { + return EXCP_DEBUG; + } + + /* Check if single stepping */ + if (cpu_single_stepping(cpu)) { + return EXCP_DEBUG; + } + + /* The guest's own #DB; caller re-injects it. */ + return 0; +} + int mshv_run_vcpu(int vm_fd, CPUState *cpu, hv_message *msg, MshvVmExit *e= xit) { int ret; @@ -1960,6 +2002,24 @@ int mshv_run_vcpu(int vm_fd, CPUState *cpu, hv_messa= ge *msg, MshvVmExit *exit) return MshvVmExitSpecial; } return MshvVmExitIgnore; + case HVMSG_X64_EXCEPTION_INTERCEPT: + bql_lock(); + if (handle_debug(cpu, msg) =3D=3D EXCP_DEBUG) { + *exit =3D MshvVmExitDebug; + bql_unlock(); + return 0; + } + /* Not ours - hand the #DB back to the guest and keep running. */ + ret =3D reinject_exception(cpu, + (struct hv_x64_exception_intercept_message *)msg->payload); + bql_unlock(); + if (ret < 0) { + error_report("failed to reinject exception on vcpu %d", + cpu->cpu_index); + return -1; + } + *exit =3D MshvVmExitIgnore; + return 0; default: break; } @@ -1973,6 +2033,32 @@ void mshv_remove_vcpu(int vm_fd, int cpu_fd) close(cpu_fd); } =20 +int mshv_arch_insert_sw_breakpoint(CPUState *cpu, struct MshvSwBreakpoint = *bp) +{ + static const uint8_t int1 =3D 0xf1; + + if (cpu_memory_rw_debug(cpu, bp->pc, (uint8_t *)&bp->saved_insn, 1, 0)= || + cpu_memory_rw_debug(cpu, bp->pc, (uint8_t *)&int1, 1, 1)) { + return -EINVAL; + } + return 0; +} + +int mshv_arch_remove_sw_breakpoint(CPUState *cpu, struct MshvSwBreakpoint = *bp) +{ + uint8_t int1; + + if (cpu_memory_rw_debug(cpu, bp->pc, &int1, 1, 0)) { + return -EINVAL; + } + if (int1 !=3D 0xf1) { + return 0; + } + if (cpu_memory_rw_debug(cpu, bp->pc, (uint8_t *)&bp->saved_insn, 1, 1)= ) { + return -EINVAL; + } + return 0; +} =20 int mshv_create_vcpu(int vm_fd, uint8_t vp_index, int *cpu_fd) { --=20 2.53.0 From nobody Mon Sep 28 02:05:28 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=linux.microsoft.com ARC-Seal: i=1; a=rsa-sha256; t=1785162547; cv=none; d=zohomail.com; s=zohoarc; b=njM/oqZ34eeEVqnDlSiMaglHjQ8/2kY/mK+NeyG0in/tHGsNMbSzu81yU7zMBhxvJG+NrFYmDlAVpbr9RabsEFgxDAtCvsR9n09d+KdpCUkHpl+S6a3ifs4YfaMDb0QupVNKg3KL2vlbMV6yLSFw3wmuqj0DF85+orJu8Yy36nM= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1785162547; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=fNVjvriP0t/laKOyNrPh+sOOPL1e470U26tG5t2dlJ4=; b=Xel1aarT3hpKRw7s5MLwYDOmy29dQKbPW51PRR+dQN+NigOPvN5SlDE/WdFEp5+kBT6rl2gegi8Jau8UYy5uiKdA4g8UamlT0+UVz/LHGzm/XZ3p5aObOWsakj5K9LYwdVlvLmv8Nycjnsw6+hUBjXtKOS4ySzevWLc3MWogAtI= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1785162547340767.6027561028562; Mon, 27 Jul 2026 07:29:07 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1woMJd-0006JU-LK; Mon, 27 Jul 2026 10:28:25 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1woMJc-0006Iy-7K for qemu-devel@nongnu.org; Mon, 27 Jul 2026 10:28:24 -0400 Received: from linux.microsoft.com ([13.77.154.182]) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1woMJa-0001wP-Kv for qemu-devel@nongnu.org; Mon, 27 Jul 2026 10:28:23 -0400 Received: from laptop.localdomain (unknown [86.121.140.206]) by linux.microsoft.com (Postfix) with ESMTPSA id 1ABB520B7166; Mon, 27 Jul 2026 07:28:03 -0700 (PDT) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com 1ABB520B7166 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.microsoft.com; s=default; t=1785162485; bh=fNVjvriP0t/laKOyNrPh+sOOPL1e470U26tG5t2dlJ4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=oFAhXIONtzDOk3yzDqNcoeYJ/mFOh86Pp2LlrePX7+IH7+6DCVRlN7OGHO2xHd8X1 j0GT4ouMbHaHMYKX/aXyiU9NEiThTiv+3soGl/rF744Xtdd4cHHit9CfjpM6RahUJO k36F5/OS40fmnSy/GyMARjphD5KV1R8NcM2AlxQY= From: =?UTF-8?q?Doru=20Bl=C3=A2nzeanu?= To: qemu-devel@nongnu.org Cc: Magnus Kulke , =?UTF-8?q?Doru=20Bl=C3=A2nzeanu?= , =?UTF-8?q?Doru=20Bl=C3=A2nzeanu?= , Wei Liu , Wei Liu , Magnus Kulke Subject: [PATCH 3/3] target/i386/mshv: support single-stepping Date: Mon, 27 Jul 2026 17:28:07 +0300 Message-ID: <20260727142807.84269-4-dblanzeanu@linux.microsoft.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260727142807.84269-1-dblanzeanu@linux.microsoft.com> References: <20260727142807.84269-1-dblanzeanu@linux.microsoft.com> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=13.77.154.182; envelope-from=dblanzeanu@linux.microsoft.com; helo=linux.microsoft.com X-Spam_score_int: -19 X-Spam_score: -2.0 X-Spam_bar: -- X-Spam_report: (-2.0 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @linux.microsoft.com) X-ZM-MESSAGEID: 1785162548135158500 Single-step by toggling RFLAGS.TF around the vCPU run, as WHPX does. TF is set only on the live register, never in env->eflags, so it is not read back and re-applied by a later register store. The resulting #DB is reported to gdb by mshv_handle_debug() whenever the vC= PU is single-stepping. Signed-off-by: Doru Bl=C3=A2nzeanu Reviewed-by: Magnus Kulke --- target/i386/mshv/mshv-cpu.c | 50 +++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/target/i386/mshv/mshv-cpu.c b/target/i386/mshv/mshv-cpu.c index 2333d3304a..036b142113 100644 --- a/target/i386/mshv/mshv-cpu.c +++ b/target/i386/mshv/mshv-cpu.c @@ -1974,17 +1974,67 @@ static int handle_debug(CPUState *cpu, hv_message *= msg) return 0; } =20 +/* + * Flip RFLAGS.TF like WHPX. Set it only on the live register, not env->ef= lags, + * so a later store won't put it back. + */ +static int arch_set_single_step(CPUState *cpu, bool enable) +{ + X86CPU *x86cpu =3D X86_CPU(cpu); + CPUX86State *env =3D &x86cpu->env; + hv_register_assoc assoc =3D { .name =3D HV_X64_REGISTER_RFLAGS }; + uint64_t rflags; + int ret; + + if (env->regs_page && env->regs_page->isvalid !=3D 0) { + rflags =3D env->regs_page->rflags; + rflags =3D enable ? (rflags | TF_MASK) : (rflags & ~TF_MASK); + env->regs_page->rflags =3D rflags; + env->regs_page->dirty |=3D (1u << HV_X64_REGISTER_CLASS_FLAGS); + return 0; + } + + ret =3D mshv_get_generic_regs(cpu, &assoc, 1); + if (ret < 0) { + return ret; + } + rflags =3D assoc.value.reg64; + rflags =3D enable ? (rflags | TF_MASK) : (rflags & ~TF_MASK); + assoc.value.reg64 =3D rflags; + return mshv_set_generic_regs(cpu, &assoc, 1); +} + int mshv_run_vcpu(int vm_fd, CPUState *cpu, hv_message *msg, MshvVmExit *e= xit) { int ret; enum MshvVmExit exit_reason; int cpu_fd =3D mshv_vcpufd(cpu); + bool single_step; + + /* enable single stepping by flipping RFLAGS.TF */ + single_step =3D cpu_single_stepping(cpu); + if (single_step) { + ret =3D arch_set_single_step(cpu, true); + if (ret < 0) { + error_report("Failed to arm single-step (TF) on vcpu %d: %s", + cpu->cpu_index, strerror(-ret)); + *exit =3D MshvVmExitShutdown; + return -1; + } + } =20 ret =3D ioctl(cpu_fd, MSHV_RUN_VP, msg); if (ret < 0) { return MshvVmExitShutdown; } =20 + /* disable single stepping by flipping RFLAGS.TF */ + if (single_step && arch_set_single_step(cpu, false) < 0) { + error_report("Failed to clear single-step (TF) on vcpu %d", + cpu->cpu_index); + return -1; + } + switch (msg->header.message_type) { case HVMSG_UNRECOVERABLE_EXCEPTION: return MshvVmExitShutdown; --=20 2.53.0