hyperlight_host/hypervisor/
hyperv_linux.rs

1/*
2Copyright 2024 The Hyperlight Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17#[cfg(mshv2)]
18extern crate mshv_bindings2 as mshv_bindings;
19#[cfg(mshv2)]
20extern crate mshv_ioctls2 as mshv_ioctls;
21
22#[cfg(mshv3)]
23extern crate mshv_bindings3 as mshv_bindings;
24#[cfg(mshv3)]
25extern crate mshv_ioctls3 as mshv_ioctls;
26
27use std::fmt::{Debug, Formatter};
28
29use log::{error, LevelFilter};
30#[cfg(mshv2)]
31use mshv_bindings::hv_message;
32#[cfg(gdb)]
33use mshv_bindings::{
34    hv_intercept_parameters, hv_intercept_type_HV_INTERCEPT_TYPE_EXCEPTION,
35    hv_message_type_HVMSG_X64_EXCEPTION_INTERCEPT, mshv_install_intercept,
36    HV_INTERCEPT_ACCESS_MASK_EXECUTE,
37};
38use mshv_bindings::{
39    hv_message_type, hv_message_type_HVMSG_GPA_INTERCEPT, hv_message_type_HVMSG_UNMAPPED_GPA,
40    hv_message_type_HVMSG_X64_HALT, hv_message_type_HVMSG_X64_IO_PORT_INTERCEPT, hv_register_assoc,
41    hv_register_name_HV_X64_REGISTER_RIP, hv_register_value, mshv_user_mem_region,
42    FloatingPointUnit, SegmentRegister, SpecialRegisters, StandardRegisters,
43};
44#[cfg(mshv3)]
45use mshv_bindings::{
46    hv_partition_property_code_HV_PARTITION_PROPERTY_SYNTHETIC_PROC_FEATURES,
47    hv_partition_synthetic_processor_features,
48};
49use mshv_ioctls::{Mshv, VcpuFd, VmFd};
50use tracing::{instrument, Span};
51
52use super::fpu::{FP_CONTROL_WORD_DEFAULT, FP_TAG_WORD_DEFAULT, MXCSR_DEFAULT};
53#[cfg(gdb)]
54use super::gdb::{DebugCommChannel, DebugMsg, DebugResponse, GuestDebug, MshvDebug};
55#[cfg(gdb)]
56use super::handlers::DbgMemAccessHandlerWrapper;
57use super::handlers::{MemAccessHandlerWrapper, OutBHandlerWrapper};
58use super::{
59    Hypervisor, VirtualCPU, CR0_AM, CR0_ET, CR0_MP, CR0_NE, CR0_PE, CR0_PG, CR0_WP, CR4_OSFXSR,
60    CR4_OSXMMEXCPT, CR4_PAE, EFER_LMA, EFER_LME, EFER_NX, EFER_SCE,
61};
62use crate::hypervisor::hypervisor_handler::HypervisorHandler;
63use crate::hypervisor::HyperlightExit;
64use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags};
65use crate::mem::ptr::{GuestPtr, RawPtr};
66#[cfg(gdb)]
67use crate::HyperlightError;
68use crate::{log_then_return, new_error, Result};
69
70#[cfg(gdb)]
71mod debug {
72    use std::sync::{Arc, Mutex};
73
74    use super::mshv_bindings::hv_x64_exception_intercept_message;
75    use super::{HypervLinuxDriver, *};
76    use crate::hypervisor::gdb::{DebugMsg, DebugResponse, VcpuStopReason, X86_64Regs};
77    use crate::hypervisor::handlers::DbgMemAccessHandlerCaller;
78    use crate::{new_error, Result};
79
80    impl HypervLinuxDriver {
81        /// Resets the debug information to disable debugging
82        fn disable_debug(&mut self) -> Result<()> {
83            let mut debug = MshvDebug::default();
84
85            debug.set_single_step(&self.vcpu_fd, false)?;
86
87            self.debug = Some(debug);
88
89            Ok(())
90        }
91
92        /// Get the reason the vCPU has stopped
93        pub(crate) fn get_stop_reason(
94            &mut self,
95            ex_info: hv_x64_exception_intercept_message,
96        ) -> Result<VcpuStopReason> {
97            let debug = self
98                .debug
99                .as_mut()
100                .ok_or_else(|| new_error!("Debug is not enabled"))?;
101
102            debug.get_stop_reason(&self.vcpu_fd, ex_info.exception_vector, self.entrypoint)
103        }
104
105        pub(crate) fn process_dbg_request(
106            &mut self,
107            req: DebugMsg,
108            dbg_mem_access_fn: Arc<Mutex<dyn DbgMemAccessHandlerCaller>>,
109        ) -> Result<DebugResponse> {
110            if let Some(debug) = self.debug.as_mut() {
111                match req {
112                    DebugMsg::AddHwBreakpoint(addr) => Ok(DebugResponse::AddHwBreakpoint(
113                        debug
114                            .add_hw_breakpoint(&self.vcpu_fd, addr)
115                            .map_err(|e| {
116                                log::error!("Failed to add hw breakpoint: {:?}", e);
117
118                                e
119                            })
120                            .is_ok(),
121                    )),
122                    DebugMsg::AddSwBreakpoint(addr) => Ok(DebugResponse::AddSwBreakpoint(
123                        debug
124                            .add_sw_breakpoint(&self.vcpu_fd, addr, dbg_mem_access_fn)
125                            .map_err(|e| {
126                                log::error!("Failed to add sw breakpoint: {:?}", e);
127
128                                e
129                            })
130                            .is_ok(),
131                    )),
132                    DebugMsg::Continue => {
133                        debug.set_single_step(&self.vcpu_fd, false).map_err(|e| {
134                            log::error!("Failed to continue execution: {:?}", e);
135
136                            e
137                        })?;
138
139                        Ok(DebugResponse::Continue)
140                    }
141                    DebugMsg::DisableDebug => {
142                        self.disable_debug().map_err(|e| {
143                            log::error!("Failed to disable debugging: {:?}", e);
144
145                            e
146                        })?;
147
148                        Ok(DebugResponse::DisableDebug)
149                    }
150                    DebugMsg::GetCodeSectionOffset => {
151                        let offset = dbg_mem_access_fn
152                            .try_lock()
153                            .map_err(|e| {
154                                new_error!("Error locking at {}:{}: {}", file!(), line!(), e)
155                            })?
156                            .get_code_offset()
157                            .map_err(|e| {
158                                log::error!("Failed to get code offset: {:?}", e);
159
160                                e
161                            })?;
162
163                        Ok(DebugResponse::GetCodeSectionOffset(offset as u64))
164                    }
165                    DebugMsg::ReadAddr(addr, len) => {
166                        let mut data = vec![0u8; len];
167
168                        debug
169                            .read_addrs(&self.vcpu_fd, addr, &mut data, dbg_mem_access_fn)
170                            .map_err(|e| {
171                                log::error!("Failed to read from address: {:?}", e);
172
173                                e
174                            })?;
175
176                        Ok(DebugResponse::ReadAddr(data))
177                    }
178                    DebugMsg::ReadRegisters => {
179                        let mut regs = X86_64Regs::default();
180
181                        debug
182                            .read_regs(&self.vcpu_fd, &mut regs)
183                            .map_err(|e| {
184                                log::error!("Failed to read registers: {:?}", e);
185
186                                e
187                            })
188                            .map(|_| DebugResponse::ReadRegisters(regs))
189                    }
190                    DebugMsg::RemoveHwBreakpoint(addr) => Ok(DebugResponse::RemoveHwBreakpoint(
191                        debug
192                            .remove_hw_breakpoint(&self.vcpu_fd, addr)
193                            .map_err(|e| {
194                                log::error!("Failed to remove hw breakpoint: {:?}", e);
195
196                                e
197                            })
198                            .is_ok(),
199                    )),
200                    DebugMsg::RemoveSwBreakpoint(addr) => Ok(DebugResponse::RemoveSwBreakpoint(
201                        debug
202                            .remove_sw_breakpoint(&self.vcpu_fd, addr, dbg_mem_access_fn)
203                            .map_err(|e| {
204                                log::error!("Failed to remove sw breakpoint: {:?}", e);
205
206                                e
207                            })
208                            .is_ok(),
209                    )),
210                    DebugMsg::Step => {
211                        debug.set_single_step(&self.vcpu_fd, true).map_err(|e| {
212                            log::error!("Failed to enable step instruction: {:?}", e);
213
214                            e
215                        })?;
216
217                        Ok(DebugResponse::Step)
218                    }
219                    DebugMsg::WriteAddr(addr, data) => {
220                        debug
221                            .write_addrs(&self.vcpu_fd, addr, &data, dbg_mem_access_fn)
222                            .map_err(|e| {
223                                log::error!("Failed to write to address: {:?}", e);
224
225                                e
226                            })?;
227
228                        Ok(DebugResponse::WriteAddr)
229                    }
230                    DebugMsg::WriteRegisters(regs) => debug
231                        .write_regs(&self.vcpu_fd, &regs)
232                        .map_err(|e| {
233                            log::error!("Failed to write registers: {:?}", e);
234
235                            e
236                        })
237                        .map(|_| DebugResponse::WriteRegisters),
238                }
239            } else {
240                Err(new_error!("Debugging is not enabled"))
241            }
242        }
243
244        pub(crate) fn recv_dbg_msg(&mut self) -> Result<DebugMsg> {
245            let gdb_conn = self
246                .gdb_conn
247                .as_mut()
248                .ok_or_else(|| new_error!("Debug is not enabled"))?;
249
250            gdb_conn.recv().map_err(|e| {
251                new_error!(
252                    "Got an error while waiting to receive a
253                    message: {:?}",
254                    e
255                )
256            })
257        }
258
259        pub(crate) fn send_dbg_msg(&mut self, cmd: DebugResponse) -> Result<()> {
260            log::debug!("Sending {:?}", cmd);
261
262            let gdb_conn = self
263                .gdb_conn
264                .as_mut()
265                .ok_or_else(|| new_error!("Debug is not enabled"))?;
266
267            gdb_conn
268                .send(cmd)
269                .map_err(|e| new_error!("Got an error while sending a response message {:?}", e))
270        }
271    }
272}
273
274/// Determine whether the HyperV for Linux hypervisor API is present
275/// and functional.
276#[instrument(skip_all, parent = Span::current(), level = "Trace")]
277pub(crate) fn is_hypervisor_present() -> bool {
278    match Mshv::new() {
279        Ok(_) => true,
280        Err(_) => {
281            log::info!("MSHV is not available on this system");
282            false
283        }
284    }
285}
286
287/// A Hypervisor driver for HyperV-on-Linux. This hypervisor is often
288/// called the Microsoft Hypervisor (MSHV)
289pub(super) struct HypervLinuxDriver {
290    _mshv: Mshv,
291    vm_fd: VmFd,
292    vcpu_fd: VcpuFd,
293    entrypoint: u64,
294    mem_regions: Vec<MemoryRegion>,
295    orig_rsp: GuestPtr,
296
297    #[cfg(gdb)]
298    debug: Option<MshvDebug>,
299    #[cfg(gdb)]
300    gdb_conn: Option<DebugCommChannel<DebugResponse, DebugMsg>>,
301}
302
303impl HypervLinuxDriver {
304    /// Create a new `HypervLinuxDriver`, complete with all registers
305    /// set up to execute a Hyperlight binary inside a HyperV-powered
306    /// sandbox on Linux.
307    ///
308    /// While registers are set up, they will not have been applied to
309    /// the underlying virtual CPU after this function returns. Call the
310    /// `apply_registers` method to do that, or more likely call
311    /// `initialise` to do it for you.
312    #[instrument(skip_all, parent = Span::current(), level = "Trace")]
313    pub(super) fn new(
314        mem_regions: Vec<MemoryRegion>,
315        entrypoint_ptr: GuestPtr,
316        rsp_ptr: GuestPtr,
317        pml4_ptr: GuestPtr,
318        #[cfg(gdb)] gdb_conn: Option<DebugCommChannel<DebugResponse, DebugMsg>>,
319    ) -> Result<Self> {
320        let mshv = Mshv::new()?;
321        let pr = Default::default();
322        #[cfg(mshv2)]
323        let vm_fd = mshv.create_vm_with_config(&pr)?;
324        #[cfg(mshv3)]
325        let vm_fd = {
326            // It's important to avoid create_vm() and explicitly use
327            // create_vm_with_args() with an empty arguments structure
328            // here, because otherwise the partition is set up with a SynIC.
329
330            let vm_fd = mshv.create_vm_with_args(&pr)?;
331            let features: hv_partition_synthetic_processor_features = Default::default();
332            vm_fd.hvcall_set_partition_property(
333                hv_partition_property_code_HV_PARTITION_PROPERTY_SYNTHETIC_PROC_FEATURES,
334                unsafe { features.as_uint64[0] },
335            )?;
336            vm_fd.initialize()?;
337            vm_fd
338        };
339
340        let mut vcpu_fd = vm_fd.create_vcpu(0)?;
341
342        #[cfg(gdb)]
343        let (debug, gdb_conn) = if let Some(gdb_conn) = gdb_conn {
344            let mut debug = MshvDebug::new();
345            debug.add_hw_breakpoint(&vcpu_fd, entrypoint_ptr.absolute()?)?;
346
347            // The bellow intercepts make the vCPU exit with the Exception Intercept exit code
348            // Check Table 6-1. Exceptions and Interrupts at Page 6-13 Vol. 1
349            // of Intel 64 and IA-32 Architectures Software Developer's Manual
350            // Install intercept for #DB (1) exception
351            vm_fd
352                .install_intercept(mshv_install_intercept {
353                    access_type_mask: HV_INTERCEPT_ACCESS_MASK_EXECUTE,
354                    intercept_type: hv_intercept_type_HV_INTERCEPT_TYPE_EXCEPTION,
355                    // Exception handler #DB (1)
356                    intercept_parameter: hv_intercept_parameters {
357                        exception_vector: 0x1,
358                    },
359                })
360                .map_err(|e| new_error!("Cannot install debug exception intercept: {}", e))?;
361
362            // Install intercept for #BP (3) exception
363            vm_fd
364                .install_intercept(mshv_install_intercept {
365                    access_type_mask: HV_INTERCEPT_ACCESS_MASK_EXECUTE,
366                    intercept_type: hv_intercept_type_HV_INTERCEPT_TYPE_EXCEPTION,
367                    // Exception handler #BP (3)
368                    intercept_parameter: hv_intercept_parameters {
369                        exception_vector: 0x3,
370                    },
371                })
372                .map_err(|e| new_error!("Cannot install breakpoint exception intercept: {}", e))?;
373
374            (Some(debug), Some(gdb_conn))
375        } else {
376            (None, None)
377        };
378
379        mem_regions.iter().try_for_each(|region| {
380            let mshv_region = region.to_owned().into();
381            vm_fd.map_user_memory(mshv_region)
382        })?;
383
384        Self::setup_initial_sregs(&mut vcpu_fd, pml4_ptr.absolute()?)?;
385
386        Ok(Self {
387            _mshv: mshv,
388            vm_fd,
389            vcpu_fd,
390            mem_regions,
391            entrypoint: entrypoint_ptr.absolute()?,
392            orig_rsp: rsp_ptr,
393
394            #[cfg(gdb)]
395            debug,
396            #[cfg(gdb)]
397            gdb_conn,
398        })
399    }
400
401    #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
402    fn setup_initial_sregs(vcpu: &mut VcpuFd, pml4_addr: u64) -> Result<()> {
403        let sregs = SpecialRegisters {
404            cr0: CR0_PE | CR0_MP | CR0_ET | CR0_NE | CR0_AM | CR0_PG | CR0_WP,
405            cr4: CR4_PAE | CR4_OSFXSR | CR4_OSXMMEXCPT,
406            cr3: pml4_addr,
407            efer: EFER_LME | EFER_LMA | EFER_SCE | EFER_NX,
408            cs: SegmentRegister {
409                type_: 11,
410                present: 1,
411                s: 1,
412                l: 1,
413                ..Default::default()
414            },
415            tr: SegmentRegister {
416                limit: 65535,
417                type_: 11,
418                present: 1,
419                ..Default::default()
420            },
421            ..Default::default()
422        };
423        vcpu.set_sregs(&sregs)?;
424        Ok(())
425    }
426}
427
428impl Debug for HypervLinuxDriver {
429    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
430        let mut f = f.debug_struct("Hyperv Linux Driver");
431
432        f.field("Entrypoint", &self.entrypoint)
433            .field("Original RSP", &self.orig_rsp);
434
435        for region in &self.mem_regions {
436            f.field("Memory Region", &region);
437        }
438
439        let regs = self.vcpu_fd.get_regs();
440
441        if let Ok(regs) = regs {
442            f.field("Registers", &regs);
443        }
444
445        let sregs = self.vcpu_fd.get_sregs();
446
447        if let Ok(sregs) = sregs {
448            f.field("Special Registers", &sregs);
449        }
450
451        f.finish()
452    }
453}
454
455impl Hypervisor for HypervLinuxDriver {
456    #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
457    fn initialise(
458        &mut self,
459        peb_addr: RawPtr,
460        seed: u64,
461        page_size: u32,
462        outb_hdl: OutBHandlerWrapper,
463        mem_access_hdl: MemAccessHandlerWrapper,
464        hv_handler: Option<HypervisorHandler>,
465        max_guest_log_level: Option<LevelFilter>,
466        #[cfg(gdb)] dbg_mem_access_fn: DbgMemAccessHandlerWrapper,
467    ) -> Result<()> {
468        let max_guest_log_level: u64 = match max_guest_log_level {
469            Some(level) => level as u64,
470            None => self.get_max_log_level().into(),
471        };
472
473        let regs = StandardRegisters {
474            rip: self.entrypoint,
475            rsp: self.orig_rsp.absolute()?,
476            rflags: 2, //bit 1 of rlags is required to be set
477
478            // function args
479            rdi: peb_addr.into(),
480            rsi: seed,
481            rdx: page_size.into(),
482            rcx: max_guest_log_level,
483
484            ..Default::default()
485        };
486        self.vcpu_fd.set_regs(&regs)?;
487
488        VirtualCPU::run(
489            self.as_mut_hypervisor(),
490            hv_handler,
491            outb_hdl,
492            mem_access_hdl,
493            #[cfg(gdb)]
494            dbg_mem_access_fn,
495        )?;
496
497        Ok(())
498    }
499
500    #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
501    fn dispatch_call_from_host(
502        &mut self,
503        dispatch_func_addr: RawPtr,
504        outb_handle_fn: OutBHandlerWrapper,
505        mem_access_fn: MemAccessHandlerWrapper,
506        hv_handler: Option<HypervisorHandler>,
507        #[cfg(gdb)] dbg_mem_access_fn: DbgMemAccessHandlerWrapper,
508    ) -> Result<()> {
509        // Reset general purpose registers, then set RIP and RSP
510        let regs = StandardRegisters {
511            rip: dispatch_func_addr.into(),
512            rsp: self.orig_rsp.absolute()?,
513            rflags: 2, //bit 1 of rlags is required to be set
514            ..Default::default()
515        };
516        self.vcpu_fd.set_regs(&regs)?;
517
518        // reset fpu state
519        let fpu = FloatingPointUnit {
520            fcw: FP_CONTROL_WORD_DEFAULT,
521            ftwx: FP_TAG_WORD_DEFAULT,
522            mxcsr: MXCSR_DEFAULT,
523            ..Default::default() // zero out the rest
524        };
525        self.vcpu_fd.set_fpu(&fpu)?;
526
527        // run
528        VirtualCPU::run(
529            self.as_mut_hypervisor(),
530            hv_handler,
531            outb_handle_fn,
532            mem_access_fn,
533            #[cfg(gdb)]
534            dbg_mem_access_fn,
535        )?;
536
537        Ok(())
538    }
539
540    #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
541    fn handle_io(
542        &mut self,
543        port: u16,
544        data: Vec<u8>,
545        rip: u64,
546        instruction_length: u64,
547        outb_handle_fn: OutBHandlerWrapper,
548    ) -> Result<()> {
549        let mut padded = [0u8; 4];
550        let copy_len = data.len().min(4);
551        padded[..copy_len].copy_from_slice(&data[..copy_len]);
552        let val = u32::from_le_bytes(padded);
553
554        outb_handle_fn
555            .try_lock()
556            .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?
557            .call(port, val)?;
558
559        // update rip
560        self.vcpu_fd.set_reg(&[hv_register_assoc {
561            name: hv_register_name_HV_X64_REGISTER_RIP,
562            value: hv_register_value {
563                reg64: rip + instruction_length,
564            },
565            ..Default::default()
566        }])?;
567        Ok(())
568    }
569
570    #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
571    fn run(&mut self) -> Result<super::HyperlightExit> {
572        const HALT_MESSAGE: hv_message_type = hv_message_type_HVMSG_X64_HALT;
573        const IO_PORT_INTERCEPT_MESSAGE: hv_message_type =
574            hv_message_type_HVMSG_X64_IO_PORT_INTERCEPT;
575        const UNMAPPED_GPA_MESSAGE: hv_message_type = hv_message_type_HVMSG_UNMAPPED_GPA;
576        const INVALID_GPA_ACCESS_MESSAGE: hv_message_type = hv_message_type_HVMSG_GPA_INTERCEPT;
577        #[cfg(gdb)]
578        const EXCEPTION_INTERCEPT: hv_message_type = hv_message_type_HVMSG_X64_EXCEPTION_INTERCEPT;
579
580        #[cfg(mshv2)]
581        let run_result = {
582            let hv_message: hv_message = Default::default();
583            &self.vcpu_fd.run(hv_message)
584        };
585        #[cfg(mshv3)]
586        let run_result = &self.vcpu_fd.run();
587
588        let result = match run_result {
589            Ok(m) => match m.header.message_type {
590                HALT_MESSAGE => {
591                    crate::debug!("mshv - Halt Details : {:#?}", &self);
592                    HyperlightExit::Halt()
593                }
594                IO_PORT_INTERCEPT_MESSAGE => {
595                    let io_message = m.to_ioport_info()?;
596                    let port_number = io_message.port_number;
597                    let rip = io_message.header.rip;
598                    let rax = io_message.rax;
599                    let instruction_length = io_message.header.instruction_length() as u64;
600                    crate::debug!("mshv IO Details : \nPort : {}\n{:#?}", port_number, &self);
601                    HyperlightExit::IoOut(
602                        port_number,
603                        rax.to_le_bytes().to_vec(),
604                        rip,
605                        instruction_length,
606                    )
607                }
608                UNMAPPED_GPA_MESSAGE => {
609                    let mimo_message = m.to_memory_info()?;
610                    let addr = mimo_message.guest_physical_address;
611                    crate::debug!(
612                        "mshv MMIO unmapped GPA -Details: Address: {} \n {:#?}",
613                        addr,
614                        &self
615                    );
616                    HyperlightExit::Mmio(addr)
617                }
618                INVALID_GPA_ACCESS_MESSAGE => {
619                    let mimo_message = m.to_memory_info()?;
620                    let gpa = mimo_message.guest_physical_address;
621                    let access_info = MemoryRegionFlags::try_from(mimo_message)?;
622                    crate::debug!(
623                        "mshv MMIO invalid GPA access -Details: Address: {} \n {:#?}",
624                        gpa,
625                        &self
626                    );
627                    match self.get_memory_access_violation(
628                        gpa as usize,
629                        &self.mem_regions,
630                        access_info,
631                    ) {
632                        Some(access_info_violation) => access_info_violation,
633                        None => HyperlightExit::Mmio(gpa),
634                    }
635                }
636                // The only case an intercept exit is expected is when debugging is enabled
637                // and the intercepts are installed.
638                // Provide the extra information about the exception to accurately determine
639                // the stop reason
640                #[cfg(gdb)]
641                EXCEPTION_INTERCEPT => {
642                    // Extract exception info from the message so we can figure out
643                    // more information about the vCPU state
644                    let ex_info = match m.to_exception_info() {
645                        Ok(info) => info,
646                        Err(e) => {
647                            log_then_return!("Error converting to exception info: {:?}", e);
648                        }
649                    };
650
651                    match self.get_stop_reason(ex_info) {
652                        Ok(reason) => HyperlightExit::Debug(reason),
653                        Err(e) => {
654                            log_then_return!("Error getting stop reason: {:?}", e);
655                        }
656                    }
657                }
658                other => {
659                    crate::debug!("mshv Other Exit: Exit: {:#?} \n {:#?}", other, &self);
660                    log_then_return!("unknown Hyper-V run message type {:?}", other);
661                }
662            },
663            Err(e) => match e.errno() {
664                // we send a signal to the thread to cancel execution this results in EINTR being returned by KVM so we return Cancelled
665                libc::EINTR => HyperlightExit::Cancelled(),
666                libc::EAGAIN => HyperlightExit::Retry(),
667                _ => {
668                    crate::debug!("mshv Error - Details: Error: {} \n {:#?}", e, &self);
669                    log_then_return!("Error running VCPU {:?}", e);
670                }
671            },
672        };
673        Ok(result)
674    }
675
676    #[instrument(skip_all, parent = Span::current(), level = "Trace")]
677    fn as_mut_hypervisor(&mut self) -> &mut dyn Hypervisor {
678        self as &mut dyn Hypervisor
679    }
680
681    #[cfg(crashdump)]
682    fn get_memory_regions(&self) -> &[MemoryRegion] {
683        &self.mem_regions
684    }
685
686    #[cfg(gdb)]
687    fn handle_debug(
688        &mut self,
689        dbg_mem_access_fn: std::sync::Arc<
690            std::sync::Mutex<dyn super::handlers::DbgMemAccessHandlerCaller>,
691        >,
692        stop_reason: super::gdb::VcpuStopReason,
693    ) -> Result<()> {
694        self.send_dbg_msg(DebugResponse::VcpuStopped(stop_reason))
695            .map_err(|e| new_error!("Couldn't signal vCPU stopped event to GDB thread: {:?}", e))?;
696
697        loop {
698            log::debug!("Debug wait for event to resume vCPU");
699
700            // Wait for a message from gdb
701            let req = self.recv_dbg_msg()?;
702
703            let result = self.process_dbg_request(req, dbg_mem_access_fn.clone());
704
705            let response = match result {
706                Ok(response) => response,
707                // Treat non fatal errors separately so the guest doesn't fail
708                Err(HyperlightError::TranslateGuestAddress(_)) => DebugResponse::ErrorOccurred,
709                Err(e) => {
710                    return Err(e);
711                }
712            };
713
714            // If the command was either step or continue, we need to run the vcpu
715            let cont = matches!(
716                response,
717                DebugResponse::Step | DebugResponse::Continue | DebugResponse::DisableDebug
718            );
719
720            self.send_dbg_msg(response)
721                .map_err(|e| new_error!("Couldn't send response to gdb: {:?}", e))?;
722
723            if cont {
724                break;
725            }
726        }
727
728        Ok(())
729    }
730}
731
732impl Drop for HypervLinuxDriver {
733    #[instrument(skip_all, parent = Span::current(), level = "Trace")]
734    fn drop(&mut self) {
735        for region in &self.mem_regions {
736            let mshv_region: mshv_user_mem_region = region.to_owned().into();
737            match self.vm_fd.unmap_user_memory(mshv_region) {
738                Ok(_) => (),
739                Err(e) => error!("Failed to unmap user memory in HyperVOnLinux ({:?})", e),
740            }
741        }
742    }
743}
744
745#[cfg(test)]
746mod tests {
747    use super::*;
748    use crate::mem::memory_region::MemoryRegionVecBuilder;
749    use crate::mem::shared_mem::{ExclusiveSharedMemory, SharedMemory};
750
751    #[rustfmt::skip]
752    const CODE: [u8; 12] = [
753        0xba, 0xf8, 0x03, /* mov $0x3f8, %dx */
754        0x00, 0xd8, /* add %bl, %al */
755        0x04, b'0', /* add $'0', %al */
756        0xee, /* out %al, (%dx) */
757        /* send a 0 to indicate we're done */
758        0xb0, b'\0', /* mov $'\0', %al */
759        0xee, /* out %al, (%dx) */
760        0xf4, /* HLT */
761    ];
762
763    fn shared_mem_with_code(
764        code: &[u8],
765        mem_size: usize,
766        load_offset: usize,
767    ) -> Result<Box<ExclusiveSharedMemory>> {
768        if load_offset > mem_size {
769            log_then_return!(
770                "code load offset ({}) > memory size ({})",
771                load_offset,
772                mem_size
773            );
774        }
775        let mut shared_mem = ExclusiveSharedMemory::new(mem_size)?;
776        shared_mem.copy_from_slice(code, load_offset)?;
777        Ok(Box::new(shared_mem))
778    }
779
780    #[test]
781    fn create_driver() {
782        if !super::is_hypervisor_present() {
783            return;
784        }
785        const MEM_SIZE: usize = 0x3000;
786        let gm = shared_mem_with_code(CODE.as_slice(), MEM_SIZE, 0).unwrap();
787        let rsp_ptr = GuestPtr::try_from(0).unwrap();
788        let pml4_ptr = GuestPtr::try_from(0).unwrap();
789        let entrypoint_ptr = GuestPtr::try_from(0).unwrap();
790        let mut regions = MemoryRegionVecBuilder::new(0, gm.base_addr());
791        regions.push_page_aligned(
792            MEM_SIZE,
793            MemoryRegionFlags::READ | MemoryRegionFlags::WRITE | MemoryRegionFlags::EXECUTE,
794            crate::mem::memory_region::MemoryRegionType::Code,
795        );
796        super::HypervLinuxDriver::new(
797            regions.build(),
798            entrypoint_ptr,
799            rsp_ptr,
800            pml4_ptr,
801            #[cfg(gdb)]
802            None,
803        )
804        .unwrap();
805    }
806}