Skip to main content

solana_sbpf/
program.rs

1//! Common interface for built-in and user supplied programs
2use {
3    crate::{
4        ebpf,
5        elf::ElfError,
6        vm::{Config, ContextObject, EncryptedHostAddressToEbpfVm},
7    },
8    std::collections::{btree_map::Entry, BTreeMap},
9};
10
11/// Defines a set of sbpf_version of an executable
12#[derive(Debug, PartialEq, PartialOrd, Eq, Clone, Copy)]
13pub enum SBPFVersion {
14    /// The legacy format
15    V0,
16    /// SIMD-0166
17    V1,
18    /// SIMD-0174, SIMD-0173
19    V2,
20    /// SIMD-0178, SIMD-0189, SIMD-0377
21    V3,
22    /// SIMD-0177
23    V4,
24    /// Used for future versions
25    Reserved,
26}
27
28impl SBPFVersion {
29    /// Enable SIMD-0166: SBPF dynamic stack frames
30    ///
31    /// Allows usage of `add64 r10, imm`.
32    pub fn manual_stack_frame_bump(self) -> bool {
33        self == SBPFVersion::V1 || self == SBPFVersion::V2
34    }
35    /// ... SIMD-0166
36    pub fn stack_frame_gaps(self) -> bool {
37        self == SBPFVersion::V0
38    }
39
40    /// Enable SIMD-0174: SBPF arithmetics improvements
41    pub fn enable_pqr(self) -> bool {
42        self == SBPFVersion::V2
43    }
44    /// ... SIMD-0174
45    pub fn explicit_sign_extension_of_results(self) -> bool {
46        self == SBPFVersion::V2
47    }
48    /// ... SIMD-0174
49    pub fn swap_sub_reg_imm_operands(self) -> bool {
50        self == SBPFVersion::V2
51    }
52    /// ... SIMD-0174
53    pub fn disable_neg(self) -> bool {
54        self == SBPFVersion::V2
55    }
56
57    /// Enable SIMD-0173: SBPF instruction encoding improvements
58    pub fn callx_uses_src_reg(self) -> bool {
59        self == SBPFVersion::V2
60    }
61    /// ... SIMD-0173
62    pub fn disable_lddw(self) -> bool {
63        self == SBPFVersion::V2
64    }
65    /// ... SIMD-0173
66    pub fn disable_le(self) -> bool {
67        self == SBPFVersion::V2
68    }
69    /// ... SIMD-0173
70    pub fn move_memory_instruction_classes(self) -> bool {
71        self == SBPFVersion::V2
72    }
73
74    /// Enable SIMD-0178: SBPF Static Syscalls
75    pub fn static_syscalls(self) -> bool {
76        self >= SBPFVersion::V3
77    }
78    /// Enable SIMD-0189: SBPF stricter ELF headers
79    pub fn enable_stricter_elf_headers(self) -> bool {
80        self >= SBPFVersion::V3
81    }
82    /// ... SIMD-0189
83    pub fn enable_lower_rodata_vaddr(self) -> bool {
84        self >= SBPFVersion::V3
85    }
86    /// ... SIMD-0377
87    pub fn enable_jmp32(self) -> bool {
88        self >= SBPFVersion::V3
89    }
90    /// ... SIMD-0377
91    pub fn callx_uses_dst_reg(self) -> bool {
92        self >= SBPFVersion::V3
93    }
94
95    /// Calculate the target program counter for a CALL_IMM instruction depending on
96    /// the SBPF version.
97    pub fn calculate_call_imm_target_pc(self, pc: usize, imm: i64) -> u32 {
98        if self.static_syscalls() {
99            (pc as i64).saturating_add(imm).saturating_add(1) as u32
100        } else {
101            imm as u32
102        }
103    }
104}
105
106/// Holds the function symbols of an Executable
107#[derive(Debug, PartialEq, Eq)]
108pub struct FunctionRegistry<T> {
109    pub(crate) map: BTreeMap<u32, (Vec<u8>, T)>,
110}
111
112impl<T> Default for FunctionRegistry<T> {
113    fn default() -> Self {
114        Self {
115            map: BTreeMap::new(),
116        }
117    }
118}
119
120impl<T: Copy + PartialEq> FunctionRegistry<T> {
121    /// Register a symbol with an explicit key
122    pub fn register_function(
123        &mut self,
124        key: u32,
125        name: impl Into<Vec<u8>>,
126        value: T,
127    ) -> Result<(), ElfError> {
128        match self.map.entry(key) {
129            Entry::Vacant(entry) => {
130                entry.insert((name.into(), value));
131            }
132            Entry::Occupied(entry) => {
133                if entry.get().1 != value {
134                    return Err(ElfError::SymbolHashCollision(key));
135                }
136            }
137        }
138        Ok(())
139    }
140
141    /// Used for transitioning from SBPFv0 to SBPFv3
142    pub(crate) fn register_function_hashed_legacy<C: ContextObject>(
143        &mut self,
144        loader: &BuiltinProgram<C>,
145        hash_symbol_name: bool,
146        name: impl Into<Vec<u8>>,
147        value: T,
148    ) -> Result<u32, ElfError>
149    where
150        usize: From<T>,
151    {
152        let name = name.into();
153        let config = loader.get_config();
154        let key = if hash_symbol_name {
155            let hash = if name == b"entrypoint" {
156                ebpf::hash_symbol_name(b"entrypoint")
157            } else {
158                ebpf::hash_symbol_name(&usize::from(value).to_le_bytes())
159            };
160            if loader.get_function_registry().lookup_by_key(hash).is_some() {
161                return Err(ElfError::SymbolHashCollision(hash));
162            }
163            hash
164        } else {
165            usize::from(value) as u32
166        };
167        self.register_function(
168            key,
169            if config.enable_symbol_and_section_labels || name == b"entrypoint" {
170                name
171            } else {
172                Vec::default()
173            },
174            value,
175        )?;
176        Ok(key)
177    }
178
179    /// Unregister a symbol again
180    pub fn unregister_function(&mut self, key: u32) -> bool {
181        self.map.remove(&key).is_some()
182    }
183
184    /// Iterate over all keys
185    pub fn keys(&self) -> impl Iterator<Item = u32> + '_ {
186        self.map.keys().copied()
187    }
188
189    /// Iterate over all entries
190    pub fn iter(&self) -> impl Iterator<Item = (u32, (&[u8], T))> + '_ {
191        self.map
192            .iter()
193            .map(|(key, (name, value))| (*key, (name.as_slice(), *value)))
194    }
195
196    /// Get a function by its key
197    pub fn lookup_by_key(&self, key: u32) -> Option<(&[u8], T)> {
198        // String::from_utf8_lossy(function_name).as_str()
199        self.map
200            .get(&key)
201            .map(|(function_name, value)| (function_name.as_slice(), *value))
202    }
203
204    /// Get a function by its name
205    pub fn lookup_by_name(&self, name: &[u8]) -> Option<(&[u8], T)> {
206        self.map
207            .values()
208            .find(|(function_name, _value)| function_name == name)
209            .map(|(function_name, value)| (function_name.as_slice(), *value))
210    }
211
212    /// Calculate memory size
213    pub fn mem_size(&self) -> usize {
214        std::mem::size_of::<Self>().saturating_add(self.map.iter().fold(
215            0,
216            |state: usize, (_, (name, value))| {
217                state.saturating_add(
218                    std::mem::size_of_val(value).saturating_add(
219                        std::mem::size_of_val(name).saturating_add(name.capacity()),
220                    ),
221                )
222            },
223        ))
224    }
225}
226
227/// Syscall handler function (ContextObject is derived from VM)
228pub type BuiltinFunction<C> = fn(EncryptedHostAddressToEbpfVm<C>, u64, u64, u64, u64, u64);
229/// Re-export of the JIT compiler for the declare_builtin_function! macro
230#[cfg(all(feature = "jit", not(target_os = "windows"), target_arch = "x86_64"))]
231pub type JitCompiler<'a, C> = crate::jit::JitCompiler<'a, C>;
232/// Re-export of the JIT compiler for the declare_builtin_function! macro
233#[cfg(not(all(feature = "jit", not(target_os = "windows"), target_arch = "x86_64")))]
234pub struct JitCompiler<'a, C> {
235    _phantom: std::marker::PhantomData<&'a C>,
236}
237#[cfg(not(all(feature = "jit", not(target_os = "windows"), target_arch = "x86_64")))]
238impl<'a, C: ContextObject> JitCompiler<'a, C> {
239    /// Dummy for declare_builtin_function!()
240    #[allow(dead_code)]
241    pub fn emit_external_call(&mut self, _function: BuiltinFunction<C>) {}
242}
243/// Syscall codegen function for JIT compiler
244pub type BuiltinCodegen<C> = fn(&mut JitCompiler<C>);
245
246/// Represents the interface to a fixed functionality program
247#[derive(Eq)]
248pub struct BuiltinProgram<C: ContextObject> {
249    /// Holds the Config if this is a loader program
250    config: Option<Box<Config>>,
251    /// Function pointers by symbol with sparse indexing
252    sparse_registry: FunctionRegistry<(BuiltinFunction<C>, BuiltinCodegen<C>)>,
253}
254
255impl<C: ContextObject> PartialEq for BuiltinProgram<C> {
256    fn eq(&self, other: &Self) -> bool {
257        self.config.eq(&other.config) && self.sparse_registry.eq(&other.sparse_registry)
258    }
259}
260
261impl<C: ContextObject> BuiltinProgram<C> {
262    /// Constructs a loader built-in program
263    pub fn new_loader(config: Config) -> Self {
264        Self {
265            config: Some(Box::new(config)),
266            sparse_registry: FunctionRegistry::default(),
267        }
268    }
269
270    /// Constructs a built-in program
271    pub fn new_builtin() -> Self {
272        Self {
273            config: None,
274            sparse_registry: FunctionRegistry::default(),
275        }
276    }
277
278    /// Constructs a mock loader built-in program
279    pub fn new_mock() -> Self {
280        Self {
281            config: Some(Box::default()),
282            sparse_registry: FunctionRegistry::default(),
283        }
284    }
285
286    /// Get the configuration settings assuming this is a loader program
287    pub fn get_config(&self) -> &Config {
288        self.config.as_ref().unwrap()
289    }
290
291    /// Get the function registry depending on the SBPF version
292    pub fn get_function_registry(
293        &self,
294    ) -> &FunctionRegistry<(BuiltinFunction<C>, BuiltinCodegen<C>)> {
295        &self.sparse_registry
296    }
297
298    /// Calculate memory size
299    pub fn mem_size(&self) -> usize {
300        std::mem::size_of::<Self>()
301            .saturating_add(if self.config.is_some() {
302                std::mem::size_of::<Config>()
303            } else {
304                0
305            })
306            .saturating_add(self.sparse_registry.mem_size())
307    }
308
309    /// Register a function both in the sparse and dense registries
310    ///
311    /// This is a low-level function. Prefer using [`Self::register_definition`].
312    pub fn register_function(
313        &mut self,
314        name: &str,
315        entry: (BuiltinFunction<C>, BuiltinCodegen<C>),
316    ) -> Result<(), ElfError> {
317        let key = ebpf::hash_symbol_name(name.as_bytes());
318        self.sparse_registry
319            .register_function(key, name, entry)
320            .map(|_| ())
321    }
322
323    /// Register a function both in the sparse and dense registries
324    pub fn register_definition<BFD: BuiltinFunctionDefinition<C>>(
325        &mut self,
326        name: &str,
327    ) -> Result<(), ElfError> {
328        self.register_function(name, (BFD::vm, BFD::codegen))
329    }
330
331    /// Remove a function by name (if it exists)
332    pub fn unregister_function(&mut self, name: &str) -> bool {
333        let key = ebpf::hash_symbol_name(name.as_bytes());
334        self.sparse_registry.unregister_function(key)
335    }
336}
337
338/// Native built-in functions that can be made available to programs to call.
339pub trait BuiltinFunctionDefinition<C>
340where
341    C: crate::vm::ContextObject,
342{
343    /// Error type returned by this built-in function.
344    type Error: Into<Box<dyn core::error::Error>>;
345
346    /// The Rust side of the function logic.
347    ///
348    /// This is the only method you are required to override.
349    fn rust(
350        vm: &mut C,
351        arg_a: u64,
352        arg_b: u64,
353        arg_c: u64,
354        arg_d: u64,
355        arg_e: u64,
356    ) -> Result<u64, Self::Error>;
357
358    /// The VM wrapper.
359    #[expect(clippy::arithmetic_side_effects)]
360    fn vm(mut vm: EncryptedHostAddressToEbpfVm<C>, a: u64, b: u64, c: u64, d: u64, e: u64) {
361        unsafe {
362            // SAFETY: Sound under the stacked lifetimes model – we've only one `VmAddress`.
363            vm.with_vm(|vm| {
364                let enable_insn_meter = vm.loader.get_config().enable_instruction_meter;
365                if enable_insn_meter {
366                    let used_cus = vm.previous_instruction_meter - vm.due_insn_count;
367                    vm.context().consume(used_cus);
368                }
369                let converted_result: crate::error::ProgramResult =
370                    Self::rust(vm.context(), a, b, c, d, e)
371                        .map_err(|err| crate::error::EbpfError::SyscallError(err.into()))
372                        .into();
373                vm.program_result = converted_result;
374                if enable_insn_meter {
375                    vm.previous_instruction_meter = vm.context().get_remaining();
376                }
377            })
378        }
379    }
380
381    /// Hook for the JIT compiler on how to codegen this built-in function.
382    ///
383    /// You could opt to codegen it in-line, but do note that defining the other methods is still
384    /// required for non-JIT execution modes.
385    fn codegen(jit: &mut JitCompiler<C>) {
386        jit.emit_external_call(Self::vm);
387    }
388
389    /// Register this syscall to the provided program.
390    fn register(program: &mut BuiltinProgram<C>, name: &str) -> Result<(), ElfError>
391    where
392        Self: Sized,
393    {
394        program.register_definition::<Self>(name)
395    }
396}
397
398impl<C: ContextObject> std::fmt::Debug for BuiltinProgram<C> {
399    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
400        f.debug_struct("BuiltinProgram")
401            .field("registry", unsafe {
402                std::mem::transmute::<
403                    &FunctionRegistry<(BuiltinFunction<C>, BuiltinCodegen<C>)>,
404                    &FunctionRegistry<(usize, usize)>,
405                >(&self.sparse_registry)
406            })
407            .finish()
408    }
409}
410
411/// Generates an adapter for a BuiltinFunction between the Rust and the VM interface
412#[macro_export]
413macro_rules! declare_builtin_function {
414    (
415        $(#[$attr:meta])*
416        $name:ident $(<$($generic_ident:tt : $generic_type:tt),+>)?,
417        fn rust(
418            $vm:ident : &mut $ContextObject:ty,
419            $arg_a:ident : u64,
420            $arg_b:ident : u64,
421            $arg_c:ident : u64,
422            $arg_d:ident : u64,
423            $arg_e:ident : u64,
424        ) -> Result<$Ok:ty, $Err:ty> {
425            $($rust:tt)*
426        }
427        $(fn codegen(
428            $jit:ident : &mut $crate::program::JitCompiler<$ContextObject2:ty>,
429        ) {
430            $($codegen:tt)*
431        })?
432    ) => {
433        $(#[$attr])*
434        pub struct $name $(<$($generic_ident),+>)? (
435            $(std::marker::PhantomData<($($generic_ident,)+)>)?
436        );
437        impl $(<$($generic_ident : $generic_type),+>)?
438            $crate::program::BuiltinFunctionDefinition<$ContextObject> for
439            $name $(<$($generic_ident),+>)?
440        {
441            type Error = $Err;
442            fn rust(
443                $vm: &mut $ContextObject,
444                $arg_a: u64,
445                $arg_b: u64,
446                $arg_c: u64,
447                $arg_d: u64,
448                $arg_e: u64,
449            ) -> core::result::Result<$Ok, $Err> {
450                $($rust)*
451            }
452            $(fn codegen(
453                $jit: &mut $crate::program::JitCompiler<$ContextObject2>,
454            ) {
455                $($codegen)*
456            })?
457        }
458    };
459}