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