From e8df84d9101e7b6ac52c9fcd1a4f1fe94923d737 Mon Sep 17 00:00:00 2001 From: Jack May Date: Tue, 1 Jun 2021 15:33:17 -0700 Subject: [PATCH] Add memory operation syscalls (#16447) (cherry picked from commit 2b5052926599478455cba6ccf1b8fa54f0fb9826) # Conflicts: # programs/bpf/Cargo.lock --- programs/bpf/Cargo.lock | 14 ++ programs/bpf/Cargo.toml | 1 + programs/bpf/build.rs | 1 + programs/bpf/rust/mem/Cargo.toml | 9 +- programs/bpf/rust/mem/src/entrypoint.rs | 39 ++++ programs/bpf/rust/mem/src/lib.rs | 245 ++++++++++------------- programs/bpf/rust/mem/tests/lib.rs | 23 +++ programs/bpf/rust/membuiltins/Cargo.toml | 20 ++ programs/bpf/rust/membuiltins/src/lib.rs | 39 ++++ programs/bpf/rust/sysvar/tests/lib.rs | 2 +- programs/bpf/tests/programs.rs | 5 +- programs/bpf_loader/src/syscalls.rs | 198 +++++++++++++++++- sdk/program/src/lib.rs | 1 + sdk/program/src/program_memory.rs | 89 ++++++++ sdk/program/src/program_stubs.rs | 63 +++++- sdk/src/feature_set.rs | 5 + 16 files changed, 609 insertions(+), 145 deletions(-) create mode 100644 programs/bpf/rust/mem/src/entrypoint.rs create mode 100644 programs/bpf/rust/mem/tests/lib.rs create mode 100644 programs/bpf/rust/membuiltins/Cargo.toml create mode 100644 programs/bpf/rust/membuiltins/src/lib.rs create mode 100644 sdk/program/src/program_memory.rs diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index 932756a963d797..aab1c0fe89c125 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -2966,7 +2966,21 @@ dependencies = [ name = "solana-bpf-rust-mem" version = "1.7.0" dependencies = [ +<<<<<<< HEAD "solana-program 1.7.0", +======= + "solana-program 1.8.0", + "solana-program-test", + "solana-sdk", +] + +[[package]] +name = "solana-bpf-rust-membuiltins" +version = "1.8.0" +dependencies = [ + "solana-bpf-rust-mem", + "solana-program 1.8.0", +>>>>>>> 2b5052926 (Add memory operation syscalls (#16447)) ] [[package]] diff --git a/programs/bpf/Cargo.toml b/programs/bpf/Cargo.toml index ceed8470bdbea2..b602d3099d2b85 100644 --- a/programs/bpf/Cargo.toml +++ b/programs/bpf/Cargo.toml @@ -64,6 +64,7 @@ members = [ "rust/many_args", "rust/many_args_dep", "rust/mem", + "rust/membuiltins", "rust/noop", "rust/panic", "rust/param_passing", diff --git a/programs/bpf/build.rs b/programs/bpf/build.rs index 6c7ad480eff235..8d16b204a76cc9 100644 --- a/programs/bpf/build.rs +++ b/programs/bpf/build.rs @@ -78,6 +78,7 @@ fn main() { "iter", "many_args", "mem", + "membuiltins", "noop", "panic", "param_passing", diff --git a/programs/bpf/rust/mem/Cargo.toml b/programs/bpf/rust/mem/Cargo.toml index f3d15b6e741952..bf788abd0b03ea 100644 --- a/programs/bpf/rust/mem/Cargo.toml +++ b/programs/bpf/rust/mem/Cargo.toml @@ -9,11 +9,18 @@ homepage = "https://solana.com/" documentation = "https://docs.rs/solana-bpf-rust-mem" edition = "2018" +[features] +no-entrypoint = [] + [dependencies] solana-program = { path = "../../../../sdk/program", version = "=1.7.0" } +[dev-dependencies] +solana-program-test = { path = "../../../../program-test", version = "=1.8.0" } +solana-sdk = { path = "../../../../sdk", version = "=1.8.0" } + [lib] -crate-type = ["cdylib"] +crate-type = ["cdylib", "lib"] [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/programs/bpf/rust/mem/src/entrypoint.rs b/programs/bpf/rust/mem/src/entrypoint.rs new file mode 100644 index 00000000000000..65d25ef8f2fd7a --- /dev/null +++ b/programs/bpf/rust/mem/src/entrypoint.rs @@ -0,0 +1,39 @@ +//! @brief Test mem functions + +use crate::{run_mem_tests, MemOps}; +use solana_program::{ + account_info::AccountInfo, + entrypoint, + entrypoint::ProgramResult, + program_memory::{sol_memcmp, sol_memcpy, sol_memmove, sol_memset}, + pubkey::Pubkey, +}; + +entrypoint!(process_instruction); +#[allow(clippy::unnecessary_wraps)] +pub fn process_instruction( + _program_id: &Pubkey, + _accounts: &[AccountInfo], + _instruction_data: &[u8], +) -> ProgramResult { + // Via syscalls + #[derive(Default)] + struct MemOpSyscalls(); + impl MemOps for MemOpSyscalls { + fn memcpy(&self, dst: &mut [u8], src: &[u8], n: usize) { + sol_memcpy(dst, src, n) + } + unsafe fn memmove(&self, dst: *mut u8, src: *mut u8, n: usize) { + sol_memmove(dst, src, n) + } + fn memset(&self, s: &mut [u8], c: u8, n: usize) { + sol_memset(s, c, n) + } + fn memcmp(&self, s1: &[u8], s2: &[u8], n: usize) -> i32 { + sol_memcmp(s1, s2, n) + } + } + run_mem_tests(MemOpSyscalls::default()); + + Ok(()) +} diff --git a/programs/bpf/rust/mem/src/lib.rs b/programs/bpf/rust/mem/src/lib.rs index 3817010ad2c64c..91a98a208c86bc 100644 --- a/programs/bpf/rust/mem/src/lib.rs +++ b/programs/bpf/rust/mem/src/lib.rs @@ -1,190 +1,165 @@ -//! @brief Test builtin mem functions +//! @brief Test mem functions -#![cfg(target_arch = "bpf")] -#![feature(rustc_private)] +#[cfg(not(feature = "no-entrypoint"))] +pub mod entrypoint; -extern crate compiler_builtins; -use solana_program::{custom_panic_default, entrypoint::SUCCESS}; +pub trait MemOps { + fn memcpy(&self, dst: &mut [u8], src: &[u8], n: usize); + /// # Safety + unsafe fn memmove(&self, dst: *mut u8, src: *mut u8, n: usize); + fn memset(&self, s: &mut [u8], c: u8, n: usize); + fn memcmp(&self, s1: &[u8], s2: &[u8], n: usize) -> i32; +} -#[no_mangle] -pub extern "C" fn entrypoint(_input: *mut u8) -> u64 { - unsafe { - // memcpy - let src = &mut [1_u8; 18]; - let dst = &mut [0_u8; 1]; - compiler_builtins::mem::memcpy(&mut src[0] as *mut u8, &mut dst[0] as *mut u8, 1); - assert_eq!(&src[..1], dst); - let dst = &mut [0_u8; 3]; - compiler_builtins::mem::memcpy(&mut src[0] as *mut u8, &mut dst[0] as *mut u8, 3); - assert_eq!(&src[..3], dst); - let dst = &mut [0_u8; 8]; - compiler_builtins::mem::memcpy(&mut src[0] as *mut u8, &mut dst[0] as *mut u8, 8); - assert_eq!(&src[..8], dst); - let dst = &mut [0_u8; 9]; - compiler_builtins::mem::memcpy(&mut src[0] as *mut u8, &mut dst[0] as *mut u8, 9); - assert_eq!(&src[..9], dst); - let dst = &mut [0_u8; 16]; - compiler_builtins::mem::memcpy(&mut src[0] as *mut u8, &mut dst[0] as *mut u8, 16); - assert_eq!(&src[..16], dst); - let dst = &mut [0_u8; 18]; - compiler_builtins::mem::memcpy(&mut src[0] as *mut u8, &mut dst[0] as *mut u8, 18); - assert_eq!(&src[..18], dst); - let dst = &mut [0_u8; 18]; - compiler_builtins::mem::memcpy(&mut src[1] as *mut u8, &mut dst[0] as *mut u8, 17); - assert_eq!(&src[1..], &dst[1..]); - let dst = &mut [0_u8; 18]; - compiler_builtins::mem::memcpy(&mut src[1] as *mut u8, &mut dst[1] as *mut u8, 17); - assert_eq!(&src[1..], &dst[..17]); +pub fn run_mem_tests(mem_ops: T) { + // memcpy + let src = &[1_u8; 18]; + let dst = &mut [0_u8; 1]; + mem_ops.memcpy(dst, src, 1); + assert_eq!(&src[..1], dst); + let dst = &mut [0_u8; 3]; + mem_ops.memcpy(dst, src, 3); + assert_eq!(&src[..3], dst); + let dst = &mut [0_u8; 8]; + mem_ops.memcpy(dst, src, 8); + assert_eq!(&src[..8], dst); + let dst = &mut [0_u8; 9]; + mem_ops.memcpy(dst, src, 9); + assert_eq!(&src[..9], dst); + let dst = &mut [0_u8; 16]; + mem_ops.memcpy(dst, src, 16); + assert_eq!(&src[..16], dst); + let dst = &mut [0_u8; 18]; + mem_ops.memcpy(dst, src, 18); + assert_eq!(&src[..18], dst); + let dst = &mut [0_u8; 18]; + mem_ops.memcpy(dst, &src[1..], 17); + assert_eq!(&src[1..], &dst[..17]); + let dst = &mut [0_u8; 18]; + mem_ops.memcpy(&mut dst[1..], &src[1..], 17); + assert_eq!(&src[1..], &dst[1..]); - // memmove + // memmove + unsafe { let buf = &mut [1_u8, 0]; - compiler_builtins::mem::memmove(&mut buf[0] as *mut u8, &mut buf[1] as *mut u8, 1); + mem_ops.memmove(&mut buf[0] as *mut u8, &mut buf[1] as *mut u8, 1); assert_eq!(buf[0], buf[1]); let buf = &mut [1_u8, 0]; - compiler_builtins::mem::memmove(&mut buf[1] as *mut u8, &mut buf[0] as *mut u8, 1); + mem_ops.memmove(&mut buf[1] as *mut u8, &mut buf[0] as *mut u8, 1); assert_eq!(buf[0], buf[1]); let buf = &mut [1_u8, 1, 1, 0, 0, 0]; - compiler_builtins::mem::memmove(&mut buf[0] as *mut u8, &mut buf[3] as *mut u8, 3); + mem_ops.memmove(&mut buf[0] as *mut u8, &mut buf[3] as *mut u8, 3); assert_eq!(buf[..3], buf[3..]); let buf = &mut [1_u8, 1, 1, 0, 0, 0]; - compiler_builtins::mem::memmove(&mut buf[3] as *mut u8, &mut buf[0] as *mut u8, 3); + mem_ops.memmove(&mut buf[3] as *mut u8, &mut buf[0] as *mut u8, 3); assert_eq!(buf[..3], buf[3..]); let buf = &mut [1_u8, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]; - compiler_builtins::mem::memmove(&mut buf[0] as *mut u8, &mut buf[8] as *mut u8, 8); + mem_ops.memmove(&mut buf[0] as *mut u8, &mut buf[8] as *mut u8, 8); assert_eq!(buf[..8], buf[8..]); let buf = &mut [1_u8, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]; - compiler_builtins::mem::memmove(&mut buf[8] as *mut u8, &mut buf[0] as *mut u8, 8); + mem_ops.memmove(&mut buf[8] as *mut u8, &mut buf[0] as *mut u8, 8); assert_eq!(buf[..8], buf[8..]); let buf = &mut [1_u8, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - compiler_builtins::mem::memmove(&mut buf[0] as *mut u8, &mut buf[9] as *mut u8, 9); + mem_ops.memmove(&mut buf[0] as *mut u8, &mut buf[9] as *mut u8, 9); assert_eq!(buf[..9], buf[9..]); let buf = &mut [0_u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - compiler_builtins::mem::memmove(&mut buf[1] as *mut u8, &mut buf[0] as *mut u8, 9); + mem_ops.memmove(&mut buf[1] as *mut u8, &mut buf[0] as *mut u8, 9); assert_eq!(&mut [0_u8, 0, 1, 2, 3, 4, 5, 6, 7, 8], buf); let buf = &mut [1_u8, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - compiler_builtins::mem::memmove(&mut buf[9] as *mut u8, &mut buf[0] as *mut u8, 9); + mem_ops.memmove(&mut buf[9] as *mut u8, &mut buf[0] as *mut u8, 9); assert_eq!(buf[..9], buf[9..]); let buf = &mut [ 1_u8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; - compiler_builtins::mem::memmove(&mut buf[0] as *mut u8, &mut buf[16] as *mut u8, 16); + mem_ops.memmove(&mut buf[0] as *mut u8, &mut buf[16] as *mut u8, 16); assert_eq!(buf[..16], buf[16..]); let buf = &mut [ 1_u8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; - compiler_builtins::mem::memmove(&mut buf[16] as *mut u8, &mut buf[0] as *mut u8, 16); + mem_ops.memmove(&mut buf[16] as *mut u8, &mut buf[0] as *mut u8, 16); assert_eq!(buf[..16], buf[16..]); let buf = &mut [ 1_u8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; - compiler_builtins::mem::memmove(&mut buf[0] as *mut u8, &mut buf[18] as *mut u8, 18); + mem_ops.memmove(&mut buf[0] as *mut u8, &mut buf[18] as *mut u8, 18); assert_eq!(buf[..18], buf[18..]); let buf = &mut [ 1_u8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; - compiler_builtins::mem::memmove(&mut buf[18] as *mut u8, &mut buf[0] as *mut u8, 18); + mem_ops.memmove(&mut buf[18] as *mut u8, &mut buf[0] as *mut u8, 18); assert_eq!(buf[..18], buf[18..]); let buf = &mut [ 1_u8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; - compiler_builtins::mem::memmove(&mut buf[1] as *mut u8, &mut buf[18] as *mut u8, 17); + mem_ops.memmove(&mut buf[1] as *mut u8, &mut buf[18] as *mut u8, 17); assert_eq!(buf[1..17], buf[18..34]); let buf = &mut [ 1_u8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; - compiler_builtins::mem::memmove(&mut buf[19] as *mut u8, &mut buf[1] as *mut u8, 17); + mem_ops.memmove(&mut buf[19] as *mut u8, &mut buf[1] as *mut u8, 17); assert_eq!(buf[..17], buf[19..]); - - // memset - let exp = &[1_u8; 18]; - let buf = &mut [0_u8; 18]; - compiler_builtins::mem::memset(&mut buf[0] as *mut u8, 1, 1); - assert_eq!(exp[..1], buf[..1]); - compiler_builtins::mem::memset(&mut buf[0] as *mut u8, 1, 3); - assert_eq!(exp[..3], buf[..3]); - compiler_builtins::mem::memset(&mut buf[0] as *mut u8, 1, 8); - assert_eq!(exp[..8], buf[..8]); - compiler_builtins::mem::memset(&mut buf[0] as *mut u8, 1, 9); - assert_eq!(exp[..9], buf[..9]); - compiler_builtins::mem::memset(&mut buf[0] as *mut u8, 1, 16); - assert_eq!(exp[..16], buf[..16]); - compiler_builtins::mem::memset(&mut buf[0] as *mut u8, 1, 18); - assert_eq!(exp[..18], buf[..18]); - compiler_builtins::mem::memset(&mut buf[1] as *mut u8, 1, 17); - assert_eq!(exp[1..18], buf[1..18]); - - // memcmp - assert_eq!( - -1, - compiler_builtins::mem::memcmp(&[0_u8] as *const u8, &[1_u8] as *const u8, 1) - ); - assert_eq!( - -1, - compiler_builtins::mem::memcmp( - &[0_u8, 0, 0] as *const u8, - &[0_u8, 0, 1] as *const u8, - 3 - ) - ); - assert_eq!( - 0, - compiler_builtins::mem::memcmp( - &[0_u8, 0, 0, 0, 0, 0, 0, 0, 0] as *const u8, - &[0_u8, 0, 0, 0, 0, 0, 0, 0, 0] as *const u8, - 9 - ) - ); - assert_eq!( - -1, - compiler_builtins::mem::memcmp( - &[0_u8, 0, 0, 0, 0, 0, 0, 0, 0] as *const u8, - &[0_u8, 0, 0, 0, 0, 0, 0, 0, 1] as *const u8, - 9 - ) - ); - assert_eq!( - -1, - compiler_builtins::mem::memcmp( - &[0_u8, 0, 0, 0, 0, 0, 0, 0, 0, 0] as *const u8, - &[0_u8, 0, 0, 0, 0, 0, 0, 0, 0, 1] as *const u8, - 10 - ) - ); - assert_eq!( - 0, - compiler_builtins::mem::memcmp(&[0_u8; 8] as *const u8, &[0_u8; 8] as *const u8, 8) - ); - assert_eq!( - -1, - compiler_builtins::mem::memcmp(&[0_u8; 8] as *const u8, &[1_u8; 8] as *const u8, 8) - ); - assert_eq!( - -1, - compiler_builtins::mem::memcmp(&[0_u8; 16] as *const u8, &[1_u8; 16] as *const u8, 16) - ); - assert_eq!( - -1, - compiler_builtins::mem::memcmp(&[0_u8; 18] as *const u8, &[1_u8; 18] as *const u8, 18) - ); - let one = &[0_u8; 18]; - let two = &[1_u8; 18]; - assert_eq!( - -1, - compiler_builtins::mem::memcmp(&one[1] as *const u8, &two[0] as *const u8, 17) - ); - assert_eq!( - -1, - compiler_builtins::mem::memcmp(&one[1] as *const u8, &two[1] as *const u8, 17) - ); + let buf = &mut [0_u8, 0, 0, 1, 1, 1, 1, 1, 0]; + mem_ops.memmove(&mut buf[0] as *mut u8, &mut buf[3] as *mut u8, 5); + assert_eq!(buf, &mut [1, 1, 1, 1, 1, 1, 1, 1, 0]); } - SUCCESS -} + // memset + let exp = &[1_u8; 18]; + let buf = &mut [0_u8; 18]; + mem_ops.memset(&mut buf[0..], 1, 1); + assert_eq!(exp[..1], buf[..1]); + mem_ops.memset(&mut buf[0..], 1, 3); + assert_eq!(exp[..3], buf[..3]); + mem_ops.memset(&mut buf[0..], 1, 8); + assert_eq!(exp[..8], buf[..8]); + mem_ops.memset(&mut buf[0..], 1, 9); + assert_eq!(exp[..9], buf[..9]); + mem_ops.memset(&mut buf[0..], 1, 16); + assert_eq!(exp[..16], buf[..16]); + mem_ops.memset(&mut buf[0..], 1, 18); + assert_eq!(exp[..18], buf[..18]); + mem_ops.memset(&mut buf[1..], 1, 17); + assert_eq!(exp[1..18], buf[1..18]); -custom_panic_default!(); + // memcmp + assert_eq!(-1, mem_ops.memcmp(&[0_u8], &[1_u8], 1)); + assert_eq!(-1, mem_ops.memcmp(&[0_u8, 0, 0], &[0_u8, 0, 1], 3)); + assert_eq!( + 0, + mem_ops.memcmp( + &[0_u8, 0, 0, 0, 0, 0, 0, 0, 0], + &[0_u8, 0, 0, 0, 0, 0, 0, 0, 0], + 9 + ) + ); + assert_eq!( + -1, + mem_ops.memcmp( + &[0_u8, 0, 0, 0, 0, 0, 0, 0, 0], + &[0_u8, 0, 0, 0, 0, 0, 0, 0, 1], + 9 + ) + ); + assert_eq!( + -1, + mem_ops.memcmp( + &[0_u8, 0, 0, 0, 0, 0, 0, 0, 0, 0], + &[0_u8, 0, 0, 0, 0, 0, 0, 0, 0, 1], + 10 + ) + ); + assert_eq!(0, mem_ops.memcmp(&[0_u8; 8], &[0_u8; 8], 8)); + assert_eq!(-1, mem_ops.memcmp(&[0_u8; 8], &[1_u8; 8], 8)); + assert_eq!(-1, mem_ops.memcmp(&[0_u8; 16], &[1_u8; 16], 16)); + assert_eq!(-1, mem_ops.memcmp(&[0_u8; 18], &[1_u8; 18], 18)); + let one = &[0_u8; 18]; + let two = &[1_u8; 18]; + assert_eq!(-1, mem_ops.memcmp(&one[1..], &two[0..], 17)); + assert_eq!(-1, mem_ops.memcmp(&one[1..], &two[1..], 17)); +} diff --git a/programs/bpf/rust/mem/tests/lib.rs b/programs/bpf/rust/mem/tests/lib.rs new file mode 100644 index 00000000000000..5bc591d728644d --- /dev/null +++ b/programs/bpf/rust/mem/tests/lib.rs @@ -0,0 +1,23 @@ +use solana_bpf_rust_mem::entrypoint::process_instruction; +use solana_program_test::*; +use solana_sdk::{ + instruction::Instruction, pubkey::Pubkey, signature::Signer, transaction::Transaction, +}; + +#[tokio::test] +async fn test_mem() { + let program_id = Pubkey::new_unique(); + let program_test = ProgramTest::new( + "solana_bpf_rust_mem", + program_id, + processor!(process_instruction), + ); + let (mut banks_client, payer, recent_blockhash) = program_test.start().await; + + let mut transaction = Transaction::new_with_payer( + &[Instruction::new_with_bincode(program_id, &(), vec![])], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer], recent_blockhash); + banks_client.process_transaction(transaction).await.unwrap(); +} diff --git a/programs/bpf/rust/membuiltins/Cargo.toml b/programs/bpf/rust/membuiltins/Cargo.toml new file mode 100644 index 00000000000000..b3a3206dc98e36 --- /dev/null +++ b/programs/bpf/rust/membuiltins/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "solana-bpf-rust-membuiltins" +version = "1.8.0" +description = "Solana BPF test program written in Rust" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana" +license = "Apache-2.0" +homepage = "https://solana.com/" +documentation = "https://docs.rs/solana-bpf-rust-mem" +edition = "2018" + +[dependencies] +solana-bpf-rust-mem = { path = "../mem", version = "=1.8.0", features = [ "no-entrypoint" ] } +solana-program = { path = "../../../../sdk/program", version = "=1.8.0" } + +[lib] +crate-type = ["cdylib"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/programs/bpf/rust/membuiltins/src/lib.rs b/programs/bpf/rust/membuiltins/src/lib.rs new file mode 100644 index 00000000000000..1ce2ed339cc683 --- /dev/null +++ b/programs/bpf/rust/membuiltins/src/lib.rs @@ -0,0 +1,39 @@ +//! @brief Test builtin mem functions + +#![cfg(target_arch = "bpf")] +#![feature(rustc_private)] + +extern crate compiler_builtins; +use solana_bpf_rust_mem::{run_mem_tests, MemOps}; +use solana_program::{custom_panic_default, entrypoint::SUCCESS}; + +#[no_mangle] +pub extern "C" fn entrypoint(_input: *mut u8) -> u64 { + #[derive(Default)] + struct MemOpSyscalls(); + impl MemOps for MemOpSyscalls { + fn memcpy(&self, dst: &mut [u8], src: &[u8], n: usize) { + unsafe { + compiler_builtins::mem::memcpy(dst.as_mut_ptr(), src.as_ptr(), n); + } + } + unsafe fn memmove(&self, dst: *mut u8, src: *mut u8, n: usize) { + compiler_builtins::mem::memmove(dst, src, n); + } + fn memset(&self, s: &mut [u8], c: u8, n: usize) { + unsafe { + compiler_builtins::mem::memset(s.as_mut_ptr(), c as i32, n); + } + } + fn memcmp(&self, s1: &[u8], s2: &[u8], n: usize) -> i32 { + unsafe { compiler_builtins::mem::memcmp(s1.as_ptr(), s2.as_ptr(), n) } + } + } + let mem_ops = MemOpSyscalls::default(); + + run_mem_tests(mem_ops); + + SUCCESS +} + +custom_panic_default!(); diff --git a/programs/bpf/rust/sysvar/tests/lib.rs b/programs/bpf/rust/sysvar/tests/lib.rs index 370cd9e3018418..bb0fbd59ae768b 100644 --- a/programs/bpf/rust/sysvar/tests/lib.rs +++ b/programs/bpf/rust/sysvar/tests/lib.rs @@ -12,7 +12,7 @@ use solana_sdk::{ }; #[tokio::test] -async fn test_noop() { +async fn test_sysvars() { let program_id = Pubkey::new_unique(); let program_test = ProgramTest::new( "solana_bpf_rust_sysvar", diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index 62bbd538f5fabb..ef24dc48906ec2 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -454,7 +454,7 @@ fn test_program_bpf_sanity() { ("solana_bpf_rust_external_spend", false), ("solana_bpf_rust_iter", true), ("solana_bpf_rust_many_args", true), - ("solana_bpf_rust_mem", true), + ("solana_bpf_rust_membuiltins", true), ("solana_bpf_rust_noop", true), ("solana_bpf_rust_panic", false), ("solana_bpf_rust_param_passing", true), @@ -1276,7 +1276,8 @@ fn assert_instruction_count() { ("solana_bpf_rust_external_spend", 521), ("solana_bpf_rust_iter", 724), ("solana_bpf_rust_many_args", 237), - ("solana_bpf_rust_mem", 2297), + ("solana_bpf_rust_mem", 3143), + ("solana_bpf_rust_membuiltins", 4069), ("solana_bpf_rust_noop", 495), ("solana_bpf_rust_param_passing", 46), ("solana_bpf_rust_rand", 498), diff --git a/programs/bpf_loader/src/syscalls.rs b/programs/bpf_loader/src/syscalls.rs index b6a0da2f46ff9d..87e06a0c6c1b5f 100644 --- a/programs/bpf_loader/src/syscalls.rs +++ b/programs/bpf_loader/src/syscalls.rs @@ -20,8 +20,8 @@ use solana_sdk::{ epoch_schedule::EpochSchedule, feature_set::{ cpi_data_cost, demote_sysvar_write_locks, enforce_aligned_host_addrs, - keccak256_syscall_enabled, set_upgrade_authority_via_cpi_enabled, sysvar_via_syscall, - update_data_on_realloc, + keccak256_syscall_enabled, memory_ops_syscalls, set_upgrade_authority_via_cpi_enabled, + sysvar_via_syscall, update_data_on_realloc, }, hash::{Hasher, HASH_BYTES}, ic_msg, @@ -74,6 +74,8 @@ pub enum SyscallError { InstructionTooLarge(usize, usize), #[error("Too many accounts passed to inner instruction")] TooManyAccounts, + #[error("Overlapping copy")] + CopyOverlapping, } impl From for EbpfError { fn from(error: SyscallError) -> Self { @@ -145,10 +147,20 @@ pub fn register_syscalls( .register_syscall_by_name(b"sol_get_rent_sysvar", SyscallGetRentSysvar::call)?; } + if invoke_context.is_feature_active(&memory_ops_syscalls::id()) { + syscall_registry.register_syscall_by_name(b"sol_memcpy_", SyscallMemcpy::call)?; + syscall_registry.register_syscall_by_name(b"sol_memmove_", SyscallMemmove::call)?; + syscall_registry.register_syscall_by_name(b"sol_memcmp_", SyscallMemcmp::call)?; + syscall_registry.register_syscall_by_name(b"sol_memset_", SyscallMemset::call)?; + } + + // Cross-program invocation syscalls syscall_registry .register_syscall_by_name(b"sol_invoke_signed_c", SyscallInvokeSignedC::call)?; syscall_registry .register_syscall_by_name(b"sol_invoke_signed_rust", SyscallInvokeSignedRust::call)?; + + // Memory allocator syscall_registry.register_syscall_by_name(b"sol_alloc_free_", SyscallAllocFree::call)?; Ok(syscall_registry) @@ -268,6 +280,43 @@ pub fn bind_syscall_context_objects<'a>( }), ); + bind_feature_gated_syscall_context_object!( + vm, + invoke_context.is_feature_active(&memory_ops_syscalls::id()), + Box::new(SyscallMemcpy { + cost: invoke_context.get_bpf_compute_budget().cpi_bytes_per_unit, + compute_meter: invoke_context.get_compute_meter(), + loader_id, + }), + ); + bind_feature_gated_syscall_context_object!( + vm, + invoke_context.is_feature_active(&memory_ops_syscalls::id()), + Box::new(SyscallMemmove { + cost: invoke_context.get_bpf_compute_budget().cpi_bytes_per_unit, + compute_meter: invoke_context.get_compute_meter(), + loader_id, + }), + ); + bind_feature_gated_syscall_context_object!( + vm, + invoke_context.is_feature_active(&memory_ops_syscalls::id()), + Box::new(SyscallMemcmp { + cost: invoke_context.get_bpf_compute_budget().cpi_bytes_per_unit, + compute_meter: invoke_context.get_compute_meter(), + loader_id, + }), + ); + bind_feature_gated_syscall_context_object!( + vm, + invoke_context.is_feature_active(&memory_ops_syscalls::id()), + Box::new(SyscallMemset { + cost: invoke_context.get_bpf_compute_budget().cpi_bytes_per_unit, + compute_meter: invoke_context.get_compute_meter(), + loader_id, + }), + ); + let is_sysvar_via_syscall_active = invoke_context.is_feature_active(&sysvar_via_syscall::id()); let invoke_context = Rc::new(RefCell::new(invoke_context)); @@ -322,7 +371,6 @@ pub fn bind_syscall_context_objects<'a>( )?; // Memory allocator - vm.bind_syscall_context_object( Box::new(SyscallAllocFree { aligned: *loader_id != bpf_loader_deprecated::id(), @@ -1137,6 +1185,150 @@ impl<'a> SyscallObject for SyscallKeccak256<'a> { } } +/// memcpy +pub struct SyscallMemcpy<'a> { + cost: u64, + compute_meter: Rc>, + loader_id: &'a Pubkey, +} +impl<'a> SyscallObject for SyscallMemcpy<'a> { + fn call( + &mut self, + dst_addr: u64, + src_addr: u64, + n: u64, + _arg4: u64, + _arg5: u64, + memory_mapping: &MemoryMapping, + result: &mut Result>, + ) { + // cannot be overlapping + if dst_addr + n > src_addr && src_addr > dst_addr { + *result = Err(SyscallError::CopyOverlapping.into()); + return; + } + + question_mark!(self.compute_meter.consume(n / self.cost), result); + let dst = question_mark!( + translate_slice_mut::(memory_mapping, dst_addr, n, self.loader_id, true), + result + ); + let src = question_mark!( + translate_slice::(memory_mapping, src_addr, n, self.loader_id, true), + result + ); + unsafe { + std::ptr::copy_nonoverlapping(src.as_ptr(), dst.as_mut_ptr(), n as usize); + } + *result = Ok(0); + } +} +/// memcpy +pub struct SyscallMemmove<'a> { + cost: u64, + compute_meter: Rc>, + loader_id: &'a Pubkey, +} +impl<'a> SyscallObject for SyscallMemmove<'a> { + fn call( + &mut self, + dst_addr: u64, + src_addr: u64, + n: u64, + _arg4: u64, + _arg5: u64, + memory_mapping: &MemoryMapping, + result: &mut Result>, + ) { + question_mark!(self.compute_meter.consume(n / self.cost), result); + let dst = question_mark!( + translate_slice_mut::(memory_mapping, dst_addr, n, self.loader_id, true), + result + ); + let src = question_mark!( + translate_slice::(memory_mapping, src_addr, n, self.loader_id, true), + result + ); + unsafe { + std::ptr::copy(src.as_ptr(), dst.as_mut_ptr(), n as usize); + } + *result = Ok(0); + } +} +/// memcmp +pub struct SyscallMemcmp<'a> { + cost: u64, + compute_meter: Rc>, + loader_id: &'a Pubkey, +} +impl<'a> SyscallObject for SyscallMemcmp<'a> { + fn call( + &mut self, + s1_addr: u64, + s2_addr: u64, + n: u64, + cmp_result_addr: u64, + _arg5: u64, + memory_mapping: &MemoryMapping, + result: &mut Result>, + ) { + question_mark!(self.compute_meter.consume(n / self.cost), result); + let s1 = question_mark!( + translate_slice::(memory_mapping, s1_addr, n, self.loader_id, true), + result + ); + let s2 = question_mark!( + translate_slice::(memory_mapping, s2_addr, n, self.loader_id, true), + result + ); + let cmp_result = question_mark!( + translate_type_mut::(memory_mapping, cmp_result_addr, self.loader_id, true), + result + ); + let mut i = 0; + while i < n as usize { + let a = s1[i]; + let b = s2[i]; + if a != b { + *cmp_result = a as i32 - b as i32; + *result = Ok(0); + return; + } + i += 1; + } + *cmp_result = 0; + *result = Ok(0); + } +} +/// memset +pub struct SyscallMemset<'a> { + cost: u64, + compute_meter: Rc>, + loader_id: &'a Pubkey, +} +impl<'a> SyscallObject for SyscallMemset<'a> { + fn call( + &mut self, + s_addr: u64, + c: u64, + n: u64, + _arg4: u64, + _arg5: u64, + memory_mapping: &MemoryMapping, + result: &mut Result>, + ) { + question_mark!(self.compute_meter.consume(n / self.cost), result); + let s = question_mark!( + translate_slice_mut::(memory_mapping, s_addr, n, self.loader_id, true), + result + ); + for val in s.iter_mut().take(n as usize) { + *val = c as u8; + } + *result = Ok(0); + } +} + // Cross-program invocation syscalls struct AccountReferences<'a> { diff --git a/sdk/program/src/lib.rs b/sdk/program/src/lib.rs index 7b550008e1f6ae..689c45d7d89e12 100644 --- a/sdk/program/src/lib.rs +++ b/sdk/program/src/lib.rs @@ -30,6 +30,7 @@ pub mod native_token; pub mod nonce; pub mod program; pub mod program_error; +pub mod program_memory; pub mod program_option; pub mod program_pack; pub mod program_stubs; diff --git a/sdk/program/src/program_memory.rs b/sdk/program/src/program_memory.rs new file mode 100644 index 00000000000000..b213aa03425900 --- /dev/null +++ b/sdk/program/src/program_memory.rs @@ -0,0 +1,89 @@ +//! @brief Solana Rust-based BPF memory operations + +/// Memcpy +/// +/// @param dst - Destination +/// @param src - Source +/// @param n - Number of bytes to copy +#[inline] +pub fn sol_memcpy(dst: &mut [u8], src: &[u8], n: usize) { + #[cfg(target_arch = "bpf")] + { + extern "C" { + fn sol_memcpy_(dst: *mut u8, src: *const u8, n: u64); + } + unsafe { + sol_memcpy_(dst.as_mut_ptr(), src.as_ptr(), n as u64); + } + } + + #[cfg(not(target_arch = "bpf"))] + crate::program_stubs::sol_memcpy(dst.as_mut_ptr(), src.as_ptr(), n); +} + +/// Memmove +/// +/// @param dst - Destination +/// @param src - Source +/// @param n - Number of bytes to copy +/// +/// # Safety +#[inline] +pub unsafe fn sol_memmove(dst: *mut u8, src: *mut u8, n: usize) { + #[cfg(target_arch = "bpf")] + { + extern "C" { + fn sol_memmove_(dst: *mut u8, src: *const u8, n: u64); + } + sol_memmove_(dst, src, n as u64); + } + + #[cfg(not(target_arch = "bpf"))] + crate::program_stubs::sol_memmove(dst, src, n); +} + +/// Memcmp +/// +/// @param s1 - Slice to be compared +/// @param s2 - Slice to be compared +/// @param n - Number of bytes to compare +#[inline] +pub fn sol_memcmp(s1: &[u8], s2: &[u8], n: usize) -> i32 { + let mut result = 0; + + #[cfg(target_arch = "bpf")] + { + extern "C" { + fn sol_memcmp_(s1: *const u8, s2: *const u8, n: u64, result: *mut i32); + } + unsafe { + sol_memcmp_(s1.as_ptr(), s2.as_ptr(), n as u64, &mut result as *mut i32); + } + } + + #[cfg(not(target_arch = "bpf"))] + crate::program_stubs::sol_memcmp(s1.as_ptr(), s2.as_ptr(), n, &mut result as *mut i32); + + result +} + +/// Memset +/// +/// @param s1 - Slice to be compared +/// @param s2 - Slice to be compared +/// @param n - Number of bytes to compare +#[inline] +pub fn sol_memset(s: &mut [u8], c: u8, n: usize) { + #[cfg(target_arch = "bpf")] + { + extern "C" { + fn sol_memset_(s: *mut u8, c: u8, n: u64); + } + unsafe { + sol_memset_(s.as_mut_ptr(), c, n as u64); + } + } + + #[cfg(not(target_arch = "bpf"))] + crate::program_stubs::sol_memset(s.as_mut_ptr(), c, n); +} diff --git a/sdk/program/src/program_stubs.rs b/sdk/program/src/program_stubs.rs index 8aa67b6c780b00..f576ae723e2a63 100644 --- a/sdk/program/src/program_stubs.rs +++ b/sdk/program/src/program_stubs.rs @@ -12,12 +12,13 @@ lazy_static::lazy_static! { static ref SYSCALL_STUBS: Arc>> = Arc::new(RwLock::new(Box::new(DefaultSyscallStubs {}))); } -// The default syscall stubs don't do much, but `set_syscalls()` can be used to swap in -// alternatives +// The default syscall stubs may not do much, but `set_syscalls()` can be used +// to swap in alternatives pub fn set_syscall_stubs(syscall_stubs: Box) -> Box { std::mem::replace(&mut SYSCALL_STUBS.write().unwrap(), syscall_stubs) } +#[allow(clippy::integer_arithmetic)] pub trait SyscallStubs: Sync + Send { fn sol_log(&self, message: &str) { println!("{}", message); @@ -34,7 +35,6 @@ pub trait SyscallStubs: Sync + Send { sol_log("SyscallStubs: sol_invoke_signed() not available"); Ok(()) } - fn sol_get_clock_sysvar(&self, _var_addr: *mut u8) -> u64 { UNSUPPORTED_SYSVAR } @@ -47,6 +47,39 @@ pub trait SyscallStubs: Sync + Send { fn sol_get_rent_sysvar(&self, _var_addr: *mut u8) -> u64 { UNSUPPORTED_SYSVAR } + /// # Safety + unsafe fn sol_memcpy(&self, dst: *mut u8, src: *const u8, n: usize) { + // cannot be overlapping + if dst as usize + n > src as usize && src as usize > dst as usize { + panic!("memcpy does not support oveerlapping regions"); + } + std::ptr::copy_nonoverlapping(src, dst, n as usize); + } + /// # Safety + unsafe fn sol_memmove(&self, dst: *mut u8, src: *const u8, n: usize) { + std::ptr::copy(src, dst, n as usize); + } + /// # Safety + unsafe fn sol_memcmp(&self, s1: *const u8, s2: *const u8, n: usize, result: *mut i32) { + let mut i = 0; + while i < n { + let a = *s1.add(i); + let b = *s2.add(i); + if a != b { + *result = a as i32 - b as i32; + return; + } + i += 1; + } + *result = 0 + } + /// # Safety + unsafe fn sol_memset(&self, s: *mut u8, c: u8, n: usize) { + let s = std::slice::from_raw_parts_mut(s, n); + for val in s.iter_mut().take(n) { + *val = c; + } + } } struct DefaultSyscallStubs {} @@ -96,3 +129,27 @@ pub(crate) fn sol_get_fees_sysvar(var_addr: *mut u8) -> u64 { pub(crate) fn sol_get_rent_sysvar(var_addr: *mut u8) -> u64 { SYSCALL_STUBS.read().unwrap().sol_get_rent_sysvar(var_addr) } + +pub(crate) fn sol_memcpy(dst: *mut u8, src: *const u8, n: usize) { + unsafe { + SYSCALL_STUBS.read().unwrap().sol_memcpy(dst, src, n); + } +} + +pub(crate) fn sol_memmove(dst: *mut u8, src: *const u8, n: usize) { + unsafe { + SYSCALL_STUBS.read().unwrap().sol_memmove(dst, src, n); + } +} + +pub(crate) fn sol_memcmp(s1: *const u8, s2: *const u8, n: usize, result: *mut i32) { + unsafe { + SYSCALL_STUBS.read().unwrap().sol_memcmp(s1, s2, n, result); + } +} + +pub(crate) fn sol_memset(s: *mut u8, c: u8, n: usize) { + unsafe { + SYSCALL_STUBS.read().unwrap().sol_memset(s, c, n); + } +} diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index ee56c8be9fc7e4..b401fc685648d9 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -135,6 +135,10 @@ pub mod stake_program_v4 { solana_sdk::declare_id!("Dc7djyhP9aLfdq2zktpvskeAjpG56msCU1yexpxXiWZb"); } +pub mod memory_ops_syscalls { + solana_sdk::declare_id!("ENQi37wsVhTvFz2gUiZAAbqFEWGN2jwFsqdEDTE8A4MU"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -169,6 +173,7 @@ lazy_static! { (update_data_on_realloc::id(), "Retain updated data values modified after realloc via CPI"), (keccak256_syscall_enabled::id(), "keccak256 syscall"), (stake_program_v4::id(), "solana_stake_program v4"), + (memory_ops_syscalls::id(), "add syscalls for memory operations"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter()