nyx_lite/
vm_continuation_statemachine.rs

1use std::time::Duration;
2
3use crate::UnparsedExitReason;
4pub use crate::nyx_vm::NyxVM;
5
6#[derive(Clone, Debug, Eq, PartialEq, Hash)]
7pub enum VMExitUserEvent {
8    Shutdown,
9    Hypercall,
10    Timeout,
11    Breakpoint,
12    HWBreakpoint(u8),
13    SingleStep,
14    Interrupted,
15    BadMemoryAccess,
16}
17
18#[derive(Clone, Debug, Eq, PartialEq, Hash)]
19pub enum VMContinuationState {
20    Main,
21    ForceSingleStep,
22    EmulateHypercall,
23    ForceSingleStepInjectBPs,
24}
25
26#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
27pub enum RunMode {
28    Run,
29    SingleStep,
30    BranchStep,
31}
32
33impl RunMode {
34    pub fn is_step(&self) -> bool {
35        match self {
36            RunMode::Run => false,
37            RunMode::SingleStep => true,
38            RunMode::BranchStep => true,
39        }
40    }
41}
42
43impl VMContinuationState {
44    pub fn step(vm: &mut NyxVM, run_mode: RunMode, timeout: Duration) -> VMExitUserEvent {
45        loop {
46            //println!("LOOP {:?} requested singlestep {}", vm.continuation_state, single_step);
47            let (new_state, res) = match vm.continuation_state {
48                Self::Main => Self::run_main(vm, run_mode, timeout),
49                Self::ForceSingleStep => Self::force_ss(vm, run_mode.is_step()),
50                Self::ForceSingleStepInjectBPs => Self::force_ss_inject_bps(vm),
51                Self::EmulateHypercall => Self::emulate_hypercall(vm),
52            };
53            vm.continuation_state = new_state;
54            if let Some(user_event) = res {
55                return user_event;
56            }
57        }
58    }
59
60    fn run_main(
61        vm: &mut NyxVM,
62        run_mode: RunMode,
63        timeout: Duration,
64    ) -> (Self, Option<VMExitUserEvent>) {
65        let vmexit_on_swbp = true;
66        vm.set_debug_state(run_mode, vmexit_on_swbp);
67        vm.breakpoint_manager
68            .enable_all_breakpoints(&mut vm.vmm.lock().unwrap());
69        let exit = vm.run_inner(timeout);
70        match exit {
71            UnparsedExitReason::BadMemoryAccess => {
72                return (Self::Main, Some(VMExitUserEvent::BadMemoryAccess));
73            }
74            UnparsedExitReason::HWBreakpoint(x) => {
75                return (Self::Main, Some(VMExitUserEvent::HWBreakpoint(x)));
76            }
77            UnparsedExitReason::GuestBreakpoint => return (Self::ForceSingleStepInjectBPs, None),
78            UnparsedExitReason::NyxBreakpoint => {
79                return (Self::ForceSingleStep, Some(VMExitUserEvent::Breakpoint));
80            }
81            UnparsedExitReason::Hypercall => {
82                return (Self::EmulateHypercall, Some(VMExitUserEvent::Hypercall));
83            }
84            UnparsedExitReason::Interrupted => return (Self::Main, None),
85            UnparsedExitReason::Shutdown => return (Self::Main, Some(VMExitUserEvent::Shutdown)),
86            UnparsedExitReason::SingleStep => {
87                if run_mode.is_step() {
88                    return (Self::Main, Some(VMExitUserEvent::SingleStep));
89                }
90                panic!("We shouldn't see singlestep exceptions unless we asked for them");
91            }
92            UnparsedExitReason::Timeout => return (Self::Main, Some(VMExitUserEvent::Timeout)),
93        };
94    }
95    fn force_ss(
96        vm: &mut NyxVM,
97        user_requested_singlestep: bool,
98    ) -> (Self, Option<VMExitUserEvent>) {
99        let vmexit_on_swbp = true;
100        vm.set_debug_state(RunMode::SingleStep, vmexit_on_swbp);
101        vm.disable_last_nyx_breakpoint(); // step over the nyx breakpoint(s)
102        let no_timeout = Duration::MAX;
103        let exit = vm.run_inner(no_timeout);
104        match exit {
105            UnparsedExitReason::SingleStep => {
106                // happy case: we just got to single step & can now reapply
107                // breakpoints and continue as is
108                Self::assert_made_progress(vm);
109                if user_requested_singlestep {
110                    return (Self::Main, Some(VMExitUserEvent::SingleStep));
111                }
112                return (Self::Main, None);
113            }
114            UnparsedExitReason::BadMemoryAccess => {
115                return (Self::Main, Some(VMExitUserEvent::BadMemoryAccess));
116            }
117            UnparsedExitReason::GuestBreakpoint => {
118                // To get here, we triggered a nyx-bp at address X (which we removed). IF there was a breakpoint under
119                // the nyx-bp, it's a guest BP that we need to inject. In that case we should have made no progress.
120                Self::assert_made_no_progress(vm);
121                return (Self::ForceSingleStepInjectBPs, None);
122            }
123            UnparsedExitReason::HWBreakpoint(x) => {
124                return (Self::Main, Some(VMExitUserEvent::HWBreakpoint(x)));
125            }
126            UnparsedExitReason::NyxBreakpoint => {
127                //To get here, we triggered a nyx-bp at address X. Then we removed the breakpoints from that address.
128                //Singlestep shouldn't trigger the next instruction. So IF we trigger a breakpoint ad X, AFTER we
129                //removed the breakpoint at X, it's because the BP is a guest BP
130                panic!("We shouild never see a nyx bp when single stepping over a previous BP");
131            }
132            UnparsedExitReason::Hypercall => {
133                // while we made no progress, we no longer need to singlestep,
134                // as EmualteHypercall will emulate single stepping past the
135                // hypercall instruction
136                Self::assert_made_no_progress(vm);
137                return (Self::EmulateHypercall, Some(VMExitUserEvent::Hypercall));
138            }
139            UnparsedExitReason::Interrupted => return (Self::ForceSingleStep, None),
140            UnparsedExitReason::Shutdown => return (Self::Main, Some(VMExitUserEvent::Shutdown)),
141            UnparsedExitReason::Timeout => {
142                return (Self::ForceSingleStep, Some(VMExitUserEvent::Timeout));
143            }
144        };
145    }
146
147    fn force_ss_inject_bps(vm: &mut NyxVM) -> (Self, Option<VMExitUserEvent>) {
148        let vmexit_on_swbp = false;
149        vm.set_debug_state(RunMode::SingleStep, vmexit_on_swbp);
150        vm.breakpoint_manager
151            .disable_all_breakpoints(&mut vm.vmm.lock().unwrap()); // step over nyx breakpoint(s), but not guest breakpoints
152        let no_timeout = Duration::MAX;
153        let exit = vm.run_inner(no_timeout);
154        match exit {
155            UnparsedExitReason::SingleStep => {
156                // happy case: we just got to single step & can now reapply
157                // breakpoints and continue as is
158                Self::assert_made_progress(vm);
159                return (Self::Main, None);
160            }
161            UnparsedExitReason::HWBreakpoint(x) => {
162                return (Self::Main, Some(VMExitUserEvent::HWBreakpoint(x)));
163            }
164            UnparsedExitReason::BadMemoryAccess => {
165                return (Self::Main, Some(VMExitUserEvent::BadMemoryAccess));
166            }
167            UnparsedExitReason::GuestBreakpoint => {
168                panic!(
169                    "We should never see a breakpoint based vm exit while injecting breakpoints interrupts"
170                );
171            }
172            UnparsedExitReason::NyxBreakpoint => {
173                panic!(
174                    "We should never see a breakpoint based vm exit while injecting breakpoints interrupts"
175                );
176            }
177            UnparsedExitReason::Hypercall => {
178                panic!(
179                    "We should never see a breakpoint based vm exit while injecting breakpoints interrupts"
180                );
181            }
182            UnparsedExitReason::Interrupted => return (Self::ForceSingleStepInjectBPs, None),
183            UnparsedExitReason::Shutdown => return (Self::Main, Some(VMExitUserEvent::Shutdown)),
184            UnparsedExitReason::Timeout => {
185                return (
186                    Self::ForceSingleStepInjectBPs,
187                    Some(VMExitUserEvent::Timeout),
188                );
189            }
190        };
191    }
192    fn emulate_hypercall(vm: &mut NyxVM) -> (Self, Option<VMExitUserEvent>) {
193        let mut regs = vm.regs();
194        regs.rax = 0; // reset rax to prevent us from accidentially misinterpreting int 3 as hypercall in the future.
195        regs.rip += 1;
196        vm.set_regs(&regs);
197        return (Self::Main, None);
198    }
199    fn assert_made_progress(_vm: &mut NyxVM) {
200        // note: needs to handle self loops, str instructions etc
201    }
202    fn assert_made_no_progress(_vm: &mut NyxVM) {}
203}