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