Skip to content

Commit cfc394c

Browse files
authored
gdb: refactor vCPU debug settings to avoid code duplication (#456)
* gdb: move arch specific values to a different file - this helps contain the x86 specific registers, masks isolated from the higher level logic Signed-off-by: Doru Blânzeanu <[email protected]> * gdb: move vcpu arch specific stop reason to different file - update kvm_debug to use this hypervisor independent function Signed-off-by: Doru Blânzeanu <[email protected]> * gdb: update mshv hypervisor to use the arch specific logic to determine stop reason Signed-off-by: Doru Blânzeanu <[email protected]> --------- Signed-off-by: Doru Blânzeanu <[email protected]>
1 parent 7373501 commit cfc394c

File tree

6 files changed

+177
-107
lines changed

6 files changed

+177
-107
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
Copyright 2024 The Hyperlight Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
//! This file contains architecture specific code for the x86_64
18+
19+
use std::collections::HashMap;
20+
21+
use super::VcpuStopReason;
22+
23+
// Described in Table 6-1. Exceptions and Interrupts at Page 6-13 Vol. 1
24+
// of Intel 64 and IA-32 Architectures Software Developer's Manual
25+
/// Exception id for #DB
26+
const DB_EX_ID: u32 = 1;
27+
/// Exception id for #BP - triggered by the INT3 instruction
28+
const BP_EX_ID: u32 = 3;
29+
30+
/// Software Breakpoint size in memory
31+
pub(crate) const SW_BP_SIZE: usize = 1;
32+
/// Software Breakpoint opcode - INT3
33+
/// Check page 7-28 Vol. 3A of Intel 64 and IA-32
34+
/// Architectures Software Developer's Manual
35+
pub(crate) const SW_BP_OP: u8 = 0xCC;
36+
/// Software Breakpoint written to memory
37+
pub(crate) const SW_BP: [u8; SW_BP_SIZE] = [SW_BP_OP];
38+
/// Maximum number of supported hardware breakpoints
39+
pub(crate) const MAX_NO_OF_HW_BP: usize = 4;
40+
41+
/// Check page 19-4 Vol. 3B of Intel 64 and IA-32
42+
/// Architectures Software Developer's Manual
43+
/// Bit position of BS flag in DR6 debug register
44+
pub(crate) const DR6_BS_FLAG_POS: usize = 14;
45+
/// Bit mask of BS flag in DR6 debug register
46+
pub(crate) const DR6_BS_FLAG_MASK: u64 = 1 << DR6_BS_FLAG_POS;
47+
/// Bit position of HW breakpoints status in DR6 debug register
48+
pub(crate) const DR6_HW_BP_FLAGS_POS: usize = 0;
49+
/// Bit mask of HW breakpoints status in DR6 debug register
50+
pub(crate) const DR6_HW_BP_FLAGS_MASK: u64 = 0x0F << DR6_HW_BP_FLAGS_POS;
51+
52+
/// Determine the reason the vCPU stopped
53+
/// This is done by checking the DR6 register and the exception id
54+
/// NOTE: Additional checks are done for the entrypoint, stored hw_breakpoints
55+
/// and sw_breakpoints to ensure the stop reason is valid with internal state
56+
pub(crate) fn vcpu_stop_reason(
57+
single_step: bool,
58+
rip: u64,
59+
dr6: u64,
60+
entrypoint: u64,
61+
exception: u32,
62+
hw_breakpoints: &[u64],
63+
sw_breakpoints: &HashMap<u64, [u8; SW_BP_SIZE]>,
64+
) -> VcpuStopReason {
65+
if DB_EX_ID == exception {
66+
// If the BS flag in DR6 register is set, it means a single step
67+
// instruction triggered the exit
68+
// Check page 19-4 Vol. 3B of Intel 64 and IA-32
69+
// Architectures Software Developer's Manual
70+
if dr6 & DR6_BS_FLAG_MASK != 0 && single_step {
71+
return VcpuStopReason::DoneStep;
72+
}
73+
74+
// If any of the B0-B3 flags in DR6 register is set, it means a
75+
// hardware breakpoint triggered the exit
76+
// Check page 19-4 Vol. 3B of Intel 64 and IA-32
77+
// Architectures Software Developer's Manual
78+
if DR6_HW_BP_FLAGS_MASK & dr6 != 0 && hw_breakpoints.contains(&rip) {
79+
if rip == entrypoint {
80+
return VcpuStopReason::EntryPointBp;
81+
}
82+
return VcpuStopReason::HwBp;
83+
}
84+
}
85+
86+
if BP_EX_ID == exception && sw_breakpoints.contains_key(&rip) {
87+
return VcpuStopReason::SwBp;
88+
}
89+
90+
// Log an error and provide internal debugging info
91+
log::error!(
92+
r"The vCPU exited because of an unknown reason:
93+
single_step: {:?}
94+
rip: {:?}
95+
dr6: {:?}
96+
entrypoint: {:?}
97+
exception: {:?}
98+
hw_breakpoints: {:?}
99+
sw_breakpoints: {:?}
100+
",
101+
single_step,
102+
rip,
103+
dr6,
104+
entrypoint,
105+
exception,
106+
hw_breakpoints,
107+
sw_breakpoints,
108+
);
109+
110+
VcpuStopReason::Unknown
111+
}

src/hyperlight_host/src/hypervisor/gdb/event_loop.rs

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ impl run_blocking::BlockingEventLoop for GdbBlockingEventLoop {
4949
// Resume execution if unknown reason for stop
5050
let stop_response = match stop_reason {
5151
VcpuStopReason::DoneStep => BaseStopReason::DoneStep,
52+
VcpuStopReason::EntryPointBp => BaseStopReason::HwBreak(()),
5253
VcpuStopReason::SwBp => BaseStopReason::SwBreak(()),
5354
VcpuStopReason::HwBp => BaseStopReason::HwBreak(()),
5455
// This is a consequence of the GDB client sending an interrupt signal

src/hyperlight_host/src/hypervisor/gdb/kvm_debug.rs

+17-44
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,10 @@ use kvm_bindings::{
2222
};
2323
use kvm_ioctls::VcpuFd;
2424

25-
use super::{
26-
GuestDebug, VcpuStopReason, X86_64Regs, DR6_BS_FLAG_MASK, DR6_HW_BP_FLAGS_MASK,
27-
MAX_NO_OF_HW_BP, SW_BP_SIZE,
28-
};
25+
use super::arch::{vcpu_stop_reason, MAX_NO_OF_HW_BP, SW_BP_SIZE};
26+
use super::{GuestDebug, VcpuStopReason, X86_64Regs};
2927
use crate::{new_error, HyperlightError, Result};
3028

31-
/// Exception id for SW breakpoint
32-
const SW_BP_ID: u32 = 3;
33-
3429
/// KVM Debug struct
3530
/// This struct is used to abstract the internal details of the kvm
3631
/// guest debugging settings
@@ -118,51 +113,29 @@ impl KvmDebug {
118113
debug_exit: kvm_debug_exit_arch,
119114
entrypoint: u64,
120115
) -> Result<VcpuStopReason> {
121-
// If the BS flag in DR6 register is set, it means a single step
122-
// instruction triggered the exit
123-
// Check page 19-4 Vol. 3B of Intel 64 and IA-32
124-
// Architectures Software Developer's Manual
125-
if debug_exit.dr6 & DR6_BS_FLAG_MASK != 0 && self.single_step {
126-
return Ok(VcpuStopReason::DoneStep);
127-
}
116+
let rip = self.get_instruction_pointer(vcpu_fd)?;
117+
let rip = self.translate_gva(vcpu_fd, rip)?;
128118

129-
let ip = self.get_instruction_pointer(vcpu_fd)?;
130-
let gpa = self.translate_gva(vcpu_fd, ip)?;
119+
// Check if the vCPU stopped because of a hardware breakpoint
120+
let reason = vcpu_stop_reason(
121+
self.single_step,
122+
rip,
123+
debug_exit.dr6,
124+
entrypoint,
125+
debug_exit.exception,
126+
&self.hw_breakpoints,
127+
&self.sw_breakpoints,
128+
);
131129

132-
// If any of the B0-B3 flags in DR6 register is set, it means a
133-
// hardware breakpoint triggered the exit
134-
// Check page 19-4 Vol. 3B of Intel 64 and IA-32
135-
// Architectures Software Developer's Manual
136-
if DR6_HW_BP_FLAGS_MASK & debug_exit.dr6 != 0 && self.hw_breakpoints.contains(&gpa) {
130+
if let VcpuStopReason::EntryPointBp = reason {
137131
// In case the hw breakpoint is the entry point, remove it to
138132
// avoid hanging here as gdb does not remove breakpoints it
139133
// has not set.
140134
// Gdb expects the target to be stopped when connected.
141-
if gpa == entrypoint {
142-
self.remove_hw_breakpoint(vcpu_fd, entrypoint)?;
143-
}
144-
return Ok(VcpuStopReason::HwBp);
135+
self.remove_hw_breakpoint(vcpu_fd, entrypoint)?;
145136
}
146137

147-
// If the exception ID matches #BP (3) - it means a software breakpoint
148-
// caused the exit
149-
if SW_BP_ID == debug_exit.exception && self.sw_breakpoints.contains_key(&gpa) {
150-
return Ok(VcpuStopReason::SwBp);
151-
}
152-
153-
// Log an error and provide internal debugging info for fixing
154-
log::error!(
155-
r"The vCPU exited because of an unknown reason:
156-
kvm_debug_exit_arch: {:?}
157-
single_step: {:?}
158-
hw_breakpoints: {:?}
159-
sw_breakpoints: {:?}",
160-
debug_exit,
161-
self.single_step,
162-
self.hw_breakpoints,
163-
self.sw_breakpoints,
164-
);
165-
Ok(VcpuStopReason::Unknown)
138+
Ok(reason)
166139
}
167140
}
168141

src/hyperlight_host/src/hypervisor/gdb/mod.rs

+6-22
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17+
mod arch;
1718
mod event_loop;
1819
#[cfg(kvm)]
1920
mod kvm_debug;
@@ -26,6 +27,7 @@ use std::net::TcpListener;
2627
use std::sync::{Arc, Mutex};
2728
use std::thread;
2829

30+
use arch::{SW_BP, SW_BP_SIZE};
2931
use crossbeam_channel::{Receiver, Sender, TryRecvError};
3032
use event_loop::event_loop_thread;
3133
use gdbstub::conn::ConnectionExt;
@@ -43,28 +45,6 @@ use crate::hypervisor::handlers::DbgMemAccessHandlerCaller;
4345
use crate::mem::layout::SandboxMemoryLayout;
4446
use crate::{new_error, HyperlightError};
4547

46-
/// Software Breakpoint size in memory
47-
const SW_BP_SIZE: usize = 1;
48-
/// Software Breakpoint opcode - INT3
49-
/// Check page 7-28 Vol. 3A of Intel 64 and IA-32
50-
/// Architectures Software Developer's Manual
51-
const SW_BP_OP: u8 = 0xCC;
52-
/// Software Breakpoint written to memory
53-
const SW_BP: [u8; SW_BP_SIZE] = [SW_BP_OP];
54-
/// Maximum number of supported hardware breakpoints
55-
const MAX_NO_OF_HW_BP: usize = 4;
56-
57-
/// Check page 19-4 Vol. 3B of Intel 64 and IA-32
58-
/// Architectures Software Developer's Manual
59-
/// Bit position of BS flag in DR6 debug register
60-
const DR6_BS_FLAG_POS: usize = 14;
61-
/// Bit mask of BS flag in DR6 debug register
62-
const DR6_BS_FLAG_MASK: u64 = 1 << DR6_BS_FLAG_POS;
63-
/// Bit position of HW breakpoints status in DR6 debug register
64-
const DR6_HW_BP_FLAGS_POS: usize = 0;
65-
/// Bit mask of HW breakpoints status in DR6 debug register
66-
const DR6_HW_BP_FLAGS_MASK: u64 = 0x0F << DR6_HW_BP_FLAGS_POS;
67-
6848
#[derive(Debug, Error)]
6949
pub(crate) enum GdbTargetError {
7050
#[error("Error encountered while binding to address and port")]
@@ -129,6 +109,10 @@ pub(crate) struct X86_64Regs {
129109
#[derive(Debug)]
130110
pub enum VcpuStopReason {
131111
DoneStep,
112+
/// Hardware breakpoint inserted by the hypervisor so the guest can be stopped
113+
/// at the entry point. This is used to avoid the guest from executing
114+
/// the entry point code before the debugger is connected
115+
EntryPointBp,
132116
HwBp,
133117
SwBp,
134118
Interrupt,

src/hyperlight_host/src/hypervisor/gdb/mshv_debug.rs

+17-33
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,8 @@ use mshv_bindings::{
3232
};
3333
use mshv_ioctls::VcpuFd;
3434

35-
use super::{
36-
GuestDebug, VcpuStopReason, X86_64Regs, DR6_BS_FLAG_MASK, DR6_HW_BP_FLAGS_MASK,
37-
MAX_NO_OF_HW_BP, SW_BP_SIZE,
38-
};
35+
use super::arch::{vcpu_stop_reason, MAX_NO_OF_HW_BP, SW_BP_SIZE};
36+
use super::{GuestDebug, VcpuStopReason, X86_64Regs};
3937
use crate::{new_error, HyperlightError, Result};
4038

4139
#[derive(Debug, Default)]
@@ -133,52 +131,38 @@ impl MshvDebug {
133131
pub(crate) fn get_stop_reason(
134132
&mut self,
135133
vcpu_fd: &VcpuFd,
134+
exception: u16,
136135
entrypoint: u64,
137136
) -> Result<VcpuStopReason> {
138-
// MSHV does not provide info on the vCPU exits but the debug
139-
// information can be retrieved from the DEBUG REGISTERS
140137
let regs = vcpu_fd
141138
.get_debug_regs()
142139
.map_err(|e| new_error!("Cannot retrieve debug registers from vCPU: {}", e))?;
143140

144141
// DR6 register contains debug state related information
145142
let debug_status = regs.dr6;
146143

147-
// If the BS flag in DR6 register is set, it means a single step
148-
// instruction triggered the exit
149-
// Check page 19-4 Vol. 3B of Intel 64 and IA-32
150-
// Architectures Software Developer's Manual
151-
if debug_status & DR6_BS_FLAG_MASK != 0 && self.single_step {
152-
return Ok(VcpuStopReason::DoneStep);
153-
}
144+
let rip = self.get_instruction_pointer(vcpu_fd)?;
145+
let rip = self.translate_gva(vcpu_fd, rip)?;
154146

155-
let ip = self.get_instruction_pointer(vcpu_fd)?;
156-
let gpa = self.translate_gva(vcpu_fd, ip)?;
147+
let reason = vcpu_stop_reason(
148+
self.single_step,
149+
rip,
150+
debug_status,
151+
entrypoint,
152+
exception as u32,
153+
&self.hw_breakpoints,
154+
&self.sw_breakpoints,
155+
);
157156

158-
// If any of the B0-B3 flags in DR6 register is set, it means a
159-
// hardware breakpoint triggered the exit
160-
// Check page 19-4 Vol. 3B of Intel 64 and IA-32
161-
// Architectures Software Developer's Manual
162-
if debug_status & DR6_HW_BP_FLAGS_MASK != 0 && self.hw_breakpoints.contains(&gpa) {
157+
if let VcpuStopReason::EntryPointBp = reason {
163158
// In case the hw breakpoint is the entry point, remove it to
164159
// avoid hanging here as gdb does not remove breakpoints it
165160
// has not set.
166161
// Gdb expects the target to be stopped when connected.
167-
if gpa == entrypoint {
168-
self.remove_hw_breakpoint(vcpu_fd, entrypoint)?;
169-
}
170-
return Ok(VcpuStopReason::HwBp);
171-
}
172-
173-
// mshv does not provide a way to specify which exception triggered the
174-
// vCPU exit as the mshv intercepts both #DB and #BP
175-
// We check against the SW breakpoints Hashmap to detect whether the
176-
// vCPU exited due to a SW breakpoint
177-
if self.sw_breakpoints.contains_key(&gpa) {
178-
return Ok(VcpuStopReason::SwBp);
162+
self.remove_hw_breakpoint(vcpu_fd, entrypoint)?;
179163
}
180164

181-
Ok(VcpuStopReason::Unknown)
165+
Ok(reason)
182166
}
183167
}
184168

0 commit comments

Comments
 (0)