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