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            rdi: peb_addr.into(),
424            rsi: seed,
425            rdx: page_size.into(),
426            rcx: 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 mut padded = [0u8; 4];
501            let copy_len = data.len().min(4);
502            padded[..copy_len].copy_from_slice(&data[..copy_len]);
503            let value = u32::from_le_bytes(padded);
504
505            outb_handle_fn
506                .try_lock()
507                .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?
508                .call(port, value)?;
509        }
510
511        Ok(())
512    }
513
514    #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
515    fn run(&mut self) -> Result<HyperlightExit> {
516        let exit_reason = self.vcpu_fd.run();
517        let result = match exit_reason {
518            Ok(VcpuExit::Hlt) => {
519                crate::debug!("KVM - Halt Details : {:#?}", &self);
520                HyperlightExit::Halt()
521            }
522            Ok(VcpuExit::IoOut(port, data)) => {
523                // because vcpufd.run() mutably borrows self we cannot pass self to crate::debug! macro here
524                crate::debug!("KVM IO Details : \nPort : {}\nData : {:?}", port, data);
525                // KVM does not need to set RIP or instruction length so these are set to 0
526                HyperlightExit::IoOut(port, data.to_vec(), 0, 0)
527            }
528            Ok(VcpuExit::MmioRead(addr, _)) => {
529                crate::debug!("KVM MMIO Read -Details: Address: {} \n {:#?}", addr, &self);
530
531                match self.get_memory_access_violation(
532                    addr as usize,
533                    &self.mem_regions,
534                    MemoryRegionFlags::READ,
535                ) {
536                    Some(access_violation_exit) => access_violation_exit,
537                    None => HyperlightExit::Mmio(addr),
538                }
539            }
540            Ok(VcpuExit::MmioWrite(addr, _)) => {
541                crate::debug!("KVM MMIO Write -Details: Address: {} \n {:#?}", addr, &self);
542
543                match self.get_memory_access_violation(
544                    addr as usize,
545                    &self.mem_regions,
546                    MemoryRegionFlags::WRITE,
547                ) {
548                    Some(access_violation_exit) => access_violation_exit,
549                    None => HyperlightExit::Mmio(addr),
550                }
551            }
552            #[cfg(gdb)]
553            // KVM provides architecture specific information about the vCPU state when exiting
554            Ok(VcpuExit::Debug(debug_exit)) => match self.get_stop_reason(debug_exit) {
555                Ok(reason) => HyperlightExit::Debug(reason),
556                Err(e) => {
557                    log_then_return!("Error getting stop reason: {:?}", e);
558                }
559            },
560            Err(e) => match e.errno() {
561                // In case of the gdb feature, the timeout is not enabled, this
562                // exit is because of a signal sent from the gdb thread to the
563                // hypervisor thread to cancel execution
564                #[cfg(gdb)]
565                libc::EINTR => HyperlightExit::Debug(VcpuStopReason::Interrupt),
566                // we send a signal to the thread to cancel execution this results in EINTR being returned by KVM so we return Cancelled
567                #[cfg(not(gdb))]
568                libc::EINTR => HyperlightExit::Cancelled(),
569                libc::EAGAIN => HyperlightExit::Retry(),
570                _ => {
571                    crate::debug!("KVM Error -Details: Address: {} \n {:#?}", e, &self);
572                    log_then_return!("Error running VCPU {:?}", e);
573                }
574            },
575            Ok(other) => {
576                let err_msg = format!("Unexpected KVM Exit {:?}", other);
577                crate::debug!("KVM Other Exit Details: {:#?}", &self);
578                HyperlightExit::Unknown(err_msg)
579            }
580        };
581        Ok(result)
582    }
583
584    #[instrument(skip_all, parent = Span::current(), level = "Trace")]
585    fn as_mut_hypervisor(&mut self) -> &mut dyn Hypervisor {
586        self as &mut dyn Hypervisor
587    }
588
589    #[cfg(crashdump)]
590    fn get_memory_regions(&self) -> &[MemoryRegion] {
591        &self.mem_regions
592    }
593
594    #[cfg(gdb)]
595    fn handle_debug(
596        &mut self,
597        dbg_mem_access_fn: Arc<Mutex<dyn super::handlers::DbgMemAccessHandlerCaller>>,
598        stop_reason: VcpuStopReason,
599    ) -> Result<()> {
600        self.send_dbg_msg(DebugResponse::VcpuStopped(stop_reason))
601            .map_err(|e| new_error!("Couldn't signal vCPU stopped event to GDB thread: {:?}", e))?;
602
603        loop {
604            log::debug!("Debug wait for event to resume vCPU");
605            // Wait for a message from gdb
606            let req = self.recv_dbg_msg()?;
607
608            let result = self.process_dbg_request(req, dbg_mem_access_fn.clone());
609
610            let response = match result {
611                Ok(response) => response,
612                // Treat non fatal errors separately so the guest doesn't fail
613                Err(HyperlightError::TranslateGuestAddress(_)) => DebugResponse::ErrorOccurred,
614                Err(e) => {
615                    return Err(e);
616                }
617            };
618
619            // If the command was either step or continue, we need to run the vcpu
620            let cont = matches!(
621                response,
622                DebugResponse::Step | DebugResponse::Continue | DebugResponse::DisableDebug
623            );
624
625            self.send_dbg_msg(response)
626                .map_err(|e| new_error!("Couldn't send response to gdb: {:?}", e))?;
627
628            if cont {
629                break;
630            }
631        }
632
633        Ok(())
634    }
635}
636
637#[cfg(test)]
638mod tests {
639    use std::sync::{Arc, Mutex};
640
641    #[cfg(gdb)]
642    use crate::hypervisor::handlers::DbgMemAccessHandlerCaller;
643    use crate::hypervisor::handlers::{MemAccessHandler, OutBHandler};
644    use crate::hypervisor::tests::test_initialise;
645    use crate::Result;
646
647    #[cfg(gdb)]
648    struct DbgMemAccessHandler {}
649
650    #[cfg(gdb)]
651    impl DbgMemAccessHandlerCaller for DbgMemAccessHandler {
652        fn read(&mut self, _offset: usize, _data: &mut [u8]) -> Result<()> {
653            Ok(())
654        }
655
656        fn write(&mut self, _offset: usize, _data: &[u8]) -> Result<()> {
657            Ok(())
658        }
659
660        fn get_code_offset(&mut self) -> Result<usize> {
661            Ok(0)
662        }
663    }
664
665    #[test]
666    fn test_init() {
667        if !super::is_hypervisor_present() {
668            return;
669        }
670
671        let outb_handler: Arc<Mutex<OutBHandler>> = {
672            let func: Box<dyn FnMut(u16, u32) -> Result<()> + Send> =
673                Box::new(|_, _| -> Result<()> { Ok(()) });
674            Arc::new(Mutex::new(OutBHandler::from(func)))
675        };
676        let mem_access_handler = {
677            let func: Box<dyn FnMut() -> Result<()> + Send> = Box::new(|| -> Result<()> { Ok(()) });
678            Arc::new(Mutex::new(MemAccessHandler::from(func)))
679        };
680        #[cfg(gdb)]
681        let dbg_mem_access_handler = Arc::new(Mutex::new(DbgMemAccessHandler {}));
682
683        test_initialise(
684            outb_handler,
685            mem_access_handler,
686            #[cfg(gdb)]
687            dbg_mem_access_handler,
688        )
689        .unwrap();
690    }
691}