Skip to main content

solana_program_runtime/
vm.rs

1//! SBF virtual machine provisioning and execution.
2
3#[cfg(feature = "svm-internal")]
4use qualifier_attr::qualifiers;
5use {
6    crate::{
7        execution_budget::MAX_INSTRUCTION_STACK_DEPTH_SIMD_0268,
8        invoke_context::{BpfAllocator, InvokeContext},
9        mem_pool::VmMemoryPool,
10        memory_context::{MemoryContext, SerializedAccountMetadata},
11        program_cache_entry::ProgramCacheEntry,
12        serialization, stable_log,
13    },
14    solana_instruction::error::InstructionError,
15    solana_program_entrypoint::{MAX_PERMITTED_DATA_INCREASE, SUCCESS},
16    solana_sbpf::{
17        ebpf::{self, MM_HEAP_START, MM_STACK_START},
18        elf::Executable,
19        error::{EbpfError, ProgramResult},
20        memory_region::{AccessType, MemoryMapping, MemoryRegion},
21        vm::{ContextObject, EbpfVm, ExecutionMode},
22    },
23    solana_sdk_ids::bpf_loader_deprecated,
24    solana_svm_log_collector::ic_logger_msg,
25    solana_svm_measure::measure::Measure,
26    solana_transaction_context::IndexOfAccount,
27    std::{cell::RefCell, mem, time::Duration},
28};
29
30thread_local! {
31    pub static MEMORY_POOL: RefCell<VmMemoryPool> = RefCell::new(VmMemoryPool::new());
32}
33
34/// Only used in macro, do not use directly!
35pub fn calculate_heap_cost(heap_size: u32, heap_cost: u64) -> u64 {
36    const KIBIBYTE: u64 = 1024;
37    const PAGE_SIZE_KB: u64 = 32;
38    let mut rounded_heap_size = u64::from(heap_size);
39    rounded_heap_size =
40        rounded_heap_size.saturating_add(PAGE_SIZE_KB.saturating_mul(KIBIBYTE).saturating_sub(1));
41    rounded_heap_size
42        .checked_div(PAGE_SIZE_KB.saturating_mul(KIBIBYTE))
43        .expect("PAGE_SIZE_KB * KIBIBYTE > 0")
44        .saturating_sub(1)
45        .saturating_mul(heap_cost)
46}
47
48/// Only used in macro, do not use directly!
49///
50/// # Safety
51///
52/// Refer to [`configure_program_regions`].
53#[cfg_attr(feature = "svm-internal", qualifiers(pub))]
54pub unsafe fn create_vm<'a, 'b>(
55    program: &'a Executable<InvokeContext<'b, 'b>>,
56    invoke_context: &'a mut InvokeContext<'b, 'b>,
57    stack: *mut [u8],
58    heap: *mut [u8],
59) -> Result<EbpfVm<'a, InvokeContext<'b, 'b>>, Box<dyn std::error::Error>> {
60    let stack_size = stack.len();
61    unsafe {
62        // SAFETY: invariants delegated to the caller.
63        configure_program_regions(invoke_context, program, stack, heap)?;
64    }
65    Ok(EbpfVm::new(
66        program.get_loader().clone(),
67        program.get_sbpf_version(),
68        invoke_context,
69        stack_size,
70    ))
71}
72
73/// # Safety
74///
75/// The `executable`, `stack` and `heap` arguments must remain allocated for at least the lifetime
76/// of [`MemoryMapping`] (or until after the `MemoryMapping` is reconfigured with different
77/// `executable`, `stack` and `heap`).
78unsafe fn configure_program_regions<C: ContextObject>(
79    invoke_context: &mut InvokeContext,
80    executable: &Executable<C>,
81    stack: *mut [u8],
82    heap: *mut [u8],
83) -> Result<(), Box<dyn std::error::Error>> {
84    let mapping = invoke_context.memory_contexts.memory_mapping_mut()?;
85    let regions = mapping.get_regions_mut();
86    let [ro_area, stack_area, heap_area, ..] = regions else {
87        panic!("the regions vector must have at least three entries")
88    };
89    *ro_area = executable.get_ro_region();
90    let sbpf_version = executable.get_sbpf_version();
91    let config = executable.get_config();
92    *stack_area = MemoryRegion::new_gapped(
93        stack,
94        MM_STACK_START,
95        if sbpf_version.stack_frame_gaps() && config.enable_stack_frame_gaps {
96            config.stack_frame_size as u64
97        } else {
98            0
99        },
100    );
101    *heap_area = MemoryRegion::new(heap, MM_HEAP_START);
102    mapping
103        .initialize()
104        .map_err(|err| Box::new(err) as Box<dyn std::error::Error>)
105}
106
107/// Create the SBF virtual machine
108#[macro_export]
109macro_rules! create_vm {
110    ($vm:ident, $program:expr, $invoke_context:expr $(,)?) => {
111        let invoke_context = &*$invoke_context;
112        let stack_size = $program.get_config().stack_size();
113        let heap_size = invoke_context.get_compute_budget().heap_size;
114        let heap_cost_result =
115            invoke_context
116                .compute_meter
117                .consume_checked($crate::__private::calculate_heap_cost(
118                    heap_size,
119                    invoke_context.get_execution_cost().heap_cost,
120                ));
121        let $vm = heap_cost_result.and_then(|_| {
122            let (mut stack, mut heap) = $crate::__private::MEMORY_POOL
123                .with_borrow_mut(|pool| (pool.get_stack(stack_size), pool.get_heap(heap_size)));
124            let vm = $crate::__private::create_vm(
125                $program,
126                $invoke_context,
127                stack
128                    .as_slice_mut()
129                    .get_mut(..stack_size)
130                    .expect("invalid stack size"),
131                heap.as_slice_mut()
132                    .get_mut(..heap_size as usize)
133                    .expect("invalid heap size"),
134            );
135            vm.map(|vm| (vm, stack, heap))
136        });
137    };
138}
139
140/// # Safety
141///
142/// The [`MemoryRegion`]s must satisfy the safety preconditions for
143/// [`MemoryMapping::new_uninitialized`].
144unsafe fn set_memory_context<'b>(
145    additional_initialized_regions: Vec<MemoryRegion>,
146    accounts_metadata: Vec<SerializedAccountMetadata>,
147    invoke_context: &mut InvokeContext<'b, 'b>,
148    executable: &Executable<InvokeContext<'b, 'b>>,
149    virtual_address_space_adjustments: bool,
150    account_data_direct_mapping: bool,
151) -> Result<(), Box<dyn std::error::Error>> {
152    let heap_size = invoke_context.get_compute_budget().heap_size;
153    let regions = vec![MemoryRegion::default(); 3]
154        .into_iter()
155        .chain(additional_initialized_regions)
156        .collect();
157    let memory_mapping = unsafe {
158        // SAFETY: all memory regions are `default` (and thus implicitly valid) or valid by
159        // delegating the safety invariant upon the caller.
160        MemoryMapping::new_uninitialized(
161            regions,
162            executable.get_config(),
163            executable.get_sbpf_version(),
164            invoke_context.transaction_context.access_violation_handler(
165                virtual_address_space_adjustments,
166                account_data_direct_mapping,
167            ),
168        )
169    };
170
171    invoke_context
172        .memory_contexts
173        .set_memory_context_abi_v1(MemoryContext::new(
174            BpfAllocator::new(heap_size as u64),
175            accounts_metadata,
176            memory_mapping,
177        ))
178        .map_err(|err| Box::new(err) as Box<dyn std::error::Error>)
179}
180
181#[cfg_attr(feature = "svm-internal", qualifiers(pub))]
182pub fn execute<'a, 'b: 'a>(
183    executable: &'a Executable<InvokeContext<'static, 'static>>,
184    invoke_context: &'a mut InvokeContext<'b, 'b>,
185    cache_entry: &ProgramCacheEntry,
186) -> Result<(), Box<dyn std::error::Error>> {
187    // We dropped the lifetime tracking in the Executor by setting it to 'static,
188    // thus we need to reintroduce the correct lifetime of InvokeContext here again.
189    let executable = unsafe {
190        mem::transmute::<
191            &'a Executable<InvokeContext<'static, 'static>>,
192            &'a Executable<InvokeContext<'b, 'b>>,
193        >(executable)
194    };
195    let log_collector = invoke_context.get_log_collector();
196    let transaction_context = &invoke_context.transaction_context;
197    let instruction_context = transaction_context.get_current_instruction_context()?;
198    let program_id = *instruction_context.get_program_key()?;
199    let is_loader_deprecated =
200        instruction_context.get_program_owner()? == bpf_loader_deprecated::id();
201    let virtual_address_space_adjustments = invoke_context
202        .get_feature_set()
203        .virtual_address_space_adjustments;
204    let account_data_direct_mapping = invoke_context.get_feature_set().account_data_direct_mapping;
205    let direct_account_pointers_in_program_input = invoke_context
206        .get_feature_set()
207        .direct_account_pointers_in_program_input;
208
209    let mut serialize_time = Measure::start("serialize");
210    let (parameter_bytes, regions, accounts_metadata, instruction_data_offset) =
211        serialization::serialize_parameters(
212            &instruction_context,
213            virtual_address_space_adjustments,
214            account_data_direct_mapping,
215            direct_account_pointers_in_program_input,
216        )?;
217    serialize_time.stop();
218
219    // save the account addresses so in case we hit an AccessViolation error we
220    // can map to a more specific error
221    let account_region_addrs = accounts_metadata
222        .iter()
223        .map(|m| {
224            let vm_end = m
225                .vm_data_addr
226                .saturating_add(m.original_data_len as u64)
227                .saturating_add(if !is_loader_deprecated {
228                    MAX_PERMITTED_DATA_INCREASE as u64
229                } else {
230                    0
231                });
232            m.vm_data_addr..vm_end
233        })
234        .collect::<Vec<_>>();
235
236    #[cfg(feature = "sbpf-debugger")]
237    let (debug_port, debug_metadata) = if invoke_context.debug_port.is_some() {
238        (
239            invoke_context.debug_port,
240            Some(format!(
241                "program_id={};cpi_level={};caller={}",
242                program_id,
243                instruction_context.get_stack_height().saturating_sub(1),
244                invoke_context
245                    .get_stack_height()
246                    .checked_sub(2)
247                    .and_then(|nesting_level| {
248                        transaction_context
249                            .get_instruction_context_at_nesting_level(nesting_level)
250                            .ok()
251                    })
252                    .and_then(|ctx| ctx.get_program_key().ok())
253                    .map(|key| key.to_string())
254                    .unwrap_or_else(|| "none".into())
255            )),
256        )
257    } else {
258        (None, None)
259    };
260
261    let mut create_vm_time = Measure::start("create_vm");
262    unsafe {
263        // SAFETY: The memory pointed to by regions is valid for the useful lifetime of
264        // `invoke_context`, which in turn contains the `MemoryMapping` that allows access to this
265        // memory.
266        set_memory_context(
267            regions,
268            accounts_metadata,
269            invoke_context,
270            executable,
271            virtual_address_space_adjustments,
272            account_data_direct_mapping,
273        )?
274    };
275
276    let execution_result = {
277        let mut execution_mode = ExecutionMode::PreferJit;
278
279        #[cfg(feature = "sbpf-debugger")]
280        if invoke_context.debug_port.is_some() {
281            execution_mode = ExecutionMode::Interpreted;
282        }
283
284        let compute_meter_prev = invoke_context.get_remaining();
285        let (mut vm, stack, heap) = unsafe {
286            // SAFETY: The `stack`, `heap` and `executable` live past the lifetime of
287            // `invoke_context`.
288            create_vm!(vm, executable, invoke_context);
289            match vm {
290                Ok(info) => info,
291                Err(e) => {
292                    ic_logger_msg!(log_collector, "Failed to create SBF VM: {}", e);
293                    return Err(Box::new(InstructionError::ProgramEnvironmentSetupFailure));
294                }
295            }
296        };
297
298        create_vm_time.stop();
299        #[cfg(feature = "sbpf-debugger")]
300        {
301            vm.debug_port = debug_port;
302            vm.debug_metadata = debug_metadata;
303        }
304
305        let execute_time = Measure::start("execute");
306        let prev_nested_exec_time = vm.context().total_nested_exec_time;
307
308        vm.registers[1] = ebpf::MM_INPUT_START;
309        vm.registers[2] = instruction_data_offset as u64;
310        let mut call_frames =
311            MEMORY_POOL.with_borrow_mut(|memory_pool| memory_pool.get_call_frames());
312        let (compute_units_consumed, result) =
313            vm.execute_program(executable, &mut execution_mode, &mut call_frames);
314        let register_trace = std::mem::take(&mut vm.register_trace);
315        MEMORY_POOL.with_borrow_mut(|memory_pool| {
316            memory_pool.put_stack(stack);
317            memory_pool.put_heap(heap);
318            memory_pool.put_call_frames(call_frames);
319            debug_assert!(memory_pool.stack_len() <= MAX_INSTRUCTION_STACK_DEPTH_SIMD_0268);
320            debug_assert!(memory_pool.heap_len() <= MAX_INSTRUCTION_STACK_DEPTH_SIMD_0268);
321        });
322        drop(vm);
323        invoke_context.insert_register_trace(register_trace);
324
325        // This section is a little convoluted due to the nested and sibling (CPI) invocations.
326        let total_execute_ns = execute_time.end_as_ns();
327        let nested_execution_time_delta = invoke_context
328            .total_nested_exec_time
329            .saturating_sub(prev_nested_exec_time);
330        let this_call_ns =
331            total_execute_ns.saturating_sub(nested_execution_time_delta.as_nanos() as u64);
332        invoke_context.total_nested_exec_time = invoke_context
333            .total_nested_exec_time
334            .saturating_add(Duration::from_nanos(this_call_ns));
335        let this_call_us = this_call_ns / 1000;
336        invoke_context.timings.execute_us += this_call_us;
337        match execution_mode {
338            ExecutionMode::Interpreted => cache_entry.stats.interpreter_executed(this_call_us),
339            ExecutionMode::Jit => cache_entry.stats.jit_executed(this_call_us),
340            ExecutionMode::PreferJit => { /* not actually executed? */ }
341        }
342
343        ic_logger_msg!(
344            log_collector,
345            "Program {} consumed {} of {} compute units",
346            &program_id,
347            compute_units_consumed,
348            compute_meter_prev
349        );
350        let (_returned_from_program_id, return_data) =
351            invoke_context.transaction_context.get_return_data();
352        if !return_data.is_empty() {
353            stable_log::program_return(&log_collector, &program_id, return_data);
354        }
355        match result {
356            ProgramResult::Ok(status) if status != SUCCESS => {
357                let error: InstructionError = status.into();
358                Err(Box::new(error) as Box<dyn std::error::Error>)
359            }
360            ProgramResult::Err(mut error) => {
361                // Don't clean me up!!
362                // This feature is active on all networks, but we still toggle
363                // it off during fuzzing.
364                if invoke_context
365                    .get_feature_set()
366                    .deplete_cu_meter_on_vm_failure
367                    && !matches!(error, EbpfError::SyscallError(_))
368                {
369                    // when an exception is thrown during the execution of a
370                    // Basic Block (e.g., a null memory dereference or other
371                    // faults), determining the exact number of CUs consumed
372                    // up to the point of failure requires additional effort
373                    // and is unnecessary since these cases are rare.
374                    //
375                    // In order to simplify CU tracking, simply consume all
376                    // remaining compute units so that the block cost
377                    // tracker uses the full requested compute unit cost for
378                    // this failed transaction.
379                    invoke_context.consume(invoke_context.get_remaining());
380                }
381
382                if virtual_address_space_adjustments {
383                    if let EbpfError::SyscallError(err) = error {
384                        error = err
385                            .downcast::<EbpfError>()
386                            .map(|err| *err)
387                            .unwrap_or_else(EbpfError::SyscallError);
388                    }
389                    if let EbpfError::AccessViolation(access_type, vm_addr, len, _section_name) =
390                        error
391                    {
392                        // If virtual_address_space_adjustments is enabled and a program tries to write to a readonly
393                        // region we'll get a memory access violation. Map it to a more specific
394                        // error so it's easier for developers to see what happened.
395                        if let Some((instruction_account_index, vm_addr_range)) =
396                            account_region_addrs
397                                .iter()
398                                .enumerate()
399                                .find(|(_, vm_addr_range)| vm_addr_range.contains(&vm_addr))
400                        {
401                            let transaction_context = &invoke_context.transaction_context;
402                            let instruction_context =
403                                transaction_context.get_current_instruction_context()?;
404                            let account = instruction_context.try_borrow_instruction_account(
405                                instruction_account_index as IndexOfAccount,
406                            )?;
407                            if vm_addr.saturating_add(len) <= vm_addr_range.end {
408                                // The access was within the range of the accounts address space,
409                                // but it might not be within the range of the actual data.
410                                let is_access_outside_of_data = vm_addr
411                                    .saturating_add(len)
412                                    .saturating_sub(vm_addr_range.start)
413                                    as usize
414                                    > account.get_data().len();
415                                error = EbpfError::SyscallError(Box::new(match access_type {
416                                    AccessType::Store => {
417                                        if let Err(err) = account.can_data_be_changed() {
418                                            err
419                                        } else {
420                                            // The store was allowed but failed,
421                                            // thus it must have been an attempt to grow the account.
422                                            debug_assert!(is_access_outside_of_data);
423                                            InstructionError::InvalidRealloc
424                                        }
425                                    }
426                                    AccessType::Load => {
427                                        // Loads should only fail when they are outside of the account data.
428                                        debug_assert!(is_access_outside_of_data);
429                                        if account.can_data_be_changed().is_err() {
430                                            // Load beyond readonly account data happened because the program
431                                            // expected more data than there actually is.
432                                            InstructionError::AccountDataTooSmall
433                                        } else {
434                                            // Load beyond writable account data also attempted to grow.
435                                            InstructionError::InvalidRealloc
436                                        }
437                                    }
438                                }));
439                            }
440                        }
441                    }
442                }
443                Err(if let EbpfError::SyscallError(err) = error {
444                    err
445                } else {
446                    error.into()
447                })
448            }
449            _ => Ok(()),
450        }
451    };
452
453    fn deserialize_parameters(
454        invoke_context: &mut InvokeContext,
455        parameter_bytes: &[u8],
456        virtual_address_space_adjustments: bool,
457        account_data_direct_mapping: bool,
458    ) -> Result<(), InstructionError> {
459        serialization::deserialize_parameters(
460            &invoke_context
461                .transaction_context
462                .get_current_instruction_context()?,
463            virtual_address_space_adjustments,
464            account_data_direct_mapping,
465            parameter_bytes,
466            &invoke_context
467                .memory_contexts
468                .memory_context_abi_v1()?
469                .accounts_metadata,
470        )
471    }
472
473    let mut deserialize_time = Measure::start("deserialize");
474    let execute_or_deserialize_result = execution_result.and_then(|_| {
475        deserialize_parameters(
476            invoke_context,
477            parameter_bytes.as_slice(),
478            virtual_address_space_adjustments,
479            account_data_direct_mapping,
480        )
481        .map_err(|error| Box::new(error) as Box<dyn std::error::Error>)
482    });
483    deserialize_time.stop();
484
485    // Update the timings
486    invoke_context.timings.serialize_us += serialize_time.as_us();
487    invoke_context.timings.create_vm_us += create_vm_time.as_us();
488    invoke_context.timings.deserialize_us += deserialize_time.as_us();
489
490    execute_or_deserialize_result
491}