hyperlight_host/hypervisor/
kvm.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
17use std::convert::TryFrom;
18use std::fmt::Debug;
19#[cfg(gdb)]
20use std::sync::{Arc, Mutex};
21
22use kvm_bindings::{kvm_fpu, kvm_regs, kvm_userspace_memory_region, KVM_MEM_READONLY};
23use kvm_ioctls::Cap::UserMemory;
24use kvm_ioctls::{Kvm, VcpuExit, VcpuFd, VmFd};
25use tracing::{instrument, Span};
26
27use super::fpu::{FP_CONTROL_WORD_DEFAULT, FP_TAG_WORD_DEFAULT, MXCSR_DEFAULT};
28#[cfg(gdb)]
29use super::gdb::{DebugCommChannel, DebugMsg, DebugResponse, GuestDebug, KvmDebug, VcpuStopReason};
30#[cfg(gdb)]
31use super::handlers::DbgMemAccessHandlerWrapper;
32use super::handlers::{MemAccessHandlerWrapper, OutBHandlerWrapper};
33use super::{
34    HyperlightExit, Hypervisor, VirtualCPU, CR0_AM, CR0_ET, CR0_MP, CR0_NE, CR0_PE, CR0_PG, CR0_WP,
35    CR4_OSFXSR, CR4_OSXMMEXCPT, CR4_PAE, EFER_LMA, EFER_LME, EFER_NX, EFER_SCE,
36};
37use crate::hypervisor::hypervisor_handler::HypervisorHandler;
38use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags};
39use crate::mem::ptr::{GuestPtr, RawPtr};
40#[cfg(gdb)]
41use crate::HyperlightError;
42use crate::{log_then_return, new_error, Result};
43
44/// Return `true` if the KVM API is available, version 12, and has UserMemory capability, or `false` otherwise
45#[instrument(skip_all, parent = Span::current(), level = "Trace")]
46pub(crate) fn is_hypervisor_present() -> bool {
47    if let Ok(kvm) = Kvm::new() {
48        let api_version = kvm.get_api_version();
49        match api_version {
50            version if version == 12 && kvm.check_extension(UserMemory) => true,
51            12 => {
52                log::info!("KVM does not have KVM_CAP_USER_MEMORY capability");
53                false
54            }
55            version => {
56                log::info!("KVM GET_API_VERSION returned {}, expected 12", version);
57                false
58            }
59        }
60    } else {
61        log::info!("Error creating KVM object");
62        false
63    }
64}
65
66#[cfg(gdb)]
67mod debug {
68    use std::sync::{Arc, Mutex};
69
70    use kvm_bindings::kvm_debug_exit_arch;
71
72    use super::KVMDriver;
73    use crate::hypervisor::gdb::{
74        DebugMsg, DebugResponse, GuestDebug, KvmDebug, VcpuStopReason, X86_64Regs,
75    };
76    use crate::hypervisor::handlers::DbgMemAccessHandlerCaller;
77    use crate::{new_error, Result};
78
79    impl KVMDriver {
80        /// Resets the debug information to disable debugging
81        fn disable_debug(&mut self) -> Result<()> {
82            let mut debug = KvmDebug::default();
83
84            debug.set_single_step(&self.vcpu_fd, false)?;
85
86            self.debug = Some(debug);
87
88            Ok(())
89        }
90
91        /// Get the reason the vCPU has stopped
92        pub(crate) fn get_stop_reason(
93            &mut self,
94            debug_exit: kvm_debug_exit_arch,
95        ) -> Result<VcpuStopReason> {
96            let debug = self
97                .debug
98                .as_mut()
99                .ok_or_else(|| new_error!("Debug is not enabled"))?;
100
101            debug.get_stop_reason(&self.vcpu_fd, debug_exit, self.entrypoint)
102        }
103
104        pub(crate) fn process_dbg_request(
105            &mut self,
106            req: DebugMsg,
107            dbg_mem_access_fn: Arc<Mutex<dyn DbgMemAccessHandlerCaller>>,
108        ) -> Result<DebugResponse> {
109            if let Some(debug) = self.debug.as_mut() {
110                match req {
111                    DebugMsg::AddHwBreakpoint(addr) => Ok(DebugResponse::AddHwBreakpoint(
112                        debug
113                            .add_hw_breakpoint(&self.vcpu_fd, addr)
114                            .map_err(|e| {
115                                log::error!("Failed to add hw breakpoint: {:?}", e);
116
117                                e
118                            })
119                            .is_ok(),
120                    )),
121                    DebugMsg::AddSwBreakpoint(addr) => Ok(DebugResponse::AddSwBreakpoint(
122                        debug
123                            .add_sw_breakpoint(&self.vcpu_fd, addr, dbg_mem_access_fn)
124                            .map_err(|e| {
125                                log::error!("Failed to add sw breakpoint: {:?}", e);
126
127                                e
128                            })
129                            .is_ok(),
130                    )),
131                    DebugMsg::Continue => {
132                        debug.set_single_step(&self.vcpu_fd, false).map_err(|e| {
133                            log::error!("Failed to continue execution: {:?}", e);
134
135                            e
136                        })?;
137
138                        Ok(DebugResponse::Continue)
139                    }
140                    DebugMsg::DisableDebug => {
141                        self.disable_debug().map_err(|e| {
142                            log::error!("Failed to disable debugging: {:?}", e);
143
144                            e
145                        })?;
146
147                        Ok(DebugResponse::DisableDebug)
148                    }
149                    DebugMsg::GetCodeSectionOffset => {
150                        let offset = dbg_mem_access_fn
151                            .try_lock()
152                            .map_err(|e| {
153                                new_error!("Error locking at {}:{}: {}", file!(), line!(), e)
154                            })?
155                            .get_code_offset()
156                            .map_err(|e| {
157                                log::error!("Failed to get code offset: {:?}", e);
158
159                                e
160                            })?;
161
162                        Ok(DebugResponse::GetCodeSectionOffset(offset as u64))
163                    }
164                    DebugMsg::ReadAddr(addr, len) => {
165                        let mut data = vec![0u8; len];
166
167                        debug
168                            .read_addrs(&self.vcpu_fd, addr, &mut data, dbg_mem_access_fn)
169                            .map_err(|e| {
170                                log::error!("Failed to read from address: {:?}", e);
171
172                                e
173                            })?;
174
175                        Ok(DebugResponse::ReadAddr(data))
176                    }
177                    DebugMsg::ReadRegisters => {
178                        let mut regs = X86_64Regs::default();
179
180                        debug
181                            .read_regs(&self.vcpu_fd, &mut regs)
182                            .map_err(|e| {
183                                log::error!("Failed to read registers: {:?}", e);
184
185                                e
186                            })
187                            .map(|_| DebugResponse::ReadRegisters(regs))
188                    }
189                    DebugMsg::RemoveHwBreakpoint(addr) => Ok(DebugResponse::RemoveHwBreakpoint(
190                        debug
191                            .remove_hw_breakpoint(&self.vcpu_fd, addr)
192                            .map_err(|e| {
193                                log::error!("Failed to remove hw breakpoint: {:?}", e);
194
195                                e
196                            })
197                            .is_ok(),
198                    )),
199                    DebugMsg::RemoveSwBreakpoint(addr) => Ok(DebugResponse::RemoveSwBreakpoint(
200                        debug
201                            .remove_sw_breakpoint(&self.vcpu_fd, addr, dbg_mem_access_fn)
202                            .map_err(|e| {
203                                log::error!("Failed to remove sw breakpoint: {:?}", e);
204
205                                e
206                            })
207                            .is_ok(),
208                    )),
209                    DebugMsg::Step => {
210                        debug.set_single_step(&self.vcpu_fd, true).map_err(|e| {
211                            log::error!("Failed to enable step instruction: {:?}", e);
212
213                            e
214                        })?;
215
216                        Ok(DebugResponse::Step)
217                    }
218                    DebugMsg::WriteAddr(addr, data) => {
219                        debug
220                            .write_addrs(&self.vcpu_fd, addr, &data, dbg_mem_access_fn)
221                            .map_err(|e| {
222                                log::error!("Failed to write to address: {:?}", e);
223
224                                e
225                            })?;
226
227                        Ok(DebugResponse::WriteAddr)
228                    }
229                    DebugMsg::WriteRegisters(regs) => debug
230                        .write_regs(&self.vcpu_fd, &regs)
231                        .map_err(|e| {
232                            log::error!("Failed to write registers: {:?}", e);
233
234                            e
235                        })
236                        .map(|_| DebugResponse::WriteRegisters),
237                }
238            } else {
239                Err(new_error!("Debugging is not enabled"))
240            }
241        }
242
243        pub(crate) fn recv_dbg_msg(&mut self) -> Result<DebugMsg> {
244            let gdb_conn = self
245                .gdb_conn
246                .as_mut()
247                .ok_or_else(|| new_error!("Debug is not enabled"))?;
248
249            gdb_conn.recv().map_err(|e| {
250                new_error!(
251                    "Got an error while waiting to receive a message from the gdb thread: {:?}",
252                    e
253                )
254            })
255        }
256
257        pub(crate) fn send_dbg_msg(&mut self, cmd: DebugResponse) -> Result<()> {
258            log::debug!("Sending {:?}", cmd);
259
260            let gdb_conn = self
261                .gdb_conn
262                .as_mut()
263                .ok_or_else(|| new_error!("Debug is not enabled"))?;
264
265            gdb_conn.send(cmd).map_err(|e| {
266                new_error!(
267                    "Got an error while sending a response message to the gdb thread: {:?}",
268                    e
269                )
270            })
271        }
272    }
273}
274
275/// A Hypervisor driver for KVM on Linux
276pub(super) struct KVMDriver {
277    _kvm: Kvm,
278    _vm_fd: VmFd,
279    vcpu_fd: VcpuFd,
280    entrypoint: u64,
281    orig_rsp: GuestPtr,
282    mem_regions: Vec<MemoryRegion>,
283
284    #[cfg(gdb)]
285    debug: Option<KvmDebug>,
286    #[cfg(gdb)]
287    gdb_conn: Option<DebugCommChannel<DebugResponse, DebugMsg>>,
288}
289
290impl KVMDriver {
291    /// Create a new instance of a `KVMDriver`, with only control registers
292    /// set. Standard registers will not be set, and `initialise` must
293    /// be called to do so.
294    #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
295    pub(super) fn new(
296        mem_regions: Vec<MemoryRegion>,
297        pml4_addr: u64,
298        entrypoint: u64,
299        rsp: u64,
300        #[cfg(gdb)] gdb_conn: Option<DebugCommChannel<DebugResponse, DebugMsg>>,
301    ) -> Result<Self> {
302        let kvm = Kvm::new()?;
303
304        let vm_fd = kvm.create_vm_with_type(0)?;
305
306        let perm_flags =
307            MemoryRegionFlags::READ | MemoryRegionFlags::WRITE | MemoryRegionFlags::EXECUTE;
308
309        mem_regions.iter().enumerate().try_for_each(|(i, region)| {
310            let perm_flags = perm_flags.intersection(region.flags);
311            let kvm_region = kvm_userspace_memory_region {
312                slot: i as u32,
313                guest_phys_addr: region.guest_region.start as u64,
314                memory_size: (region.guest_region.end - region.guest_region.start) as u64,
315                userspace_addr: region.host_region.start as u64,
316                flags: match perm_flags {
317                    MemoryRegionFlags::READ => KVM_MEM_READONLY,
318                    _ => 0, // normal, RWX
319                },
320            };
321            unsafe { vm_fd.set_user_memory_region(kvm_region) }
322        })?;
323
324        let mut vcpu_fd = vm_fd.create_vcpu(0)?;
325        Self::setup_initial_sregs(&mut vcpu_fd, pml4_addr)?;
326
327        #[cfg(gdb)]
328        let (debug, gdb_conn) = if let Some(gdb_conn) = gdb_conn {
329            let mut debug = KvmDebug::new();
330            // Add breakpoint to the entry point address
331            debug.add_hw_breakpoint(&vcpu_fd, entrypoint)?;
332
333            (Some(debug), Some(gdb_conn))
334        } else {
335            (None, None)
336        };
337
338        let rsp_gp = GuestPtr::try_from(RawPtr::from(rsp))?;
339
340        let ret = Self {
341            _kvm: kvm,
342            _vm_fd: vm_fd,
343            vcpu_fd,
344            entrypoint,
345            orig_rsp: rsp_gp,
346            mem_regions,
347
348            #[cfg(gdb)]
349            debug,
350            #[cfg(gdb)]
351            gdb_conn,
352        };
353
354        Ok(ret)
355    }
356
357    #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
358    fn setup_initial_sregs(vcpu_fd: &mut VcpuFd, pml4_addr: u64) -> Result<()> {
359        // setup paging and IA-32e (64-bit) mode
360        let mut sregs = vcpu_fd.get_sregs()?;
361        sregs.cr3 = pml4_addr;
362        sregs.cr4 = CR4_PAE | CR4_OSFXSR | CR4_OSXMMEXCPT;
363        sregs.cr0 = CR0_PE | CR0_MP | CR0_ET | CR0_NE | CR0_AM | CR0_PG | CR0_WP;
364        sregs.efer = EFER_LME | EFER_LMA | EFER_SCE | EFER_NX;
365        sregs.cs.l = 1; // required for 64-bit mode
366        vcpu_fd.set_sregs(&sregs)?;
367        Ok(())
368    }
369}
370
371impl Debug for KVMDriver {
372    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
373        let mut f = f.debug_struct("KVM Driver");
374        // Output each memory region
375
376        for region in &self.mem_regions {
377            f.field("Memory Region", &region);
378        }
379        let regs = self.vcpu_fd.get_regs();
380        // check that regs is OK and then set field in debug struct
381
382        if let Ok(regs) = regs {
383            f.field("Registers", &regs);
384        }
385
386        let sregs = self.vcpu_fd.get_sregs();
387
388        // check that sregs is OK and then set field in debug struct
389
390        if let Ok(sregs) = sregs {
391            f.field("Special Registers", &sregs);
392        }
393
394        f.finish()
395    }
396}
397
398impl Hypervisor for KVMDriver {
399    /// Implementation of initialise for Hypervisor trait.
400    #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
401    fn initialise(
402        &mut self,
403        peb_addr: RawPtr,
404        seed: u64,
405        page_size: u32,
406        outb_hdl: OutBHandlerWrapper,
407        mem_access_hdl: MemAccessHandlerWrapper,
408        hv_handler: Option<HypervisorHandler>,
409        #[cfg(gdb)] dbg_mem_access_fn: DbgMemAccessHandlerWrapper,
410    ) -> Result<()> {
411        let regs = kvm_regs {
412            rip: self.entrypoint,
413            rsp: self.orig_rsp.absolute()?,
414
415            // function args
416            rcx: peb_addr.into(),
417            rdx: seed,
418            r8: page_size.into(),
419            r9: self.get_max_log_level().into(),
420
421            ..Default::default()
422        };
423        self.vcpu_fd.set_regs(&regs)?;
424
425        VirtualCPU::run(
426            self.as_mut_hypervisor(),
427            hv_handler,
428            outb_hdl,
429            mem_access_hdl,
430            #[cfg(gdb)]
431            dbg_mem_access_fn,
432        )?;
433
434        Ok(())
435    }
436
437    #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
438    fn dispatch_call_from_host(
439        &mut self,
440        dispatch_func_addr: RawPtr,
441        outb_handle_fn: OutBHandlerWrapper,
442        mem_access_fn: MemAccessHandlerWrapper,
443        hv_handler: Option<HypervisorHandler>,
444        #[cfg(gdb)] dbg_mem_access_fn: DbgMemAccessHandlerWrapper,
445    ) -> Result<()> {
446        // Reset general purpose registers, then set RIP and RSP
447        let regs = kvm_regs {
448            rip: dispatch_func_addr.into(),
449            rsp: self.orig_rsp.absolute()?,
450            ..Default::default()
451        };
452        self.vcpu_fd.set_regs(&regs)?;
453
454        // reset fpu state
455        let fpu = kvm_fpu {
456            fcw: FP_CONTROL_WORD_DEFAULT,
457            ftwx: FP_TAG_WORD_DEFAULT,
458            mxcsr: MXCSR_DEFAULT,
459            ..Default::default() // zero out the rest
460        };
461        self.vcpu_fd.set_fpu(&fpu)?;
462
463        // run
464        VirtualCPU::run(
465            self.as_mut_hypervisor(),
466            hv_handler,
467            outb_handle_fn,
468            mem_access_fn,
469            #[cfg(gdb)]
470            dbg_mem_access_fn,
471        )?;
472
473        Ok(())
474    }
475
476    #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
477    fn handle_io(
478        &mut self,
479        port: u16,
480        data: Vec<u8>,
481        _rip: u64,
482        _instruction_length: u64,
483        outb_handle_fn: OutBHandlerWrapper,
484    ) -> Result<()> {
485        // KVM does not need RIP or instruction length, as it automatically sets the RIP
486
487        // The payload param for the outb_handle_fn is the first byte
488        // of the data array cast to an u64. Thus, we need to make sure
489        // the data array has at least one u8, then convert that to an u64
490        if data.is_empty() {
491            log_then_return!("no data was given in IO interrupt");
492        } else {
493            let payload_u64 = u64::from(data[0]);
494            outb_handle_fn
495                .try_lock()
496                .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?
497                .call(port, payload_u64)?;
498        }
499
500        Ok(())
501    }
502
503    #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
504    fn run(&mut self) -> Result<HyperlightExit> {
505        let exit_reason = self.vcpu_fd.run();
506        let result = match exit_reason {
507            Ok(VcpuExit::Hlt) => {
508                crate::debug!("KVM - Halt Details : {:#?}", &self);
509                HyperlightExit::Halt()
510            }
511            Ok(VcpuExit::IoOut(port, data)) => {
512                // because vcpufd.run() mutably borrows self we cannot pass self to crate::debug! macro here
513                crate::debug!("KVM IO Details : \nPort : {}\nData : {:?}", port, data);
514                // KVM does not need to set RIP or instruction length so these are set to 0
515                HyperlightExit::IoOut(port, data.to_vec(), 0, 0)
516            }
517            Ok(VcpuExit::MmioRead(addr, _)) => {
518                crate::debug!("KVM MMIO Read -Details: Address: {} \n {:#?}", addr, &self);
519
520                match self.get_memory_access_violation(
521                    addr as usize,
522                    &self.mem_regions,
523                    MemoryRegionFlags::READ,
524                ) {
525                    Some(access_violation_exit) => access_violation_exit,
526                    None => HyperlightExit::Mmio(addr),
527                }
528            }
529            Ok(VcpuExit::MmioWrite(addr, _)) => {
530                crate::debug!("KVM MMIO Write -Details: Address: {} \n {:#?}", addr, &self);
531
532                match self.get_memory_access_violation(
533                    addr as usize,
534                    &self.mem_regions,
535                    MemoryRegionFlags::WRITE,
536                ) {
537                    Some(access_violation_exit) => access_violation_exit,
538                    None => HyperlightExit::Mmio(addr),
539                }
540            }
541            #[cfg(gdb)]
542            // KVM provides architecture specific information about the vCPU state when exiting
543            Ok(VcpuExit::Debug(debug_exit)) => match self.get_stop_reason(debug_exit) {
544                Ok(reason) => HyperlightExit::Debug(reason),
545                Err(e) => {
546                    log_then_return!("Error getting stop reason: {:?}", e);
547                }
548            },
549            Err(e) => match e.errno() {
550                // In case of the gdb feature, the timeout is not enabled, this
551                // exit is because of a signal sent from the gdb thread to the
552                // hypervisor thread to cancel execution
553                #[cfg(gdb)]
554                libc::EINTR => HyperlightExit::Debug(VcpuStopReason::Interrupt),
555                // we send a signal to the thread to cancel execution this results in EINTR being returned by KVM so we return Cancelled
556                #[cfg(not(gdb))]
557                libc::EINTR => HyperlightExit::Cancelled(),
558                libc::EAGAIN => HyperlightExit::Retry(),
559                _ => {
560                    crate::debug!("KVM Error -Details: Address: {} \n {:#?}", e, &self);
561                    log_then_return!("Error running VCPU {:?}", e);
562                }
563            },
564            Ok(other) => {
565                crate::debug!("KVM Other Exit {:?}", other);
566                HyperlightExit::Unknown(format!("Unexpected KVM Exit {:?}", other))
567            }
568        };
569        Ok(result)
570    }
571
572    #[instrument(skip_all, parent = Span::current(), level = "Trace")]
573    fn as_mut_hypervisor(&mut self) -> &mut dyn Hypervisor {
574        self as &mut dyn Hypervisor
575    }
576
577    #[cfg(crashdump)]
578    fn get_memory_regions(&self) -> &[MemoryRegion] {
579        &self.mem_regions
580    }
581
582    #[cfg(gdb)]
583    fn handle_debug(
584        &mut self,
585        dbg_mem_access_fn: Arc<Mutex<dyn super::handlers::DbgMemAccessHandlerCaller>>,
586        stop_reason: VcpuStopReason,
587    ) -> Result<()> {
588        self.send_dbg_msg(DebugResponse::VcpuStopped(stop_reason))
589            .map_err(|e| new_error!("Couldn't signal vCPU stopped event to GDB thread: {:?}", e))?;
590
591        loop {
592            log::debug!("Debug wait for event to resume vCPU");
593            // Wait for a message from gdb
594            let req = self.recv_dbg_msg()?;
595
596            let result = self.process_dbg_request(req, dbg_mem_access_fn.clone());
597
598            let response = match result {
599                Ok(response) => response,
600                // Treat non fatal errors separately so the guest doesn't fail
601                Err(HyperlightError::TranslateGuestAddress(_)) => DebugResponse::ErrorOccurred,
602                Err(e) => {
603                    return Err(e);
604                }
605            };
606
607            // If the command was either step or continue, we need to run the vcpu
608            let cont = matches!(
609                response,
610                DebugResponse::Step | DebugResponse::Continue | DebugResponse::DisableDebug
611            );
612
613            self.send_dbg_msg(response)
614                .map_err(|e| new_error!("Couldn't send response to gdb: {:?}", e))?;
615
616            if cont {
617                break;
618            }
619        }
620
621        Ok(())
622    }
623}
624
625#[cfg(test)]
626mod tests {
627    use std::sync::{Arc, Mutex};
628
629    #[cfg(gdb)]
630    use crate::hypervisor::handlers::DbgMemAccessHandlerCaller;
631    use crate::hypervisor::handlers::{MemAccessHandler, OutBHandler};
632    use crate::hypervisor::tests::test_initialise;
633    use crate::Result;
634
635    #[cfg(gdb)]
636    struct DbgMemAccessHandler {}
637
638    #[cfg(gdb)]
639    impl DbgMemAccessHandlerCaller for DbgMemAccessHandler {
640        fn read(&mut self, _offset: usize, _data: &mut [u8]) -> Result<()> {
641            Ok(())
642        }
643
644        fn write(&mut self, _offset: usize, _data: &[u8]) -> Result<()> {
645            Ok(())
646        }
647
648        fn get_code_offset(&mut self) -> Result<usize> {
649            Ok(0)
650        }
651    }
652
653    #[test]
654    fn test_init() {
655        if !super::is_hypervisor_present() {
656            return;
657        }
658
659        let outb_handler: Arc<Mutex<OutBHandler>> = {
660            let func: Box<dyn FnMut(u16, u64) -> Result<()> + Send> =
661                Box::new(|_, _| -> Result<()> { Ok(()) });
662            Arc::new(Mutex::new(OutBHandler::from(func)))
663        };
664        let mem_access_handler = {
665            let func: Box<dyn FnMut() -> Result<()> + Send> = Box::new(|| -> Result<()> { Ok(()) });
666            Arc::new(Mutex::new(MemAccessHandler::from(func)))
667        };
668        #[cfg(gdb)]
669        let dbg_mem_access_handler = Arc::new(Mutex::new(DbgMemAccessHandler {}));
670
671        test_initialise(
672            outb_handler,
673            mem_access_handler,
674            #[cfg(gdb)]
675            dbg_mem_access_handler,
676        )
677        .unwrap();
678    }
679}