Skip to main content

solana_sbpf/
vm.rs

1#![allow(clippy::arithmetic_side_effects)]
2// Derived from uBPF <https://github.com/iovisor/ubpf>
3// Copyright 2015 Big Switch Networks, Inc
4//      (uBPF: VM architecture, parts of the interpreter, originally in C)
5// Copyright 2016 6WIND S.A. <quentin.monnet@6wind.com>
6//      (Translation to Rust, MetaBuff/multiple classes addition, hashmaps for syscalls)
7// Copyright 2020 Solana Maintainers <maintainers@solana.com>
8//
9// Licensed under the Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0> or
10// the MIT license <http://opensource.org/licenses/MIT>, at your option. This file may not be
11// copied, modified, or distributed except according to those terms.
12
13//! Virtual machine for eBPF programs.
14
15use crate::{
16    ebpf,
17    elf::Executable,
18    error::{EbpfError, ProgramResult},
19    interpreter::Interpreter,
20    memory_region::MemoryMapping,
21    program::{BuiltinFunction, BuiltinProgram, FunctionRegistry, SBPFVersion},
22    static_analysis::{Analysis, DummyContextObject, RegisterTraceEntry},
23};
24// Re-export defaults for direct access without the module path.
25pub use defaults::get_stack_frame_size;
26use std::{collections::BTreeMap, fmt::Debug, marker::PhantomData, mem::offset_of, ptr};
27
28#[cfg(feature = "shuttle-test")]
29use shuttle::sync::Arc;
30#[cfg(not(feature = "shuttle-test"))]
31use std::sync::Arc;
32
33/// Returns (and if not done before generates) the encryption key for the VM pointer
34pub fn get_runtime_environment_key() -> i32 {
35    cfg_select! {
36        feature = "jit" => {
37            cfg_select! {
38                feature = "shuttle-test" => { use shuttle::rand::{thread_rng, Rng}; }
39                _ => { use rand::{thread_rng, Rng}; }
40            }
41            static RUNTIME_ENVIRONMENT_KEY: std::sync::OnceLock<i32> = std::sync::OnceLock::new();
42            *RUNTIME_ENVIRONMENT_KEY.get_or_init(|| thread_rng().gen::<i32>() >> 1)
43        }
44        _ => 0
45    }
46}
47
48/// Default VM configuration settings.
49pub(crate) mod defaults {
50    const DEFAULT_STACK_FRAME_SIZE: usize = 4_096;
51
52    /// Returns the stack frame size in bytes.
53    ///
54    /// With the `conf-stack-frame-size` feature enabled, the size can be overridden
55    /// at runtime via the `VM_STACK_FRAME_SIZE` environment variable. The value is
56    /// read once and cached. If not set, the default is always returned.
57    ///
58    /// Note: the `conf-stack-frame-size` variant can't be `const fn` (it uses
59    /// `OnceLock`), while the production variant is `const fn`. Callers that need
60    /// `const` evaluation (e.g. array sizes, const generics) should be aware that
61    /// those uses will not compile when `conf-stack-frame-size` is enabled.
62    #[cfg(feature = "conf-stack-frame-size")]
63    #[inline(always)]
64    pub fn get_stack_frame_size() -> usize {
65        static STACK_FRAME_SIZE_CACHE: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
66        *STACK_FRAME_SIZE_CACHE.get_or_init(|| {
67            let size = std::env::var("VM_STACK_FRAME_SIZE")
68                .ok()
69                .and_then(|v| {
70                    v.parse::<usize>().ok().filter(|sfz| *sfz > 0).or_else(|| {
71                        log::warn!(
72                            "Invalid VM_STACK_FRAME_SIZE={}, falling back to {}.",
73                            v,
74                            DEFAULT_STACK_FRAME_SIZE
75                        );
76                        None
77                    })
78                })
79                .unwrap_or(DEFAULT_STACK_FRAME_SIZE);
80            if size != DEFAULT_STACK_FRAME_SIZE {
81                log::warn!(
82                    "VM_STACK_FRAME_SIZE is set to {} (default: {}).",
83                    size,
84                    DEFAULT_STACK_FRAME_SIZE
85                );
86            }
87            size
88        })
89    }
90
91    /// Returns the stack frame size in bytes.
92    #[cfg(not(feature = "conf-stack-frame-size"))]
93    pub const fn get_stack_frame_size() -> usize {
94        DEFAULT_STACK_FRAME_SIZE
95    }
96}
97
98/// Specify the execution method.
99pub enum ExecutionMode {
100    /// Execute the program in an interpreted mode.
101    Interpreted,
102    /// Execute the program in JIT mode.
103    ///
104    /// The program must be JIT compiled.
105    Jit,
106    /// Allow JIT execution, if compiled. Otherwise fallback to interpreted.
107    PreferJit,
108}
109
110/// VM configuration settings
111#[derive(Debug, Clone, PartialEq, Eq)]
112pub struct Config {
113    /// Maximum call depth
114    pub max_call_depth: usize,
115    /// Size of a stack frame in bytes, must match the size specified in the LLVM BPF backend
116    pub stack_frame_size: usize,
117    /// Enables the use of MemoryMapping and MemoryRegion for address translation
118    pub enable_address_translation: bool,
119    /// Enables gaps in VM address space between the stack frames
120    pub enable_stack_frame_gaps: bool,
121    /// Maximal pc distance after which a new instruction meter validation is emitted by the JIT
122    pub instruction_meter_checkpoint_distance: usize,
123    /// Enable instruction meter and limiting
124    pub enable_instruction_meter: bool,
125    /// Enable dynamic string allocation for labels
126    pub enable_symbol_and_section_labels: bool,
127    /// Reject ELF files containing issues that the verifier did not catch before (up to v0.2.21)
128    pub reject_broken_elfs: bool,
129    #[cfg(feature = "jit")]
130    /// Ratio of native host instructions per random no-op in JIT (0 = OFF)
131    pub noop_instruction_rate: u32,
132    #[cfg(feature = "jit")]
133    /// Enable disinfection of immediate values and offsets provided by the user in JIT
134    pub sanitize_user_provided_values: bool,
135    /// Avoid copying read only sections when possible
136    pub optimize_rodata: bool,
137    /// Use aligned memory mapping
138    pub aligned_memory_mapping: bool,
139    /// Allowed [SBPFVersion]s
140    pub enabled_sbpf_versions: std::ops::RangeInclusive<SBPFVersion>,
141}
142
143impl Config {
144    /// Returns the size of the stack memory region
145    pub fn stack_size(&self) -> usize {
146        self.stack_frame_size * self.max_call_depth
147    }
148}
149
150impl Default for Config {
151    fn default() -> Self {
152        Self {
153            max_call_depth: 64,
154            stack_frame_size: defaults::get_stack_frame_size(),
155            enable_address_translation: true,
156            enable_stack_frame_gaps: true,
157            instruction_meter_checkpoint_distance: 1000,
158            enable_instruction_meter: true,
159            enable_symbol_and_section_labels: false,
160            reject_broken_elfs: false,
161            #[cfg(feature = "jit")]
162            noop_instruction_rate: 256,
163            #[cfg(feature = "jit")]
164            sanitize_user_provided_values: true,
165            optimize_rodata: true,
166            aligned_memory_mapping: false,
167            enabled_sbpf_versions: SBPFVersion::V0..=SBPFVersion::V4,
168        }
169    }
170}
171
172/// Static constructors for Executable
173impl<C: ContextObject> Executable<C> {
174    /// Creates an executable from an ELF file
175    pub fn from_elf(elf_bytes: &[u8], loader: Arc<BuiltinProgram<C>>) -> Result<Self, EbpfError> {
176        let executable = Executable::load(elf_bytes, loader)?;
177        Ok(executable)
178    }
179    /// Creates an executable from machine code
180    pub fn from_text_bytes(
181        text_bytes: &[u8],
182        loader: Arc<BuiltinProgram<C>>,
183        sbpf_version: SBPFVersion,
184        function_registry: FunctionRegistry<usize>,
185    ) -> Result<Self, EbpfError> {
186        Executable::new_from_text_bytes(text_bytes, loader, sbpf_version, function_registry)
187            .map_err(EbpfError::ElfError)
188    }
189}
190
191/// Runtime context
192pub trait ContextObject {
193    /// Consume instructions from meter
194    fn consume(&mut self, amount: u64);
195    /// Get the number of remaining instructions allowed
196    fn get_remaining(&self) -> u64;
197    /// Return a mutable pointer to the active MemoryMapping
198    fn active_mapping_ptr(&mut self) -> ptr::NonNull<MemoryMapping>;
199}
200
201/// Statistic of taken branches (from a recorded trace)
202pub struct DynamicAnalysis {
203    /// Maximal edge counter value
204    pub edge_counter_max: usize,
205    /// src_node, dst_node, edge_counter
206    pub edges: BTreeMap<usize, BTreeMap<usize, usize>>,
207}
208
209impl DynamicAnalysis {
210    /// Accumulates a trace
211    pub fn new(register_trace: &[[u64; 12]], analysis: &Analysis) -> Self {
212        let mut result = Self {
213            edge_counter_max: 0,
214            edges: BTreeMap::new(),
215        };
216        let mut last_basic_block = usize::MAX;
217        for traced_instruction in register_trace.iter() {
218            let pc = traced_instruction[11] as usize;
219            if analysis.cfg_nodes.contains_key(&pc) {
220                let counter = result
221                    .edges
222                    .entry(last_basic_block)
223                    .or_default()
224                    .entry(pc)
225                    .or_insert(0);
226                *counter += 1;
227                result.edge_counter_max = result.edge_counter_max.max(*counter);
228                last_basic_block = pc;
229            }
230        }
231        result
232    }
233}
234
235/// A call frame used for function calls inside the Interpreter
236#[derive(Clone, Default)]
237pub struct CallFrame {
238    /// The caller saved registers
239    pub caller_saved_registers: [u64; ebpf::SCRATCH_REGS],
240    /// The callers frame pointer
241    pub frame_pointer: u64,
242    /// The target_pc of the exit instruction which returns back to the caller
243    pub target_pc: u64,
244}
245
246/// Indices of slots inside [EbpfVm]
247pub enum RuntimeEnvironmentSlot {
248    /// [EbpfVm::host_stack_pointer]
249    HostStackPointer = offset_of!(EbpfVm<DummyContextObject>, host_stack_pointer) as isize,
250    /// [EbpfVm::call_depth]
251    CallDepth = offset_of!(EbpfVm<DummyContextObject>, call_depth) as isize,
252    /// [EbpfVm::context_object_pointer]
253    ContextObjectPointer = offset_of!(EbpfVm<DummyContextObject>, context_object_pointer) as isize,
254    /// [EbpfVm::previous_instruction_meter]
255    PreviousInstructionMeter =
256        offset_of!(EbpfVm<DummyContextObject>, previous_instruction_meter) as isize,
257    /// [EbpfVm::due_insn_count]
258    DueInsnCount = offset_of!(EbpfVm<DummyContextObject>, due_insn_count) as isize,
259    /// [EbpfVm::stopwatch_numerator]
260    StopwatchNumerator = offset_of!(EbpfVm<DummyContextObject>, stopwatch_numerator) as isize,
261    /// [EbpfVm::stopwatch_denominator]
262    StopwatchDenominator = offset_of!(EbpfVm<DummyContextObject>, stopwatch_denominator) as isize,
263    /// [EbpfVm::registers]
264    Registers = offset_of!(EbpfVm<DummyContextObject>, registers) as isize,
265    /// [EbpfVm::program_result]
266    ProgramResult = offset_of!(EbpfVm<DummyContextObject>, program_result) as isize,
267    /// [EbpfVm::memory_mapping]
268    MemoryMapping = offset_of!(EbpfVm<DummyContextObject>, memory_mapping) as isize,
269    /// [EbpfVm::register_trace]
270    RegisterTrace = offset_of!(EbpfVm<DummyContextObject>, register_trace) as isize,
271}
272
273/// A virtual machine to run eBPF programs.
274///
275/// # Examples
276///
277/// ```
278/// use solana_sbpf::{
279///     aligned_memory::AlignedMemory,
280///     ebpf,
281///     elf::Executable,
282///     memory_region::{MemoryMapping, MemoryRegion},
283///     program::{BuiltinProgram, FunctionRegistry, SBPFVersion},
284///     verifier::RequisiteVerifier,
285///     vm::{CallFrame, Config, EbpfVm, ExecutionMode},
286/// };
287/// use test_utils::TestContextObject;
288///
289/// let prog = &[
290///     0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // add64 r0, 0
291///     0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  // exit
292/// ];
293/// let mut mem: [u8; _] = [0xaa, 0xbb, 0x11, 0x22, 0xcc, 0xdd];
294///
295/// let loader = std::sync::Arc::new(BuiltinProgram::new_mock());
296/// let function_registry = FunctionRegistry::default();
297/// let mut executable = Executable::<TestContextObject>::from_text_bytes(prog, loader.clone(), SBPFVersion::V4, function_registry).unwrap();
298/// executable.verify::<RequisiteVerifier>().unwrap();
299/// let mut context_object = TestContextObject::new(2);
300/// let sbpf_version = executable.get_sbpf_version();
301///
302/// let mut stack = AlignedMemory::<{ebpf::HOST_ALIGN}>::zero_filled(executable.get_config().stack_size());
303/// let stack_len = stack.len();
304/// let mut heap = AlignedMemory::<{ebpf::HOST_ALIGN}>::with_capacity(0);
305///
306/// let regions: Vec<MemoryRegion> = vec![
307///     executable.get_ro_region(),
308///     MemoryRegion::new(&mut stack, ebpf::MM_STACK_START),
309///     MemoryRegion::new(&mut heap, ebpf::MM_HEAP_START),
310///     MemoryRegion::new(&raw mut mem, ebpf::MM_INPUT_START),
311/// ];
312///;
313/// context_object.memory_mapping = unsafe {
314///     MemoryMapping::new(regions, executable.get_config(), sbpf_version).unwrap()
315/// };
316///
317/// let mut vm = EbpfVm::new(loader, sbpf_version, &mut context_object, stack_len);
318///
319/// let mut call_frames = vec![CallFrame::default(); executable.get_config().max_call_depth];
320/// let (instruction_count, result) = vm.execute_program(
321///     &executable,
322///     &mut ExecutionMode::Interpreted,
323///     &mut call_frames,
324/// );
325/// assert_eq!(instruction_count, 2);
326/// assert_eq!(result.unwrap(), 0);
327/// ```
328#[repr(C)]
329pub struct EbpfVm<'a, C: ContextObject> {
330    /// Needed to exit from the guest back into the host
331    pub host_stack_pointer: *mut u64,
332    /// The current call depth.
333    ///
334    /// Incremented on calls and decremented on exits. It's used to enforce
335    /// config.max_call_depth and to know when to terminate execution.
336    pub call_depth: u64,
337    /// Pointer to ContextObject
338    pub(crate) context_object_pointer: ptr::NonNull<C>,
339    /// The lifetime for the context object pointer
340    context_object_lifetime: PhantomData<&'a mut C>,
341    /// Last return value of instruction_meter.get_remaining()
342    pub previous_instruction_meter: u64,
343    /// Outstanding value to instruction_meter.consume()
344    pub due_insn_count: u64,
345    /// CPU cycles accumulated by the stop watch
346    pub stopwatch_numerator: u64,
347    /// Number of times the stop watch was used
348    pub stopwatch_denominator: u64,
349    /// Registers inlined
350    pub registers: [u64; 12],
351    /// ProgramResult inlined
352    pub program_result: ProgramResult,
353    /// MemoryMapping inlined
354    pub(crate) memory_mapping: ptr::NonNull<MemoryMapping>,
355    /// Loader built-in program
356    pub loader: Arc<BuiltinProgram<C>>,
357    /// Collector for the instruction trace
358    pub register_trace: Vec<RegisterTraceEntry>,
359    /// TCP port for the debugger interface
360    #[cfg(feature = "debugger")]
361    pub debug_port: Option<u16>,
362    /// Debug metadata passed
363    #[cfg(feature = "debugger")]
364    pub debug_metadata: Option<String>,
365}
366
367impl<'a, C: ContextObject> EbpfVm<'a, C> {
368    /// Creates a new virtual machine instance.
369    pub fn new(
370        loader: Arc<BuiltinProgram<C>>,
371        sbpf_version: SBPFVersion,
372        context_object: &'a mut C,
373        stack_len: usize,
374    ) -> Self {
375        let config = loader.get_config();
376        let mut registers = [0u64; 12];
377        registers[ebpf::FRAME_PTR_REG] =
378            ebpf::MM_STACK_START.saturating_add(if !sbpf_version.manual_stack_frame_bump() {
379                config.stack_frame_size
380            } else {
381                stack_len
382            } as u64);
383
384        let memory_mapping = context_object.active_mapping_ptr();
385        EbpfVm {
386            host_stack_pointer: std::ptr::null_mut(),
387            call_depth: 0,
388            context_object_pointer: ptr::NonNull::from_mut(context_object),
389            context_object_lifetime: PhantomData,
390            previous_instruction_meter: 0,
391            due_insn_count: 0,
392            stopwatch_numerator: 0,
393            stopwatch_denominator: 0,
394            registers,
395            program_result: ProgramResult::Ok(0),
396            memory_mapping,
397            loader,
398            #[cfg(feature = "debugger")]
399            debug_port: std::env::var("VM_DEBUG_PORT")
400                .ok()
401                .and_then(|v| v.parse::<u16>().ok()),
402            #[cfg(feature = "debugger")]
403            debug_metadata: None,
404            register_trace: Vec::default(),
405        }
406    }
407
408    /// Execute the program
409    ///
410    /// Use `mode` parameter to request a specific execution type. This function will write back
411    /// the execution mode used back to the reference passed in.
412    ///
413    /// It is required to provide `call_frames` when executing in interpreted mode.
414    /// `call_frames` must be large enough to hold the executable config's `max_call_depth`
415    /// frames.
416    ///
417    /// Returns the instruction meter count (CUs) and the execution result of the program.
418    pub fn execute_program(
419        &mut self,
420        executable: &Executable<C>,
421        mode: &mut ExecutionMode,
422        call_frames: &mut [CallFrame],
423    ) -> (u64, ProgramResult) {
424        debug_assert!(Arc::ptr_eq(&self.loader, executable.get_loader()));
425        self.registers[11] = executable.get_entrypoint_instruction_offset() as u64;
426        let config = executable.get_config();
427        let initial_insn_count = self.context().get_remaining();
428        self.previous_instruction_meter = initial_insn_count;
429        self.due_insn_count = 0;
430        self.program_result = ProgramResult::Ok(0);
431
432        'execute: {
433            match *mode {
434                ExecutionMode::Interpreted => {}
435
436                #[cfg(all(feature = "jit", not(target_os = "windows"), target_arch = "x86_64"))]
437                ExecutionMode::PreferJit => {
438                    if let Some(compiled_program) = executable.get_compiled_program() {
439                        *mode = ExecutionMode::Jit;
440                        break 'execute compiled_program.invoke(config, self, self.registers);
441                    }
442                }
443                #[cfg(not(all(
444                    feature = "jit",
445                    not(target_os = "windows"),
446                    target_arch = "x86_64"
447                )))]
448                ExecutionMode::PreferJit => {}
449
450                #[cfg(all(feature = "jit", not(target_os = "windows"), target_arch = "x86_64"))]
451                ExecutionMode::Jit => {
452                    let Some(compiled_program) = executable.get_compiled_program() else {
453                        return (0, ProgramResult::Err(EbpfError::JitNotCompiled));
454                    };
455                    *mode = ExecutionMode::Jit;
456                    break 'execute compiled_program.invoke(config, self, self.registers);
457                }
458                #[cfg(not(all(
459                    feature = "jit",
460                    not(target_os = "windows"),
461                    target_arch = "x86_64"
462                )))]
463                ExecutionMode::Jit => return (0, ProgramResult::Err(EbpfError::JitNotCompiled)),
464            }
465
466            *mode = ExecutionMode::Interpreted;
467            let interpreter = Interpreter::new(self, executable, self.registers, call_frames);
468            break 'execute run_interpreter(interpreter);
469        }
470
471        let instruction_count = if config.enable_instruction_meter {
472            let due_insn_count = self.due_insn_count;
473            let context = self.context();
474            context.consume(due_insn_count);
475            initial_insn_count.saturating_sub(context.get_remaining())
476        } else {
477            0
478        };
479        let mut result = ProgramResult::Ok(0);
480        std::mem::swap(&mut result, &mut self.program_result);
481        (instruction_count, result)
482    }
483
484    /// Invokes a built-in function
485    pub fn invoke_function(&mut self, function: BuiltinFunction<C>) {
486        function(
487            self.encrypted_host_address(),
488            self.registers[1],
489            self.registers[2],
490            self.registers[3],
491            self.registers[4],
492            self.registers[5],
493        );
494    }
495
496    /// Build a `VmAddress` containing a (potentially) encrypted host pointer to self.
497    ///
498    /// Note that this type is effectively a mutable pointer to `self` and although it valid to
499    /// create multiple of these addresses, using them to violate the Rust mutable references'
500    /// uniqueness rule is not sound.
501    pub(crate) fn encrypted_host_address(&mut self) -> EncryptedHostAddressToEbpfVm<C> {
502        let addr = (&raw mut *self).expose_provenance() as isize;
503        EncryptedHostAddressToEbpfVm(
504            addr.wrapping_add(get_runtime_environment_key() as isize) as usize as u64,
505            PhantomData,
506        )
507    }
508
509    /// Get a reference to the context object referenced by this EbpfVm.
510    pub fn context(&mut self) -> &mut C {
511        // SAFETY: we've the unique reference to self here, so there can't be other live references
512        // to `C` either, whether via the memory_mapping or the context_object_pointer itself.
513        //
514        // The `context_object_pointer` is pointing at a valid-to-dereference `C` at all times
515        // through the EbpfVm lifetime.
516        //
517        // Note: for that reason we are intentionally tying the lifetime of the returned `C` to the
518        // lifetime of `&mut self`, rather than returning `&'a mut C`, which would allow aliasing
519        // the returned reference.
520        unsafe { self.context_object_pointer.as_mut() }
521    }
522
523    // Intentionally not public. Users are expected to store their memory mapping inside – and
524    // access from – C.
525    pub(crate) fn memory(&mut self) -> &mut MemoryMapping {
526        // SAFETY: we've the unique reference to self here, so there can't be other live references
527        // to `C` either, whether via the memory_mapping or the context_object_pointer itself.
528        //
529        // The `context_object_pointer` is pointing at a valid-to-dereference `C` at all times
530        // through the EbpfVm lifetime.
531        unsafe { self.memory_mapping.as_mut() }
532    }
533}
534
535/// Encrypted address to the [`EbpfVm`] object.
536#[repr(transparent)]
537pub struct EncryptedHostAddressToEbpfVm<C>(
538    // This ends up having to be public to the crate because inline assembly wants to deal with
539    // integers, not `VmAddress` (even though VmAddress has the same layout.)
540    pub(crate) u64,
541    PhantomData<C>,
542);
543
544impl<C: ContextObject> EncryptedHostAddressToEbpfVm<C> {
545    /// Work on [`EbpfVm`] pointed to by this address.
546    ///
547    /// ## Safety
548    ///
549    /// Multiple concurrently live addresses can reference the same [`EbpfVm`] but under no
550    /// circumstances may they be used to create multiple concurrent mutable references to the
551    /// `EbpfVm`.
552    pub unsafe fn with_vm<R>(&mut self, cb: impl FnOnce(&mut EbpfVm<'_, C>) -> R) -> R {
553        let addr = (self.0 as usize as isize)
554            .wrapping_sub(crate::vm::get_runtime_environment_key() as isize);
555        // SAFETY: we've recovered the same pointer address as that of the reference used to
556        // produce this offset address in the first place.
557        // SAFETY: The mutable reference is unique due to invariant being passed onto the caller.
558        let vm = unsafe {
559            std::ptr::with_exposed_provenance_mut::<crate::vm::EbpfVm<C>>(addr as usize)
560                .as_mut()
561                .unwrap()
562        };
563        cb(vm)
564    }
565}
566
567#[cold]
568#[inline(never)]
569fn run_interpreter<C: ContextObject>(mut interpreter: Interpreter<C>) {
570    #[cfg(feature = "debugger")]
571    if let Some(debug_port) = interpreter.vm.debug_port {
572        return crate::debugger::execute(&mut interpreter, debug_port);
573    }
574
575    while interpreter.step() {}
576    interpreter.vm.registers[11] = interpreter.reg[11];
577}
578
579#[cfg(test)]
580mod tests {
581    use crate::{
582        memory_region::MemoryMapping,
583        program::{BuiltinProgram, SBPFVersion},
584        vm::{Config, ContextObject, RuntimeEnvironmentSlot},
585    };
586    use std::{ptr::NonNull, sync::Arc};
587
588    #[test]
589    fn test_runtime_environment_slots() {
590        struct DummyContextObject(MemoryMapping);
591        impl ContextObject for DummyContextObject {
592            fn consume(&mut self, _: u64) {
593                todo!()
594            }
595            fn get_remaining(&self) -> u64 {
596                todo!()
597            }
598            fn active_mapping_ptr(&mut self) -> NonNull<MemoryMapping> {
599                NonNull::from_mut(&mut self.0)
600            }
601        }
602        let version = SBPFVersion::V4;
603        let config = Config::default();
604        let mut context_object =
605            unsafe { DummyContextObject(MemoryMapping::new(vec![], &config, version).unwrap()) };
606        let env = super::EbpfVm::new(
607            Arc::new(BuiltinProgram::new_mock()),
608            version,
609            &mut context_object,
610            4096,
611        );
612
613        macro_rules! check_slot {
614            ($env:expr, $entry:ident, $slot:ident) => {
615                assert_eq!(
616                    unsafe {
617                        std::ptr::addr_of!($env.$entry)
618                            .cast::<u8>()
619                            .offset_from(std::ptr::addr_of!($env).cast::<u8>()) as usize
620                    },
621                    RuntimeEnvironmentSlot::$slot as usize,
622                );
623            };
624        }
625
626        check_slot!(env, host_stack_pointer, HostStackPointer);
627        check_slot!(env, call_depth, CallDepth);
628        check_slot!(env, context_object_pointer, ContextObjectPointer);
629        check_slot!(env, previous_instruction_meter, PreviousInstructionMeter);
630        check_slot!(env, due_insn_count, DueInsnCount);
631        check_slot!(env, stopwatch_numerator, StopwatchNumerator);
632        check_slot!(env, stopwatch_denominator, StopwatchDenominator);
633        check_slot!(env, registers, Registers);
634        check_slot!(env, program_result, ProgramResult);
635        check_slot!(env, memory_mapping, MemoryMapping);
636        check_slot!(env, register_trace, RegisterTrace);
637    }
638}